你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于STM32F103自制CMSIS-DAP下载器

[复制链接]
STMCU-管管 发布时间:2020-12-2 12:35
基于STM32F103自制CMSIS-DAP下载器

) D& {7 P: @2 L# L2 l+ `# Z
( W' [& l) v1 {4 |  v
市面上针对Cortex-M处理器的下载器,有很多是基于CMSIS-DAP演变而来,比如:e-Link、GD-Link等。6 g; l" Y5 Y9 _5 a+ N* O, v

# g: E! v+ A7 U6 e, k' X# p

) K0 H* Y; h& T/ u之前给大家分享过自制ST-Link的教程,今天继续为大家分享一篇:基于STM32F103C8,自制CMSIS-DAP下载器。
3 |6 J( S- @. |& \% M' }1 Y# m9 X( g
. f$ u6 b5 h+ g7 Q
8 N3 o2 y2 A; {

7 F$ k0 G. _' E5 ?' O6 c
. W* Q# I9 G/ |) Z, J; t- I8 t
1关于CMSIS-DAP8 l9 e: @3 H. P0 P
CMSIS-DAP是支持访问 CoreSight 调试访问端口(DAP)的固件规范和实现,以及各种Cortex处理器提供CoreSight调试和跟踪。: _4 x% _, g2 h0 j
11.png
地址:
8 Z  o  j' i, c" E! n1 fhttps://arm-software.github.io/CMSIS_5/DAP/html/index.html- @8 G1 Q/ ~4 v  {8 s

3 G9 u* l$ |6 A& t+ ^CMSIS-DAP固件作为源代码提供,并且可以完全配置为新的调试单元。7 u+ p$ r; n6 |) x
12.png
这里相关的更多内容,可以参看我之前分享过的一篇文章:Cortex-M软件接口标准CMSIS那些重要内容。. z  H$ i+ L& c5 g
, \3 {) m3 {; k5 q8 ]& C8 \
2CMSIS-DAP固件; ~' S9 n, \& g1 e* H! r3 U
CMSIS-DAP固件Arm以源码形式提供,不存在版权问题(因为针对Arm Cortex处理器,他们还希望更多人使用)。
& X/ R% _1 \5 L2 \: f9 K
: F, I; t: ~. K' {$ f& W  }1 P2 W

) t- ~6 l# G6 a* X4 U7 E. E" r
6 {1 B. `( P1 U/ O- m/ Z
1.固件版本
/ _5 F4 y* [: p7 `  [( h* }% F目前有两个版本:
# ~' Y3 l7 h. U版本1配置使用USB HID作为与主机PC的接口。  Z+ ^& b2 q# u" d) k" W$ E
版本2配置使用WinUSB作为与主机PC的接口,并提供高速SWO跟踪流。
1 f  r5 V# Y! e5 e( {: s4 @; V! {! F

  h" E- F, q, w" e: H) [6 H2.源码位置5 J. V5 o$ F) y" A+ x, r7 x0 S
目前源码提供在Keil MDK V5版本,安装好Keil MDK,你在安装目录下就能找到源码。. ]+ B' R& O* T8 R1 h1 `! c7 N
% C0 t* M8 _2 z) @7 Y0 l4 A3 s  N0 }) k
' `/ ?7 ]" o( V
C:\Keil_v5\ARM\Packs\ARM\CMSIS\5.7.0\CMSIS\DAP\Firmware
8 c$ Z6 Y* Y. ]8 ]. M" L- k% M(目前MDK V5.33,CMSIS版本为5.7.0)
. N# \! S' q2 _1 d3 C- u9 g1 g
13.png
3.源码描述
% G7 @/ C+ \7 E6 m$ J  z5 i4 `; b7 ~/ v# C9 }) _

  X( c* L- S* C! \' M6 a" C) y5 J从文件目录可以看出,官方源码提供了一些模板和例子。: ]: ~# q3 J+ u

9 H( ?: p+ t1 e6 B8 z5 a( e目前只提供了LPC处理器的例子,如果你有这个处理器对应的板卡,可以直接使用该源码做一个下载调试器。(下面就针对于LPC这个例子进行“改装”)
0 w( c+ \6 d# J0 I- ?/ J/ \2 \  V0 A( B+ {9 T
3配置
: ^$ z# J% Z. X, @, [# S3 F4 w利用STM32CubeMX图形化配置工具,帮助用户选择单片机引脚的功能,并自动生成外设初始化代码。配置了USB、SPI1和USART1,并选择了USB的Custom HID middleware模式。GPIOB10到GPIO15被配置为JTAG调试需要的引脚。GPIOC13用于驱动单片机上的LED灯。' s/ s) a( M# Y# j, p! X) I
14.png
ST公司也开发了他们自己的JTAG调试器——STLink。当然它并不是必要的,你也可以使用J-Link或者其他种类的调试器。STLink的驱动和程序可以在ST官网上下载。在网站里还有一个基于Eclipse开发环境开发的IDE,STM32CubeMX也被包含其中。我选择的IDE是基于CodeBlocks的Embitz,IDE中的arm_none_eabi_gcc版本是5.4.1。在我完成我的CMSIS-DAP之前,我必须使用STLink来调试我的代码。现在我在使用新做出来的CMSIS-DAP结合OpenOCD进行日常的开发。
: B. X) a; V8 r- p( H* B) b( `
7 Y( y' @1 p1 B$ s4从CMSIS-DAP的源码开始
6 E$ z* \- A) b0 C源码可以在官网下载:- O' A; T8 X; f- T

