8.1 重要提示(必读)
# c; S* Y$ B i/ Y( N 只要是MDK支持的调试下载器,基本都支持Event Recorder,本教程测试了JLINK,STLINK和CMSIS-DAP。' B" J6 h' ] i# F, H
务必使用MDK5.25及其以上版本。& Q7 n$ W e( K! w1 p6 A
CMSIS软件包 要是使用V5.3.0及其以上版本,详情本教程8.3小节末尾的说明。' }( z% I h2 \+ w+ l
为了实现Event Recorder组件的最高性能,最好将下载器的时钟速度设置到所支持的最大值,另外,根据需要加大EventRecorderConf.h文件中的缓冲大小,默认可以缓冲64个消息(动态更新的FIFO空间)。) a6 ?# \' X3 C/ E
此调试组件不需要用到SWO引脚,使用标准的下载接口即可。以我们的开发板为例,用到VCC,GND,SWDIO,SWCLK和NRST。大家使用三线JLINK-OB也是没问题的,仅需用到GND,SWDIO和SWCLK。
1 a, j, ^& X& {* X% k( n; k8.2 Event Recorder简介9 B- g5 c* Z+ f6 d f3 ]$ R% W
前面的专题教程中为大家讲解了使用SEGGER的RTT功能来替代串口打印,比较方便。只是这种方法限制用户必须使用JLINK才可以。而使用Event Recorder的话,无此限制,各种LINK通吃。只要是MDK支持的即可。
, j4 ?9 |- g8 ]: s. r( T. F4 D/ e4 q/ o0 Z; W! V$ i
Event Recorder是MDK在5.22版本的时增加的功能,到了5.25版本后,这个功能就更加完善了,增加了时间测量和功耗测量的功能。
3 T% ~' U( e/ r" ?" W& A; D7 ~( g& J0 B6 |8 z' S
此调试组件不需要用到SWO引脚,使用标准的下载接口即可。以我们的开发板为例,用到VCC,GND,SWDIO,SWCLK和NRST。大家使用三线JLINK-OB也是没问题的,仅需用到GND,SWDIO和SWCLK。
@ D% x* ]# _2 f& |4 ^8 d% z* [ |( d+ Z
3 I& ?! d. U1 P) S( [$ d% J
0 r3 W5 I7 u2 o2 i1 z
JTAG接口和SWD接口区别+ _, m4 R) |$ x% N
下图分别是20pin的标准JTAG引脚和SWD( Serial Wire Debug)引脚,一般SWD接口仅需要Vref,SWDIO,SWCLK,RESET和GND五个引脚即可,SWO(Serial Wire Output)引脚是可选的。有了SWO引脚才可以实现数据从芯片到电脑端的数据发送。
! @8 g4 r+ Y6 F0 S- u$ F! L% ] J, {
- C, y) B' H6 w/ k8 o: G" j! ?( E$ @! y
7 s9 V7 j; ]2 B' H/ l
词条 SWV(Serial Wire Viewer)
! K' V+ n2 |! J/ ]0 s% ?/ \* dSWV是由仪器化跟踪宏单元ITM(Instrumentation Trace Macrocell)和SWO构成的。SWV实现了一种从MCU内部获取信息的低成本方案,SWO接口支持输出两种格式的跟踪数据,但是任意时刻只能使用一种。两种格式的数据编码分别是UART(串行)和Manchester(曼彻斯特)。当前JLINK仅支持UART编码,SWO引脚可以根据不同的信息发送不同的数据包。当前M3/M4可以通过SWO引脚输出以下三种信息:" {+ ~" T* D& I- i% }! k% a6 [, F
* C e' R7 l3 n( P( ^1 Q: v0 E0 r
ITM支持printf函数的debug调用(工程需要做一下接口重定向即可)。ITM有32个通道,如果使用MDK的话,通道0用于输出调试字符或者实现printf函数,通道31用于Event Viewer,这就是为什么实现Event Viewer需要配置SWV的原因。7 n8 c8 i* A) Q+ `2 Q9 L- ^2 _
数据观察点和跟踪DWT(Data Watchpoint and Trace)可用于变量的实时监测和PC程序计数器采样。
4 w/ B z/ c. E' S% g9 B+ {ITM 还附带了一个时间戳的功能:当一个新的跟踪数据包进入了ITM的FIFO 时,ITM 就会把一个差分的时间戳数据包插入到跟踪数据流中。跟踪捕获设备在得到了这些时间戳后,就可以找出各跟踪数据之间的时间相关信息。另外,在时间戳计数器溢出时也会发送时间戳数据包。' d* r# ?# W0 I* _0 A
8.2.1 Event Recorder的特色1 o* i6 |+ y8 O3 W1 q1 S
Event Recorder的特色主要有以下几点:% I) A0 T9 V4 t4 H
8 @" r. J5 e+ v0 ^* ^5 ~
提升应用程序动态执行期间的检测能力。
T4 o6 b* ?. P0 p6 T q4 r+ m0 J支持的事件类型滤除机制,比如运行错误、API调用、内部操作和操作信息的区分。
( ?& Z( n% p! G$ ]' D' E# Z可以在任务中、RTOS内核中和中断服务程序中任意调用。
' Z: i0 _! f1 D! _/ M! G U对于带ITM功能的Cortex-M3/M4/M7/M33内核芯片,执行记录期间,全程无需开关中断操作。对于不带ITM功能的Cortex-M0/M0+/M23,是需要开关中断的。* S- _. v3 n ]; a0 Q# E# L" S) z6 U
支持printf重定向。3 x2 s9 f! S5 Y+ w8 i0 O- K
各种link通吃,支持SWD接口或者JTAG接口方式的JLINK、STLINK、ULINK和CMSIS-DAP。7 w, l: h' h- J
对于带DWT时钟周期计数器功能的Cortex-M3/M4/M7/M33内核芯片,创建时间戳时,可以有效降低系统负担,无需专用定时器来实现。
" b' | {2 i( N: `1 n0 }Event Recorder执行时间具有时间确定性,即执行的时间是确定的,而且执行速度超快,因此,实际产品中的代码依然可以带有这部分,无需创建debug和release两种版本。
$ [/ w' m4 c2 g% q/ H. BRTX5及其所有中间件都支持Event Recorder调试。2 a" r" v5 k$ Z
8.2.2 Event Recorder是如何工作的+ t0 P# z$ E: i# h* t- w
首先来看下面这张图:
) e: e9 y% ?7 T8 w, D* K! C- ]3 J. p" m2 d2 y+ p0 C
- l5 z) U8 \5 j* S3 M9 q
8 `. I: T" {3 m4 \ y' }
在截图的左下角有个Memory内存区,在这个内存区里面有一个缓冲Event Buffer,其实就是一个大数组。MDK通过访问这个数组实现消息的图形化展示。为了正确的图形化展示,数组缓冲里面的数据就得有一定的数据格式。而这个数据格式就是通过左侧截图里面的Event Recorder和Event Filter来实现的。Event Recorder的API实现数据记录和整理,Event Filter的API实现数据的筛选,从而可以选择哪些数据可以在MDK的Event Recorder调试组件里面展示出来。
0 U; A7 p& q9 F" ?+ T
/ Y! |; x/ u, |# L# G) E2 S这就是Event Recorder的基本工作流程。0 `6 B2 k' z& C- _( `
" U/ N+ b' ]: k: [$ w
8.2.3 Event Statistics时间测量功能
7 a: a1 B1 O. t" v5 ?# Z, FEvent Statistics提供的时间测量功能简单易用,在测试代码前后加上测量函数即可:! n% i$ r! |' [7 ~; G
6 W: t4 }" f+ q; B+ h* t0 I
2 h. J2 G& c8 y( b/ l+ K4 Z, T. s
4 s! q0 f3 Z a$ S8 e在本章教程程的8.6小节为大家详细进行了讲解。通过这个时间测量功能,用户可以方便测试代码的执行时间,从而根据需要,进行合理的优化,提高代码执行效率。; z6 O' z! z' C% D0 ^+ d2 o
, L/ P! t# R; h8 ?" C: J
8.2.4 Event Statistics功耗测量功能8 M( y0 G* @+ T
Event Statistics提供的功耗测量功能,当前只有KEIL的ULINKplus支持此功能,由于ULINKplus价格不便宜,一套5000多,大家作为了解即可,实际效果如下:/ U& n. p9 V. N% R5 U
1 R, N4 G, k% h' C& P5 H& v% k% C; @8 C6 h3 S5 `8 W- @$ y
# X+ I4 g8 L9 S* h
8.2.5 Event Recorder的实现原理. B9 y. g, d* _7 _' D7 }: V( Z) r
每条Event Recorder消息是由16字节的数据组成,32位的ID,32位的时间戳,两个32位的数据,共计16个字节。其中32位ID最重要,格式如下: + ~: O w9 r+ H/ O1 _
5 G3 u+ |" q* b F" D* o! n' a
B e4 H# U# U- e: n3 U+ U% m6 y3 o- l) E. M- |' T
Level指定消息分类,主要用于消息筛选:
- b% |) |; T* I/ a* f" {5 P9 m% R" x0 r; K( c4 h1 h( N5 H1 t
& A! b0 D4 D& Z! R6 gComponent number指定事件消息所属的软件组件,也可用于过滤: . }) e' w$ L% ^0 s6 D/ o
1 b! ]% Q8 f0 P) u
7 l4 L0 r9 O. U) C6 v
9 ^- A% O s/ {2 x看了下Event Recorder的源码,每条消息大体是一样的:
0 B9 F* n' d4 {) y6 m
( G- j } R3 y' @" i% h! r5 X2 T: B- typedef struct {
5 m) k& i8 M0 ^3 b8 P) o - uint32_t ts; // Timestamp (32-bit, Toggle bit instead of MSB), M( Q6 ~8 u- L% M3 r- Q) N
- uint32_t val1; // Value 1 (32-bit, Toggle bit instead of MSB)9 D4 h$ ]9 R3 V9 P w( B o
- uint32_t val2; // Value 2 (32-bit, Toggle bit instead of MSB)
2 F# P0 u4 W' i; e - uint32_t info; // Record Information
6 N( U! A4 J# _ - // [ 7.. 0]: Message ID (8-bit)# r" P' P4 s, U% x4 t
- // [15.. 8]: Component ID (8-bit) q/ r, q7 Q9 [' X8 G) a
- // [18..16]: Data Length (1..8) / Event Context1 e: t- K6 j9 [" |" D! G
- // [19]: IRQ Flag
: E+ n5 t; Z! P - // [23..20]: Sequence Number6 f# `) Q- N* u F3 t
- // [24]: First Record. U0 b) G' c! u' f
- // [25]: Last Record8 y) ]# J4 R. y# @! p9 F
- // [26]: Locked Record Z+ j6 k+ [3 I& R' A
- // [27]: Valid Record. W6 l: h3 T, V
- // [28]: Timestamp MSB+ I7 T0 V' n/ F3 O- w
- // [29]: Value 1 MSB
. i2 |# J4 F0 a$ P* V: F5 |2 R0 H" i: [' w - // [30]: Value 2 MSB x1 E# V) B$ y4 s3 N' @5 m2 B
- // [31]: Toggle bit
; c+ R. r' X- F+ O1 U' H9 b - } EventRecord_t;
复制代码 / q" J: I6 g9 Z! X$ r$ ]2 p' Z% V1 N
其中参数成员info最重要,也就是前面说的32位ID,这里的说明与前面的说明稍有不同。这里是经过处理后,实际存储到Event Recorder缓冲里面的数据。
" J5 O$ F) I" f
2 D& ~) O8 H: ^$ L# b5 e" W对于Event Recorder,大家了解了这些知识点基本就够用了。- K/ q+ L! ~* ~; v1 ]/ H
+ l e0 p p8 W0 A% j
8.3 创建工程模板和注意事项
& E3 B/ |. l2 aEvent Recorder工程的创建比较简单,这里分步为大家做个介绍。 o. e2 w- p1 F! G" m
' B S# ]/ C4 Y; j8 s 第1步:准备好一个使用MDK5.25或以上版本创建的工程模板。
5 _* ]( v& x# Y h8 ?' G, t9 r4 O# Y+ b- S* f
, J! j, L# Z9 }# K* x0 V- z
n2 @$ ?2 B: @' y& f, a 第2步:安装ARM_Compiler V1.4.0或以上版本(如果有最新版,直接安装最新的)
4 `8 H9 x. f3 o& a# a1 p) z; Q+ Q2 l
第3步:打开MDK5.25或以上版本创建的RTE环境。
" m4 S7 p' Q7 S% i: c: e$ E2 P7 A7 d1 W' M& b/ S
& C0 L4 Q$ [% n; w- P4 A- D) h6 g: I& v& ?9 z
第4步:通过RTE环境,为工程添加Event Recorder功能。
|% w* a3 g9 k
- T8 e) `+ }0 Q f8 o, o0 P* s- y* q- L \* a9 ?7 B
, V; n2 Z, a8 ~8 e$ n
第5步:为了实现printf重定向,我们需要将STDOUT的输出方式改为Event Recorder,即选项里面的EVR。
4 z5 z5 {' \! A0 |; }3 b4 n
4 E3 c5 P; l3 {+ F, F B/ v9 ?5 P9 n% W4 @% [5 m+ V
- L) M+ L0 p* l/ j
第6步:打开通过RTE环境为工程添加的文件EventRecorderConf.h,配置如下:
% b! G) x; ]" K
1 b$ \; K# t9 b# [8 B5 Y9 {
% e; `: J/ G: z. ^# z: @5 L
% \9 p2 ?7 j6 `: M, n这里主要设置方框里面的两个参数。$ {( ]5 G+ x, \
+ A! t$ W# L& E9 R# H
Number of Records:表示Event Recorder缓冲可以记录的消息条数。0 l# d4 F. A! a ?! m7 F+ \0 `
0 v5 _+ f: G: Z& cTime Stamp Source:表示时间戳来源,有如下四种可以选择,我们这里使用DWT时钟周期计数器。3 k: g0 Z! |4 p R
1 U3 J: L" i" X: N ? u) X3 Y
" X* G5 p, A: p R; [# @: \
6 T# D6 q* {6 @- |# P9 b! u# ~由于选择的是DWT,因此EventRecorderCong.h文件中的Systick Configuration配置就不用管了。
3 J" F# R1 N2 i0 _$ k: b9 W& i4 M# e5 W# g
==========================
% P/ Z2 k! Q. L' m5 {, \. T
/ s; \2 ~7 z: R& |0 U通过上面的6步就完成了Event Recorder功能的添加,效果如下:& o# _3 U$ u" H# P8 U+ A! Q4 i1 J
# l5 U) c3 T* O$ D; R( z0 d. s
" o6 j( j2 ^ m/ t
4 t* z) f( F) g/ i4 R; x
添加完成后,还有非常重要的两点要特别注意:% w0 Y& i6 m# f. v5 e4 s
4 j% S' l5 R2 k0 L# Z+ S 第1点:一定要使用当前最新的CMSIS软件包,当前是V5.4.0(随着时间的推移,如果升级了新版本,直接使用新版即可)。
( d) a" S8 R/ k- L7 v
3 _! M- Z- o9 O7 h @* T
" A, @, u) Q8 A* o
& E4 r, n/ P8 l: ?9 _0 e下载并导入到MDK后,需要大家更新自己现有工程CMSIS文件里面的头文件,可以直接将CMSIS文件夹中Include文件里面的所有文件全部删掉,替换为MDK安装目录如下路径里面的所有头文件:
/ A$ J V) i; { o+ D
8 e# ]2 ?: m3 w2 R+ hARM\PACK\ARM\CMSIS\5.4.0\CMSIS\Include。保证头文件都是最新的5.4.0版本。
! F4 M* P# n) f7 \( [: e& a- U3 g4 L3 O5 x$ O; P
第2点:由于使能了printf重定向,大家的工程里面一定不要再做重定向了,比如fpuc,fgetc。另外当前选择了微库MicroLib:& B' r% N1 y+ ?6 O8 ^8 U
' w' H' c/ |6 t
6 h M9 E: m8 _9 k
8 S% K6 ?0 [( Q; p注意这两点后,就可以使用Event Recorder的功能了。
5 N+ A, x% U2 ^( o n; b& U& R% [8 N( @- L( j& ?/ C
8.4 Event Recorder事件记录的实现
4 V5 b7 h& U9 o# L" ~% k! aEvent Recorder的使用也比较省事,这里也分步为大家进行说明:. u2 P' ~0 m# b1 |# `- K: g
+ G7 B( }7 N0 M4 A* p 第1步:初始化,仅需添加如下两行代码即可。
u' Y& s1 |& ?' U; `+ \: L9 k r1 l; i' O& F9 X) `$ G
- /* 初始化EventRecorder并开启 */
/ B8 z+ I' o. [3 E& j - EventRecorderInitialize(EventRecordAll, 1U);
) w1 t. X/ {( W! i# s- H; B ~3 z - EventRecorderStart();
复制代码
2 L+ C* P9 `7 S: ]& @4 z 第2步:调用Event Recorder的API就可以使用了,主要有以下三个API:
4 S# {- ?# K Y. ?0 ?7 ~; e' Z) y6 J% e. }' ~) O3 X
EventRecord2:可以发送两个32位数据。7 i( c& X( s' p& _
! `# ]3 U7 d; K% U8 ?EventRecord4:可以发送四个32位数据。
! u6 [/ F$ i8 J) ^/ f% c- j' t) i+ C h
) z! a1 q; Y( k4 VEventRecordData:可以发送字符串。- h% U9 l p1 a+ V- u+ [: D
( p7 _$ t9 @* \
显然这三个函数没有printf使用方便,所以对于这三个函数,大家做个简单的了解即可。教程配套例子里面有调用到这三个函数,可以操作熟悉下。这三个API的说明是在对应的help文档中,即MDK安装目录路径:/ARM/PACK/Keil/ARM_Compiler/1.6.0/Doc/General/html/index.html。
5 w' p% O2 T% P" m2 c u3 p% H! K \- I l3 V6 U
1 s. {0 h4 T1 j) H H$ L
) H/ \( d y# S. R 第3步:进入调试状态,选上周期更新:
# h8 |- T9 B( ^7 v- b
5 Y; V3 K2 C# e- x- L, r# ]6 t0 G4 G% r3 i1 k/ A: c M8 `5 I
$ O4 ~, X7 L) a+ }- O( `8 E点击全速运行:) ~& w+ \1 k4 n+ u( h
! R5 A; m, L, O) D/ k
% H: [: x% C/ o" n5 ^! x
) j0 R# S5 Y# V# |1 P, ^& \然后将Event Recorder调试组件展示出来:6 }$ H* H6 T1 y- h) q
$ o' h1 r' o9 W
6 s( K5 D3 z! X3 C
8 P7 B8 F8 h; X g9 V效果如下:
6 j" a2 k! i) G5 }/ T/ o) Q4 j4 {5 v( B: C8 n7 O
* ~; l( c9 S% D: N& A
$ W4 W1 z; U( ]9 _) l9 W
另外,这里有个知识点需要大家了解下,如果程序里面也调用了Event Statistics时间测量函数,那么也会在这个界面里面展示消息的,如何才能仅展示大家想看的功能呢?这就需要用到Event Recorder支持的筛选功能。使用这个功能需要大家先暂停全速运行,然后点击下面这个选项:
8 V# e& H& F; K& J# ?
0 a: n4 }5 m9 s' u
; M$ }5 E( B: M+ x- L+ w3 q+ H( P0 }4 T6 M: b* G/ H
弹出的界面里面可以设置哪些选项显示,哪些选项不显示(勾上表示显示),我们这里取消Event Statistics的显示,设置完毕后记得点击OK按钮。
& A- L7 |: _/ U( e
4 l' Z M' q/ c, E' q7 h1 X, }; }5 K) \1 Z* Z/ F- Z6 B
" J& D5 m* J: [) r这就不展示Event Statistics的内容了。再次启动全速运行前,下面这个选项的对勾别忘了勾上。5 m, C! {7 ^1 @9 b
& t5 H# {- J5 @* B+ d J4 q
9 \# j" A# s- L( q
: Z& r9 w+ ]. l& l8.5 Event Recorder实现printf重定向. }/ X6 a, [! r6 H; d. l. z
实现printf输出需要用到MDK调试组件中的Debug(printf) Viewer,输出效果就跟大家使用串口调试软件一样,可以输出中文和英文。
+ I& y( h- e9 l0 Q3 l3 f1 ], Z
3 h [- u) f- \ l7 \: \MDK的printf调试组件使用方法跟本章8.4小节中的说明一样,点击调试,选中周期运行,然后显示Debug(printf) Viewer调试组件:& r( _4 v4 ?3 j* h+ E9 A
" r4 v+ y& T- R" h- E
/ {- P5 I- F; a
3 i6 g; Y2 c8 j( m w' q效果如下:
+ v: }8 ^( W& N# w1 ~4 A, F$ c8 M' @3 N3 _
! g! n0 I/ M0 R- G- i I4 B# Q* t: Y; @9 `% R
另外,还有一个知识点需要给大家做个补充,使用SWD接口的SWO引脚也是可以做串口打印的,并且也是通过这个调试组件Debug(printf) Viewer进行输出。只是这种方式的性能没有Event Viewer强,而且要多占用一个SWO引脚。9 x- C& W. V' ?8 Q0 @+ j7 @ F- X
# p4 _# O) d" G7 g% m& [% ?6 S. S
8 F) F% h. Q A0 J \( Y) Z
( i1 a6 E- r# x' z. w% h
8.6 Event Statistics 时间测量功能的实现
) k( ~# m3 |( W- f" e时间测量功能简单易用,仅需一个起始函数,一个停止函数即可。当前支持4组,每组支持16路测量,也就是可以同时测量64路。6 [6 w, r0 S0 U$ l7 C3 f( i
8 J& `: }% Y) A& s( V- d& D: }' i时间测量的API函数支持多任务和中断里面随意调用。1 G! F: x/ V/ I7 S7 i4 |
2 V$ k0 {$ q) N1、 测量起始函数:EventStartG (slot) 或者EventStartGv (slot, val1, val2)
( I" O, ]1 H2 A9 n) g
) a+ V* f' o+ Y: Q" U; |9 x 函数中的字母G是表示分组A,B,C,D,即实际调用函数为EventStartA,EventStartB,EventStartC和EventStartD。: A# q/ O& u! [2 ^
函数的第一个形参slot的范围是0-15,也就是每个分组可以测试16路。. z9 {# H. p `$ ~* K
函数后面的两个形象val1和val2是32位变量,用户可以用这两个形参来传递变量数值给Event Statistics调试组件里面,方便图形化展示。简单的说,这两个变量仅仅起到一个传递变量数值的作用。
$ n- a# n5 [- w2、 测量停止函数:EventStopG (slot) 或者 EventStopGv (slot, val1, val2)
* c# W* `6 n$ v) i# \' G4 T; O1 K7 k# z6 Z" W$ c5 Q7 g, |
函数中的字母G是表示分组A,B,C,D,即实际调用函数为EventStopA,EventStopB,EventStopC和EventStopD。
% E/ m; t$ d1 M% V. [ 函数的第一个形参slot的范围是0-15,也就是每个分组可以测试16路。" A5 S9 D+ j, l/ p& [: P3 f# U k" `/ a
函数后面的两个形象val1和val2是32位变量,用户可以用这两个形参来传递变量数值给Event Statistics调试组件里面,方便图形化展示。简单的说,这两个变量仅仅起到一个传递变量数值的作用。1 j. Q: S& `9 R$ A7 Z( w
- }, S" X* e3 s, b2 q, @! @* _6 }
; n1 e' y: b; `% F" v. W( K这里也分步为大家说明Event Statistics时间测量功能的使用方法。: k# ]/ a6 H9 W: h0 d$ i; a% }- y7 n! t
+ S8 q1 |% K# @ 第1步:初始化,仅需添加如下两行代码即可。
. D% z: F9 I# n6 S( M/ z9 O* Q& t/ Q4 u
- /* 初始化EventRecorder并开启 */
/ L: D& a6 `8 k! V6 f3 y - EventRecorderInitialize(EventRecordAll, 1U); ( l3 @- _) \1 _1 F5 L
- EventRecorderStart();
复制代码
% ~" p" K, e" q4 \6 w( o 第2步:在要测量的代码前后加上起始和结束时间。
; b& C0 Z5 j- x$ J H2 \% L% ?, Z) x7 g" {* d, ]0 m1 L' |& I
- EventStartA(0); & a% Q1 c: Q7 D. v! U: C$ _0 i
- 测量的代码部分
& l$ @# r1 O& g- e& y - EventStopA(0);
复制代码
" f1 A; `6 i+ x) N" g这里是用分组A的测量通道0。
: d1 ]) @# s. D8 j) u3 q7 ~1 K: v* K0 }) }7 y
第3步:跟本章8.4小节讲解的一样,点击调试,选择周期更新选项,然后全速运行。% k1 z+ ~ q( `9 F1 c
2 d3 P! N4 H; U: r% u9 y
第4步:全速运行后,显示Event Statistics调试组件。
9 d) B, J" d, g- C9 {
& _' a: V: _/ A( }; o( f+ I, c4 H3 }' m4 d, d0 I7 ^; j
/ z& h. o8 E2 Y+ V- r# }比如我这里简单的测试了一个5ms的延迟函数,效果如下(测量时间是动态更新的):
2 j! [% l+ x' f) K5 P9 l; j
; B5 J2 x# U+ q! g! J
! U/ s% \# ~2 h$ u, @4 q3 D7 M( m# n& L8 |
另外要注意一点,微秒的时间单位us可能无法正常显示,这个是没有关系的:
t4 E6 K; G1 y$ p7 q+ x; e/ e6 ?7 ^2 d% Q9 q) L& v4 d8 @
2 g% y) X+ ~! m) Q2 }+ `
, V% a. z: h6 y, j# m+ D
8.7 Event Statistics 功耗测量功能的实现+ Y) Q$ X( m2 }4 a# J& U
当前仅KEIL自家的ULINKplus支持功耗测量功能,这款下载器不便宜,一套5000多,大家有个了解即可,我们这里就不做讲解了。
& _- _% ?0 m% j; b
% Y2 j% ?1 ^8 i9 |3 @) T0 k8.8 Event Recorder对RTX5及其所有中间件的支持
. Q: z9 d% |2 m3 ^3 w" P$ V后面做RTX5及其所有中间件的教程时会为大家做讲解,这里让大家看下效果:4 X8 B5 d9 f6 y: W8 z) y
2 d( L. z7 g' R/ H3 v; p3 d RTX5组件和使用Event Recoder的效果:4 w- }* z8 i, z' `
( `9 O' u# w8 ?' M% y* T+ Z1 F" e' r
" X5 t+ e6 W) I1 c( t
4 a4 ~/ Y! R0 S G. B _0 ?
5 ?$ Y: R/ Q5 m$ Z/ a* l% f
4 L" y) M( Y! O. T) R$ Z$ \ 网络调试组件效果展示:& x; [$ J! K0 z9 \! d: s8 [! o
9 J# F9 L: I4 c& `
4 j m3 |4 v' s- D4 ~6 _% N
$ |+ o. c2 r4 Q: e0 ^+ k' F5 H5 U4 e [ m- S: F
2 W. b0 N; ~- P7 F; p6 g9 K$ N7 Z
文件系统和USB协议栈的效果展示:
b4 q" G+ @8 F' [: F# }% k9 Q J; i/ L2 X: g+ \, F1 B
$ v3 p0 ]6 b, G# ^' P# {2 g
$ I/ W& Z, j$ U& ^. d# _8.9 JLINK配置说明, P* _& e- s5 j" W0 B( C$ n4 N
为了帮助大家更好的使用JLINK,这里将JLINK配置中关键的几个地方做个说明。' p& m( O( Q; ` h% N
- @: Y; [0 W+ Q4 v. R 下面这个地方最重要,一定要正确设置当前系统工作的主频,如果不正确,会导致Event Statistics的时间统计不正确(对于H7,Core部分要填400MHz)。% W' H! |+ ?% d) \8 }
; W# n( N0 g' n9 O9 V
* E* ~0 I/ @3 J
# d6 |- b! a* {7 m, g/ s注:如果大家调试状态弹出SWD配置时钟超出范围的问题,可以考虑将上面截图中的Enable选项的对勾取消掉即可,但内核时钟一定要修改为芯片的主频。$ D5 @3 w, p; n( u
- `5 n$ B h) J7 O: F
另外,进入调试状态后,右下角的时间是否正常更新都没有关系:
6 v; R/ g ^& V! a7 @/ j# ~; |9 U9 d1 e; S1 V
6 }/ Q, s0 j5 J a7 A
7 i) k0 s; n7 f 其它选项配置如下(只要大家的工程能够正常调试,配置就是没问题的):# Z9 O# ]2 M7 t. S2 U
9 m, g5 c @- B1 [+ i8 }7 Y: k" h& U4 K& X' x
9 G! `: S) @$ `1 j9 U+ m
0 ^9 ^$ z/ `1 H3 f4 C F+ ~. Y* D
& F0 e$ N, I+ ]# F) s6 g
" z! [. v& L! ~) z- L: K! {" K
- N% d# Z R, a2 i" n9 |3 F* f1 G$ W
& I% Q. n* t* b/ @0 ~% B% ?8 @" p7 N
- i/ h% ^* I* Q7 S. D' d, s
8.10 STLINK配置说明
3 c6 v8 m3 A; _4 W7 @为了帮助大家更好的使用STLINK,这里将STLINK配置中关键的几个地方做个说明。* f6 k# X9 v' z2 U& y( B1 e
. I G. z3 r8 C6 |: b5 y 下面这个地方最重要,一定要正确设置当前系统工作的主频,如果不正确,会导致Event Statistics的时间统计是不正确的(对于H7,Core部分要填400MHz)。
B8 B: K- G: M) y' o' [, {$ h
9 l G( B! A; q2 @# a, G* y4 c2 d. A
% q& G: @5 t# M ~' j: J/ V( H另外注意,进入调试状态后,右下角的时间是否正常更新都没有关系:
( t: ^1 D3 k5 \' o" O: N
- C( f `, X/ w N6 P4 W) }. Q. Z4 Z8 H1 V
+ |' F% k( W2 z, e
其它选项配置如下(只要大家的工程能够正常调试,配置就是没问题的):
$ v9 o! K) j: K/ P$ ~( L1 Q
- @' k, H" ~' n& B# ?! o! w# N
+ B( }, e* b( Z8 D, i5 v
$ [6 ~# a$ Y1 y( A0 I4 j
r6 h1 y* ~; _+ D0 Z$ ~
5 {# ?* D/ y( h! }" K/ @* f j6 l# F3 ^7 S% Y a
5 W4 V7 |' H5 D# D" i
4 [% H1 O) d, ^, l; v& A8.11 CMSIS-DAP配置说明
9 e- P" J/ E( T. A7 n7 _为了帮助大家更好的使用CMSIS-DAP,这里将CMSIS-DAP配置中关键的几个地方做个说明。" L4 h1 L! C' s- a
# U6 W* g5 x8 W5 T7 ], R, \ 下面这个地方最重要,一定要正确设置当前系统工作的主频,如果不正确,会导致Event Statistics的时间统计不正确(对于H7,Core部分要填400MHz)。 C! D+ G1 ~! A, P
) ? _6 K) {6 D8 c; q O' ^0 i
( S5 D5 }( |; Z% b
% a. J6 o- u2 t9 Z另外注意,进入调试状态后,右下角的时间是否正常更新都没有关系:4 S* X; W* Z) U
0 M+ k7 Q- | [' d5 X
0 t! y) C' I1 ~8 B
; z. {7 f Z: e0 \- H9 z' z3 R- O7 h' D 其它选项配置如下(只要大家的工程能够正常调试,配置就是没问题的):
* w) {3 f4 I* C) Y. a
2 a9 s3 T& X& L5 c* b; w9 m' E" w) X2 _, T6 ^/ _* ~, q, I# |$ _
3 m, V f3 S; q* X0 z/ m
& A" F- R1 X4 K& L4 ^, S5 [9 Q" ^3 Q
0 {; G1 G3 u8 q8 x0 _5 A- y3 |7 x
7 `5 J' j. @3 [: u/ E9 n
0 ^' I+ ^- h( s. S) g8 S! N
: X* y" @, h* E ?! G$ E
9 n& R+ t# d) p2 l0 O! C
4 ]6 Q# |( e. w( C. n2 w7 v
) H9 B/ Y% q* _- O8.12 ULINK配置说明
, _# k. _) q) w! m- t由于手头没有ULINK,这里就不做讲解了。如果大家需要相关配置,按照前面小节三款LINK的配置照葫芦画瓢搞一下即可,或者在MDK安装目录的路径ARM\Hlp下有对应的文档说明:
0 W: j' T! O# L5 v5 V& A* s& x6 K1 r4 E& Q- T1 p0 e/ Q# o
" X$ R$ S- j+ A' W
( R, X. e. W& j+ X8.13 配套例子
1 ?0 D) C4 S$ @1 Z) y; z1 M5 }本章节教程配套了如下例程,仅MDK版本。; u0 r6 l% R- E6 M- v
) I! j6 }* c# `$ P
V7-008_终极调试组件EventRecoder的使用
+ V; q6 t# e" t( w3 S$ [ e5 L9 A7 R
0 Q& ^" ]5 u0 a% t2 V具体代码实现也比较简单,以V6开发板为例,定义一个TIM6的中断,中断频率是500Hz,通过Event Statistics测量中断的执行频率。代码如下:( \) d$ B9 u/ i6 T
1 d$ ~" f* X2 s6 V
- #include "bsp.h"
* }% Y ^* r- u1 o" e& W5 T, g - #include "EventRecorder.h"" Q1 I1 Z5 x: y
1 W4 x6 J e1 e ]6 Z7 p
% C. o" j5 R6 n
$ h3 W! s$ R! y% m1 _& P
\- |% ~3 u% E' _) [* T- /* 定时器频率,500Hz */6 H, P1 S [3 M/ T2 g0 x5 |
- #define timerINTERRUPT_FREQUENCY 500
' i) ?2 ?$ a b/ e F) J$ R0 J, H - + M D& e1 _3 o3 ]2 g2 \% S+ @
- /* 中断优先级 */3 [ }$ Q4 }9 e- _
- #define timerHIGHEST_PRIORITY 100 c1 Z" e* L7 F4 \( z
- & e( X7 }1 R, g
-
! k( a" w0 E0 A* ?" @- m6 e - /*
8 C6 n7 @9 C1 H, d& X( \4 ]+ v; f# y - *********************************************************************************************************2 v8 {7 t: [! O' }! F0 L
- * 函 数 名: vEventRecorderTest8 H9 i. t U5 Y( d) r1 f4 |
- * 功能说明: 创建定时器( Z7 x6 J6 a7 f
- * 形 参: 无
# y0 X4 n2 `! i8 d; m9 k - * 返 回 值: 无
, v4 t' z+ N% K- m- C. x - *********************************************************************************************************2 W9 H/ a/ X+ p# j% O6 Z
- */
+ p x6 _1 A2 B" H# D7 ] - void vEventRecorderTest(void)
. Z* g% V! s( ~, K8 M- g - {6 P4 X o4 ~ V! k3 D
- bsp_SetTIMforInt(TIM6, timerINTERRUPT_FREQUENCY, timerHIGHEST_PRIORITY, 0);
% c9 _2 c- C9 G4 A, Q6 Z d - EventStartB(0); 6 `- z% J$ Y% C! D: }9 N( S
- }, U2 p2 `9 [; x7 p7 V$ h
4 y0 x5 C1 K; \0 h- /** G& J4 L2 P+ n% i( d
- *********************************************************************************************************6 U$ b- `1 M7 i4 W- y9 M
- * 函 数 名: TIM6_DAC_IRQHandler! e" h5 e% v$ m
- * 功能说明: TIM6中断服务程序。/ N1 o5 @7 J9 P- X; `( {
- * 形 参: 无
5 e4 V' H" W- W6 K. n( ` - * 返 回 值: 无
+ i) `2 i0 ]8 u' A1 h - *********************************************************************************************************( z. j4 ?! K/ I
- */, x$ y( P8 P: U1 Q% B
- void TIM6_DAC_IRQHandler( void )7 P3 V! K* [* ~& K( {6 h/ u
- {
; \6 g% ?( c& o - if((TIM6->SR & TIM_FLAG_UPDATE) != RESET)
9 C8 @" y. ]( ^0 ~+ ~1 F - {
5 @; i# b, w9 c0 B* \ - EventStopB(0); % `. \1 @: t# M
- EventStartB(0); 1 K$ q, t& A! K5 C
- ) M T: G) ^; g& R" d* Y
- /* 清除更新标志 */: e1 V2 b2 |- J' P, m8 ?" B L
- TIM6->SR = ~ TIM_FLAG_UPDATE;
2 S0 o5 t! _5 q; ? - }
5 C( a4 L: p, {; |% `4 t8 W - }
复制代码 * G: A) T2 a% Z, Y
效果如下,测量的平均频率是1.98ms,与我们设计的500Hz基本符合:9 @. [" i- K$ L; t
6 y9 F# ~ O* J: }
3 x. q$ d' `8 k4 O( E' l% q7 Z( l: V+ k& }% G
应用程序的设计如下:% d3 q- O. B- d: W/ k
' K; r2 Q! o! x
- #include "bsp.h" /* 底层硬件驱动 */
0 [+ q- R" A# t7 } - #include "EventRecorder.h"
- h4 g3 J+ d9 {. t - : r7 I5 o& R. m$ H
- 6 V2 X# |' u$ j1 T" }* h. K V0 g
- /*! |' u( w6 X& Q7 t2 Z/ _
- *********************************************************************************************************$ g9 W2 ^* a2 j9 V
- * 函数和变量
; w$ @+ O8 f, ] w A8 Z& t - *********************************************************************************************************
* i+ E8 r% f! q9 Z+ [: _ - */. h9 R! E' z' G" j0 Q
- extern void vEventRecorderTest(void);
3 ]- f( d3 M0 M! ` - uint8_t s_ucBuf[10] = "armfly";% j. m* ^; ^& F$ c% ]% ?
8 X4 V) `* d6 Y- _2 ?4 j& k/ m2 q
- /*: w4 R* ]9 i+ n9 t/ R; V. P
- *********************************************************************************************************
* r1 I! t5 O4 [8 t - * 函 数 名: main8 V' M3 y- @6 F0 {$ [
- * 功能说明: c程序入口7 M$ p5 ^# Z7 W/ A
- * 形 参:无
. [" K# N" _1 S/ f, [) K' ]& R - * 返 回 值: 错误代码(无需处理)5 \" \ q8 e; ~* a. c
- *********************************************************************************************************
- ~+ V/ k1 G1 A, O3 w9 H& I" L8 I - */
& x& Q5 T- c& S- ?9 d - int main(void)9 Y; K, Z' j1 ^% z* h
- {7 l8 r( ~4 \, {% z
- uint8_t ucKeyCode; /* 按键代码 */
# _# v Q/ a, [, G( E' |8 C% J' ] - uint32_t t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0;: B+ j) _% Z* p- K; F
5 `$ a7 U, F; y5 L5 \( ]- 4 c9 F9 l; x, C o& y& l( j
- /* 初始化EventRecorder并开启 */7 R% ]) K3 x9 k* g% e4 H
- EventRecorderInitialize(EventRecordAll, 1U);
4 W5 y3 D$ K# R3 m' X1 |7 } - EventRecorderStart();
& J" z! K) F6 u8 f7 O; d -
& l, D8 Y7 H9 { H2 w - bsp_Init(); /* 硬件初始化 */, u( c6 z; A" Z: G: o
- J6 P/ P! |" L; L( n* C- bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器 *// {, ?+ N7 Q+ q; X
+ i7 H8 l' H& q# @* H4 {- /* 测量中断周期 */) @0 |9 @. j+ e5 r8 l9 p: C0 p
- vEventRecorderTest();* [) n" E( Y' y( {7 }. ~
-
: r* u; Y; |( L: \& g" ~ - /* 进入主程序循环体 */! U1 g+ o9 D1 l/ ^7 D
- while (1)
1 J4 C$ V3 E- S% _/ L - {! h9 h6 X! l; r# y/ Q
- bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
# L# P, d( c1 x% H3 K - # _2 w B* ~) e, L, v( G- W
- /* 判断定时器超时时间 */
) f+ D8 H1 ?" c7 M- e - if (bsp_CheckTimer(0)) ! \' J" L& f" K' Q j
- {# S+ i! {5 k8 e9 U! O
- EventStartA(0);
1 e8 G! j: t- p$ z# l - EventStopA(0);* X6 ~0 d4 m1 y( f0 b
-
5 N! M4 L& M0 k/ A4 ^ - EventStartA(1);
+ g" b' G" V. c; [/ i) f/ A& j) X( o - bsp_DelayMS(5);
9 I Q% [1 t* e - EventStopA(1);
* }5 [% \% J% N' d! \) X2 X9 l -
! X& o, _1 ?. N - EventStartA(2);
$ C2 Z9 z) M' o8 V - bsp_DelayMS(30);
' L0 i+ R+ }* Y- I, n- C5 O+ h3 ^ - EventStopA(2);" ]& I6 e* V) n) \
- : _) ?& W1 K9 ?/ D. t6 x6 J
- t0++;+ \( Y/ B% W* \# O1 A. d
- EventStartAv(3, t0, t0);# {# b3 I; z! q7 X p
- bsp_DelayMS(30);& i: i) [" I. n. M$ y0 |1 L
- EventStopAv(3, t0, t0);
9 w7 [2 ^7 \- r/ F8 `8 h - }
( ~ O5 B2 L2 R+ X8 g, F) l - 4 L4 n) @ c/ R% `
- /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */) E' |2 ]4 n/ W( l
- ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
& q' X$ U- }0 } - if (ucKeyCode != KEY_NONE)
' L* X3 C6 W5 [0 R) b - {, S+ |& i$ P6 \, r1 P
- switch (ucKeyCode)
* I+ t/ q6 V9 `( q) A - {1 `% ?5 N: l+ O# ?
- case KEY_DOWN_K1: /* K1键按下 */
" w7 c' D1 ]' L; j" } - t1 += 1;
) U$ m x9 Y3 d @6 z - t2 += 2;& X/ d( T: I' k) p3 K1 X
- EventRecord2(1+EventLevelAPI, t1, t2); B: B/ z: B8 Z1 @
- t3 += 3;" Z3 S! d& C+ b4 M$ |4 I$ X1 y
- t4 += 4;+ M8 s. k/ u% I: u
- EventRecord4(2+EventLevelOp, t1, t2, t3, t4);# j2 a7 O; j2 S. g/ A- h# I- z
- EventRecordData(3+EventLevelOp, s_ucBuf, sizeof(s_ucBuf));* c+ u7 h6 g$ O! o- |
- break;) ^8 w. f9 |# o: }( w
- ) Y1 A7 _+ `) e* T8 H* s8 T
- case KEY_DOWN_K2: /* K2键按下 */
9 ^7 c5 g' e; G5 d0 n; C9 ^8 I - printf("K2按键按下\r\n");; D, c; W5 c# |
- break;
. u" e% `/ i$ [8 z$ g, x7 s - 6 i* J- ]. }: E# B1 \! ]" V
- case KEY_DOWN_K3: /* K3键按下 */7 B6 }& d5 R& U; m2 c& A* m% G; O
- printf("K3按键按下\r\n");
. J0 {. u# p* f - break;
. {, {2 Y: R' Y8 h" I& n - - ]5 c! c; T7 ^ Z: `) S* L* p( Z. n
- default:
' a, A9 \- A# }9 E - /* 其它的键值不处理 */
, \" b5 s' \0 L( [- ^% E - break;: @$ _& P( V3 L' z6 a6 N! y7 A
- }0 |' \" ?) d! N Y
- }. Y/ n/ v) E0 y d( j2 v+ `
- }8 G. s1 S6 v8 r2 o
- }
复制代码 / ^3 }/ I3 C6 z( w5 Q+ h; O2 D
应用程序里面主要实现了三个功能:
+ \3 I& z6 I* r# @. |/ u( { I/ Q) w
1、利用测量分组A实现4路时间的测量(第1路什么也没有测量,可以用来表示这两个函数本身执行占用的时间)。每100ms测量一次时间,效果如下:3 ^9 ` z2 C8 U) p; N) ]9 a
z9 G* \4 r# i) c! P/ A. ^4 [
( K/ E9 ?9 G% x( w
* l3 X$ k4 X$ Y, k& F
2、利用函数EventRecord2,EventRecord4和EventRecordData发送消息事件。按下按键K1进行更新,效果如下:
) f5 Y* E _& Z- i: c# i, [1 o- m) v) f
6 }6 X. i8 a7 y! M' K) ~
' D" p" L% K2 C 3、基于Event Recorder的printf重定向。按下按键K2或者K3会打印消息,效果如下:; D" {$ `: [9 J8 D" ]3 ~+ H
* w' z* `6 G2 H3 P+ T; U" p* k. [* d8 Z; o( a) n
. a$ Z$ g- ?. T5 V1 {5 P8.14 总结
8 e* X- s/ F- d' I: i- q$ t: t( iEvent Recoder还是非常实用的,建议大家多使用几次,熟练掌握。基本用上几次就上瘾,离不开了,的确是工程调试的利器。$ X3 M# V5 z4 Q; P/ x2 |
4 z/ D: @* s! @1 ]3 h+ g" A# V; q! g$ R
+ @" W5 Y/ I4 R# T X5 G
|