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

基于STM32F103自制CMSIS-DAP下载器

[复制链接]
STMCU-管管 发布时间:2020-12-2 12:35
基于STM32F103自制CMSIS-DAP下载器
+ u6 w' R; N% [, ~( M( u; w% m/ e

/ N! x1 ~1 o: u+ E5 T市面上针对Cortex-M处理器的下载器,有很多是基于CMSIS-DAP演变而来,比如:e-Link、GD-Link等。3 f. A% E: B$ F3 M3 s) f$ C* _. A
2 w5 Q. h, Q/ e; v
% p1 t8 @  l, b7 m! F
之前给大家分享过自制ST-Link的教程,今天继续为大家分享一篇:基于STM32F103C8,自制CMSIS-DAP下载器。: S+ |9 a; a$ |/ R7 Q6 B8 F/ X
) @6 J( m/ o5 e1 H% E) U

$ E7 t- ?) a) Y$ [0 R' R) Q4 M* n* R" P7 Z4 L1 B

9 a5 D) R& s. H! s8 G7 E( X1关于CMSIS-DAP
. U$ K# n; o8 w  @  v. W% `' I4 aCMSIS-DAP是支持访问 CoreSight 调试访问端口(DAP)的固件规范和实现,以及各种Cortex处理器提供CoreSight调试和跟踪。
/ G; ]* ^: ~2 n, @# ], F( a
11.png
地址:
5 @4 T8 J1 R  s) |8 E5 a4 }1 A  ]https://arm-software.github.io/CMSIS_5/DAP/html/index.html) Z! X7 `& ]! b. D$ o- O

& w$ [- ^9 r& ACMSIS-DAP固件作为源代码提供,并且可以完全配置为新的调试单元。
+ |& X7 C% `  H- v; r6 a
12.png
这里相关的更多内容,可以参看我之前分享过的一篇文章:Cortex-M软件接口标准CMSIS那些重要内容。
/ q9 {, b' V1 r- ]- H
: \3 K+ b- ?1 x6 }* i2CMSIS-DAP固件) [& X$ Z& D3 W" o
CMSIS-DAP固件Arm以源码形式提供,不存在版权问题(因为针对Arm Cortex处理器,他们还希望更多人使用)。
4 e2 M" t. n1 J; U, [
) c, u4 w. V( V/ G  D6 [
3 g6 J: n. g+ G0 U9 v& u
/ Y$ I: E& n7 H( x) c+ z
: p( c$ m0 c( u; m
1.固件版本/ p9 b9 N/ B3 S7 E
目前有两个版本:
6 y3 |2 P0 G- r) b5 N0 U版本1配置使用USB HID作为与主机PC的接口。
  X* C  Y. b. M! Q- t版本2配置使用WinUSB作为与主机PC的接口,并提供高速SWO跟踪流。
; U, y: d# M8 C/ N
, E4 g& W: L$ w& Z  M: f
( c/ k; l+ h, t' r
2.源码位置+ \& D: t9 m5 w% Y1 R
目前源码提供在Keil MDK V5版本,安装好Keil MDK,你在安装目录下就能找到源码。+ q/ l) j, p! U" Y! y* y

7 {- }6 @; z, I% I

- p) }1 W1 V+ h9 _( GC:\Keil_v5\ARM\Packs\ARM\CMSIS\5.7.0\CMSIS\DAP\Firmware9 p. n: b7 a' r' n6 M" `  @) q! F  j7 }
(目前MDK V5.33,CMSIS版本为5.7.0)
$ Z7 ?4 ~0 A! F3 z) s) D# F
13.png
3.源码描述
* I. P3 C0 C$ m" d4 G
! n+ n; T0 ^. r  s) D' W

$ z2 R; G) t- w1 I; p6 g' A从文件目录可以看出,官方源码提供了一些模板和例子。; o7 `# k% T- b" Y1 G* q

