1 前言8 n9 W* ~/ S) P8 d$ R% [
我们之前经常有讲到过如何通过 USB 的 DFU 方式来对固件进行升级,在示例中我们通常是通过一个按键来触发APP 跳转到 BOOT 从而进入到 DFU 升级模式。但是也有一种情况,客户的环境是没有任何按键或者其它触发条件,只有一个 USB 口连接运行 windows 操作系统的上位机。这不,前不久就碰到这么个客户,下位机 MCU 端仅仅只有一个 USB 口连接上位机,且 MCU 端与上位机之间的 USB 是常连的,并不能通过 USB 的拔插操作来触发 APP 的跳转,在此情况下,我们又该如何来实现客户的需求?
8 |( F6 A4 ], V1 T7 U" i" i2 分析
0 v) f1 w, h. I: y
6 k! I0 Z( ?7 Y8 O7 i
, Y* g9 q0 q3 |& B0 s% g
7 u6 s# E3 D2 K8 X如上图所示,在 MCU 内部 FLASH 中,BOOT 与 APP 同时存在,BOOT 为 DFU 模式,负责对固件进行升级,而 APP(Runtime模式)为客户的应用程序。当 APP 运行时,通过某个事件触发(DFU_DETACH)程序从 APP 跳转到 BOOT 中从而进入到DFU 模式中。
5 [5 W9 p$ g4 Y/ o$ i9 L: Z2 L" A
8 G2 L7 [& Y5 u9 o* k' d( O# P3 W _+ s6 b5 O+ \/ I
这个 DFU_DETACH 的操作在这里我们可以通过 PC 端软件 DfuSe Demo 来触发2 o4 k* [! `9 D5 `. X' t
" D6 J7 D, v' N* | h/ |
7 }( A+ x t' ~$ M, X T
$ m; U7 V5 h( f
在进入到 DFU 模式时,DfuSe Demo 软件的运行界面如上所示,上图界面中有一个“Leave DFU mode”的按键,通过它可以使运行在 MCU 的程序从 DFU 模式切换到 APP 中。同时我们也发现,在按键“Leave DFU mode”的左边有一个灰色按键“Enter DFU mode/HID detach”,从字面就可以知道,它应该是进入到 DFU 模式的按键,在 APP 模式下此按键应该是激活的,但是,在进入到 APP 模式后,此界面就检测不到 DFU 设备了,此软件将不再可用。那么我们又将如何来激活这个“Enter DFU mode/HID detach”按键呢?. r* x- o3 S/ c* U9 x& E6 Q
* I+ v- V4 A! j; |* \- c从帮助文档可知,在 APP 模式时,当 DfuSe Demo 这个软件识别到一个特定的 HID 设备时,“Enter DFU mod/HID detach”按键将激活,通过进一步了解,我们知道此时 DfuSe Demo 这个软件是需要使能此项功能才可以,所幸地是,我们已经有了此软件,在与此文对应的附件中,可以找到一个名为 DfuSeDemo_A.exe 的可执行程序,将它拷贝到 DfuSeDemo 的安装目录下,如下图所示:
8 v9 o( s* l8 q$ R" Z3 V
( E7 [( j4 g. A( z
( m' x* V% N3 r& v
+ o8 l/ u1 j: E当运行在 APP 模式下时如下所示:9 t, f* m& F0 @- E
3 t9 Y, T4 P1 r0 k+ O
1 R6 ?3 p9 D! ^, R, p1 a3 {1 N& p9 Y: G* R1 y% F
/ @! m$ X& T! R N) W如上图所示,在 APP 模式下时,当程序检测到有效的 HID 设备时,DfuSe Demo 的”Available DFU Device”下拉框中会显示这个 HID 设备,并且“Enter DFU mode/HID detach”按键激活。当按下此键时,MCU 会重启并进入到 DFU 模式。当然此 APP是需要实现一个符合某种要求的 HID 设备才行。下面我们通过制作这样一个 APP 来讲解。
6 L% ?- g2 o+ K" B; {) W6 c1 ?
4 _) B) b3 S; |+ E0 I& z3 制作 APP/ o; ~. M$ {) S7 d( k$ K4 k
我们基于之前 DFU 培训时使用的 STM32F072-Discovery 板来实现此 APP。" X5 {( H- A1 j" E! R. m. y
打开 STM32CubeMx 软件新建一个基于 STM32F072RB 的工程,使能 USB 和 RTC 外设,RTC 我们主要用它的备份域寄存器,用来保存跳进入到 DFU 模式的标志;( C- f" k) N2 g/ \. J
Pinout 如下所示:
4 Q1 [$ }* d" {/ D" Q- _* R8 ^0 X
; s, H1 x/ d# h
( m4 L7 E: i& q2 g2 _
0 e8 }& \9 T% Y- l$ G1 ?" c' n6 }; @: Q
使用 HSI48 作为系统时钟源,同时给 USB 提供 48M 时候,这样可以省一个外部晶振,RTC 就使用 LSI 时钟源,我们不要求它的时间精确度,只求备份域寄存器可用。
6 t; u# W5 K! l) e/ J5 A$ \8 @& v& {
. ~ e1 h9 k$ }
: w7 |) q2 a6 J P7 b( d4 B1 R然后我们将 USB 类选择 “Custom Human Interface Device(HID)”,USB 中断等级设置为 1,堆为0x500,栈大小设置为 0x2000,然后生成工程。( Q$ `( Z1 }6 \2 N/ D
9 t4 x3 v0 [% ]! N
打开 usbd_custom_hid_if.c 文件,找到 CUSTOM_HID_ReportDesc_FS,修改报告描述符为:$ H. v% J) c" K
- #define USBD_CUSTOM_HID_REPORT_DESC_SIZE 23
4 C8 S3 X/ u6 O- Y2 Y - /** Usb HID report descriptor. */
+ c4 R6 ~+ F4 Z- x - __ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE]$ S+ ~1 x8 T, k1 s+ f3 J
- __ALIGN_END =
5 r' p/ N, \8 N+ ]% w - {
7 |4 g0 F' ?- ~+ m! y6 f - /* USER CODE BEGIN 0 */
7 [6 m# F, I Q1 D - 0x06, 0x00, 0xFF, /* USAGE_PAGE (Vendor Page: 0xFF00) */
' O& _9 q# D( s; Z+ l* x# I - 0x09, 0x01, /* USAGE (Demo Kit) */; d3 }0 K: k k
- 0xa1, 0x00, /* COLLECTION (Physical) */
1 ^$ m9 j+ o0 t8 _, u - 0x85, 0x80, /* REPORT_ID (128) */, c/ T: s' T; g' e ~
- 0x09, 0x55, /*USAGE (LED 1) */
( K5 g( I2 M3 F. d. m/ }. o - 0x15, 0x00, /* LOGICAL_MINIMUM (0) */6 b! X$ d, Z% t- B+ X
- 0x26, 0xFF, 0x00, /* LOGICAL_MAXIMUM (255) */, N3 j9 [1 ~: v2 |. b; S) i; W6 z
- 0x75, 0x08, /* REPORT_SIZE (8 bits) */
* y5 S5 |5 Q- i4 P0 ` - 0x95, 0x01, /* REPORT_COUNT (1) */9 Q Q% Q9 P. U! e
- 0xB1, 0x82, /* FEATURE (Data,Var,Abs,Vol */
6 ?: n0 f! x$ f6 e - /* USER CODE END 0 */3 W8 B* f9 B3 G1 `% e" o& E
- 0xC0 /* END_COLLECTION */
5 R) v1 D ?% |& t - };
复制代码
6 u- b5 E6 D8 x8 T然后找到 CUSTOM_HID_OutEvent_FS 函数,修改为:$ _. E* k7 m9 T" m+ O7 L, a$ V: \/ i
- static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)
& Q% T1 J6 H4 {; X- x8 t+ a - {! M' ]9 `* R4 m& I5 O1 J
- /* USER CODE BEGIN 6 */9 x& B. D# g. l: |: k$ T
- extern RTC_HandleTypeDef hrtc; n) ~7 d" ` K9 H/ ]
- if(event_idx ==0x80 && state ==0x55)
3 T* w3 I- m) o: X$ O+ I. T o7 x* ] - {
4 m; Q& t) K" o) a2 ?) Y- { - __HAL_RCC_PWR_CLK_ENABLE();' P% A5 H" {2 {
- HAL_PWR_EnableBkUpAccess();
6 w1 D# V3 R$ m - HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR0,0x32F2);4 Z+ N0 ]% Y3 r( g
- HAL_PWR_DisableBkUpAccess();9 m* F$ s4 n: g- Z+ E/ Y P. ?# R
- HAL_NVIC_SystemReset();
9 f7 U" l. h# i9 G - while (1);
+ B+ G2 J* L) C Y f3 w( C. W2 V0 R - }4 ]0 T0 e) u8 `0 L8 I
- return (USBD_OK);9 `5 `! o) G7 b) }5 C
- /* USER CODE END 6 */1 l# A: j. [" y+ d4 S( C$ f8 {4 X
- }
复制代码
e8 j, V7 T& q; t: t3 C如上代码所示,只有当 PC 端软件 DfuSe Demo 检测到具有这个报告描述符的 HID 设备后才会认可其为满足要求的 HID 设备,并将其显示在设备列表中,按键“Enter DFU mode/HID detach”激活。当用户按下此按键后,DfuSe Demo 会向 HID 设备发送一条 SetFeature 指令。
) f/ z' Q1 O2 U2 K+ H* F+ a
' n9 G, X4 T4 ^( E8 O. x" `, }' g当 MCU 收到 SetFeature 指令后,往备份域寄存器写入 0x32F2 作为进入 DFU 模式的标志,然后重启切换到 BOOT,接下来在 BOOT 内如果程序检测到备份域寄存器的标志时则进入到 DFU 模式。整个过程如图 2 所示。' h+ W) i$ H1 \. k
, k9 d1 U) i( l
当然,APP 还需要修改 FLASH 中的偏移位置,通过工具烧录到 0x0800 7000 的位置,这个在之前 DFU 培训时已经讲述过如何操作了,这里就不具体再重复了,有兴趣的同学可以查看此文档附件内的 APP 源码。
+ i1 |0 D) H- N' o( e- s- o+ Q6 h Y+ b! U1 a; a" a. f
接下来我们再来看看 BOOT 程序中检测跳转标志的过程:
# U- W) f: X4 v: X9 b2 Y5 n6 x- /* USER CODE BEGIN 2 */( Q+ A+ i, a( N Z9 e
- if (((*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD) & 0x2FFE0000 ) != 0x20000000), K, X/ h8 y7 X( t
- {
9 z: n% _8 N) Y( J) t - jump2App =JUMP_FLAG_DFU;# p6 ~8 y- W M- q
- }
' w1 W+ S$ w: q" |6 m q - if(jump2App !=JUMP_FLAG_DFU)
; U! {1 l w& A" i, K5 p% u - {
' V6 M! z) A* `4 n/ A - __HAL_RCC_PWR_CLK_ENABLE();0 |/ R3 J" F- a/ |8 m! P" d: H
- HAL_PWR_EnableBkUpAccess();
& Y F0 d$ r7 \5 c7 S4 U; n - if(0x32F2 ==HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR0))//检测备份域寄存器中的标志
! C, n9 X% M6 d8 r - {
, Q3 ~" A' F" [! Z4 W - HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR0,0);) f& h% m" |5 A9 _1 o
- jump2App = JUMP_FLAG_DFU;
$ c% J h% o O$ t7 Q - }
3 s$ h( U. p0 `* o' @ - HAL_PWR_DisableBkUpAccess();, {1 k8 r" \5 H1 u( B
- }$ k$ a4 U) q1 _/ Q5 ~& Q
- if(jump2App == JUMP_FLAG_INIT). R" r0 ?: i5 s( |
- {9 V& q9 N: R d1 X" v
- if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) !=GPIO_PIN_SET)9 c! F: G9 [9 i' |
- {, k& x& v& G+ q# d. C
- jump2App = JUMP_FLAG_APP;( C9 B; l0 N& ~& @7 A. ?
- }9 w4 U, v6 U1 ?: j
- else# h1 A% {6 ~" h$ c
- {
2 t6 a% A3 J" L2 T! P5 M - jump2App =JUMP_FLAG_DFU ;
% k$ S% G; |3 {4 I$ j/ T, m8 U - }' `4 L& l- x$ n3 K
- }
; ^+ @; b) U. S2 ~3 J# ]2 r# t - if(jump2App ==JUMP_FLAG_APP)* a2 H' y( y' y8 K C0 N
- {+ a D/ T5 v0 d7 S, I( T: P
- /* Jump to user application */- U' {- ?4 ?/ _. J' X
- JumpAddress = *(__IO uint32_t*) (USBD_DFU_APP_DEFAULT_ADD + 4);
1 C5 s0 Q- G7 a7 N' y6 j - JumpToApplication = (pFunction) JumpAddress;
+ y6 m/ T' L5 O% {0 q - /* Initialize user application's Stack Pointer */
6 s% w( {7 `' o - __set_MSP(*(__IO uint32_t*) USBD_DFU_APP_DEFAULT_ADD);3 b% i! y+ ~! V5 v; k
- JumpToApplication();: v! w# n! a- X! E! s
- }. s4 S7 E- P& I. m: _/ `4 _8 W; G
- //进入到 DFU 模式5 Q/ }6 V: F" V- q$ Y* s9 j
- MX_USB_DEVICE_Init();) U9 s. ]* x0 C- {( @
- /* USER CODE END 2 */
复制代码
$ l6 ?/ H) c+ }2 d. u如上代码所示,只有当 jump2App ==JUMP_FLAG_APP 时程序才会跳转到 APP 中运行,否则进入到 DFU 模式。7 d4 E, b! w) O
$ }4 `' j) k# L8 u
; V- b, k# a- _& j4 L0 G4 总结# ]8 Z& w) S9 j% v3 T
使用此方法毕竟在 APP 中实现了一套 HID,如果不是必要的话,建议还是使用按键的方式来触发从 APP 跳转到 DFU 模式,除非遇到像本文中客户的情况,或者 APP 本身就需要实现某个 USB 类的功能,此时可以做成 USB 复合设备,其中一个 HID设备就是本文中的 HID 设备,这样 PC 端的软件 DfuSe Demo 也可以识别。
9 t0 u' Q1 o3 c- I" Q) Q
. l* c: y1 k2 m" M |