1 前言+ A- e$ f0 j3 w9 x
我们之前经常有讲到过如何通过 USB 的 DFU 方式来对固件进行升级,在示例中我们通常是通过一个按键来触发APP 跳转到 BOOT 从而进入到 DFU 升级模式。但是也有一种情况,客户的环境是没有任何按键或者其它触发条件,只有一个 USB 口连接运行 windows 操作系统的上位机。这不,前不久就碰到这么个客户,下位机 MCU 端仅仅只有一个 USB 口连接上位机,且 MCU 端与上位机之间的 USB 是常连的,并不能通过 USB 的拔插操作来触发 APP 的跳转,在此情况下,我们又该如何来实现客户的需求?# f9 g0 k* }; z# R( Q g. Z9 o
2 分析
, K9 y' u) J& I3 E* q2 A. C5 C0 A/ I9 \% ]
% a+ l1 @6 b, Z; l' J; }3 ]( d
0 X1 o- m; o7 Q" ~9 i1 b5 w5 a如上图所示,在 MCU 内部 FLASH 中,BOOT 与 APP 同时存在,BOOT 为 DFU 模式,负责对固件进行升级,而 APP(Runtime模式)为客户的应用程序。当 APP 运行时,通过某个事件触发(DFU_DETACH)程序从 APP 跳转到 BOOT 中从而进入到DFU 模式中。/ M6 t0 z ~# v! Z+ H8 D @; `
9 c8 _- Y) [) P+ y% _( B
0 I" h! M/ \3 W9 {8 I2 E
& c8 ~, n8 R, Q; U i) ^
这个 DFU_DETACH 的操作在这里我们可以通过 PC 端软件 DfuSe Demo 来触发
. b1 U3 p8 |9 i" r) ^
) K5 j2 c1 U* _* }7 a3 U
( I% ~0 Q6 }, D# j
0 Z6 E9 ^7 ~$ V5 |" r: c
在进入到 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”按键呢?
; k5 z g# L5 e+ f# G0 v3 w9 d% B" ^+ B% F9 }2 f: B
从帮助文档可知,在 APP 模式时,当 DfuSe Demo 这个软件识别到一个特定的 HID 设备时,“Enter DFU mod/HID detach”按键将激活,通过进一步了解,我们知道此时 DfuSe Demo 这个软件是需要使能此项功能才可以,所幸地是,我们已经有了此软件,在与此文对应的附件中,可以找到一个名为 DfuSeDemo_A.exe 的可执行程序,将它拷贝到 DfuSeDemo 的安装目录下,如下图所示:
* h; Q4 M' W, p4 @1 T
, G( K/ m" f, a' ^
( _! M) W t, S, u/ U
; P+ s/ c C# I$ U1 M: A" _1 u4 }当运行在 APP 模式下时如下所示:5 |, a) }) r3 B# {4 ^
1 Q# T) ^# r. p9 J& o# I' T b* l- F
( x0 m" ?3 J0 K' l
" K* |3 R j R" H- J1 p, }+ }+ ?; F1 ~
如上图所示,在 APP 模式下时,当程序检测到有效的 HID 设备时,DfuSe Demo 的”Available DFU Device”下拉框中会显示这个 HID 设备,并且“Enter DFU mode/HID detach”按键激活。当按下此键时,MCU 会重启并进入到 DFU 模式。当然此 APP是需要实现一个符合某种要求的 HID 设备才行。下面我们通过制作这样一个 APP 来讲解。) f2 ?5 O6 H, t
o) U$ z! A" [8 _$ i! j3 制作 APP- z" K8 u6 u% t: o
我们基于之前 DFU 培训时使用的 STM32F072-Discovery 板来实现此 APP。
. N" Y# K; J0 r( ~5 t; b& _6 N6 P打开 STM32CubeMx 软件新建一个基于 STM32F072RB 的工程,使能 USB 和 RTC 外设,RTC 我们主要用它的备份域寄存器,用来保存跳进入到 DFU 模式的标志;
! V) K: [: h( k$ J% sPinout 如下所示:+ q: U3 G5 R" M
/ i( p: S- P. p4 l' b* ~
2 ~3 J! Z, G! [( R
2 m; B# o& }+ y2 a使用 HSI48 作为系统时钟源,同时给 USB 提供 48M 时候,这样可以省一个外部晶振,RTC 就使用 LSI 时钟源,我们不要求它的时间精确度,只求备份域寄存器可用。
) c) F% j, k) Y5 m* ?; p
0 K7 L3 i- z, ]' `8 C9 e
) k( L4 F* F! o1 H1 g5 j
* i/ ~) y* z, L# q' ~6 h然后我们将 USB 类选择 “Custom Human Interface Device(HID)”,USB 中断等级设置为 1,堆为0x500,栈大小设置为 0x2000,然后生成工程。2 b+ L \2 D3 O$ E( H
* k5 {3 n. k6 F+ F% g8 I打开 usbd_custom_hid_if.c 文件,找到 CUSTOM_HID_ReportDesc_FS,修改报告描述符为:5 Q6 Q, m5 ]$ i- ~
- #define USBD_CUSTOM_HID_REPORT_DESC_SIZE 239 l5 g7 c$ O! {8 N9 `: u e5 c
- /** Usb HID report descriptor. */
1 M- ~1 ~: A6 x - __ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE]; z" H' W' q( r& c; m0 G& j
- __ALIGN_END =$ S+ O+ l( [6 I# v6 f: g
- {
' B- H, c/ m0 q! X9 i% C - /* USER CODE BEGIN 0 */5 M4 j) w, o" F) X+ H: P9 F
- 0x06, 0x00, 0xFF, /* USAGE_PAGE (Vendor Page: 0xFF00) */
- ~1 z( D; |7 F5 R - 0x09, 0x01, /* USAGE (Demo Kit) */
% F, j! Z8 b# b0 C3 T- m! P7 E* X ~; O8 X - 0xa1, 0x00, /* COLLECTION (Physical) */& {% n3 j' v# X+ @; s% r, s
- 0x85, 0x80, /* REPORT_ID (128) */$ x* C5 O3 a: |: j m
- 0x09, 0x55, /*USAGE (LED 1) */8 ?# i& ]" `6 p/ \# b5 H0 {7 w! d
- 0x15, 0x00, /* LOGICAL_MINIMUM (0) */
7 z5 b" I7 ?7 Z" n5 y - 0x26, 0xFF, 0x00, /* LOGICAL_MAXIMUM (255) */8 \# j3 ]0 r7 d7 q
- 0x75, 0x08, /* REPORT_SIZE (8 bits) */
/ G# n S- n' C, a9 W - 0x95, 0x01, /* REPORT_COUNT (1) */0 X! a# g7 v- ]0 }! d* b- g, E2 u Z
- 0xB1, 0x82, /* FEATURE (Data,Var,Abs,Vol */
; n6 F4 Q9 Y! v" g - /* USER CODE END 0 */
7 e- a% J! B6 R4 t - 0xC0 /* END_COLLECTION */
% K2 C/ h/ @8 O5 w4 i - };
复制代码 & u. R) }5 q- H1 J) z
然后找到 CUSTOM_HID_OutEvent_FS 函数,修改为:, O! O" }- ~3 X, v) O
- static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)! V Q. m) W# N
- {" Q: ~; `1 ~1 |2 I
- /* USER CODE BEGIN 6 */" e5 W! H! \2 q8 T1 E
- extern RTC_HandleTypeDef hrtc;
# g9 A# B( K7 Z5 u2 `0 E - if(event_idx ==0x80 && state ==0x55)! k4 I& W5 N* _& m
- {
' s% X0 j9 n7 e; R: S3 T1 R- j: N - __HAL_RCC_PWR_CLK_ENABLE();
# u/ m# ]5 b9 U' |1 Y; K4 }5 m - HAL_PWR_EnableBkUpAccess();: i& A, r! b b3 o* {
- HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR0,0x32F2);8 k8 S7 A! y1 [9 N' `
- HAL_PWR_DisableBkUpAccess();
( M3 f! l1 b* U) u' t- b2 @ - HAL_NVIC_SystemReset();. m6 z/ |" _9 ]* X) G- N
- while (1);; w: Q. r& E$ L5 D* ^- ~
- }0 w- ]5 [8 p$ U* Q- p2 J6 {
- return (USBD_OK);/ Z7 ~& O$ n/ @; u7 B7 ]
- /* USER CODE END 6 */
8 r4 A& ?# G8 `1 F, v) t8 d - }
复制代码 5 M: X6 a/ [& q" j
如上代码所示,只有当 PC 端软件 DfuSe Demo 检测到具有这个报告描述符的 HID 设备后才会认可其为满足要求的 HID 设备,并将其显示在设备列表中,按键“Enter DFU mode/HID detach”激活。当用户按下此按键后,DfuSe Demo 会向 HID 设备发送一条 SetFeature 指令。
( p+ m* U' p! i# s- p/ A
" S1 N7 [0 O- y f当 MCU 收到 SetFeature 指令后,往备份域寄存器写入 0x32F2 作为进入 DFU 模式的标志,然后重启切换到 BOOT,接下来在 BOOT 内如果程序检测到备份域寄存器的标志时则进入到 DFU 模式。整个过程如图 2 所示。
* g) s2 B5 k) O+ A
K3 O3 v) y2 A! a7 G `5 x3 g1 Z. i当然,APP 还需要修改 FLASH 中的偏移位置,通过工具烧录到 0x0800 7000 的位置,这个在之前 DFU 培训时已经讲述过如何操作了,这里就不具体再重复了,有兴趣的同学可以查看此文档附件内的 APP 源码。: J0 i( N2 |: F. R- B# o0 s
+ c" M3 ^6 q( ^4 W( K接下来我们再来看看 BOOT 程序中检测跳转标志的过程:& @# T" k' }6 l) o9 O% h! b0 i
- /* USER CODE BEGIN 2 */& t* p+ E# ^* h E3 f
- if (((*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD) & 0x2FFE0000 ) != 0x20000000)# f9 ]! T f, d) h7 k2 K
- {
5 @" k5 {: _0 e: l1 s0 H - jump2App =JUMP_FLAG_DFU;
/ P4 L) W& N O# L7 u% } - }
, ]# M b& |% Q, v. ?) \) K - if(jump2App !=JUMP_FLAG_DFU)
6 `+ ?% Z' H: O& d+ ^ - {/ T3 L z) e4 @# F
- __HAL_RCC_PWR_CLK_ENABLE();; o& H. c; m8 o2 D6 \3 i0 ]3 s" B
- HAL_PWR_EnableBkUpAccess();
H! A0 U- Z: x/ a" } N - if(0x32F2 ==HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR0))//检测备份域寄存器中的标志
5 D+ @0 r9 d' e/ ~ - {7 u5 @ Z* L9 _
- HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR0,0);* t( a9 T' z+ Y- z
- jump2App = JUMP_FLAG_DFU;
! I# ]. Y2 v% R! H - }
+ L) ]0 R6 [7 O- U$ G, K1 n - HAL_PWR_DisableBkUpAccess();; {$ f3 C4 H! R7 o# z3 d' H# A
- }
% x) R/ _6 ?* w0 z - if(jump2App == JUMP_FLAG_INIT)" o+ Y/ v* } M8 O, e
- {
0 }. o T, E1 w0 A - if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) !=GPIO_PIN_SET)
: f) Q- f& p6 v7 \) d - {
: x" w, R) u% ~$ z! f0 q - jump2App = JUMP_FLAG_APP;6 G( T4 T) N: t: u$ m
- }
; i- g# {6 j8 Q: b: D2 { - else
& _% K% R& ~; o2 i# e) V& D; _5 |4 ` - {; d1 ^5 I2 S# ]# j! A
- jump2App =JUMP_FLAG_DFU ;9 ]( u8 A- _+ p" P/ M- C1 y
- }. r6 c" m5 d$ v. a9 c. w! E
- }7 N3 Q& F0 j4 |& z) R% p2 b
- if(jump2App ==JUMP_FLAG_APP)
, H, U( |& K _& o7 Z3 I5 ?4 k - {3 ^+ Z" G- B/ C: T
- /* Jump to user application */
8 r6 @4 p! x1 q% a - JumpAddress = *(__IO uint32_t*) (USBD_DFU_APP_DEFAULT_ADD + 4);
/ R" u1 ]& W" [! H6 R. X - JumpToApplication = (pFunction) JumpAddress;
. n' w! ~, U: L% A - /* Initialize user application's Stack Pointer */& e1 Z2 ^+ ~& y9 U) W# u
- __set_MSP(*(__IO uint32_t*) USBD_DFU_APP_DEFAULT_ADD);- D# f% D5 n) N) m
- JumpToApplication();
: i2 J- v4 Y* t- V4 S7 f - }/ Y0 d+ y5 }4 K5 h8 w* Q* L5 M
- //进入到 DFU 模式$ _- y1 `% o6 ]7 g# D& H/ p2 O! N" v
- MX_USB_DEVICE_Init();+ S+ r- I! }: ]) h! L& d
- /* USER CODE END 2 */
复制代码
4 r0 @5 X% p0 k" |+ a# E t, w如上代码所示,只有当 jump2App ==JUMP_FLAG_APP 时程序才会跳转到 APP 中运行,否则进入到 DFU 模式。9 D; t' `' V6 I2 I- u3 e
" k( |( o, `1 T- g5 i- b. O9 h
, H$ u$ {# I/ E4 总结! V9 }* S: q; [
使用此方法毕竟在 APP 中实现了一套 HID,如果不是必要的话,建议还是使用按键的方式来触发从 APP 跳转到 DFU 模式,除非遇到像本文中客户的情况,或者 APP 本身就需要实现某个 USB 类的功能,此时可以做成 USB 复合设备,其中一个 HID设备就是本文中的 HID 设备,这样 PC 端的软件 DfuSe Demo 也可以识别。
4 { x* L; j$ R
$ E5 ^% ^6 B- `; y! M |