基于STM32F103自制CMSIS-DAP下载器 + Q, W7 S' n; k+ r$ P T- S
: O) k( q6 s3 @4 E市面上针对Cortex-M处理器的下载器,有很多是基于CMSIS-DAP演变而来,比如:e-Link、GD-Link等。
) T. ~6 Y' |* L$ Z/ j7 `" P5 p1 z2 R# \2 t& f3 F4 R7 a3 ?
( g. F: s* e9 d! m, b* F之前给大家分享过自制ST-Link的教程,今天继续为大家分享一篇:基于STM32F103C8,自制CMSIS-DAP下载器。6 S) X. H5 U% c- h4 D* V2 F5 W# V
3 g7 m0 \# U7 O
' c2 a- `& X! O' K- }7 R6 G0 C- e" L" u3 i0 O+ L- G! }, A
! I& a: K4 p+ R, [) x) \* }& `
1关于CMSIS-DAP2 G' |: W2 ^) e9 N! y1 s
CMSIS-DAP是支持访问 CoreSight 调试访问端口(DAP)的固件规范和实现,以及各种Cortex处理器提供CoreSight调试和跟踪。( K8 g; [. m, W( ^: O9 {
地址:1 \* a* z v) @! l% W
https://arm-software.github.io/CMSIS_5/DAP/html/index.html0 Y3 g7 F; O* e8 M+ o
2 m. m+ d/ m& M2 B8 [3 p
CMSIS-DAP固件作为源代码提供,并且可以完全配置为新的调试单元。& ~# [8 B; w/ I3 } I! ]
这里相关的更多内容,可以参看我之前分享过的一篇文章:Cortex-M软件接口标准CMSIS那些重要内容。) h+ z0 V/ H$ }. w) G
5 J6 k( d& Z6 ^* x
2CMSIS-DAP固件3 }5 v4 w6 N- m2 l' _6 Z3 m; h' }
CMSIS-DAP固件Arm以源码形式提供,不存在版权问题(因为针对Arm Cortex处理器,他们还希望更多人使用)。
- ~ r1 s8 `7 A/ x! L+ \- s1 `5 [3 i h0 f: b% [6 F
( l# i8 I6 ^# l. n8 X1 {
8 v4 b, r3 p- e9 I. ?8 _6 ?; | s) }* H
. c* G9 `( H$ `. Z5 g1.固件版本3 N! f! E% E ?+ V# }+ [" U$ O
目前有两个版本:
/ v$ X) m5 F8 O" F7 q版本1配置使用USB HID作为与主机PC的接口。
' g M7 O5 [8 C0 ~( ^3 s1 `6 T, e版本2配置使用WinUSB作为与主机PC的接口,并提供高速SWO跟踪流。* Q! U4 Y1 _0 M3 P' p
& M# q" u. R; V P0 `" ]
3 ^) n* Z1 Z6 E E# o
2.源码位置% c% f$ E4 O3 v9 ~: S. n
目前源码提供在Keil MDK V5版本,安装好Keil MDK,你在安装目录下就能找到源码。6 x8 v; a( _8 t! W$ ~
: N% A& W, P# @: F5 G7 {7 y
: `6 J; @+ ]! j2 R$ N$ o+ NC:\Keil_v5\ARM\Packs\ARM\CMSIS\5.7.0\CMSIS\DAP\Firmware
0 l) k8 [1 U! K4 m3 a8 E! g# x(目前MDK V5.33,CMSIS版本为5.7.0)2 o& s) ~! ~* T
3.源码描述8 ?4 X: s( `) [! b j$ C
, p, T; |, r3 {/ M' l$ u
: Y# H6 b* f5 c+ v5 r从文件目录可以看出,官方源码提供了一些模板和例子。: ?+ N5 Q+ U5 F- J% |
u/ Q' @3 a# a7 Q
目前只提供了LPC处理器的例子,如果你有这个处理器对应的板卡,可以直接使用该源码做一个下载调试器。(下面就针对于LPC这个例子进行“改装”)$ O8 s- h' a) G
% A3 x& K$ C' | H: d# r3配置
, p/ ^' P1 d; A7 A! z5 [利用STM32CubeMX图形化配置工具,帮助用户选择单片机引脚的功能,并自动生成外设初始化代码。配置了USB、SPI1和USART1,并选择了USB的Custom HID middleware模式。GPIOB10到GPIO15被配置为JTAG调试需要的引脚。GPIOC13用于驱动单片机上的LED灯。
2 B$ R0 E2 | C# v, GST公司也开发了他们自己的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进行日常的开发。
7 f( ^7 X/ f2 }! o& C
: n7 k5 Z7 p5 ~# ~ U4从CMSIS-DAP的源码开始
- d( G$ g. k' S/ W4 }3 u4 P( W [" n源码可以在官网下载:
3 g5 k6 g$ H( ?( V Y# c' `3 q& Q
$ L4 Q% Z2 U" Z* s& K
; g# m$ X1 x! G' m1 j5 s9 q5 ~https://github.com/ARM-software/CMSIS_5: Z" U8 g4 h4 K" N1 O: y5 A! Q
2 Y' x' z9 h2 @/ l4 `# T也可以直接在 Keil MDK 安装目录下获取:" N) l8 l" n# h4 y+ Y* {
) g' V% D" ^! `3 k3 |. }$ y
& j! d7 T; S7 P. ~% [5 k. \/ ~8 JC:\Keil_v5\ARM\Packs\ARM\CMSIS\5.7.0\CMSIS\DAP
. j+ D7 L2 u3 e9 M& q! j% ]: v w2 c2 x
将从示例V1的头文件 DAP_config.h 开始分析。4 i4 T- e6 Y7 b
, g/ M! ~+ t8 Z1 W6 L
选择LPC-Link-II V1作为我的参考是因为它是通过USB HID实现的(V2是通过WinUSB实现)。我分析的第一个文件是DAP_Config.h。第一个关键位置如下:8 m: k! g; {) {- s1 l0 `
2 e3 n" i! c3 }# f- #ifdef _RTE_
, n" `: g0 y4 l ]% R+ ~' c - #include "RTE_Components.h"
1 s6 { u( h2 H) ~: X - #include CMSIS_device_header
7 I. U' U/ g" ] - #else
+ b" B, X; x8 k( Q" d - #include "device.h" $ z7 I, E5 z7 ~& E# G
- #endif
复制代码 不用RTE的相关文件,创建我自己的device.h。) ?# `* P' s( [6 f
; l* @ b6 v% B( V- F' [: H
' p) B' W. l" S! ^; S
我将参数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 ]8 j# D5 I4 d( F! \! u
- CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
$ M* E2 t& Z" j - /**
0 k& ~9 ^2 t7 A8 ]$ y - * On Cortex-M7 core there is a LAR register in DWT domain.* \+ g# E) L# `# u+ ^6 I
- * Any time we need to setup DWT registers, we MUST write( `. y8 j* j6 Y3 c* r3 X& E
- * 0xC5ACCE55 into LAR first. LAR means Lock Access Register.
% T8 k7 k2 t: i( \5 t8 \. t) x - */
- Z6 y6 h- Y$ J' | - DWT->CYCCNT = 0;
" `( `) D$ l1 r, _* A+ a - DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
复制代码 我定义的引脚和NXP LPCxx的完全不同。我为STM32F103重写了所有的引脚的操作代码。在DAP_Config.h这个文件中还有一些奇怪的地方,比如:% ]2 A& B0 ^3 G! x6 d
- // SWCLK/TCK I/O pin -------------------------------------" U8 R7 x6 \ w. S8 X0 k
- / X- n7 ^. n' J
- /** SWCLK/TCK I/O pin: Get Input.% l/ W e9 A; f9 m/ }
- \return Current status of the SWCLK/TCK DAP hardware I/O pin./ Y C: o4 u. v1 x8 M
- */
& q: D) s; `3 X$ J9 l - __STATIC_FORCEINLINE uint32_t PIN_SWCLK_TCK_IN (void) {5 k5 F( L# c; Q$ E' ^7 P
- return ((LPC_GPIO_PORT->PIN[PIN_SWCLK_TCK_PORT]>> PIN_SWCLK_TCK_BIT) & 1U);
+ a5 O s) w/ i. D9 |4 ~ - }
复制代码 我不明白为什么DAP的代码里需要读取SWCLK/TCK引脚的电平,这个引脚明明是被配置为推挽输出来产生时钟信号输送给JTAG从机的。从上面列出来的代码可以看出,它是希望返回当前引脚的电平值,我的实现方式稍微有点不同:
4 M+ T2 m7 d; W# m) a! F- __STATIC_FORCEINLINE uint32_t PIN_SWCLK_TCK_IN (void) {
. i8 s$ T6 a( K& v% h - return (uint32_t)(JTAG_TCK_GPIO_Port->ODR & JTAG_TCK_Pin ? 1:0);
& o9 K* }' g* M+ b - }
复制代码 我返回的是当前引脚的输出值。我不确定这是否正确。在整个代码中,这句话只被DAP.c中的一个叫DAP_SWJ_Pins的函数调用了两次。我猜测DAP_SWJ_Pins这个函数是用来测试IO口是否工作正常的。
+ U6 Y/ X: }: p2 h1 G6 K/ S0 }5 B. n8 o6 l! L4 m
, \2 L6 q* j8 c5 [2 P另一个奇怪的地方是PIN_nRESET_OUT:
2 k# ]( A3 ?: u1 }+ m! Y- /** nRESET I/O pin: Set Output.3 |4 ^2 i e+ e9 @' c
- \param bit target device hardware reset pin status:" |' a; t/ a, _' b, G; i5 ^
- - 0: issue a device hardware reset.1 J$ ?; r) Y4 X( e, u
- - 1: release device hardware reset.
* W; t, X R5 I - */7 U& K8 y) u& y
- __STATIC_FORCEINLINE void PIN_nRESET_OUT (uint32_t bit) {
, ^+ y. j3 J8 T - if (bit) {
' w) z! z: E; i4 _ - LPC_GPIO_PORT->DIR[PIN_nRESET_PORT] &= ~(1U <<PIN_nRESET_BIT);% E# g! u$ l$ H
- LPC_GPIO_PORT->CLR[PIN_nRESET_OE_PORT] = (1U <<PIN_nRESET_OE_BIT);
- | `3 h9 _! b4 S) ^( Y6 d4 D - } else {$ m: @& H: h( p- H$ N
- LPC_GPIO_PORT->SET[PIN_nRESET_OE_PORT] = (1U <<PIN_nRESET_OE_BIT);
: ^5 j5 u( E. B$ X, j$ I3 F - LPC_GPIO_PORT->DIR[PIN_nRESET_PORT] |= (1U <<PIN_nRESET_BIT);$ h+ x6 w7 ^: u
- }
g! G* \, i/ ^4 {, [% O/ E n1 s - }
复制代码 为猜测release的意思可能是将nRESET引脚重新配置为一个失能的引脚。我的代码如下:
% d6 t* ?; f/ h- __STATIC_FORCEINLINE void PIN_nRESET_OUT (uint32_t bit) {
9 e" V# `! M# k! J6 o" v$ L - GPIO_InitTypeDef GPIO_InitStruct = {0};
6 _ J/ q* C* i" S+ K) M2 H -
7 v. S* |: p( e - if ((bit & 1U) == 1) {
$ t" e# z c3 C& s W( \ - JTAG_nRESET_GPIO_Port->BSRR = JTAG_nRESET_Pin;
) u7 y3 a/ ?/ N0 ~ -
/ M9 n# t. p9 k1 y' k7 j2 ?! a - GPIO_InitStruct.Pin = JTAG_nRESET_Pin;
% }8 A) [+ l& f" f$ M3 k8 U - GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
# c8 i, b( s3 ]0 R7 V - GPIO_InitStruct.Pull = GPIO_NOPULL;
; I( W% J% r5 e2 O8 g! C2 ^& f - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
# f# H& P( n' l - HAL_GPIO_Init(JTAG_nRESET_GPIO_Port, &GPIO_InitStruct);
& W; n+ T! N' c3 y, b0 F. P - } else {
/ _; n; c$ A- f7 P9 r - JTAG_nRESET_GPIO_Port->BRR = JTAG_nRESET_Pin;+ c6 l( K: ~! l. c, [
-
s" J! h, K" P$ q4 u# X - GPIO_InitStruct.Pin = JTAG_nRESET_Pin;$ @- |! P" H# H2 U3 Q
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
6 z+ D7 {8 m0 P; x - GPIO_InitStruct.Pull = GPIO_PULLUP;! \. n& e2 Y9 d% h
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
9 W; N' R# {! V. z3 E8 w" L - HAL_GPIO_Init(JTAG_nRESET_GPIO_Port, &GPIO_InitStruct);) m3 F+ x! F; o& Q( R" H, Y
- }
, w1 y6 u3 j% y& Z- { - }
复制代码 给你一个特殊的提示:
9 j3 l# |1 W0 l
3 y* F: X) ]( y( x, ?' U$ C' R5 H ]$ Y9 ~' {
你可能在一些地方看见我写了if(...)之类的代码,像下面这样:
1 i) C# ?) l z: S$ h5 f- if ((bit & 1U) == 1) {& I7 ]" Y* Z7 p6 n: W% U
- ...
. V! C5 x! |1 z+ Y - } else {
. r! g0 W+ G# v; }' ?* b - ...
5 y1 F2 c2 w: e" w' b* M - }
复制代码 我使用参数bit的最低位是因为有时候这个bit既不是0也不是1,它可能是2或者其他奇怪的值,所以不要把你的代码写成这样:* D! v" B, L7 X3 ]# l. a, @
- if (bit) {7 V% ~, a' O7 ?
- ...
/ Z& V' ?) F" u1 ]8 d& J* ^ - } else {
6 _: e" [" ?0 Z4 Q8 ? - ...
: Y8 v& Z' g6 m1 \6 | - }
复制代码 6 S0 f- x# j4 ~% h8 e! I( }2 b
这会出问题的!: A s" l- n! D6 w7 x, ]
+ K7 t) h% w$ f# ?) w/ n. y% {& _) M7 ~6 }: `6 v. ]# Q
2 b/ y% l u+ C2 N2 R5 n- W# G
osObjects.h
6 j$ P( ~, C* V' B4 w- #ifndef __osObjects_h__
; y- D( E4 w* T( p: f - #define __osObjects_h__
! M: v( a3 Y' U- r4 d: a& D: m0 q -
% ~2 l4 ?% ~$ `* ~ v - #include "cmsis_os2.h": J2 r6 U+ {. S$ y
-
: E2 p" G# g8 `; T0 B$ J - #ifdef osObjectsExternal5 [& K ^0 W4 X3 b0 Z$ E) m% [: ?
- extern osThreadId_t DAP_ThreadId;: {* f! h* D( X. v# ^4 H& B" V% b2 l
- #else3 `$ e; h' i* U' ` T5 Q1 a
- extern osThreadId_t DAP_ThreadId;
, W7 K: C3 }: c+ X0 O( ] - osThreadId_t DAP_ThreadId;
3 D0 s. r5 } U - #endif& x+ M5 z: K7 { {
- # e8 n _/ h4 @+ E p9 u/ u
- extern void DAP_Thread (void *argument);0 T2 B1 s5 i) |( E, _0 N( F
-
9 p& T. s) W5 y1 @ - extern void app_main (void *argument);+ I" b# N1 a; U# a6 q- p
- + k8 A$ }: \. r( I* p# P! D' ?
- #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工程。4 V, H) H/ P5 C) k- D5 |
4 ^0 g! o/ [2 x! \2 A6 K8 p5 c ^8 _6 [+ ^! Y
接下来我会对main.c和USBD_User_HID_0.c做一些一些简单的介绍。* u V0 u; C6 Z2 P: M* P
, D8 \& q) X+ O& T2 m; R
" ~, x- h! ~" C# j# a/ Lmain.c, g. k+ S. W' J3 A5 d
1 G. ~2 ]8 y {6 j
/ L6 m, p- L1 Q1 B [- c我在尽可能地精简我下载下来的这些源文件,所以我也没有要示例工程中的rl_usb.h文件。于是我还需要一个头文件来定义一些关于USB通信的函数和参数。这里有一些来自CMSIS RTOS库的函数,其中最重要的一个是osThreadNew,在我的工程中我把它实现如下:# a! E7 x7 s* l" D
- osThreadId_t osThreadNew(void (*func)(void *), void * n, void * ctx): r9 e8 _( J8 t% F; d
- {, i8 H$ S+ z+ R- I% U5 k
- (void)n;
" T% L% P% P5 p% R+ X' ? - $ Y4 G2 T& X: m$ v2 b. b
- (*func)(ctx);
: W1 T. g7 e3 x6 @ - return 0;
# J4 V6 A& ?) z! r1 L1 V - }
复制代码 我直接“跳转”到本需要被创建的线程函数中,这就意味着main.c中的osKernelGetState&osKernelStart&osDelay三个函数永远不会被执行。下一个重要的函数是USBD_Configured,我将在使用STM32CubeMX生成初始化代码那一节解释这个函数。
! {- |6 Y5 d; p9 E' O4 O2 G4 }+ ^1 ~' F$ A6 P+ w; f" W% C
" r) @- o, n! @" @( O1 C
USBD_User_HID_0.c
5 o, i4 m% O: D2 K" E% X$ b8 |8 b% o- e7 E
4 [$ b# h6 E& t% n% E: R. ] O我移除了RTE\USB\USBD_Config_HID_0.h并在我自己的rl_usb.h中重新定义了USBD_HID0_OUT_REPORT_MAX_SZ & USBD_HID0_IN_REPORT_MAX_SZ两个参数。
- W9 Y" l* y9 @+ l; T+ E
% D% v+ n7 S2 j& r
, ^, y) A3 A: Q& N5 _" hUSB HID通信的核心是由两个接口中断函数管理的两个循环队列:
0 R, r7 v+ E+ g! g7 i1 b) l8 \' q; [- r- int32_t USBD_HID0_GetReport (uint8_t rtype, uint8_t req, uint8_t rid, uint8_t *buf) {
- m6 z T/ M! J% T6 G - (void)rid; h4 I; z6 |) [& X
-
0 V' G' I' m! ]( e0 [ A5 s - switch (rtype) {
0 u0 U8 ]4 `( }* K6 E0 i1 b. M - case HID_REPORT_INPUT:0 y! Z" P$ L. d5 n4 A# q( n. N6 j$ w
- .../ r& n& W1 \. |* w, G1 ?; I% D: K
- break;' B% z* y: B* c1 u% s
- }5 Q, M4 M! x9 ]" e
- return (0);, w9 [3 Z! i5 w% G) C9 I* c
- }
复制代码- bool USBD_HID0_SetReport (uint8_t rtype, uint8_t req, uint8_t rid, const uint8_t *buf, int32_t len) {
9 ~( V, Y( o' q, V5 |, f* ^ - (void)req; [: ^# O% m- \1 B
- (void)rid;
: Q: Q" k( D* t i -
% Y& c2 y! i) V2 e1 C - switch (rtype) {2 P& `1 i0 i* u { r
- case HID_REPORT_OUTPUT:
) A6 n% `9 q7 e' F, N) l6 w - ... [& f$ I$ n& K1 q" d" x
- break;. u: p0 g, R" i) A( {- v l9 ]) ^
- }
' X- R1 O( v B0 W$ t! \ - return true;- j: t% |8 Z4 p7 C7 m1 ~9 ]
- }
复制代码 当上位机向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中断端点传输。' K) R$ k0 P4 a5 z8 ^4 b- C
9 m4 W/ }( j% b" E* d3 A5 C% J! D* K$ Y) D
线程DAP_Thread只是一个命令判断选择器。在这个函数中有一个很重要的语句:
+ ?6 i# f+ x9 g: p4 k+ @- USBD_HID_GetReportTrigger(0U, 0U, USB_Response[n], DAP_PACKET_SIZE);
复制代码 我们必须实现一个叫做USBD_HID_GetReportTrigger的函数来想上位机发送INPUT REPORT。0 U5 ?8 z2 `0 E
* f3 A: l8 \- a: Q" K
" O8 a, C* y s M' I. i6 D; i+ S$ P6 |: \3 L
" k( W- k) s& l& O
# y. y5 q" n) w
9 g5 j, |. \7 c6 i) S0 p5使用STM32CubeMX生成初始化代码
* ~8 |8 v4 E2 z2 |在我的单片机上有一个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。
: M6 |- p7 [4 |
7 c" z9 o; m/ q+ o2 u4 o. q M' u2 D0 f( d# `
注意:
4 Q7 B/ K% t- Q. s% `8 p8 M我没有修改设备的VID和PID。但我猜测有些上位机软件会检测这两个ID( Y( Q- k J) ?5 h0 V9 C: @
7 g5 X2 U. _* [, H2 F, ^) s2 k3 O
0 B& E' w7 h9 X8 b如果你发现你的软件不能识别我这个CMSIS-DAP,或许你需要恰当的VID和PID。可以试试示例代码中的VID/PID,它在一个叫做USBD_Config_0.c的文件中,我的工程中没有这个文件。
) W J4 T6 ?' I1 x# |$ q5 s, S* P+ J: \
/ `7 B5 ` D2 y2 C9 d+ Q) z% _有STM32CubeMX生成的代码需要一些修改。在usbd_customhid.h中,CUSTOM_HID_EPIN_SIZE和CUSTOM_HID_EPOUT_SIZE必须设置为0x40U。我把CUSTOM_HID_FS_BINTERVAL改为0x01来尝试提升HID的通信速度。
; b2 R3 R2 ~4 B! L
) C3 A, d% d, ]% e
6 P5 T) J2 }( O在_USBD_CUSTOM_HID_Itf结构体中,我新增了一个成员:! T+ t1 l4 {4 {* b
- typedef struct _USBD_CUSTOM_HID_Itf
5 {2 x( t7 X I) @, y0 }/ ~; e% b - {) a4 U; P3 R# E5 b" B' l3 Y
- uint8_t *pReport;
1 ~' J# O! @; ^$ R' X& c' P - int8_t (* Init)(void);
0 c$ f7 b4 H' W: P1 f# q6 i. _ - int8_t (* DeInit)(void);
" G7 ^. @% `: [4 o- D% Q - int8_t (* OutEvent)(uint8_t event_idx, uint8_t state);) c1 a0 [5 w& d6 l# x) G( x
- /* I add an extra interface func below. Zach Lee */
& T# z3 M% p1 c6 t2 E - int8_t (* InEvent)(uint8_t event_idx, uint8_t state);
$ U+ s2 |0 H- e5 ~. k8 _0 L( r4 k - } USBD_CUSTOM_HID_ItfTypeDef;
复制代码 当INPUT REPORT报文成功发给上位机时,InEvent函数应当被调用,所以usbd_customhid.c中的USBD_CUSTOM_HID_DataIn函数需要修改如下:8 a0 J' L' b# k/ |. c, C
- static uint8_t USBD_CUSTOM_HID_DataIn(USBD_HandleTypeDef *pdev,) q1 @0 G/ }' C- ?2 t* V# a$ i
- uint8_t epnum)$ Z7 }) v( J" n
- {6 }2 m6 P4 e, d5 ^
- /* Ensure that the FIFO is empty before a new transfer, this condition could0 D! h% f+ \* H4 E6 h4 j# K
- be caused by a new transfer before the end of the previous transfer */1 M4 U* i9 `. z) i" u6 c
- USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassData;
0 {9 P7 R- o3 ]" H, q2 z - hhid->state = CUSTOM_HID_IDLE;
5 b+ m1 T: K$ e8 X! M* {" y0 k! @ -
& N6 u* r# S1 c6 E. u& X - /* I add a new interface func in the structure USBD_CUSTOM_HID_ItfTypeDef. Zach Lee */) p1 P" ]: B: \" q( U) k
- ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->InEvent(hhid->Report_buf[0],
6 h; ?* ]" z! z - hhid->Report_buf[1]);
9 ^ T) q" @* f1 { - 1 F o& H" t9 F+ F4 D0 h
- return USBD_OK;, [* u2 J3 H- e/ H
- }
复制代码 设备描述符CUSTOM_HID_ReportDesc_FS被定义在usbd_suctom_hid_if.c中,我定义了一个简单的描述符:
T1 F# D' d* y4 G6 ]- /** Usb HID report descriptor. *// k* ~5 i6 D" _# T- e" }
- __ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =8 D- Q$ I3 T5 w' Y# e8 Q R8 _
- {
( |0 w* }8 v2 h - /* USER CODE BEGIN 0 */ /* A minimal Report Desc with INPUT/OUTPUT/FEATURE report. Zach Lee */
. F3 D# a# c" W( L - 0x06,0x00,0xFF, /* Usage Page (vendor defined) ($FF00) global */
3 w9 d6 T# f$ F* N) ]8 C - 0x09,0x01, /* Usage (vendor defined) ($01) local */
* D6 A; w( K: E8 m- a+ \2 u - 0xA1,0x01, /* Collection (Application) */% `3 h) L: O1 d4 W* \ j
- 0x15,0x00, /* LOGICAL_MINIMUM (0) */: C( n8 ?- w6 F t4 Y0 i
- 0x25,0xFF, /* LOGICAL_MAXIMUM (255) */( ]9 b4 S) E. R1 ~4 m2 b
- 0x75,0x08, /* REPORT_SIZE (8bit) */& X9 `& K" \5 z* x3 U9 T4 @
-
* @& L" Y& s6 O9 _5 }3 i \ - // Input Report$ k2 p6 Z' Z! f* z' r
- 0x95,64, /* Report Length (64 REPORT_SIZE) */
; N p# S- s9 F$ w0 M( C) ] - 0x09,0x01, /* USAGE (Vendor Usage 1) */
: F$ U8 L8 A, ]6 H( U; M- w - 0x81,0x02, /* Input(data,var,absolute) */+ m" W/ D* A9 a& g& N4 H$ C1 V
- 6 r% d! f' G+ g( R
- // Output Report
7 k) T5 f- x% P& [ - 0x95,64, /* Report Length (64 REPORT_SIZE) */
% U" o2 P5 c0 @2 k+ g& T" d - 0x09,0x01, /* USAGE (Vendor Usage 1) */3 C' q4 q: O9 {# o. F' P, [6 i
- 0x91,0x02, /* Output(data,var,absolute) */
8 `! `1 Z, {1 d - / Y8 f" [! i8 R' `3 F" ]" v
- // Feature Report/ X/ |# }' v8 e5 A; S
- 0x95,64, /* Report Length (64 REPORT_SIZE) */
% [. m8 T9 t; F - 0x09,0x01, /* USAGE (Vendor Usage 1) */
: V3 z9 X* l N) u6 Y - 0xB1,0x02, /* Feature(data,var,absolute) */
4 F; q* i& x0 ?" P8 ^6 K5 e - /* USER CODE END 0 */; M( Z3 p. O% k& }, n5 | z
- 0xC0 /* END_COLLECTION */; r6 P+ ]. w. _; b3 [8 v" p7 H$ o
- };
复制代码 可能Feature Report在CMSIS-DAP中不是必要的,就留着它吧。& r7 B7 n! s% V! @/ j X9 ^
1 a- \; M. |9 [3 [* U) a* J
4 x! F {( X& y% m我在这个C文件中还实现了一个新的接口函数CUSTOM_HID_InEvent_FS:
' R+ y* z% ~9 _% V$ B8 f% j& |- static int8_t CUSTOM_HID_InEvent_FS(uint8_t event_idx, uint8_t state); /* An extra interface func. */
9 q, n" h- U1 P% x6 H - ' O: M( `/ {4 c
- USBD_CUSTOM_HID_ItfTypeDef USBD_CustomHID_fops_FS =2 P& Z9 P O% x. M7 D
- {2 |; V: U0 X# g
- CUSTOM_HID_ReportDesc_FS,
; j% F$ |) ^. o - CUSTOM_HID_Init_FS,' s! _$ J! k- l) J
- CUSTOM_HID_DeInit_FS,
; C& E( ]$ T# p4 ] - CUSTOM_HID_OutEvent_FS,* e0 I4 z" M) G2 |! I
- /* I add an extra interface func below. Zach Lee */: t) q' x# W& c4 J- n5 t4 |
- CUSTOM_HID_InEvent_FS( F8 y- ^) Q3 {" C" F$ p
- };
复制代码- extern void USBD_OutEvent(void); /* Implemented in file "device.h" */
w+ x5 `+ q& K+ }2 X -
0 Z1 N# l7 a' |* O: o ~* Q9 m - static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state): h [: O. p' C" Y1 Z/ n! r2 g. v
- {1 y2 H7 ]9 |9 X8 [) Y; f2 T
- /* USER CODE BEGIN 6 */
8 M% g6 Y9 V8 f) a( b, f& Y ] v - USBD_OutEvent(); /* OUTPUT REPORT was received. Zach Lee */
2 n; B9 q2 R* `/ k/ }( K8 G" Y - return (USBD_OK);
4 @- y3 j7 G# p& E - /* USER CODE END 6 */, n7 p/ k! T% W/ R5 j8 z
- }: g, h6 ]9 b2 i+ a3 U+ Y( j r
- % y' w2 k% Y0 {8 U
- extern void USBD_InEvent(void); /* Implemented in file "device.h" */" t$ W6 u8 Z) g- g: f
- 8 G: x4 [, i1 u- i* x4 M
- static int8_t CUSTOM_HID_InEvent_FS(uint8_t event_idx, uint8_t state)3 \; F3 @+ h! E
- {2 W9 r; R' I6 O2 s5 V( m
- /* USER CODE BEGIN extra */# |* }" G9 g! B$ |3 k" I! P
- USBD_InEvent(); /* INPUT REPORT has been sent. Zach Lee */
b/ l- p) Q: R9 g* m6 v$ \9 J - return (USBD_OK);$ z& s q7 Z: t% b9 F: k
- /* USER CODE END extra */( _+ X) w' r1 x# H
- }
复制代码 CUSTOM_HID_Init_FS和CUSTOM_HID_DeInit_FS两个函数被实现为使能/失能DAP功能:
6 M* p+ o6 Z6 \. X2 h0 w& z- o- extern void USBD_HID0_Initialize (void);
7 [- c' C1 W, \3 ^) g -
, J+ ]4 K! u4 @# Y, \ - static int8_t CUSTOM_HID_Init_FS(void)
! S5 g) T h/ u$ J1 s - {: Y: b4 Z, J& @; C9 z% x( j
- /* USER CODE BEGIN 4 */
6 }8 y( h3 \ I4 [1 `; z$ u1 P* t - USBD_HID0_Initialize(); /* Initialize USB communication of DAP. Zach Lee */
1 b2 Q3 H/ `8 E8 F6 n - return (USBD_OK);
" K1 z, Q0 O7 E - /* USER CODE END 4 */4 g1 [+ V2 n' X k V
- }9 O' z2 }& O( A$ F5 W: r; l
- 0 {# P6 v0 ~2 p6 b. U4 l
- extern void USBD_HID0_Uninitialize (void);
" q. T( z8 Y5 W5 k, L0 P - & y$ _9 u+ p- h$ B% a
- static int8_t CUSTOM_HID_DeInit_FS(void)6 R2 ], |0 G" K+ S) b( ~
- {* S, G. B2 C" f* d4 E W2 _. h
- /* USER CODE BEGIN 5 */
9 |* @1 k S& @; E) l - USBD_HID0_Uninitialize(); /* Uninitialize. Zach Lee */
3 \: a0 ^1 w6 u) p - return (USBD_OK);/ R, ~# a+ q( ^/ q0 {( A
- /* USER CODE END 5 */: Y0 b! C3 t: a8 g8 R7 s
- }
复制代码 编写将所有源代码关联起来的桥梁$ {) m- w, [2 U& D: y% w9 j2 j
为了移除CMSIS RTOS,我写了一些函数来模拟RTOS:
" a- z; {; G# S( Y0 A1 m) q- /**2 A; n/ |+ {1 q: Y x
- * Replace CMSIS RTOS api4 ^8 @# u" T0 k+ v* t
- */
2 D' Z9 N2 T4 y- F* s - static volatile int osFlags; /* Use "volatile" to prevent GCC optimizing the code. */; x5 w, I1 w! m
-
$ Q7 F5 V8 w" S5 s) t9 `; E- } v - void osKernelInitialize(void)/ o3 D7 n0 M6 q% } ~" |4 h
- {8 D2 H) X, e6 b+ F& @2 J
- osFlags = 0;/ }3 U- H3 z; ^( l/ _/ ?
- return;) ~' ?% q- k: @; |
- }
0 V; ]7 x0 a# R6 k% [ - int osThreadFlagsWait(int mask, int b, int c)
0 J' a* V5 P' L% e+ O0 b - {- ?; N" q) [. ?
- (void)b;8 {* v# C; K2 N+ _& C3 E% \: j0 j; ^
- (void)c;
2 |$ C% r7 J, {% B -
5 f; D" y3 K/ S0 n6 n8 h- s% @, S - int ret;/ ~' c: M _. U/ P
-
/ L. e4 [. s/ F% z - while((osFlags&mask) == 0)3 s/ g( @6 q1 f
- {
: o% R, J$ ?2 L9 R' j& t' G+ F - ;
" n5 C. W: o @: p s& K - }
+ C' w# Q7 [. @( z8 f! n+ A; x - ret = osFlags; osFlags &= ~mask;
, W5 j( t/ P, i7 w8 [ - return ret;, e" N c( \3 Z
- }
; C# A: [( d" e( r+ Q3 H* v* z% p - void osThreadFlagsSet(int tid, int f)* r* t0 ^: P4 c! M' X
- {
3 X$ N9 @, [1 m! B+ G - (void)tid;
1 ] o. a& k A# v. \1 b% D3 `; c6 Y& X -
, k' Q% z5 J# @) W$ C1 r - osFlags |= f; W& Q" {; U7 g
- return;
) G3 Z: O, a# P - }
复制代码 函数USBD_Configured和USBD_HID_GetReportTrigger实现如下:. P _2 m) W+ E( w) ?4 u; U2 {
- intUSBD_Configured(int n){
+ S% W; t+ i2 b/ Y5 ~3 d5 L. | - (void)n;9 i }, |- V) H, M+ Y/ N
-
. X2 m+ Q( x+ ~8 K* I2 p' a9 V - return(hUsbDeviceFS.dev_state == USBD_STATE_CONFIGURED ?1:0);}
复制代码- void USBD_HID_GetReportTrigger(int a, int b, void * report, int len)8 ^$ z; Z* N) z' P
- { c% B6 A' j) ~1 l/ W9 Z
- (void)a;
4 E' g! O- F$ E% G - (void)b;6 L0 {9 J4 |5 e. `! E" R8 D* E/ X
- ) b4 u. b0 U) G4 B0 e
- if (USBD_OK != USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, report, len))
7 ]! R1 a R: @5 s W3 Q1 S/ \+ l - {
, u- ?. l" o5 f' ? - ;
3 }6 \$ n5 C- B; o: ~/ i1 u p% x0 N - }$ h! v. o3 p) x5 j) C
- return;* f2 ~( j/ ?2 [
- }
复制代码 函数USBD_CUSTOM_HID_SendReport是由STM32CubeMX生成的,它被定义在usbd_customhid.c中,我自己的事件句柄如下:
$ |7 w I6 B9 N1 J, K* ?- bool USBD_HID0_SetReport (uint8_t rtype, uint8_t req, uint8_t rid, const uint8_t *buf, int32_t len);
0 l3 a0 x; y9 E2 J5 h/ o/ ~1 S - , W! m0 {. _* ]# A6 U: ^- V- X9 `
- void USBD_OutEvent(void)+ m/ N! ]' N; f J( S1 l
- {' t# d5 P: U2 {/ y
- USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)hUsbDeviceFS.pClassData;2 M6 x- D4 {! W' V. t! i
- USBD_HID0_SetReport(HID_REPORT_OUTPUT, 0, 0, hhid->Report_buf, USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);3 D: F# x, Q% Q) K1 q- C$ h
- }
1 [6 n3 ]4 u( o$ d2 K -
- O5 L' N% p; J$ N) ?( Y+ W+ c* g - int32_t USBD_HID0_GetReport (uint8_t rtype, uint8_t req, uint8_t rid, uint8_t *buf);
) H5 v+ S g8 M! E) }/ ^- E - 4 T3 v7 q2 P3 d# b
- void USBD_InEvent(void)8 Q* J/ C+ Y' q% \( k1 j. O* M$ h
- {1 ?8 U9 L6 r$ p+ p* ~- Z: C
- int32_t len;) |* o6 s* i( d
-
, ^4 ~; j9 z. W1 a. _) E - USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)hUsbDeviceFS.pClassData;
" ?* J3 A ?! Q, ~$ q, k+ Y t: j - if ((len=USBD_HID0_GetReport(HID_REPORT_INPUT, USBD_HID_REQ_EP_INT, 0, hhid->Report_buf)) > 0)
* i% [% z: F) m, I* |6 @" j n - {
; H. _" Y# g' c4 P - USBD_HID_GetReportTrigger(0, 0, hhid->Report_buf, len);8 R9 {# m$ M5 e* \+ N$ [. Q4 d
- }
' h+ u, q/ `* \ - }
复制代码 注意:! @1 j1 q# O" H. r8 g! x
在DAP.h中,这里有个名为PIN_DELAY_SLOW的函数,它原本的实现是这样的:
! J) m/ g- M" C9 u! p: j- __STATIC_FORCEINLINE void PIN_DELAY_SLOW (uint32_t delay) {
5 j9 W0 e( Y! G: k6 n - uint32_t count;# |/ k( |; v, \/ X3 J+ R
- $ Z4 h% E4 C: x( k8 o
- count = delay;
( Q+ k7 m; f" A/ `6 y( g; | - while (--count);+ R5 G! V. g+ J$ E( G% |, B( X
- }
复制代码 这里的空循环while (–count);会被GCC优化。我在StackOverflow中找到了一个好点子,它能正常工作但不是太合适,你有更好的方法吗?
6 u8 D# s+ o' }& Z- __STATIC_FORCEINLINE void PIN_DELAY_SLOW (uint32_t delay) {
* |7 C o* V1 v5 k, Q. ? - uint32_t count;$ g" S* Y9 S) c2 s
-
) B$ U) [$ k1 _ - count = delay;
s% G* c7 `# t& q - while (--count) {
* C& Y' |+ p* o - /**/ B r5 M8 n: \! ?4 Y1 ~
- * Empty loop will be totally omitted by GCC.' F' z4 _! K. O2 V% |
- * Search "How to prevent GCC from optimizing out a busy wait loop?" @ StackOverflow.$ b: W" w% o9 \/ |
- * This solution isn't portable. Zach Lee. p" G$ U8 r2 x8 U" b% q$ y- u) G
- */
' w) ]/ W x! I) l - __ASM("");
% ~" }6 S& k, P: [3 z6 ? - }: ~5 l* D; @, g7 z5 S; J) U
- }
复制代码 至此,相关源码就介绍完毕,源码文件:; l4 p4 f- a7 V; P) u
7 K7 i" u4 \, t9 \( |) x
# ^( F% C9 ?/ s" q4 [http://wiki.geniekits.com/downloads; v! u5 E- t4 J! q6 r
# x9 }* Y. B$ K# a
: t0 J/ B) `* M0 L i2 \, F W6 @" c9 e2 x+ o9 K7 D+ v2 E
; h( f7 K/ d8 Z! | T3 [
* D9 ^% s; z0 Y1 P1 e# E* J& W# o# d6 @2 }' i" I! M
, x7 ^4 x( Z/ h( g1 u- [
: _) [: _( @: `5 T- b! y5 @- ]% H8 N% m% c
|
https://www.stmcu.org.cn/module/forum/thread-616081-1-1.html
评分
查看全部评分
$ j2 `# j1 L! l# Q
顺便提一下,从实用的角度来说,我的代码有以下优点:3 Q) O$ D0 R' J# s: k$ y
1. 硬件便宜,几乎人人都有bluepill小板。 最实用的是用STLINKv2手指来改,稳定,方便。
2. 软件有VCP(CDC)功能,有软启动功能。送SWO功能,虽然用的人不多。
3. 速度已经调到最高了。如要进一步提高,不换F103的情况下,就得超频。还有一种方法就是,用SPI驱动USB通信。: Y a N$ w! P9 f