- v- y: _/ Z0 Z1 Y
& F7 `: K* [- m: Y) }: P5 w( D
https://github.com/ARM-software/CMSIS_56 l: d& V9 y- S" a

: Z" f$ r1 S% v1 @也可以直接在 Keil MDK 安装目录下获取:
' Z4 d3 d5 A9 U' J+ {) o+ q
1 e# V1 ~$ K& c
  \0 _. ~6 Q3 l! H$ k9 d+ ^. q. Z# ]0 @
C:\Keil_v5\ARM\Packs\ARM\CMSIS\5.7.0\CMSIS\DAP0 T5 P& b& |4 B. P; {# Q; X
9 Z4 }8 j" |6 I9 ?- O; \& `
将从示例V1的头文件 DAP_config.h 开始分析。
, y% G, Y( t. X1 ~( J7 q6 F+ W
( q6 \0 N2 y, u* r" ?+ @; z选择LPC-Link-II V1作为我的参考是因为它是通过USB HID实现的(V2是通过WinUSB实现)。我分析的第一个文件是DAP_Config.h。第一个关键位置如下:2 b1 e7 Z" y; a1 ?6 e4 v
  1. . O( `; G7 j5 o9 Y8 A0 F4 L
  2. #ifdef _RTE_
    4 Q' g: D% e5 g- ~4 J" j  N
  3. #include "RTE_Components.h"5 B5 B& B4 n0 g/ ?$ ?" i4 N- S
  4. #include CMSIS_device_header
    . K; k$ g) J+ G, E: D! _6 P( T7 w
  5. #else
    8 ~% x9 R9 n. @( k+ X$ @1 j& `
  6. #include "device.h"  ' J5 [8 a1 [2 `8 _9 s
  7. #endif
复制代码
不用RTE的相关文件,创建我自己的device.h。1 [2 K" G  v1 k3 W0 V. x

; F  R; i9 w: {+ r* e/ n' z2 m5 s
9 D  ^! u- L" {. \+ k$ X6 A
我将参数CPU_CLOCK重定义为72000000(72MHz)。根据文件里的注释,参数DAP_PACKET_SIZE必须重新定义为64U。我把SWO_UART改为0,这让我的工作轻松不少。参数TIMESTAMP_CLOCK也要重定义为72000000。LPC-Link-II使用Cortex-M3 的 DWT模块实现时间戳(TIMESTAMP),这也是为什么我想在CubeMX中尝试配置STM32F103的DWT。最后我自己写了一小段代码来实现这个功能(在device.c中):
! ?8 K6 ]6 O$ b, H
  1. CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    4 M7 x- z. c( L/ {7 p( t
  2. /**4 u: Y' B) z/ z0 \  z+ g
  3.   * On Cortex-M7 core there is a LAR register in DWT domain.
    * w3 R/ B7 a" C, r5 R) @- m
  4.   * Any time we need to setup DWT registers, we MUST write
    9 |+ F, ^% E$ a" ~- f
  5.   * 0xC5ACCE55 into LAR first. LAR means Lock Access Register.: S5 v3 f, @" ~2 i0 D* L
  6.   */
      S2 ]' F  P2 w$ |: G
  7. DWT->CYCCNT = 0;. K, r  P) m8 x( A8 p
  8. DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
复制代码
我定义的引脚和NXP LPCxx的完全不同。我为STM32F103重写了所有的引脚的操作代码。在DAP_Config.h这个文件中还有一些奇怪的地方,比如:
8 Z0 t( ~4 L& F$ v) w
  1. // SWCLK/TCK I/O pin -------------------------------------# X) i2 H/ R4 A0 M$ |' |6 V% C
  2. : `7 G; ?/ E6 m$ D* g( e
  3. /** SWCLK/TCK I/O pin: Get Input.
    . t: f7 H7 T3 H' |; p* B) T
  4. \return Current status of the SWCLK/TCK DAP hardware I/O pin.1 X( R$ I9 q1 y) L; |4 W
  5. */
    , p8 p. @; U" n; U( m
  6. __STATIC_FORCEINLINE uint32_t PIN_SWCLK_TCK_IN  (void) {( c* S. h: Q7 V  ~1 Z
  7.   return ((LPC_GPIO_PORT->PIN[PIN_SWCLK_TCK_PORT]>> PIN_SWCLK_TCK_BIT) & 1U);' ?) E. Z% Q$ e7 l
  8. }
复制代码
我不明白为什么DAP的代码里需要读取SWCLK/TCK引脚的电平,这个引脚明明是被配置为推挽输出来产生时钟信号输送给JTAG从机的。从上面列出来的代码可以看出,它是希望返回当前引脚的电平值,我的实现方式稍微有点不同:
8 _( R( ]7 R* P1 @# d
  1. __STATIC_FORCEINLINE uint32_t PIN_SWCLK_TCK_IN  (void) {
    0 x8 X6 r1 T3 Z- ^& D
  2.   return (uint32_t)(JTAG_TCK_GPIO_Port->ODR & JTAG_TCK_Pin ? 1:0);% b) u" G' n+ Q$ H+ S7 q0 i7 ~
  3. }
复制代码
我返回的是当前引脚的输出值。我不确定这是否正确。在整个代码中,这句话只被DAP.c中的一个叫DAP_SWJ_Pins的函数调用了两次。我猜测DAP_SWJ_Pins这个函数是用来测试IO口是否工作正常的。
: \% E' `2 K1 t1 D% X9 N) N
; W4 r# z; z+ D/ S7 o3 g' q

, A$ u0 t5 V1 M+ w另一个奇怪的地方是PIN_nRESET_OUT:+ o* O4 b5 a% i/ P; Q# U; ^
  1. /** nRESET I/O pin: Set Output.
    4 r/ B* t! L  D/ D3 e' f
  2. \param bit target device hardware reset pin status:( {/ D* j) u/ M2 ?  R' r* l+ ]
  3.            - 0: issue a device hardware reset.
    - Z4 ?' F8 M) ?; I: B3 f  s
  4.            - 1: release device hardware reset.1 V0 e% u. k1 s  x% h1 N" {9 Q
  5. */
    9 ]/ m  L! c7 x; m; ~' n
  6. __STATIC_FORCEINLINE void     PIN_nRESET_OUT (uint32_t bit) {
    ; r$ Q$ C2 b6 P4 w2 G! h
  7.   if (bit) {/ q! L$ y, v4 U
  8.     LPC_GPIO_PORT->DIR[PIN_nRESET_PORT]    &= ~(1U <<PIN_nRESET_BIT);
    7 x7 @' i* T  U% Z  h. M
  9.     LPC_GPIO_PORT->CLR[PIN_nRESET_OE_PORT]  =  (1U <<PIN_nRESET_OE_BIT);
    & t$ B7 F% v2 B4 Y
  10.   } else {3 ^9 t0 }% |) ?! @- b3 R' e
  11.     LPC_GPIO_PORT->SET[PIN_nRESET_OE_PORT]  =  (1U <<PIN_nRESET_OE_BIT);
    * p' k2 t$ c* P% h* w( W" g
  12.     LPC_GPIO_PORT->DIR[PIN_nRESET_PORT]    |=  (1U <<PIN_nRESET_BIT);% C5 |. V( X- @0 f: ^. b$ B
  13.   }* q  D- V4 A/ e  l+ y9 e1 ^
  14. }
复制代码
为猜测release的意思可能是将nRESET引脚重新配置为一个失能的引脚。我的代码如下:
2 E  Y) e% |- B5 b7 I; f& Z
  1. __STATIC_FORCEINLINE void     PIN_nRESET_OUT (uint32_t bit) {0 @9 Y3 Y9 W' E6 C
  2.   GPIO_InitTypeDef GPIO_InitStruct = {0};
    . \( f& y. g& n) G. W1 j

  3. ; S# g" _" z+ j6 h/ N4 G; `
  4.   if ((bit & 1U) == 1) {
    / ?+ R- T$ h, f# a
  5.     JTAG_nRESET_GPIO_Port->BSRR = JTAG_nRESET_Pin;4 H: K; s/ }% y$ F1 F

  6. ! D* D& `: a' G5 u
  7.     GPIO_InitStruct.Pin = JTAG_nRESET_Pin;
    / n% z, u; o/ u9 L
  8.     GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    # |! {) g1 H! G5 y0 ^2 x% R& c
  9.     GPIO_InitStruct.Pull = GPIO_NOPULL;
    ' J8 ~. H( `7 n) t
  10.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    1 @9 @! ^, f1 O) u; p- a* D$ `
  11.     HAL_GPIO_Init(JTAG_nRESET_GPIO_Port, &GPIO_InitStruct);- Z7 w3 ?+ {9 R" o/ K+ H
  12.   } else {
    : N- |" {# d1 V$ A5 W
  13.     JTAG_nRESET_GPIO_Port->BRR = JTAG_nRESET_Pin;
    ) ^. Q/ G7 N# D! O( k
  14. : ^: D: Y" e  e& A* j
  15.     GPIO_InitStruct.Pin = JTAG_nRESET_Pin;
    : Z/ |2 K9 f& ]$ a. [/ M5 Y
  16.     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    6 J3 @/ u2 V8 N& ]
  17.     GPIO_InitStruct.Pull = GPIO_PULLUP;5 w# a& x& w; l" ^( Q
  18.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;! I7 s. w) G, e  }1 z0 U! e
  19.     HAL_GPIO_Init(JTAG_nRESET_GPIO_Port, &GPIO_InitStruct);
    3 U+ J$ B1 y* S0 S+ H3 L, F
  20.   }" P0 z+ f% v2 T; z& {
  21. }
复制代码
给你一个特殊的提示:
' i0 @% k; l5 L& K' ~& U1 n2 o1 `0 z; u
9 M, Q! i3 \. j
你可能在一些地方看见我写了if(...)之类的代码,像下面这样:
! Z* `3 K2 E4 N
  1.   if ((bit & 1U) == 1) {5 ]: j5 ~; n% r$ u2 a. T+ }
  2.   ...
    " B5 {# {/ m4 F& R3 |
  3.   } else {
    * [7 R. q  @" j5 n! l7 j% d
  4.   ...
    : s( I. |# P5 N* V
  5.   }
复制代码
我使用参数bit的最低位是因为有时候这个bit既不是0也不是1,它可能是2或者其他奇怪的值,所以不要把你的代码写成这样:; ]: J2 Q: |" m& G6 M. |6 x
  1.   if (bit) {" o$ L- D3 y  A, N; ^8 a
  2.   ...
    , B; E, j9 Q% o8 U! ~' `
  3.   } else {* H( a* \' n7 g! H5 v+ ^# W
  4.   ...
      _, V3 o" V8 i. i; g
  5.   }
复制代码

' y: S& ?8 a2 P$ `8 \# \这会出问题的!
3 R% Z7 V5 Q: j1 H/ q
( q: H" F3 b6 d) j% V, O

8 V; H& D4 t9 {0 I: |4 ?
% m# H9 C7 N. o& [) A5 UosObjects.h  E. |2 V2 _- Y3 A
  1. #ifndef __osObjects_h__3 g3 O8 ]. Y# M% l$ N$ L
  2. #define __osObjects_h__1 j: \  M2 Y& E

  3. % @6 f( ?% H- T
  4. #include "cmsis_os2.h"
    # e6 ]1 a8 H/ Q; ]

  5. 3 Y' g; u( e- H& \  a5 y: \# O
  6. #ifdef osObjectsExternal$ v( S: h5 F1 W
  7. extern osThreadId_t DAP_ThreadId;
    ' `( m, G. L: g( m- B& v2 R
  8. #else4 x5 V3 z4 z5 F8 a5 J
  9. extern osThreadId_t DAP_ThreadId;% X: \5 B/ k& Q
  10.        osThreadId_t DAP_ThreadId;% I3 b+ N2 w; a
  11. #endif
    + t' u2 v) ?: t4 I9 R; l
  12. 0 G8 U2 ]9 ^( ]+ v( r$ b& D( B2 x
  13. extern void DAP_Thread (void *argument);
    , U* U4 N% t! I' m! Y5 ^2 N) P

  14.   i9 d/ X$ d* U+ T
  15. extern void app_main (void *argument);6 {; W6 a4 r8 q( N( N" t
  16. - v' N0 _; B2 h8 v. o) _
  17. #endif  /* __osObjects_h__ */
复制代码
这是一个很简单的头文件。但它引用了另一个叫做cmsis_os2.h的头文件,这是CMSIS库的一部分,但我没有没有从CMSIS库中复制到我的工程中,因为并不是其中所有的内容我都需要。我选择写一个“假”的cmsis_os2.h头文件而不是直接使用原有的头文件。这里还有另一个叫做DAP.h的头文件,它属于DAP的核心模块,在这里面引用了cmsis_compiler.h文件,这也是CMSIS库的一部分。毫无疑问,我也写了一个“假”的cmsis_compiler.h。分析到现在,我需要创建三个头文件(device.h&cmsis_os2.h&cmsis_compiler.h)来实现我的DAP工程。2 C! K) p0 B6 l

- k. x+ Z1 O( C3 m6 S
# z, k6 K" I% u
接下来我会对main.c和USBD_User_HID_0.c做一些一些简单的介绍。, V% B1 Y* I! y( R6 M" E, W& {+ {9 }& y

1 `. U* F: }, w% Q4 T2 C+ N: k
' R( S; ?- `3 |6 B9 Y
main.c1 z! x' l3 d4 z6 y4 Q) o2 P

' ]* k, r! j3 K: q% E; U

+ S: I0 ?7 O, Z% G我在尽可能地精简我下载下来的这些源文件,所以我也没有要示例工程中的rl_usb.h文件。于是我还需要一个头文件来定义一些关于USB通信的函数和参数。这里有一些来自CMSIS RTOS库的函数,其中最重要的一个是osThreadNew,在我的工程中我把它实现如下:# s! T& ^" P! ?# i$ r* R
  1. osThreadId_t osThreadNew(void (*func)(void *), void * n, void * ctx)
    9 n  K3 d* w, b" e
  2. {/ v5 c4 J9 D6 X; n% Q6 A
  3.   (void)n;/ T0 g6 `# H) t$ G2 l# e$ S

  4. * a% @9 l# S0 V8 _  ]
  5.   (*func)(ctx);+ Q6 }7 ?$ y# b. Y# M
  6.   return 0;8 ^2 w5 s1 n- V
  7. }
复制代码
我直接“跳转”到本需要被创建的线程函数中,这就意味着main.c中的osKernelGetState&osKernelStart&osDelay三个函数永远不会被执行。下一个重要的函数是USBD_Configured,我将在使用STM32CubeMX生成初始化代码那一节解释这个函数。
. K4 A' @5 ?0 E1 v' r1 S+ f( D+ e7 h0 M, m/ c* q9 @8 i' g, D
0 b$ k! }" ], W) ~9 |
USBD_User_HID_0.c* E' N: K5 j' r$ h4 L6 c# j

. B! ?5 L8 x, A  a; t
9 Q! ?4 A" T8 `! Y& N. h+ t0 z5 f
我移除了RTE\USB\USBD_Config_HID_0.h并在我自己的rl_usb.h中重新定义了USBD_HID0_OUT_REPORT_MAX_SZ & USBD_HID0_IN_REPORT_MAX_SZ两个参数。3 H; O; b7 @6 r* O. X3 Z
9 P3 t0 Q- t( v! N9 b

- V6 [6 |2 m7 o: B! m6 b, {USB HID通信的核心是由两个接口中断函数管理的两个循环队列:
4 S/ Z$ x- R+ |8 A
  1. int32_t USBD_HID0_GetReport (uint8_t rtype, uint8_t req, uint8_t rid, uint8_t *buf) {
    ! `: ^- y. L1 @% y1 R7 Q
  2.   (void)rid;
    5 b* q6 {3 G9 V$ W

  3. 2 W- I9 |- ~& ]5 s6 m* x
  4.   switch (rtype) {
    : {% f, v1 m: A/ J- G- n
  5.     case HID_REPORT_INPUT:
    7 I2 d+ ]1 D$ a% H/ }
  6.     ...* V+ b" ~3 a5 c* d; A, }" q: N
  7.       break;; p2 Y: I& y: ^+ M" b8 I
  8.   }% S1 G# |7 n- ^
  9.   return (0);, Y/ Z3 h7 J7 W+ I9 K1 k
  10. }
复制代码
  1. bool USBD_HID0_SetReport (uint8_t rtype, uint8_t req, uint8_t rid, const uint8_t *buf, int32_t len) {
    & M' c( {6 T$ V& Z3 v/ x) \$ z7 f
  2.   (void)req;
    6 n4 m. j+ `2 |5 a* s: z/ N
  3.   (void)rid;# `- S) v* [  H( m& }) @

  4. 6 ^' B, H3 x4 t# |/ }( b
  5.   switch (rtype) {. }+ q0 O1 k+ Q5 [
  6.     case HID_REPORT_OUTPUT:$ J3 J( s* h5 K1 l7 ^
  7.     ...( _  _3 |$ C! o: r2 j+ Q
  8.       break;
    : y' q9 \0 F7 ~2 T& Z* o
  9.   }
    ( `' Q" G; i7 W  z( o: `
  10.   return true;
    ' L8 z( T  i5 h+ P4 p5 w7 W
  11. }
复制代码
当上位机向DAP发送OUTPUT REPORT报文后,DAP会调用USBD_HID0_SetReport函数,该参数的输入形参rtype必须为HID_REPORT_OUTPUT。当DAP成功向上位机发送INPUT REPORT报文时,函数USB_HID0_GetReport被调用,该函数的输入形参rtype必须为HID_REPORT_INPUT,并且形参req必须为USBD_HID_REQ_EP_INT。这意味着我们所有的报文必须通过64B数据包大小的USB中断端点传输。7 Y4 y. ~( S# l/ B( L
  ~* s0 H5 p  m9 m, [
, g- s3 l1 e+ F9 \- M1 w
线程DAP_Thread只是一个命令判断选择器。在这个函数中有一个很重要的语句:5 m4 J0 k$ R/ G6 q; G
  1. USBD_HID_GetReportTrigger(0U, 0U, USB_Response[n], DAP_PACKET_SIZE);
复制代码
我们必须实现一个叫做USBD_HID_GetReportTrigger的函数来想上位机发送INPUT REPORT。, Z( `5 M3 g) f( |: K- D

' ?+ h0 Z0 K- W9 ]% I4 V& O0 ]  F
  I- h- [4 o, t8 C6 h

, c$ X, c9 A0 s, }9 d# ^, ^/ h

2 E  ]: n8 l1 W$ |& L  Q6 ?! H
6 J: o' J# r/ |4 E2 H: O  Y  X
5 |* I3 d9 M0 T. m
5使用STM32CubeMX生成初始化代码; v; h* O7 i8 k5 n
在我的单片机上有一个8MHz的晶振,所以我选择HSE为时钟信号源。PLLMul配置为x9,得到72MHz的PLLCLK,提供给CPU和AHB/APB2总线,提供给APB1总线的PCLK1配置为36MHz。USB预分频配置为1.5分频,得到48MHz的USB时钟。SPI1配置为Full-Duplex Master,舍去NSS信号,USART1配置为Asynchronous。USB设备进一步配置为Custom HID Class,USBD_CUSTOMHID_OUTREPORT_BUF_SIZE设置为64 Bytes。- J/ t3 Y* e& r& O( R' D

9 G0 f% x7 U; U" z  n% @$ r9 D
, F5 U+ v. ~2 C
注意:
! u7 _4 k0 A, S% h* b我没有修改设备的VID和PID。但我猜测有些上位机软件会检测这两个ID  W5 a; l8 l' g

  ]& [: u; `5 o& y; p1 Y* D
- {% C; U+ Q6 j5 X, p* y3 p
如果你发现你的软件不能识别我这个CMSIS-DAP,或许你需要恰当的VID和PID。可以试试示例代码中的VID/PID,它在一个叫做USBD_Config_0.c的文件中,我的工程中没有这个文件。
5 p5 E: F) S* P5 H! c
. J. H. h: F6 G- G! l# O
& k! H! J* O  I9 e% r
有STM32CubeMX生成的代码需要一些修改。在usbd_customhid.h中,CUSTOM_HID_EPIN_SIZE和CUSTOM_HID_EPOUT_SIZE必须设置为0x40U。我把CUSTOM_HID_FS_BINTERVAL改为0x01来尝试提升HID的通信速度。1 P- L5 ]' y8 H' V5 D  i1 l
0 g! w4 {# B; O7 Z0 X

* c) ~! P3 F# N/ U- x& Z( C在_USBD_CUSTOM_HID_Itf结构体中,我新增了一个成员:
8 Z; a, }. i. z- b: ^7 a3 s
  1. typedef struct _USBD_CUSTOM_HID_Itf' T/ }! _2 W& f4 k/ ^
  2. {0 Y8 R- s, }0 ^- j% M
  3.   uint8_t                  *pReport;4 b" x0 Z2 O5 X4 \8 U
  4.   int8_t (* Init)(void);
    & Q- i8 A. _) Y' J
  5.   int8_t (* DeInit)(void);8 \) [  c$ s! T; V% o' \7 j( n
  6.   int8_t (* OutEvent)(uint8_t event_idx, uint8_t state);
    . }! g) N, n  m. Q/ i* s
  7.   /* I add an extra interface func below. Zach Lee */
    2 N1 r6 W$ G) |  R
  8.   int8_t (* InEvent)(uint8_t event_idx, uint8_t state);
    - J5 z: K2 u9 X8 ?
  9. } USBD_CUSTOM_HID_ItfTypeDef;
复制代码
当INPUT REPORT报文成功发给上位机时,InEvent函数应当被调用,所以usbd_customhid.c中的USBD_CUSTOM_HID_DataIn函数需要修改如下:5 c& J2 R7 G" I9 e% A" w/ M
  1. static uint8_t  USBD_CUSTOM_HID_DataIn(USBD_HandleTypeDef *pdev,
    6 z7 B# @7 z- E; D. \+ Y
  2.                                        uint8_t epnum)# i& B. [; K( ?& l, N1 ]
  3. {' ^' |2 f2 }7 R: _+ S% X
  4.   /* Ensure that the FIFO is empty before a new transfer, this condition could5 \6 B4 D0 f. D1 U+ ~6 z3 X! X
  5.   be caused by  a new transfer before the end of the previous transfer */
    ' p, q/ [: Y  c* E  G" Z
  6.   USBD_CUSTOM_HID_HandleTypeDef     *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassData;
    . N7 U1 G7 e( O+ j# D
  7.   hhid->state = CUSTOM_HID_IDLE;! f9 z  X& q: O9 |/ @' M% b" _6 a

  8. % u: e7 w+ T. w9 w
  9.   /* I add a new interface func in the structure USBD_CUSTOM_HID_ItfTypeDef. Zach Lee */$ P( F4 G; v0 n4 G) }: X2 E
  10.   ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->InEvent(hhid->Report_buf[0],
    7 R+ }* a; s2 i- t8 P8 V3 W1 t
  11.                                                            hhid->Report_buf[1]);
    ( f, n# w) W; T  P. O
  12. 0 Q$ m( V9 {3 k% P2 I, K& P
  13.   return USBD_OK;' v' m1 E5 m$ k, [
  14. }
复制代码
设备描述符CUSTOM_HID_ReportDesc_FS被定义在usbd_suctom_hid_if.c中,我定义了一个简单的描述符:
( N; |) o4 M0 ^2 a
  1. /** Usb HID report descriptor. */5 M2 [+ y  |& L: D* X& d
  2. __ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
    0 s( n6 O( A, D2 c( C* H: ~" ^8 I
  3. {* L. B7 A$ E# C! Z5 K
  4.   /* USER CODE BEGIN 0 */ /* A minimal Report Desc with INPUT/OUTPUT/FEATURE report. Zach Lee */
    0 ]6 g3 ^# X1 V; U
  5.   0x06,0x00,0xFF,         /*  Usage Page (vendor defined) ($FF00) global */
    ! @4 t9 G3 z- j; d! ]
  6.   0x09,0x01,              /*  Usage (vendor defined) ($01) local */6 u) d4 S9 N; ~# Q: b
  7.   0xA1,0x01,              /*  Collection (Application) */' `7 v! }7 M( d# K+ g
  8.   0x15,0x00,              /*   LOGICAL_MINIMUM (0) */
    0 Q1 W2 |3 A* U9 p+ A7 `; C
  9.   0x25,0xFF,              /*   LOGICAL_MAXIMUM (255) */
    - S0 P7 `! N0 X# R! f
  10.   0x75,0x08,              /*   REPORT_SIZE (8bit) */
    . w# E: S) H2 a- T7 g: ]! A9 \& K
  11. % F4 b% c7 l  J& T3 ?2 h. p
  12.   // Input Report6 B% S1 p+ ], K7 f7 z7 s9 K
  13.   0x95,64,                /*   Report Length (64 REPORT_SIZE) */6 _* i; w6 Q& v* _+ n
  14.   0x09,0x01,              /*   USAGE (Vendor Usage 1) */
    9 j& }9 a* U5 d' J; O. H$ v
  15.   0x81,0x02,              /*   Input(data,var,absolute) */
    , Y2 S- w4 S9 f6 C
  16. 5 ^* g' \: G/ I/ i4 C
  17.   // Output Report
    . u) o9 _8 a, y' l
  18.   0x95,64,                /*   Report Length (64 REPORT_SIZE) */
    , [  @  \  M6 u7 U4 {! M/ y. G
  19.   0x09,0x01,              /*   USAGE (Vendor Usage 1) */8 a# U& U$ {: Y9 ]
  20.   0x91,0x02,              /*   Output(data,var,absolute) */
    ( |+ s2 O) b1 ^# Z) P

  21. 5 C  y" T* m( V. y( a
  22.   // Feature Report( ^9 d2 ^1 s* W0 {8 B' X8 r) O; a
  23.   0x95,64,                /*   Report Length (64 REPORT_SIZE) */# Z! u/ m1 u/ k/ Y+ W9 `
  24.   0x09,0x01,              /*   USAGE (Vendor Usage 1) */$ i: v3 H! O5 R: p( W% [
  25.   0xB1,0x02,              /*   Feature(data,var,absolute) */" `/ O% b) d' h' l" m
  26.   /* USER CODE END 0 */
    " A* m$ P, ~% d& `& G$ X# I: P
  27.   0xC0                    /*  END_COLLECTION                     */
    2 u( e! X% B6 X0 ?# h* K
  28. };
复制代码
可能Feature Report在CMSIS-DAP中不是必要的,就留着它吧。3 @$ p- E1 P9 E( N

4 u  a8 D4 a, }9 u1 U* g& p

" f: S5 Y6 |  W8 S( u. G5 g* l* }我在这个C文件中还实现了一个新的接口函数CUSTOM_HID_InEvent_FS:
1 g. p; p+ t8 F; F
  1. static int8_t CUSTOM_HID_InEvent_FS(uint8_t event_idx, uint8_t state);  /* An extra interface func. */
    6 W6 C* p. Z" v! D! d/ f. t1 @

  2. 8 E  ~4 I5 J- F) f$ s! l2 j; t6 m
  3. USBD_CUSTOM_HID_ItfTypeDef USBD_CustomHID_fops_FS =
    $ M* U( S9 f# h
  4. {# J1 v3 V6 C5 k& s! ~: ~) z, a7 z
  5.   CUSTOM_HID_ReportDesc_FS,
    / @* `  N# V* k% {1 r/ E# ~
  6.   CUSTOM_HID_Init_FS,5 S- ]0 i  `- T# ?& T- m6 v/ U
  7.   CUSTOM_HID_DeInit_FS,
    6 A, `1 d( ^. {2 {! t3 I1 ?5 m, J
  8.   CUSTOM_HID_OutEvent_FS,' C1 p6 S, A4 z/ o( f% }
  9.   /* I add an extra interface func below. Zach Lee */
    * r2 ?* \9 f0 r* i! ]/ @8 A
  10.   CUSTOM_HID_InEvent_FS
    & }* t5 Y4 T$ v0 A- r: \" M
  11. };
复制代码
  1. extern void USBD_OutEvent(void); /* Implemented in file "device.h" */: ~$ c- |" N, \

  2. ' [6 U, d  W3 P0 K) m/ o
  3. static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)
    0 L: }$ s& H0 V$ P9 }
  4. {: I" n/ l: l+ V
  5.   /* USER CODE BEGIN 6 */
    ; n- Q+ G+ _, A( r6 H3 U/ f! e
  6.   USBD_OutEvent();        /* OUTPUT REPORT was received. Zach Lee *// X& O5 n1 \+ y/ f9 X
  7.   return (USBD_OK);; t/ _3 ^5 a$ v* b8 {; w+ Q) B
  8.   /* USER CODE END 6 */6 s. Y" g; e% F  C9 ~8 D  ?' u
  9. }# K2 ~# y1 T1 E+ q2 x5 @: L, {' [
  10. & |2 i( w1 Q/ R3 s& N
  11. extern void USBD_InEvent(void); /* Implemented in file "device.h" */& B: P6 S" h4 Y# L5 M7 d; i

  12. # g& N$ Y3 `# T
  13. static int8_t CUSTOM_HID_InEvent_FS(uint8_t event_idx, uint8_t state)
    5 F) V$ K( J. c! D% m' i! Q
  14. {
    3 U. ]4 b, C3 h1 Z; h7 `
  15.   /* USER CODE BEGIN extra */* J; t2 ~3 n5 [. l  l
  16.   USBD_InEvent();       /* INPUT REPORT has been sent. Zach Lee */
    4 w2 B: |& _) e# {& k- c
  17.   return (USBD_OK);2 }/ [  [% c0 A" p/ v) m$ X
  18.   /* USER CODE END extra */
    ; T: o2 i' E) b# G
  19. }
复制代码
CUSTOM_HID_Init_FS和CUSTOM_HID_DeInit_FS两个函数被实现为使能/失能DAP功能:% ^/ K5 }, c) s0 z% b
  1. extern void USBD_HID0_Initialize (void);
    0 Y( m( n' B. R0 {: X
  2. . Y1 h# u% ?! k% U
  3. static int8_t CUSTOM_HID_Init_FS(void)5 c: G5 a) H# K" s9 I2 j  T. u
  4. {  w' B+ K8 x5 l5 C/ g$ }' K
  5.   /* USER CODE BEGIN 4 *// {9 K3 E# @+ g4 K1 m  {' p
  6.   USBD_HID0_Initialize(); /* Initialize USB communication of DAP. Zach Lee */
    5 b5 u1 a( \  e4 c" T
  7.   return (USBD_OK);* r2 J: Z' N3 J5 @: L
  8.   /* USER CODE END 4 */) y1 [7 z2 f) i: `5 N( e1 W% m
  9. }
    4 ~  q9 P3 c7 x4 O' h" n

  10. " M' g, _; T  J2 N
  11. extern void USBD_HID0_Uninitialize (void);
    4 x) d* J6 f8 c8 r: ^

  12. : f! p; ~& V8 i! i$ m: {; ~) H
  13. static int8_t CUSTOM_HID_DeInit_FS(void)
    " F- \+ A7 n2 w5 ~$ i0 @# V- E
  14. {5 e3 j  `0 v  D" `
  15.   /* USER CODE BEGIN 5 */* Y0 a! c% E1 P& x
  16.   USBD_HID0_Uninitialize(); /* Uninitialize. Zach Lee */
    ( l( [; A* Z, M% v, C
  17.   return (USBD_OK);; k5 k5 h( }% U/ b6 }2 U4 R" \
  18.   /* USER CODE END 5 */7 E0 B( D5 j1 S! `( v3 J# W1 F
  19. }
复制代码
编写将所有源代码关联起来的桥梁
3 h$ e) x8 q3 p; t5 y* \- U6 a为了移除CMSIS RTOS,我写了一些函数来模拟RTOS:; l2 L( u) A3 d1 g! o9 m
  1. /**
    4 ]0 j0 F% u3 ~8 _$ F
  2.   * Replace CMSIS RTOS api
    ; b4 x; f5 [1 S! K- N/ b
  3.   */
    & C, x* X& u' {5 B1 ~  b
  4. static volatile int osFlags;  /* Use "volatile" to prevent GCC optimizing the code. */6 W, d+ e) |+ i2 u* i
  5.   f3 k! O1 e4 d* {2 b; [
  6. void osKernelInitialize(void)
    : ], R4 A1 l8 _% a$ Y3 q
  7. {/ K9 `! `- }$ \
  8.   osFlags = 0;
      ^" U( C  E% R' _0 X; i7 Y
  9.   return;
    3 o# `3 v- O4 i/ t. P
  10. }
    ) V" P( n8 [# z" A& l! z( C: `
  11. int osThreadFlagsWait(int mask, int b, int c)
    ! O% P# @/ d% q1 L8 S; D6 [
  12. {
    9 q6 J  M* J5 b1 S, L5 O
  13.   (void)b;
    # r5 @. H2 v# n
  14.   (void)c;' I; Y' R+ j. b/ f

  15. 3 X. }2 n2 q( V) c1 X
  16.   int ret;
    - h& o, Y# d. F$ a  U
  17. 9 k/ E' X+ s" d9 ?+ |
  18.   while((osFlags&mask) == 0)
    & @' O6 ^2 N* c8 {6 j) F2 F5 T
  19.   {* u. S1 K1 h: M+ g1 Q
  20.     ;; Q- Z% C( f5 {5 w0 k) e7 `2 Y% a, r
  21.   }! B$ X: R" p7 L' q; s& F
  22.   ret = osFlags; osFlags &= ~mask;* u* v) K3 s$ ?4 {' T
  23.   return ret;
    4 q4 r2 K- H& ?, k
  24. }% q( a0 \# @# D2 P- @1 u; \
  25. void osThreadFlagsSet(int tid, int f)
    : U- r. Z0 G% H- Z& |" l+ o
  26. {
    7 K4 @- z' p7 E$ o
  27.   (void)tid;
    7 L" {! ?# s8 e5 ]
  28. / Z$ `8 C$ Y0 H! \: W
  29.   osFlags |= f;$ F* q2 Q$ H) D9 e. D
  30.   return;
    ( N1 h% x2 e  y7 u$ n
  31. }
复制代码
函数USBD_Configured和USBD_HID_GetReportTrigger实现如下:& Y. `* ?" _3 H7 |
  1. intUSBD_Configured(int n){3 k! h: \+ F# G& I" l$ R: ?
  2.   (void)n;! b0 Z0 J5 [" E* N

  3. 6 w) I- r4 Z! d: D
  4.   return(hUsbDeviceFS.dev_state == USBD_STATE_CONFIGURED ?1:0);}
复制代码
  1. void USBD_HID_GetReportTrigger(int a, int b, void * report, int len)8 g/ z1 R3 D8 `) J: w( |
  2. {
    ( F' j  h% b5 y
  3.   (void)a;
    5 Q4 a( g" h! U# U3 k
  4.   (void)b;1 j; M1 U* J0 L& d

  5.   y8 A/ g' {7 r0 u
  6.   if (USBD_OK != USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, report, len))
    ) u2 z/ B( T; T( k" B
  7.   {
    9 A! k, F' K0 K/ G) ~9 U
  8.     ;) S+ e  t/ m; D4 l3 s. X0 k
  9.   }  ?- i6 k9 X' Y7 Z* P0 n# K
  10.   return;
    6 n7 k4 v/ O3 v1 X8 w  H1 j
  11. }
复制代码
函数USBD_CUSTOM_HID_SendReport是由STM32CubeMX生成的,它被定义在usbd_customhid.c中,我自己的事件句柄如下:
$ z4 j% E/ f: {0 B* h( p6 a
  1. bool USBD_HID0_SetReport (uint8_t rtype, uint8_t req, uint8_t rid, const uint8_t *buf, int32_t len);
    2 K& |/ w8 w( r* p
  2. " s6 V& h/ j* [
  3. void USBD_OutEvent(void)6 _: r0 t* h/ ~& A& x& m+ E. V0 J( }
  4. {- r& g# H: J$ j3 |, T
  5.   USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)hUsbDeviceFS.pClassData;% {* M. {* {0 k, R1 y( i1 z
  6.   USBD_HID0_SetReport(HID_REPORT_OUTPUT, 0, 0, hhid->Report_buf, USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);
    + E& [* M- M4 l" p5 e1 P
  7. }
    ( e2 a2 d) V" d7 e

  8. 6 _3 x7 B  g( C* i
  9. int32_t USBD_HID0_GetReport (uint8_t rtype, uint8_t req, uint8_t rid, uint8_t *buf);
    4 k9 c: y1 U) o+ z/ y, W
  10. & {. h& j' b9 [. f' x5 e$ m
  11. void USBD_InEvent(void)
    4 c' d! O- J  F! v, Z5 Z2 @  _3 x
  12. {5 @4 v' x2 W# r: w8 V+ y
  13.   int32_t len;3 ^, p5 v- C4 F
  14. % I' x3 U- I% l5 i* A8 B
  15.   USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)hUsbDeviceFS.pClassData;1 B- Y6 Y1 W% @2 H# d# A
  16.   if ((len=USBD_HID0_GetReport(HID_REPORT_INPUT, USBD_HID_REQ_EP_INT, 0, hhid->Report_buf)) > 0), h- o( ]( Z/ g, p9 b% ?
  17.   {$ _: }/ m8 N1 Q, l' U* m, l4 j
  18.     USBD_HID_GetReportTrigger(0, 0, hhid->Report_buf, len);
    8 \# R- x) J; S# P$ [* }
  19.   }
    % H' L0 M" {9 n4 i5 O9 X1 h9 F
  20. }
复制代码
注意:
0 x$ f  _( @6 e8 l$ v. e) Z在DAP.h中,这里有个名为PIN_DELAY_SLOW的函数,它原本的实现是这样的:
: S- ]/ }& F: Y) h9 h2 B
  1. __STATIC_FORCEINLINE void PIN_DELAY_SLOW (uint32_t delay) {' h9 x0 i2 J, t$ T. m( k! D; W
  2.   uint32_t count;
    9 y2 m* h" _" O/ u
  3. % H: b  C! C' F
  4.   count = delay;
    1 r3 }( ^! B2 y) I# ?
  5.   while (--count);8 s4 o& Z2 @( W" d2 {
  6. }
复制代码
这里的空循环while (–count);会被GCC优化。我在StackOverflow中找到了一个好点子,它能正常工作但不是太合适,你有更好的方法吗?$ H; R: J9 G# E$ ]1 E8 |- g
  1. __STATIC_FORCEINLINE void PIN_DELAY_SLOW (uint32_t delay) {2 K) e3 w# e, y  C; A$ w
  2.   uint32_t count;2 L$ z# U3 `5 z( h$ ?9 z
  3. % F# ~3 c$ S& ~8 F& z
  4.   count = delay;
    3 l: N. [" R' E% L" e$ Y' ^/ U
  5.   while (--count) {5 ]/ y' l0 v1 x0 S8 x
  6.     /**$ R/ n1 A) U" y/ B
  7.       * Empty loop will be totally omitted by GCC.
    $ G( B8 ^/ X7 y* ~, i
  8.       * Search "How to prevent GCC from optimizing out a busy wait loop?" @ StackOverflow.) T& H$ L0 w/ J, Y1 h* ~0 p5 V8 \
  9.       * This solution isn't portable. Zach Lee( G9 e3 G- s( U) Q6 {
  10.       */ 3 D0 j, a0 B: K6 d$ R& {/ {) V: {1 T
  11.     __ASM("");
    , z3 U) R4 ?- Q9 T7 \- T6 B* G
  12.   }' C& R% |9 ~4 M5 i$ d, \
  13. }
复制代码
至此,相关源码就介绍完毕,源码文件:, P0 p# I6 _1 b+ @% F0 ^8 u

/ P7 x& B; f2 \' W( ?

) m3 w# `! p/ J, L- Hhttp://wiki.geniekits.com/downloads
" q* i  S2 N) n! a6 ]6 l& O
$ G4 E% z* {8 z7 |% i  Q. g! Q& Z2 ?- F
7 b) ?, L1 N/ ?9 k

6 Y" \" z* s1 }  b0 C) Z4 O

' f- D: |. U3 M. o8 J' v% |/ c6 `( [7 H+ I3 j  B2 S! l# ]
+ H, A& \& K" m
  C  D% |# ?5 ?' u
8 o! o$ y$ E; g. X

- I% ^( P, a; U: x& D* j
收藏 评论2 发布时间:2020-12-2 12:35

举报

2个回答
radio2radio 回答时间:2020-12-2 13:17:40
最后的PIN_DELAY_SLOW的问题,请参考我的方案:0 s: H3 d: I, t7 w( S6 I; V8 g  g
https://www.stmcu.org.cn/module/forum/thread-616081-1-1.html. z0 d# W8 \8 z0 O# _

评分

参与人数 1ST金币 +20 收起 理由
role_2099 + 20 赞一个!

查看全部评分

radio2radio 回答时间:2020-12-2 20:25:00
谢网友role_2099。
7 d( ~; Q8 o" [$ u2 |
3 r/ b" L4 u& e顺便提一下,从实用的角度来说,我的代码有以下优点:
; h# F0 ]! m3 O* q3 B8 B1. 硬件便宜,几乎人人都有bluepill小板。 最实用的是用STLINKv2手指来改,稳定,方便。% E0 T: m! |7 R0 Q& V
2. 软件有VCP(CDC)功能,有软启动功能。送SWO功能,虽然用的人不多。
1 G7 r1 C8 i. ?3. 速度已经调到最高了。如要进一步提高,不换F103的情况下,就得超频。还有一种方法就是,用SPI驱动USB通信。) f1 C9 s9 v: s8 s( c
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版