* |; z# L3 w7 S* ~4 [; X目前只提供了LPC处理器的例子,如果你有这个处理器对应的板卡,可以直接使用该源码做一个下载调试器。(下面就针对于LPC这个例子进行“改装”)
9 x2 P# N7 A6 {3 |1 D' F; ^' @: |) Q1 i' c
3配置
7 z+ v$ J; |4 Z' F4 o0 q$ h利用STM32CubeMX图形化配置工具,帮助用户选择单片机引脚的功能,并自动生成外设初始化代码。配置了USB、SPI1和USART1,并选择了USB的Custom HID middleware模式。GPIOB10到GPIO15被配置为JTAG调试需要的引脚。GPIOC13用于驱动单片机上的LED灯。
9 B; K* F! K+ c, a
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进行日常的开发。
4 Z& v4 y3 V3 e( F
% {- n# W* e, u4 {4从CMSIS-DAP的源码开始2 `  B9 S. D  ?9 }0 p, e9 T
源码可以在官网下载:* }( n# ~4 f( i% [

8 v$ j3 C+ l' i' k0 R' y
, v5 y& H4 s/ f# \& d
https://github.com/ARM-software/CMSIS_5
, A2 g4 b; K* |/ [6 B( Y/ F, A
4 A- x  M8 p' P4 |1 m/ [( B也可以直接在 Keil MDK 安装目录下获取:
9 ?; {. N' x3 m$ y' y9 t( k* H2 a! ?$ s# _: _/ h/ P& y

) C* B$ Q% A7 T+ D/ W8 cC:\Keil_v5\ARM\Packs\ARM\CMSIS\5.7.0\CMSIS\DAP1 ~/ i6 X; b; m8 ?8 Z2 f. o
9 N# u! z' D; F. y) Z8 C6 d
将从示例V1的头文件 DAP_config.h 开始分析。. V) H1 r: r! Q, U
. N* n" Y1 o& J8 B$ I! l
选择LPC-Link-II V1作为我的参考是因为它是通过USB HID实现的(V2是通过WinUSB实现)。我分析的第一个文件是DAP_Config.h。第一个关键位置如下:9 `( }1 Z% ~, q2 U2 B6 p1 R7 e

  1. 8 ?2 p0 m. W7 \: B9 K+ n  u
  2. #ifdef _RTE_
    7 `% O) X; G9 P. W, q6 i$ C; R' J  m
  3. #include "RTE_Components.h"  ?* F& c5 I; v+ J; x
  4. #include CMSIS_device_header
    + K: n8 m, n/ A$ H" {: v
  5. #else
    , y' W8 D8 H% ]: J
  6. #include "device.h"  9 o4 z) d9 v- V' k* @% {
  7. #endif
复制代码
不用RTE的相关文件,创建我自己的device.h。
. T( O" a" z  M' P: ]" g3 p( {, T; f0 _. L
  h* B) P4 f6 z! X3 j) P$ p
我将参数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中):0 X* n2 ?7 B1 W/ u
  1. CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    & @# ]( y6 }& I9 L  t
  2. /**2 j6 T( }" H9 G7 w8 U
  3.   * On Cortex-M7 core there is a LAR register in DWT domain.
      F4 c% a  C3 Q0 Y5 R
  4.   * Any time we need to setup DWT registers, we MUST write
    ; T$ r; n& x4 O% ?
  5.   * 0xC5ACCE55 into LAR first. LAR means Lock Access Register.( ^; v; m, L* O% T
  6.   */
    7 z" P4 u& r( u7 @
  7. DWT->CYCCNT = 0;9 Q0 n' W$ q( m. F
  8. DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
复制代码
我定义的引脚和NXP LPCxx的完全不同。我为STM32F103重写了所有的引脚的操作代码。在DAP_Config.h这个文件中还有一些奇怪的地方,比如:
1 G( [" F/ U: Z" E
  1. // SWCLK/TCK I/O pin -------------------------------------8 F4 G+ j& I; Z' }

  2. , x; R- T! s- f9 {
  3. /** SWCLK/TCK I/O pin: Get Input.
    % U- ~* v$ W0 a" R# A
  4. \return Current status of the SWCLK/TCK DAP hardware I/O pin.. l: P8 h( G& q7 @7 a
  5. */8 j/ l4 `6 |" A& m: w
  6. __STATIC_FORCEINLINE uint32_t PIN_SWCLK_TCK_IN  (void) {4 M! o% M" Y0 @; _, Z% T
  7.   return ((LPC_GPIO_PORT->PIN[PIN_SWCLK_TCK_PORT]>> PIN_SWCLK_TCK_BIT) & 1U);. b, ~) Q8 L: I: b
  8. }
复制代码
我不明白为什么DAP的代码里需要读取SWCLK/TCK引脚的电平,这个引脚明明是被配置为推挽输出来产生时钟信号输送给JTAG从机的。从上面列出来的代码可以看出,它是希望返回当前引脚的电平值,我的实现方式稍微有点不同:
6 Q* \+ d: J' t! P7 Z
  1. __STATIC_FORCEINLINE uint32_t PIN_SWCLK_TCK_IN  (void) {6 a/ D! }/ C" u" v9 y. b3 J
  2.   return (uint32_t)(JTAG_TCK_GPIO_Port->ODR & JTAG_TCK_Pin ? 1:0);4 ?8 K3 k7 C( u( }
  3. }
复制代码
我返回的是当前引脚的输出值。我不确定这是否正确。在整个代码中,这句话只被DAP.c中的一个叫DAP_SWJ_Pins的函数调用了两次。我猜测DAP_SWJ_Pins这个函数是用来测试IO口是否工作正常的。
" n* x- L8 _7 k- M  _+ E) i+ N, J# Z
# g  u, N. R) h# V6 ?
另一个奇怪的地方是PIN_nRESET_OUT:
( A6 G7 ^+ `; `! U( Z2 Z  r( c
  1. /** nRESET I/O pin: Set Output.# g( ~# P' K; S  `- I1 r
  2. \param bit target device hardware reset pin status:! D- |: |, h. ]" i
  3.            - 0: issue a device hardware reset.
    9 E: v* ^, B" ]4 S0 k! `! s
  4.            - 1: release device hardware reset.
    & \& O/ [$ z6 X1 g' P
  5. */9 r( m6 s4 n. w- @$ k
  6. __STATIC_FORCEINLINE void     PIN_nRESET_OUT (uint32_t bit) {+ P& j1 v* q6 ~' P" \. m
  7.   if (bit) {
    + h) J6 d7 c" J
  8.     LPC_GPIO_PORT->DIR[PIN_nRESET_PORT]    &= ~(1U <<PIN_nRESET_BIT);. w6 @+ q! U. V, k
  9.     LPC_GPIO_PORT->CLR[PIN_nRESET_OE_PORT]  =  (1U <<PIN_nRESET_OE_BIT);
    9 C7 O2 e7 r4 A, s7 J( ^  L
  10.   } else {9 w/ [0 P- v: O2 N; J
  11.     LPC_GPIO_PORT->SET[PIN_nRESET_OE_PORT]  =  (1U <<PIN_nRESET_OE_BIT);$ k  k& S/ h) ?) ~1 w4 O& H
  12.     LPC_GPIO_PORT->DIR[PIN_nRESET_PORT]    |=  (1U <<PIN_nRESET_BIT);
    : g6 L! Y8 s4 E* j0 M
  13.   }
    : e* h+ U3 |( O0 t# G% l
  14. }
复制代码
为猜测release的意思可能是将nRESET引脚重新配置为一个失能的引脚。我的代码如下:
' f4 W( A/ f- p! E' Q
  1. __STATIC_FORCEINLINE void     PIN_nRESET_OUT (uint32_t bit) {
    % p% m6 q2 J  p
  2.   GPIO_InitTypeDef GPIO_InitStruct = {0};6 U5 p2 d+ k; A2 S1 I$ V

  3. ! H9 ^0 ?0 I! j- `# A
  4.   if ((bit & 1U) == 1) {
    + w, F5 S2 {0 @' G) y
  5.     JTAG_nRESET_GPIO_Port->BSRR = JTAG_nRESET_Pin;4 j" F( c" G7 G+ R( S, u

  6. $ `. ~+ w. m$ F
  7.     GPIO_InitStruct.Pin = JTAG_nRESET_Pin;
      P" i6 r- R9 T# K
  8.     GPIO_InitStruct.Mode = GPIO_MODE_INPUT;5 f5 N4 W+ J% g: d: F6 [6 X
  9.     GPIO_InitStruct.Pull = GPIO_NOPULL;) j3 T  {3 \; a; A# C; f$ [
  10.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    ) }% I- p9 N: z" X1 u
  11.     HAL_GPIO_Init(JTAG_nRESET_GPIO_Port, &GPIO_InitStruct);
    . `! T/ I6 v3 d, i
  12.   } else {
    ) s* e4 I1 Z+ s* N- R
  13.     JTAG_nRESET_GPIO_Port->BRR = JTAG_nRESET_Pin;
    & m5 j" j$ M3 `1 b5 v3 h: P
  14. 2 h" b# _, w3 e" C7 n
  15.     GPIO_InitStruct.Pin = JTAG_nRESET_Pin;& A5 V4 q* m. q4 R$ v
  16.     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    - V9 c3 N, S3 u( J1 Y- ^
  17.     GPIO_InitStruct.Pull = GPIO_PULLUP;
    1 \& l8 d+ v/ t# P
  18.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    ; H' Y4 c# K6 y3 f5 j1 E' o
  19.     HAL_GPIO_Init(JTAG_nRESET_GPIO_Port, &GPIO_InitStruct);
    & D- z" a7 j1 X; R% \! H  c
  20.   }
    - g9 Z* }/ ?4 Q( _; ?: R  _
  21. }
复制代码
给你一个特殊的提示:2 m* w5 ^% `0 V/ G+ F) k
+ Z! ?) Y) C; y

9 ~# W# J; C% k8 A! {你可能在一些地方看见我写了if(...)之类的代码,像下面这样:8 }6 H3 c# g/ e
  1.   if ((bit & 1U) == 1) {
    6 G8 d. _9 O- n8 n. _2 c$ T* o( s
  2.   ...- z# e9 z2 I7 Q7 S
  3.   } else {, R1 g' C- \& d7 z
  4.   ...
    - M' @, @) M5 {1 @/ i! b
  5.   }
复制代码
我使用参数bit的最低位是因为有时候这个bit既不是0也不是1,它可能是2或者其他奇怪的值,所以不要把你的代码写成这样:8 i1 K; U( {/ q" ^6 ]
  1.   if (bit) {- w8 G" H) r2 I8 W- J8 |( Q. i
  2.   ...; `: N: T" N' z! m  @' }
  3.   } else {
    + Z8 b3 q! `* ]) d+ U: ^) i0 F
  4.   ...
    $ @! j) g) Q4 ~* |. b" D2 X5 ~
  5.   }
复制代码
7 h; L0 y. \6 Q3 j1 _5 b
这会出问题的!
0 _, D7 w" A  Q, u+ _
4 K9 I, I1 [' N1 v; D
. F6 `  y" O+ [7 j

5 p8 k: M# F* f3 l* GosObjects.h
3 u  F9 C+ u- B1 w/ S: i
  1. #ifndef __osObjects_h__
    - G+ `8 }- Y* c/ l* Y* [1 e
  2. #define __osObjects_h__
    8 w1 J" t7 m4 E. Q2 p* H
  3. + q) h" V9 S6 ]5 \
  4. #include "cmsis_os2.h"
    3 Q& _3 R3 J6 ]4 @& e  q$ _

  5. ) G2 d, A: c% ?( ]' |- u
  6. #ifdef osObjectsExternal
    3 a. J, ~7 y9 V
  7. extern osThreadId_t DAP_ThreadId;
    # K3 K$ J: b  _) U& r9 i
  8. #else
    / g: I' x/ d5 J! K+ ?( P
  9. extern osThreadId_t DAP_ThreadId;$ N8 r; o7 k/ z6 g' e
  10.        osThreadId_t DAP_ThreadId;/ `% d4 Q+ R) J/ g- Z0 C3 F
  11. #endif
    ' d/ l4 K, f0 l  \4 `; f( b

  12. 9 |0 C9 o0 t; e4 `
  13. extern void DAP_Thread (void *argument);
    . Q9 g; y  v7 C4 g4 [4 i# q
  14. 5 L7 c: g3 @) u2 W
  15. extern void app_main (void *argument);
    ' e( k/ H& H7 `0 A( a. j

  16. 8 n' L) Y/ g- V$ u. j
  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 ], R5 J9 e( A* H( W( f$ {$ ]
: o1 E9 A2 Y6 D$ x2 \4 o) u' r( ^

4 |, g) q+ s; I0 k接下来我会对main.c和USBD_User_HID_0.c做一些一些简单的介绍。0 }8 ^; p2 T9 K

! p% M8 ?. U# [
8 O" C2 Q3 `# K* ]+ @
main.c  U4 W3 M( {7 E  l9 e9 b2 S
/ f" Q; D$ N& {
: K" e4 w& N7 a9 X3 Z
我在尽可能地精简我下载下来的这些源文件,所以我也没有要示例工程中的rl_usb.h文件。于是我还需要一个头文件来定义一些关于USB通信的函数和参数。这里有一些来自CMSIS RTOS库的函数,其中最重要的一个是osThreadNew,在我的工程中我把它实现如下:
) J- [' m# b7 S& C
  1. osThreadId_t osThreadNew(void (*func)(void *), void * n, void * ctx)
    8 A- x! ]4 f. }
  2. {
    * W: \6 V8 q+ a/ q3 f7 u
  3.   (void)n;
    ' C2 s! _3 O( u' R* e

  4. + O# P1 M# Y6 K
  5.   (*func)(ctx);
    ( Y  A6 E# P; W/ _  Z
  6.   return 0;
    , X3 R- t: k4 ^6 Z2 F
  7. }
复制代码
我直接“跳转”到本需要被创建的线程函数中,这就意味着main.c中的osKernelGetState&osKernelStart&osDelay三个函数永远不会被执行。下一个重要的函数是USBD_Configured,我将在使用STM32CubeMX生成初始化代码那一节解释这个函数。
" c4 g4 ?; P5 N. W5 J9 `" e  W+ Z2 ^
- N" o5 ]* ~  l1 o. ^# L2 z' ?
USBD_User_HID_0.c. s% T* o: |" s( N5 x. Y8 _
" T2 {) X5 e( t( `
8 c' f; v- O" z  ]. B
我移除了RTE\USB\USBD_Config_HID_0.h并在我自己的rl_usb.h中重新定义了USBD_HID0_OUT_REPORT_MAX_SZ & USBD_HID0_IN_REPORT_MAX_SZ两个参数。
' }0 ^( V5 l( b6 s! d: y- J& }6 r/ l/ U) a2 m0 m1 f5 ^  c

! h' D3 z7 i8 d4 W8 BUSB HID通信的核心是由两个接口中断函数管理的两个循环队列:. L/ b) P1 `; x2 z5 j" N
  1. int32_t USBD_HID0_GetReport (uint8_t rtype, uint8_t req, uint8_t rid, uint8_t *buf) {7 H% e. G+ j. p' n  o
  2.   (void)rid;$ r# R% A6 |* |  r' R5 ?6 i' V

  3. & g0 h3 ~! T# q/ B. c; Z& b5 S! Q
  4.   switch (rtype) {4 s- U! k: M1 V1 X; u
  5.     case HID_REPORT_INPUT:' K  N: o  \3 u; D, Y
  6.     ...' b0 b/ i: v: ?- @8 V5 _
  7.       break;
    2 u. c: M$ n. h8 E4 x
  8.   }
    5 A% J( @: r3 m. j
  9.   return (0);' |% r% M" E7 ~# v/ i) n
  10. }
复制代码
  1. bool USBD_HID0_SetReport (uint8_t rtype, uint8_t req, uint8_t rid, const uint8_t *buf, int32_t len) {
    ' ]- a2 x( j! _# J) [* i
  2.   (void)req;' |, t/ L4 ?$ Y, B
  3.   (void)rid;
    7 N9 {3 e$ S8 U! {

  4. " Y2 q+ R+ ]1 Q0 f* l
  5.   switch (rtype) {+ V" a4 q9 p: u; Y6 e4 @
  6.     case HID_REPORT_OUTPUT:' w4 u2 B% ^' y2 M
  7.     ...
    * f6 N* L/ ~! G! \
  8.       break;
    : G$ q$ ^' \* E% \3 a* m
  9.   }
    . z" Q1 v" K+ J5 I! L# ?3 e
  10.   return true;
    6 x. c: R' l3 t6 |
  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中断端点传输。0 a7 x: t( d  j) Z

8 T+ E! {0 d% e1 z  R

2 W! @1 A& s7 {8 U4 ~4 _线程DAP_Thread只是一个命令判断选择器。在这个函数中有一个很重要的语句:9 a! T4 [+ _# Q. U
  1. USBD_HID_GetReportTrigger(0U, 0U, USB_Response[n], DAP_PACKET_SIZE);
复制代码
我们必须实现一个叫做USBD_HID_GetReportTrigger的函数来想上位机发送INPUT REPORT。$ f: W8 Z; U, I( a+ g. R! {( {. Y
7 w+ A: I# o6 q  _2 K9 U4 a

) Z# e7 z2 q) ~' w5 W2 ~4 Q3 g% Z  O/ ^& c/ E* j; x7 E' ~. }1 g+ u
' s. M. j, z. N! \

) a5 q( [4 w4 U: Z8 ^  \$ v

" F1 A( A) ^5 d' d5使用STM32CubeMX生成初始化代码
6 z% L0 N; E$ z# g/ v在我的单片机上有一个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。9 k' J! C* `4 G+ c) e
& R+ m; S' w9 D; w8 M

( G- \  a; g3 X" Z/ a$ Z/ L9 l, n注意:: W2 Z8 n" _/ i! M
我没有修改设备的VID和PID。但我猜测有些上位机软件会检测这两个ID6 s; A! F; ?2 m8 M9 X
7 [1 T. ?7 [' ]

' V) ^; ]: n" w. i如果你发现你的软件不能识别我这个CMSIS-DAP,或许你需要恰当的VID和PID。可以试试示例代码中的VID/PID,它在一个叫做USBD_Config_0.c的文件中,我的工程中没有这个文件。
  N1 l4 l% M% m/ z+ ]8 H
6 d1 O$ K& p) t6 ~  ?  I
# R) f& O. }! D, y* b# w$ d
有STM32CubeMX生成的代码需要一些修改。在usbd_customhid.h中,CUSTOM_HID_EPIN_SIZE和CUSTOM_HID_EPOUT_SIZE必须设置为0x40U。我把CUSTOM_HID_FS_BINTERVAL改为0x01来尝试提升HID的通信速度。& O% _; M4 N! \, s
# q/ Y" \5 j3 L& S( L* w/ ?

5 L  J6 U5 v. {+ {( s: f4 s: W在_USBD_CUSTOM_HID_Itf结构体中,我新增了一个成员:1 t) o0 {7 \! u3 b' n  X; h7 ^
  1. typedef struct _USBD_CUSTOM_HID_Itf! H' ]' U; _2 L( u* d
  2. {% p- a' Z4 V' y) p# ~
  3.   uint8_t                  *pReport;
    4 ^  `6 l) j4 Q, h, G
  4.   int8_t (* Init)(void);
    1 G5 l' w3 w* i0 T) H* K! f# R! T
  5.   int8_t (* DeInit)(void);
    - }( K) F. ]- ~! N4 ^: [3 N
  6.   int8_t (* OutEvent)(uint8_t event_idx, uint8_t state);
    % t1 C9 y" S/ l% [2 z" G4 H
  7.   /* I add an extra interface func below. Zach Lee */
    2 F% T. p  E8 |# [: j8 h. d
  8.   int8_t (* InEvent)(uint8_t event_idx, uint8_t state);- T# i& m$ h! Z. }; m0 x' Y6 ?
  9. } USBD_CUSTOM_HID_ItfTypeDef;
复制代码
当INPUT REPORT报文成功发给上位机时,InEvent函数应当被调用,所以usbd_customhid.c中的USBD_CUSTOM_HID_DataIn函数需要修改如下:  _+ C# N  f3 ^0 @% H
  1. static uint8_t  USBD_CUSTOM_HID_DataIn(USBD_HandleTypeDef *pdev,/ O% I3 C8 }3 W/ }* j! N
  2.                                        uint8_t epnum)
    ) |# [4 b0 p! Z$ d4 f# u
  3. {7 E  ~* y4 N+ p! k* T
  4.   /* Ensure that the FIFO is empty before a new transfer, this condition could
    * \8 t4 m, f3 e" E0 F. h
  5.   be caused by  a new transfer before the end of the previous transfer */
    2 f, }( o) y* _% Q0 ?' u; P
  6.   USBD_CUSTOM_HID_HandleTypeDef     *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassData;
    2 W* k) d" `3 }
  7.   hhid->state = CUSTOM_HID_IDLE;
    6 B( O8 J5 i2 Q) G

  8. / H8 X. T, S% Y* M% s: C7 d
  9.   /* I add a new interface func in the structure USBD_CUSTOM_HID_ItfTypeDef. Zach Lee */; u( e& g6 I9 N% B7 C
  10.   ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->InEvent(hhid->Report_buf[0],
    , l% `" z2 Y/ [! s: V) @
  11.                                                            hhid->Report_buf[1]);2 X) Z; a' B1 z
  12. & ~2 l# i+ ^6 K3 l+ o9 V5 ?9 R
  13.   return USBD_OK;# T- {% o- J, l' F! t+ c
  14. }
复制代码
设备描述符CUSTOM_HID_ReportDesc_FS被定义在usbd_suctom_hid_if.c中,我定义了一个简单的描述符:
# _8 }3 A9 H. V6 \1 f
  1. /** Usb HID report descriptor. */
    & x9 V( L5 T. _& N! D/ O: ~& o
  2. __ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
    " l" O* {. `3 D
  3. {1 ~! W; P$ E. L5 N# ]$ G/ E
  4.   /* USER CODE BEGIN 0 */ /* A minimal Report Desc with INPUT/OUTPUT/FEATURE report. Zach Lee */' I* W6 L8 S# J: |9 F# W
  5.   0x06,0x00,0xFF,         /*  Usage Page (vendor defined) ($FF00) global */
    % C9 z* s) n6 N' b$ Z: @
  6.   0x09,0x01,              /*  Usage (vendor defined) ($01) local */
    * R: u9 }! s4 C- {/ T$ \. {
  7.   0xA1,0x01,              /*  Collection (Application) */
    : z# F% M8 W" o1 N8 R7 y- K! g
  8.   0x15,0x00,              /*   LOGICAL_MINIMUM (0) */
    # y& S' k: k  y# ^6 A; |
  9.   0x25,0xFF,              /*   LOGICAL_MAXIMUM (255) */$ N7 A4 @$ g+ J7 c" V  j* x
  10.   0x75,0x08,              /*   REPORT_SIZE (8bit) */! |  |$ Y' \, r2 O) Y/ W7 u4 {

  11. 3 `+ r( v/ q0 ~
  12.   // Input Report
    3 B$ n' w+ ?+ E! Z8 b2 v
  13.   0x95,64,                /*   Report Length (64 REPORT_SIZE) */
    ! P& \/ b5 x0 m3 ~  ~  G7 S
  14.   0x09,0x01,              /*   USAGE (Vendor Usage 1) */8 F$ X, \' G  N8 @! }
  15.   0x81,0x02,              /*   Input(data,var,absolute) */
    9 T1 ~5 l" r% `# q  D% t0 {3 p0 X

  16. 6 i' G# k3 \9 s0 H! d
  17.   // Output Report
    0 z) s- K  m/ p% M/ X
  18.   0x95,64,                /*   Report Length (64 REPORT_SIZE) */; k) I4 F( u, p# ]: U' |. m  [
  19.   0x09,0x01,              /*   USAGE (Vendor Usage 1) */6 N# a8 \6 S- u
  20.   0x91,0x02,              /*   Output(data,var,absolute) */* z1 X8 c: C% N+ w* k  Y6 W6 H4 f

  21. 4 t# p$ ]* ^; f8 e
  22.   // Feature Report
    ; a% V& Y0 c4 v  J' I+ j8 X7 L
  23.   0x95,64,                /*   Report Length (64 REPORT_SIZE) */4 s# g. @0 E- O
  24.   0x09,0x01,              /*   USAGE (Vendor Usage 1) */
    8 p/ y( ^7 s6 T! j% S: e8 ^
  25.   0xB1,0x02,              /*   Feature(data,var,absolute) */
    6 x& G/ \4 A2 I6 t% \3 R
  26.   /* USER CODE END 0 */1 H+ t/ N4 O, c/ f
  27.   0xC0                    /*  END_COLLECTION                     */
    8 V( w  e- U( o6 g9 W" M
  28. };
复制代码
可能Feature Report在CMSIS-DAP中不是必要的,就留着它吧。* Y% o/ _. q, p- E4 m

% i; u+ U! [$ Z) J9 o* _
! a4 M0 [: Z8 ^6 O) c
我在这个C文件中还实现了一个新的接口函数CUSTOM_HID_InEvent_FS:
# P  r0 I! `4 P- @; l: ^% M
  1. static int8_t CUSTOM_HID_InEvent_FS(uint8_t event_idx, uint8_t state);  /* An extra interface func. */2 @+ b. S$ r! e! I- @% @
  2. 0 @9 @% o0 V# Y" ?4 o/ N
  3. USBD_CUSTOM_HID_ItfTypeDef USBD_CustomHID_fops_FS =3 \% H4 L0 P: f% J
  4. {* e& O# U( u6 R5 X2 a
  5.   CUSTOM_HID_ReportDesc_FS," P$ k% C4 G7 E) \5 R
  6.   CUSTOM_HID_Init_FS,
    / w  M. }( j5 S1 M# @
  7.   CUSTOM_HID_DeInit_FS,+ Z( P+ ^  l+ r
  8.   CUSTOM_HID_OutEvent_FS,# b& A, I6 X  g( A( v4 [
  9.   /* I add an extra interface func below. Zach Lee */1 ?; c( M$ Y0 @' T1 B. h
  10.   CUSTOM_HID_InEvent_FS# x& z2 p9 o9 }
  11. };
复制代码
  1. extern void USBD_OutEvent(void); /* Implemented in file "device.h" */
    0 f4 w2 y% W/ H/ Z7 Q

  2. . a( \; [& Z) q4 X! t
  3. static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)
    8 E# Z* U: h6 ]% q# w* N
  4. {
    6 |/ k1 u2 C7 V* m  M" v4 M
  5.   /* USER CODE BEGIN 6 */
    ( ^6 T) W0 B( a  L
  6.   USBD_OutEvent();        /* OUTPUT REPORT was received. Zach Lee */
    7 h1 C0 O, p- ^! i9 z
  7.   return (USBD_OK);# r2 ~3 b9 ]- }; @& ^( j
  8.   /* USER CODE END 6 */
    + l# X) v5 l( e3 m+ R2 B# K
  9. }# l! r+ D  w) y7 E8 F/ ]2 @# F6 {; f
  10. 0 J* H/ n8 V+ F' J  E- Y" b
  11. extern void USBD_InEvent(void); /* Implemented in file "device.h" */
    : j, I# L, z+ j3 n! ~: Y  w# K- W

  12. + y( J2 u# e1 s) W* u$ b# J1 g
  13. static int8_t CUSTOM_HID_InEvent_FS(uint8_t event_idx, uint8_t state)* o' ]8 l6 Z9 [0 ?
  14. {& o  z/ `7 T1 q) A7 v8 F% O  x. m
  15.   /* USER CODE BEGIN extra */
    . k% e7 N/ P, z( A
  16.   USBD_InEvent();       /* INPUT REPORT has been sent. Zach Lee */$ b( B( L/ A6 ^- h/ g( t
  17.   return (USBD_OK);! B( C$ V9 F2 B* S
  18.   /* USER CODE END extra */
    . i( h& g' d# C8 h/ |% ]
  19. }
复制代码
CUSTOM_HID_Init_FS和CUSTOM_HID_DeInit_FS两个函数被实现为使能/失能DAP功能:, R7 N: ], L: o8 N9 |$ p
  1. extern void USBD_HID0_Initialize (void);) y4 i2 _% [% c6 [, C7 H

  2. : N9 y. f% J/ ~9 _( a
  3. static int8_t CUSTOM_HID_Init_FS(void)+ b1 J  K4 U. F$ x" |
  4. {" Q- W5 N/ E( J+ f0 l2 \
  5.   /* USER CODE BEGIN 4 */
    0 Z* _8 L3 t! K: V9 A& ]
  6.   USBD_HID0_Initialize(); /* Initialize USB communication of DAP. Zach Lee */
    ' S2 c8 u& i7 d* B2 b% Q& H: |
  7.   return (USBD_OK);
    0 w7 A% C9 H' N& E, u7 _- t& j
  8.   /* USER CODE END 4 */
    ( B, `* Q* k( P: V5 H1 |
  9. }$ B2 ~5 p" N4 }
  10. 9 X5 q- Y; [5 _; N, ~. J# S
  11. extern void USBD_HID0_Uninitialize (void);
    / n* C5 b$ I) s! _, ~( F& ?' N
  12. " c2 Q5 V  u7 \4 {( Z0 U0 t
  13. static int8_t CUSTOM_HID_DeInit_FS(void)
    & ^+ Y5 H+ W" A6 Q1 w, Y
  14. {8 S! ?  w: O1 I2 z, E" }. v
  15.   /* USER CODE BEGIN 5 */
    ' O" u( v. B* X4 G( Z: B# u
  16.   USBD_HID0_Uninitialize(); /* Uninitialize. Zach Lee */
    6 _. }  o0 Q& f+ x$ B
  17.   return (USBD_OK);- r2 d4 ~5 ]6 u, Z8 r6 b
  18.   /* USER CODE END 5 */. m: ^  g2 R$ B
  19. }
复制代码
编写将所有源代码关联起来的桥梁6 r6 G, \2 m) B
为了移除CMSIS RTOS,我写了一些函数来模拟RTOS:- m/ a7 T/ S8 p6 o$ Z5 z3 b. f
  1. /**
    0 X) O/ J1 v, W: E
  2.   * Replace CMSIS RTOS api
    1 w) D: C* |6 {# Q' C, V: M
  3.   */
    , f  V, z+ _2 h* j0 K, S
  4. static volatile int osFlags;  /* Use "volatile" to prevent GCC optimizing the code. */6 w4 f8 W! _# I3 Z- T8 l4 j- W
  5. 2 w: i6 O- N9 n
  6. void osKernelInitialize(void)- x4 r8 i) p/ ~5 O5 @
  7. {
    - }9 x6 W" G4 b% K
  8.   osFlags = 0;
    3 [0 k, P4 ]) q3 E3 t0 U
  9.   return;
    " [# q4 k) i  u! n
  10. }% [5 j- Q! N  {" K; p8 f/ d
  11. int osThreadFlagsWait(int mask, int b, int c)
    - ~3 X. d0 g5 |- m3 ~; b
  12. {6 e# e2 n0 s- \
  13.   (void)b;
    ) p0 H# N& L- T  d8 H
  14.   (void)c;
    & M. I  l- L, q9 X$ V7 I' \
  15. & ]) |' [* t( j2 A& t/ F
  16.   int ret;
    # V% H2 z) B& l

  17. # |7 y4 L; X  [  x0 U
  18.   while((osFlags&mask) == 0)% r5 ?" I: e" d
  19.   {
    7 S0 N1 y; l1 n% N
  20.     ;
    & J7 |2 P5 X2 ]0 ~+ M, k5 r
  21.   }
    + d$ S! h, A+ w1 E% n3 `$ c
  22.   ret = osFlags; osFlags &= ~mask;
    % B4 o8 v1 ]/ u# b( ~: m
  23.   return ret;
    & ?/ V/ _2 z' q  ^4 u
  24. }) l0 }9 ^7 N$ ^  N% p* N# u
  25. void osThreadFlagsSet(int tid, int f)" S& M0 }/ d' K3 |8 ~# L* n) ^
  26. {
    6 V% U. o5 X" ^" P! V, w" q: O
  27.   (void)tid;4 U  R: D5 T0 i  [9 c: K; r

  28. " Y0 `0 P  W1 K) k
  29.   osFlags |= f;
    # e% `: P. `! l9 M  z0 u
  30.   return;! B& i! k) r1 M+ ?2 Z% h
  31. }
复制代码
函数USBD_Configured和USBD_HID_GetReportTrigger实现如下:5 U9 \6 ?& |3 R
  1. intUSBD_Configured(int n){
    0 F) v1 T$ e3 d0 b( E
  2.   (void)n;4 m/ p! Z! ?9 O
  3. # h# D  D% Q/ Q, u" F* w$ y* O' ]" }2 r
  4.   return(hUsbDeviceFS.dev_state == USBD_STATE_CONFIGURED ?1:0);}
复制代码
  1. void USBD_HID_GetReportTrigger(int a, int b, void * report, int len)
    2 J" T2 Y0 H) K# f% s9 O
  2. {
    4 I* h1 c7 @( X' s9 f2 w: K. {
  3.   (void)a;
    * `  q5 ]# l3 e8 s+ \3 H( `
  4.   (void)b;, |/ R4 D0 H* S$ C5 ?1 s
  5. / a+ Z! _" z* z# i- {4 a" e
  6.   if (USBD_OK != USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, report, len))
    0 P4 ^$ m  r) u! K  H
  7.   {) F2 _& L3 T+ z$ ]$ I
  8.     ;
    0 u9 ^3 N- |8 u: e+ ?: t( _
  9.   }/ N% G- k8 W7 n/ z6 B
  10.   return;
      p" \( f$ }5 @8 {
  11. }
复制代码
函数USBD_CUSTOM_HID_SendReport是由STM32CubeMX生成的,它被定义在usbd_customhid.c中,我自己的事件句柄如下:
. b( A: n3 m3 h1 ^2 U
  1. bool USBD_HID0_SetReport (uint8_t rtype, uint8_t req, uint8_t rid, const uint8_t *buf, int32_t len);
    9 L% v- x. ~1 F2 Q
  2. 5 ?  B4 x  V2 W: V2 g! Y
  3. void USBD_OutEvent(void)
    9 d9 P! ~; C1 n* [, [( t1 x  z* i+ J/ F2 E
  4. {
      Z6 F( c0 f" R8 W9 i$ P$ f
  5.   USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)hUsbDeviceFS.pClassData;
    ( ?1 X& _" g1 ^8 e0 F
  6.   USBD_HID0_SetReport(HID_REPORT_OUTPUT, 0, 0, hhid->Report_buf, USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);) S& ^0 X+ |# M, H" \
  7. }( \/ ^% @) j. x( `

  8. - Z7 I1 k1 x8 y
  9. int32_t USBD_HID0_GetReport (uint8_t rtype, uint8_t req, uint8_t rid, uint8_t *buf);
    : G; u& J6 b$ ]

  10. - l  G) U( F9 R
  11. void USBD_InEvent(void)& R2 p. d" x. i, K7 w
  12. {- E  e; Z2 g. n- s, C* F
  13.   int32_t len;- Q7 h8 Y+ D8 P1 n
  14.   b! m( d' t3 Q5 y
  15.   USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)hUsbDeviceFS.pClassData;9 }  J8 C* `  n$ ]
  16.   if ((len=USBD_HID0_GetReport(HID_REPORT_INPUT, USBD_HID_REQ_EP_INT, 0, hhid->Report_buf)) > 0)1 C, h5 \: h. u
  17.   {
    & I) d  u. I7 E% v1 |* N0 S
  18.     USBD_HID_GetReportTrigger(0, 0, hhid->Report_buf, len);, w9 P5 A; w. M; b  O6 Q3 e
  19.   }4 h1 _( ~3 @, Y! t; B
  20. }
复制代码
注意:. h7 I4 w' q/ Q. x+ K
在DAP.h中,这里有个名为PIN_DELAY_SLOW的函数,它原本的实现是这样的:1 ]2 z# T6 h: Q1 j6 Q2 {  K* K0 p6 i
  1. __STATIC_FORCEINLINE void PIN_DELAY_SLOW (uint32_t delay) {$ q0 X( K( B* _8 m* J# Z9 r
  2.   uint32_t count;2 G6 ~1 g! A0 b. |5 o( q
  3. 1 j4 B2 j' a4 k
  4.   count = delay;9 t( h' H* m' Z! l4 x1 u' _
  5.   while (--count);4 }& V  v* \/ U/ h  `: T
  6. }
复制代码
这里的空循环while (–count);会被GCC优化。我在StackOverflow中找到了一个好点子,它能正常工作但不是太合适,你有更好的方法吗?
0 ?* M9 b& I0 ~$ i- Q# ]
  1. __STATIC_FORCEINLINE void PIN_DELAY_SLOW (uint32_t delay) {
    1 b& e4 J5 }0 ~$ j0 R
  2.   uint32_t count;
    / F' L% `; x* Z6 z. \1 x3 Q
  3.   Q; A- a1 W; c( I3 ?& k  `
  4.   count = delay;
    4 T2 _* M8 J/ Q% Q0 b5 A
  5.   while (--count) {
    , q' }7 ~) i2 l, d
  6.     /**
    ! ~: p; m3 u2 p) P% P
  7.       * Empty loop will be totally omitted by GCC.1 w: t0 S# l& [2 h+ [3 D
  8.       * Search "How to prevent GCC from optimizing out a busy wait loop?" @ StackOverflow.
    0 o; ^2 w1 p; @* c& Q( o7 }
  9.       * This solution isn't portable. Zach Lee
    4 q7 R+ V* w/ ?. N+ _5 k, Q
  10.       */
    / \9 [4 W/ e6 }" q
  11.     __ASM("");& M9 A& S8 J0 m$ h! n; t6 q. z
  12.   }& ]. ?  V5 v9 b! F7 p
  13. }
复制代码
至此,相关源码就介绍完毕,源码文件:
- ~( b1 R3 N- f2 q$ J' z0 [; J- _8 x  W/ B; T
6 z. u$ j% D8 p" N1 m
http://wiki.geniekits.com/downloads. r' H7 p% Q7 z: H
2 D! I2 t1 d! P. Y5 x6 d  J7 a

1 G) b) ]/ f  ?4 i9 s6 }, D" S
- G0 ]8 T# I9 S% i

% B( S4 s! \6 y/ j7 w  q3 W1 l4 |! m" M) }& {& h

+ D9 T- x$ ~8 W: V' _
0 u9 P& W0 ^  O: A& W
, s  B6 Q: a: r. M

' i% D2 o1 n' t: L
赞 收藏 评论2 发布时间:2020-12-2 12:35

举报

2个回答
radio2radio 回答时间:2020-12-2 13:17:40
最后的PIN_DELAY_SLOW的问题,请参考我的方案:
3 w( D6 M7 s- l8 phttps://www.stmcu.org.cn/module/forum/thread-616081-1-1.html8 P# X; t7 n8 P! u- y+ @, h! |3 V

评分

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

查看全部评分

radio2radio 回答时间:2020-12-2 20:25:00
谢网友role_2099。3 T8 k% D6 q* F( \$ g/ l

: L/ T0 L; H7 J! W4 y/ K" ~# f顺便提一下,从实用的角度来说,我的代码有以下优点:
6 |$ m, w/ E3 ~. V: g1. 硬件便宜,几乎人人都有bluepill小板。 最实用的是用STLINKv2手指来改,稳定,方便。3 P! m$ D0 V1 a: u# O7 R
2. 软件有VCP(CDC)功能,有软启动功能。送SWO功能,虽然用的人不多。
8 _8 R2 A0 H, z: r3. 速度已经调到最高了。如要进一步提高,不换F103的情况下,就得超频。还有一种方法就是,用SPI驱动USB通信。
. r3 |5 q+ n/ C# e

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版