8.1 重要提示(必读) 只要是MDK支持的调试下载器,基本都支持Event Recorder,本教程测试了JLINK,STLINK和CMSIS-DAP。 务必使用MDK5.25及其以上版本。 使用ARM_Compiler 软件包V1.4.0及其以上版本。 CMSIS软件包 要是使用V5.3.0及其以上版本,详情本教程8.3小节末尾的说明。 为了实现Event Recorder组件的最高性能,最好将下载器的时钟速度设置到所支持的最大值,另外,根据需要加大EventRecorderConf.h文件中的缓冲大小,默认可以缓冲64个消息(动态更新的FIFO空间)。 此调试组件不需要用到SWO引脚,使用标准的下载接口即可。以我们的开发板为例,用到VCC,GND,SWDIO,SWCLK和NRST。大家使用三线JLINK-OB也是没问题的,仅需用到GND,SWDIO和SWCLK。 ; H$ O4 W Q6 e
+ k) P" ]* W" q$ c
+ J n: {% _3 X 8.2 Event Recorder简介前面的专题教程中为大家讲解了使用SEGGER的RTT功能来替代串口打印,比较方便。只是这种方法限制用户必须使用JLINK才可以。而使用Event Recorder的话,无此限制,各种LINK通吃。只要是MDK支持的即可。 Event Recorder是MDK在5.22版本的时增加的功能,到了5.25版本后,这个功能就更加完善了,增加了时间测量和功耗测量的功能。 此调试组件不需要用到SWO引脚,使用标准的下载接口即可。以我们的开发板为例,用到VCC,GND,SWDIO,SWCLK和NRST。大家使用三线JLINK-OB也是没问题的,仅需用到GND,SWDIO和SWCLK。 JTAG接口和SWD接口区别 ' a, o1 ]+ u1 P8 r( A
1 [- E1 v5 J1 [2 U6 [( Q) y
& {# V. w) f; O' v' e
下图分别是20pin的标准JTAG引脚和SWD( Serial Wire Debug)引脚,一般SWD接口仅需要Vref,SWDIO,SWCLK,RESET和GND五个引脚即可,SWO(Serial Wire Output)引脚是可选的。有了SWO引脚才可以实现数据从芯片到电脑端的数据发送。 
SWV是由仪器化跟踪宏单元ITM(Instrumentation Trace Macrocell)和SWO构成的。SWV实现了一种从MCU内部获取信息的低成本方案,SWO接口支持输出两种格式的跟踪数据,但是任意时刻只能使用一种。两种格式的数据编码分别是UART(串行)和Manchester(曼彻斯特)。当前JLINK仅支持UART编码,SWO引脚可以根据不同的信息发送不同的数据包。当前M3/M4可以通过SWO引脚输出以下三种信息: ITM支持printf函数的debug调用(工程需要做一下接口重定向即可)。ITM有32个通道,如果使用MDK的话,通道0用于输出调试字符或者实现printf函数,通道31用于Event Viewer,这就是为什么实现Event Viewer需要配置SWV的原因。 数据观察点和跟踪DWT(Data Watchpoint and Trace)可用于变量的实时监测和PC程序计数器采样。 ITM 还附带了一个时间戳的功能:当一个新的跟踪数据包进入了ITM的FIFO 时,ITM 就会把一个差分的时间戳数据包插入到跟踪数据流中。跟踪捕获设备在得到了这些时间戳后,就可以找出各跟踪数据之间的时间相关信息。另外,在时间戳计数器溢出时也会发送时间戳数据包。
: q- s. d5 @# J# o* L
! w) x- X# X6 A' N* c/ C& S( R4 \% a/ I! r8 d
8.2.1 Event Recorder的特色Event Recorder的特色主要有以下几点: 提升应用程序动态执行期间的检测能力。 支持的事件类型滤除机制,比如运行错误、API调用、内部操作和操作信息的区分。 可以在任务中、RTOS内核中和中断服务程序中任意调用。 对于带ITM功能的Cortex-M3/M4/M7/M33内核芯片,执行记录期间,全程无需开关中断操作。对于不带ITM功能的Cortex-M0/M0+/M23,是需要开关中断的。 支持printf重定向。 各种link通吃,支持SWD接口或者JTAG接口方式的JLINK、STLINK、ULINK和CMSIS-DAP。 对于带DWT时钟周期计数器功能的Cortex-M3/M4/M7/M33内核芯片,创建时间戳时,可以有效降低系统负担,无需专用定时器来实现。 Event Recorder执行时间具有时间确定性,即执行的时间是确定的,而且执行速度超快,因此,实际产品中的代码依然可以带有这部分,无需创建debug和release两种版本。 RTX5及其所有中间件都支持Event Recorder调试。 / t: j' w9 F4 B. u
8 }2 {3 Q+ d' h! b) }
. f7 c6 }; L: m0 ^$ j3 a 8.2.2 Event Recorder是如何工作的首先来看下面这张图:  在截图的左下角有个Memory内存区,在这个内存区里面有一个缓冲Event Buffer,其实就是一个大数组。MDK通过访问这个数组实现消息的图形化展示。为了正确的图形化展示,数组缓冲里面的数据就得有一定的数据格式。而这个数据格式就是通过左侧截图里面的Event Recorder和Event Filter来实现的。Event Recorder的API实现数据记录和整理,Event Filter的API实现数据的筛选,从而可以选择哪些数据可以在MDK的Event Recorder调试组件里面展示出来。 这就是Event Recorder的基本工作流程。 8.2.3 Event Statistics时间测量功能Event Statistics提供的时间测量功能简单易用,在测试代码前后加上测量函数即可:  在本章教程程的8.6小节为大家详细进行了讲解。通过这个时间测量功能,用户可以方便测试代码的执行时间,从而根据需要,进行合理的优化,提高代码执行效率。 8.2.4 Event Statistics功耗测量功能Event Statistics提供的功耗测量功能,当前只有KEIL的ULINKplus支持此功能,由于ULINKplus价格不便宜,一套5000多,大家作为了解即可,实际效果如下:  8.2.5 Event Recorder的实现原理每条Event Recorder消息是由16字节的数据组成,32位的ID,32位的时间戳,两个32位的数据,共计16个字节。其中32位ID最重要,格式如下:  Level指定消息分类,主要用于消息筛选:  Component number指定事件消息所属的软件组件,也可用于过滤:  看了下Event Recorder的源码,每条消息大体是一样的: - typedef struct {2 p/ n1 _ H/ C& p
-
7 x0 D& K4 J) ]: Y1 ^! ?) K; W6 t - uint32_t ts; // Timestamp (32-bit, Toggle bit instead of MSB)
' _' h; T1 r; u2 W - ' F- C! A6 @ _' V
- uint32_t val1; // Value 1 (32-bit, Toggle bit instead of MSB)2 l$ O3 N z* t0 w$ E: J' E
-
2 p* X2 W- ?$ G/ _) f/ n$ q% X - uint32_t val2; // Value 2 (32-bit, Toggle bit instead of MSB)
) E. T' y- K; u. d -
& N1 N6 X7 ~; F3 l' i; { - uint32_t info; // Record Information, N7 X* z5 N. _% }( k
- 9 ?: r" i U2 m* Y0 C4 K
- // [ 7.. 0]: Message ID (8-bit)! ?/ c; E; g2 G
- + d4 A% {, u1 A' M# @6 a2 y9 F! P
- // [15.. 8]: Component ID (8-bit)+ x' x3 c, e! t3 M" b; P& c/ A* X
- 9 [: D) m% Y4 {
- // [18..16]: Data Length (1..8) / Event Context
7 x; O2 i: f% j, R -
' g& O! N' K5 \+ P+ g$ L - // [19]: IRQ Flag. \! V0 E; S: H7 p' f+ @0 u& ~
-
6 X# \! C' f; {& w* q3 W - // [23..20]: Sequence Number
: E6 |8 u9 ^. R5 S: {/ S - & l* ~. ]3 d" v5 b+ Y
- // [24]: First Record: J- ^1 R) ^3 b9 R2 |- p
- ' Q0 x" l0 V/ L% ?
- // [25]: Last Record6 z( i5 f- `- w# j: g
- 6 D$ l$ E0 ^9 Y, M- @( @
- // [26]: Locked Record
8 _1 l0 H9 D- F: R& E: S -
2 J* S( G9 I5 I$ R3 B l - // [27]: Valid Record1 _4 y9 H( M% k; s: y) V6 p
-
( A( L8 e3 b$ m" N/ { - // [28]: Timestamp MSB
5 J! T5 E3 U8 m# T; ? -
* S7 H d& W1 M; c( J - // [29]: Value 1 MSB
+ d* O3 R& t3 c0 Z+ q! ?/ ?7 R0 M s -
! H2 d( ]5 x: r0 M( G - // [30]: Value 2 MSB7 K& A' n! m& o, c$ z& N
-
& A) z/ q4 t. ^, j# O. B - // [31]: Toggle bit
3 V& \8 H( D! |% V T; G5 W! [ -
% H" h: \3 a5 x& F6 p, G - } EventRecord_t;
复制代码. i8 b! D$ z* z d1 d
其中参数成员info最重要,也就是前面说的32位ID,这里的说明与前面的说明稍有不同。这里是经过处理后,实际存储到Event Recorder缓冲里面的数据。" G. E# r' ^+ C5 e
对于Event Recorder,大家了解了这些知识点基本就够用了。 8.3 创建工程模板和注意事项Event Recorder工程的创建比较简单,这里分步为大家做个介绍。 第1步:准备好一个使用MDK5.25或以上版本创建的工程模板。
第2步:安装ARM_Compiler V1.4.0或以上版本(如果有最新版,直接安装最新的),详情见帖子: http://www.armbbs.cn/forum.php?mod=viewthread&tid=87175 。 第3步:打开MDK5.25或以上版本创建的RTE环境。
第4步:通过RTE环境,为工程添加Event Recorder功能。
第5步:为了实现printf重定向,我们需要将STDOUT的输出方式改为Event Recorder,即选项里面的EVR。
第6步:打开通过RTE环境为工程添加的文件EventRecorderConf.h,配置如下:
这里主要设置方框里面的两个参数。 Number of Records:表示Event Recorder缓冲可以记录的消息条数。 Time Stamp Source:表示时间戳来源,有如下四种可以选择,我们这里使用DWT时钟周期计数器。
由于选择的是DWT,因此EventRecorderCong.h文件中的Systick Configuration配置就不用管了。 ========================== 通过上面的6步就完成了Event Recorder功能的添加,效果如下:
添加完成后,还有非常重要的两点要特别注意:
下载并导入到MDK后,需要大家更新自己现有工程CMSIS文件里面的头文件,可以直接将CMSIS文件夹中Include文件里面的所有文件全部删掉,替换为MDK安装目录如下路径里面的所有头文件: ARM\PACK\ARM\CMSIS\5.4.0\CMSIS\Include。保证头文件都是最新的5.4.0版本。  注意这两点后,就可以使用Event Recorder的功能了。 8.4 Event Recorder事件记录的实现Event Recorder的使用也比较省事,这里也分步为大家进行说明: 第1步:初始化,仅需添加如下两行代码即可。 - /* 初始化EventRecorder并开启 */
6 W' M( O! Q( F* p -
x% T% U3 ]9 a% a7 N! _ - EventRecorderInitialize(EventRecordAll, 1U);) A% b+ \% e* w/ ^ S' o; L# v- k
- 6 z( [% v" J! n2 s
- EventRecorderStart();
复制代码
* P+ ^8 P# X# D2 B( X+ n
第2步:调用Event Recorder的API就可以使用了,主要有以下三个API: EventRecord2:可以发送两个32位数据。 EventRecord4:可以发送四个32位数据。 EventRecordData:可以发送字符串。 显然这三个函数没有printf使用方便,所以对于这三个函数,大家做个简单的了解即可。教程配套例子里面有调用到这三个函数,可以操作熟悉下。这三个API的说明是在对应的help文档中,即MDK安装目录路径:/ARM/PACK/Keil/ARM_Compiler/1.6.0/Doc/General/html/index.html。
第3步:进入调试状态,选上周期更新:
点击全速运行:
然后将Event Recorder调试组件展示出来:
效果如下:
另外,这里有个知识点需要大家了解下,如果程序里面也调用了Event Statistics时间测量函数,那么也会在这个界面里面展示消息的,如何才能仅展示大家想看的功能呢?这就需要用到Event Recorder支持的筛选功能。使用这个功能需要大家先暂停全速运行,然后点击下面这个选项:
弹出的界面里面可以设置哪些选项显示,哪些选项不显示(勾上表示显示),我们这里取消Event Statistics的显示,设置完毕后记得点击OK按钮。
这就不展示Event Statistics的内容了。再次启动全速运行前,下面这个选项的对勾别忘了勾上。 8.5 Event Recorder实现printf重定向实现printf输出需要用到MDK调试组件中的Debug(printf) Viewer,输出效果就跟大家使用串口调试软件一样,可以输出中文和英文。 MDK的printf调试组件使用方法跟本章8.4小节中的说明一样,点击调试,选中周期运行,然后显示Debug(printf) Viewer调试组件:
效果如下:
另外,还有一个知识点需要给大家做个补充,使用SWD接口的SWO引脚也是可以做串口打印的,并且也是通过这个调试组件Debug(printf) Viewer进行输出。只是这种方式的性能没有Event Viewer强,而且要多占用一个SWO引脚。 % E+ Y. U E U9 P2 R, q) ^9 I
8.6 Event Statistics 时间测量功能的实现时间测量功能简单易用,仅需一个起始函数,一个停止函数即可。当前支持4组,每组支持16路测量,也就是可以同时测量64路。 时间测量的API函数支持多任务和中断里面随意调用。 1、 测量起始函数:EventStartG (slot) 或者EventStartGv (slot, val1, val2) 函数中的字母G是表示分组A,B,C,D,即实际调用函数为EventStartA,EventStartB,EventStartC和EventStartD。 函数的第一个形参slot的范围是0-15,也就是每个分组可以测试16路。 函数后面的两个形象val1和val2是32位变量,用户可以用这两个形参来传递变量数值给Event Statistics调试组件里面,方便图形化展示。简单的说,这两个变量仅仅起到一个传递变量数值的作用。 1 Q; e2 |; e6 M( O7 o
7 Y% w( V3 h! S0 n0 ~% y
% h$ \) u! V' g9 g+ f% J2 Y
2、 测量停止函数:EventStopG (slot) 或者 EventStopGv (slot, val1, val2) 函数中的字母G是表示分组A,B,C,D,即实际调用函数为EventStopA,EventStopB,EventStopC和EventStopD。 函数的第一个形参slot的范围是0-15,也就是每个分组可以测试16路。 函数后面的两个形象val1和val2是32位变量,用户可以用这两个形参来传递变量数值给Event Statistics调试组件里面,方便图形化展示。简单的说,这两个变量仅仅起到一个传递变量数值的作用。 ' W: p5 z+ {( s! @5 Y9 w3 g
0 m2 c) C6 h9 Q% H7 z+ R3 X
这里也分步为大家说明Event Statistics时间测量功能的使用方法。 第1步:初始化,仅需添加如下两行代码即可。
, {. _* y; _4 i
# }: j; ~: K5 C7 Z: J7 e/* 初始化EventRecorder并开启 */7 r2 b3 i/ f/ q5 I- z+ u0 B
/ \% P4 s; a. l& c) Q* C+ S, o# h- 9 e* P, c6 ]' f9 p5 D" U
( w# w6 A$ E. V9 a
& F2 F+ e' L: U% s( ~" y9 ?- x
9 I8 H6 h6 S4 X9 X# c& W
; h' x6 G7 u' {* L2 O5 o( s& p- |( v9 `+ g
EventRecorderInitialize(EventRecordAll, 1U);+ A+ V3 x! W! o) e3 C( M) s
* G* r* i( n& j- k! k9 t( s- $ x& R- }1 {% y# \
: r3 t! e$ j" O7 V; J
9 J9 ^% i; L2 J- `
6 i& C6 Q- U! E7 b# r! @
3 B' O/ b4 C1 h8 Q6 M* Z G
, a" W& }0 F2 b6 I. T mEventRecorderStart();& M" l" h* B* v
7 B7 s% H- h5 N
' d' a$ `6 L& @0 _6 g
! r Q1 }; ]2 W1 d
第2步:在要测量的代码前后加上起始和结束时间。
! l! _0 v) M; i- _% x
* v; Y! o& @0 e) V7 OEventStartA(0);: G# E4 f; N; {
1 W) O- F! ^' ]) e
- q7 ^, L5 Q: B& t: _' O) @2 L' B+ k* N' r9 g% n
: m5 I& ^0 J; Q. P! P* V5 W. T- K
# P* a9 X* y- `; O- # P- Q* M6 _7 ]% R A4 V
9 ^( P3 q& c/ W7 {, [* j' h! r( j+ I//测量的代码部分
" L. D! P8 x) T( `" Q- V5 b
5 _2 f5 y# P3 s3 U* Y, i' a8 x - ( w3 F+ Q8 [4 C. u/ z4 t; K2 t
3 K/ B1 |8 ^- j
" q9 L. ~8 n. _7 S9 ~! r# b' ` i2 D1 u( O) l" ], |4 B
- % d8 O0 K& u1 Z" x3 c
' N3 P- b. Z9 o0 q9 v; v+ m
EventStopA(0);
. g& R- ?. W" ~9 Y8 u8 g! b/ n7 Q* S% S2 V, A; V
, W) s& s. |0 Y- @* k+ I% }1 o8 S% c, e( E
这里是用分组A的测量通道0。 第3步:跟本章8.4小节讲解的一样,点击调试,选择周期更新选项,然后全速运行。 第4步:全速运行后,显示Event Statistics调试组件。
比如我这里简单的测试了一个5ms的延迟函数,效果如下(测量时间是动态更新的):  另外要注意一点,微秒的时间单位us可能无法正常显示,这个是没有关系的:  8.7 Event Statistics 功耗测量功能的实现当前仅KEIL自家的ULINKplus支持功耗测量功能,这款下载器不便宜,一套5000多,大家有个了解即可,我们这里就不做讲解了。 8.8 Event Recorder对RTX5及其所有中间件的支持后面做RTX5及其所有中间件的教程时会为大家做讲解,这里让大家看下效果:
网络调试组件效果展示: ( m% _: u: V+ G5 c
- L, R) e- v E) S8 j6 ?, P
文件系统和USB协议栈的效果展示:
+ `- s- P* n. |" t" p* r) k+ v* Z6 U3 d9 _3 i, I3 L. Z5 P
 8.9 JLINK配置说明为了帮助大家更好的使用JLINK,这里将JLINK配置中关键的几个地方做个说明。
注:如果大家调试状态弹出SWD配置时钟超出范围的问题,可以考虑将上面截图中的Enable选项的对勾取消掉即可,但内核时钟一定要修改为芯片的主频。 另外,进入调试状态后,右下角的时间是否正常更新都没有关系:
8.10 STLINK配置说明为了帮助大家更好的使用STLINK,这里将STLINK配置中关键的几个地方做个说明。
另外注意,进入调试状态后,右下角的时间是否正常更新都没有关系:

8.11 CMSIS-DAP配置说明为了帮助大家更好的使用CMSIS-DAP,这里将CMSIS-DAP配置中关键的几个地方做个说明。
另外注意,进入调试状态后,右下角的时间是否正常更新都没有关系:
8.12 ULINK配置说明由于手头没有ULINK,这里就不做讲解了。如果大家需要相关配置,按照前面小节三款LINK的配置照葫芦画瓢搞一下即可,或者在MDK安装目录的路径ARM\Hlp下有对应的文档说明: 8.13 配套例子本章节教程配套了如下例程,仅MDK版本。 具体代码实现也比较简单,以V6开发板为例,定义一个TIM6的中断,中断频率是500Hz,通过Event Statistics测量中断的执行频率。代码如下: - #include "bsp.h"6 H, D8 m& E! B8 p" b7 M2 x
- ; U- v# I% r8 ^ b: ~7 r7 S. B
- #include "EventRecorder.h"2 A3 d% m8 R r6 V: e8 S9 Z
- /* 定时器频率,500Hz */
# j3 {/ {$ g6 ^; y5 n% b# E: P: { - #define timerINTERRUPT_FREQUENCY 500
/ E9 `9 ]4 D) b7 | - /* 中断优先级 */
5 s8 k% j7 \3 u* R - #define timerHIGHEST_PRIORITY 10
+ D' [0 p" Y8 @8 a2 K - /*
8 k' e, c# n2 i6 i9 d7 ~ - *********************************************************************************************************
4 {. c; `( C9 U! P- t% D+ H7 u - * 函 数 名: vEventRecorderTest
* W0 r. J8 R" V+ j7 R! E# ] - * 功能说明: 创建定时器% a" s! @0 t) M
- * 形 参: 无
) L5 S; R+ p! Q+ g: o9 d - * 返 回 值: 无
9 I* ?% J7 L; t* ` - *********************************************************************************************************
2 y/ r$ r0 s B* o# L8 l - */, C/ ~* s E" ` Y. z: l
- void vEventRecorderTest(void) M+ p6 g! X8 u. C# ?
- {
* [9 q, L" Q) d% A, ~2 C. Y - bsp_SetTIMforInt(TIM6, timerINTERRUPT_FREQUENCY, timerHIGHEST_PRIORITY, 0);
% M+ ]$ O; @6 z3 y# p1 i3 f7 m - EventStartB(0); ! D, x" g' ]/ |5 ?1 K
- }) T! d$ N2 y( a/ e+ Z0 r: T6 l' P
- 4 N ?+ N) m5 e3 Y) A& K7 b! d% H
- /*
0 |% d/ g# B( w+ J3 [ - *********************************************************************************************************0 T4 j, N* Y$ f5 d8 K2 |- t
- * 函 数 名: TIM6_DAC_IRQHandler# Q( |. ]$ u$ r2 C) L8 U
- * 功能说明: TIM6中断服务程序。 ?7 C* F2 L% t1 E: G5 A+ U
- * 形 参: 无% }. c) F2 H8 G: |* E$ b5 X" u
- * 返 回 值: 无/ @; U: f# S& J5 F6 q
- *********************************************************************************************************, v1 S% N( r3 ]! a
- */
4 E3 P# T0 z7 w5 V5 r4 Z8 o5 r6 D - void TIM6_DAC_IRQHandler( void )+ B% C! @# n( U- c1 g# ^
- {
1 q) @2 u5 I+ [ - if((TIM6->SR & TIM_FLAG_UPDATE) != RESET)
( n4 a0 P2 P; Z j3 e' L. ] - {
& Z0 L* H, F3 G' [: e% `+ F - EventStopB(0); & Y/ R" r! P& x% [* O; {
- EventStartB(0); ' u+ B0 ]4 Z \7 _
- /* 清除更新标志 */! ]3 F* ~7 Z7 [% k) C
- TIM6->SR = ~ TIM_FLAG_UPDATE;
2 O# z6 @1 f4 F9 t0 c$ o - }
, j& n4 ]% v$ A, |2 |: w( W - }
复制代码 ( s# }% `) u$ l$ G; }0 e2 j& O0 k
效果如下,测量的平均频率是1.98ms,与我们设计的500Hz基本符合:  应用程序的设计如下: - #include "bsp.h" /* 底层硬件驱动 */
3 m9 f: U; r$ t& }4 z, V - #include "EventRecorder.h"
1 e2 R7 W% _. N' Q -
, `4 Q$ P5 z6 @) l6 ~# m8 u$ i - /*; k7 t& o, C8 [" N
- *********************************************************************************************************
[0 ?9 O4 ~: h5 {+ C Z3 I - * 函数和变量
P. r9 e% g* D# M6 E - *********************************************************************************************************: r( x8 Y' m! M
- */5 K0 h5 U* F. \/ ^# I
- extern void vEventRecorderTest(void);
$ ]2 e+ Z. [/ j) u N - uint8_t s_ucBuf[10] = "armfly";0 P# \- |2 {+ W8 }- j
-
: ]2 D2 A- y9 }8 I - /*
4 ^/ l+ `7 m; \/ j- r2 j# c# k - *********************************************************************************************************( q% U7 ?) f& j8 Q
- * 函 数 名: main0 m( P: e& \9 h; S* d" o
- * 功能说明: c程序入口- C& q2 Z6 r d3 ]; e
- * 形 参:无3 g& W3 ?! c. \, O/ [; X/ ]. U
- * 返 回 值: 错误代码(无需处理)
& H% v1 I3 M, E/ y! x+ K4 |7 X% d - *********************************************************************************************************
7 L* ?# e' C d! i5 O5 P - */
: n; w: v2 x* ~ - / Y" J3 ? k! X0 `' X
- int main(void)
3 f# H) F' t% L5 U, c; I - {
2 @/ \: b+ ?- T; f - uint8_t ucKeyCode; /* 按键代码 */
! V, n k3 _- Y; U& T - ) w" Z) f; p) V$ c/ {& }! i" L
- uint32_t t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0;
& C9 G. p; _+ c6 O, E1 R8 ~ -
: Z1 } w0 ^( b3 V! E - /* 初始化EventRecorder并开启 */
' C' g& y- i! U* j' ~" X -
% r7 { L( n) B5 z- f( R) Z - EventRecorderInitialize(EventRecordAll, 1U);
: q& Y3 Y5 g) t# I" Q -
. c8 T; b% b' q - EventRecorderStart();
% @. o/ O" F/ U( W7 ?: D7 Q+ H -
; V' \3 t+ a8 Y! x2 {0 R4 H: F" Z - bsp_Init(); /* 硬件初始化 */ b! v k6 L* V
-
: I3 f3 \: V: B% |* p - bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器 */6 @" M4 R/ d7 ^. ]
- # `0 U. ?' Z8 g& Y/ B3 y
- /* 测量中断周期 */6 I! G/ G+ v- k9 m* R; N' O5 N) o
- vEventRecorderTest();
$ i$ b3 w& z1 s -
6 N' c& U/ |+ | - /* 进入主程序循环体 */
/ r. z4 g5 x5 v; z& X1 _ - while (1) L. T0 Y! h- T$ T8 l G+ I
- {
7 t6 j/ Z+ S ~8 s. L$ ? w! ~ - bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
& @/ A2 t, K# W' { - 0 K1 C( B9 G- t$ ^. O
- /* 判断定时器超时时间 */
( }, t( e. q0 g - if (bsp_CheckTimer(0))
, I% I9 ?5 P6 T* s$ H - {+ w& q1 x, @/ l+ f
- EventStartA(0);
% C, @0 ~* O0 o' q( _5 Q - EventStopA(0);' y5 y/ ^) j: |; J
- EventStartA(1);
5 z2 `- Z- t* H - bsp_DelayMS(5);# d- e# M) T+ i) \
- EventStopA(1);* r) ~! n L8 Z, h0 f& m: b
- EventStartA(2);
) H- ?2 f- M7 H0 \( | - bsp_DelayMS(30);
" T$ g8 K* J ^( M% Y - EventStopA(2);( ^1 w: T0 \ d- u* e
- ( e& _* x/ M5 x4 s1 i
- t0++;3 H1 F! B I. o
- EventStartAv(3, t0, t0);& R8 U- a2 W/ H% W5 J+ U; C! H
- bsp_DelayMS(30);
8 i) U2 l$ Q0 K/ e/ B* J4 _+ n6 a! L9 L% G - EventStopAv(3, t0, t0);6 M, R" P$ ?5 `% j) Y
- }
, \0 z1 n8 C S# c -
; s2 {6 L' \# m8 x" ` - /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */: O( w( h% t7 `0 h& S1 v; e$ t+ w
- ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */9 Z' v" E- _. t3 |3 n
- if (ucKeyCode != KEY_NONE)
! |; N/ O/ u5 V; |1 V. W - {# I x, i& ]9 I X" F# U
- switch (ucKeyCode)$ u( w0 T/ D. r, e+ ]3 k, `
- {0 s) r. b& E9 U' J/ F
- case KEY_DOWN_K1: /* K1键按下 */& ?) |2 M4 T$ h1 o( @: j
- t1 += 1;. K0 s( X3 a. y7 p$ i! }4 R
- t2 += 2;
. U y4 P6 |+ D( l6 @ - EventRecord2(1+EventLevelAPI, t1, t2);
7 U* u* X9 @! W - t3 += 3;, K: @% o0 @; Y( Z- }* g! s
- t4 += 4;
: k5 a, `) e8 S t% e. L - EventRecord4(2+EventLevelOp, t1, t2, t3, t4);
) t; o! d3 r4 {( ?9 T4 j - EventRecordData(3+EventLevelOp, s_ucBuf, sizeof(s_ucBuf));
; ?, X% H6 d3 {+ [ - break;6 T) M7 f: L! ?) r0 q
-
; d3 L3 s) J9 |& ?! \9 t6 S" k2 P' G9 E - case KEY_DOWN_K2: /* K2键按下 */* A0 k2 e' |: |1 E4 | T l
- printf("K2按键按下\r\n");4 r: r9 {- I; C4 E
- break;& J/ q' o+ a) c9 @1 S7 w
- 2 B! W1 g& c& @0 I
- case KEY_DOWN_K3: /* K3键按下 */" P+ z: i+ C7 l' ]1 e
- printf("K3按键按下\r\n");! a r5 i. W6 `+ m6 N+ R5 v" n
- break;( B/ I5 M( H( t& x g, Y
- 8 v$ w# t' D" k2 K" R+ Z! f" V0 Y
- default:
* K( M$ i# g* v& ] - /* 其它的键值不处理 */
: }# ^3 L- ~* x6 R$ e - break;
" B) Z4 o Y. @3 J2 S# S - }" {1 F! F( F! L; m5 @
- }! ~3 O$ G& X( I [9 h
- }7 E# n: W8 }6 g- A5 a) J/ k) S
- }
复制代码7 z! k' `; S& g/ @. r: o# b
应用程序里面主要实现了三个功能: 1、利用测量分组A实现4路时间的测量(第1路什么也没有测量,可以用来表示这两个函数本身执行占用的时间)。每100ms测量一次时间,效果如下:  2、利用函数EventRecord2,EventRecord4和EventRecordData发送消息事件。按下按键K1进行更新,效果如下:
3、基于Event Recorder的printf重定向。按下按键K2或者K3会打印消息,效果如下: 8.14 总结Event Recoder还是非常实用的,建议大家多使用几次,熟练掌握。基本用上几次就上瘾,离不开了,的确是工程调试的利器。
5 ^' h: p7 p' H |