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

【经验分享】STM32H7的终极调试组件Event Recorder

[复制链接]
STMCU小助手 发布时间:2022-1-1 18:00
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。


    9 J! I; L4 T8 }6 [7 }) A' w
    . `6 {  ?4 e5 ]& p" T

    6 d: E/ ]+ S$ `# ?
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接口区别

    - @5 d( a. h7 m4 y6 I' A  ~# q
      ^! v" L! z% K3 q

    / U1 M- s2 i: \9 ~

下图分别是20pin的标准JTAG引脚和SWD( Serial Wire Debug)引脚,一般SWD接口仅需要Vref,SWDIO,SWCLK,RESET和GND五个引脚即可,SWO(Serial Wire Output)引脚是可选的。有了SWO引脚才可以实现数据从芯片到电脑端的数据发送。

  •   词条 SWV(Serial Wire Viewer)


    $ p. x; [' d. G

      j: T! O) K6 V! J9 G5 T/ x

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 就会把一个差分的时间戳数据包插入到跟踪数据流中。跟踪捕获设备在得到了这些时间戳后,就可以找出各跟踪数据之间的时间相关信息。另外,在时间戳计数器溢出时也会发送时间戳数据包。

      T# k, I0 g- K5 J3 `

    8 f9 y/ I  M8 |4 E) x6 ?5 s

    - E* y# ]* A  t4 @
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调试。


    0 J* k4 z# j' T$ g( {  Z3 l2 q/ P4 }

    1 `/ [) G$ _+ [
    5 W# s  K7 ?! B
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的源码,每条消息大体是一样的:

  1. typedef struct {) ]/ ]2 K3 N0 o* s
  2.   z+ E7 t+ E! ?' ?% w& B2 M
  3.   uint32_t ts;                  // Timestamp (32-bit, Toggle bit instead of MSB)2 N, `1 i8 Q7 `) p, i$ ^
  4. ' C' l; z; v& f5 k! Z
  5.   uint32_t val1;                // Value 1   (32-bit, Toggle bit instead of MSB)) E, h2 O8 f/ f2 ?! B' V0 X
  6. * h: M5 F6 e0 j' q4 Y1 L$ m+ s
  7.   uint32_t val2;                // Value 2   (32-bit, Toggle bit instead of MSB)
    ! w9 ~0 i$ _2 M- d

  8. ! p# _! ?0 y: g. Y$ P
  9.   uint32_t info;                // Record Information
    ( L( x1 k! X- j+ h

  10. / ?9 A6 i: d0 T3 x& g5 q
  11.                                 //  [ 7.. 0]: Message ID (8-bit); e! k/ S. U# U+ |5 T
  12. % H# S- l! c/ g+ I, l/ |
  13.                                 //  [15.. 8]: Component ID (8-bit)
    ( G$ ^9 f5 ?$ G

  14. + ]: n: B& i2 Z+ C% T2 m) M
  15.                                 //  [18..16]: Data Length (1..8) / Event Context" Y6 j- L, n, `+ J) T
  16. + v; v+ w0 `1 F6 W
  17.                                 //      [19]: IRQ Flag
    - C+ N; Q% ^7 G1 V6 j

  18. + W) u$ }. R" ?
  19.                                 //  [23..20]: Sequence Number
    & U) s4 i, `1 r0 \& J

  20.   j" _' n% i1 k; ]5 x
  21.                                 //      [24]: First Record
    . _/ \' y* W5 M# H( q
  22. ( G5 {6 x4 Q+ m
  23.                                 //      [25]: Last Record( J$ O" g9 I$ C( j5 t- B! E9 E

  24. 4 L- z9 H& X5 _1 x' s
  25.                                 //      [26]: Locked Record; h2 F6 u! u9 [& Y! g- k2 ?
  26. ' ~! P/ B# f% o+ t% L
  27.                                 //      [27]: Valid Record
    ( F. k' L! ~* [2 ~

  28. 0 ~$ ?9 |2 m, R4 w8 s
  29.                                 //      [28]: Timestamp MSB
    3 g3 L) r" _2 D; M
  30. ' f/ x3 N/ `) _0 o. L% [
  31.                                 //      [29]: Value 1 MSB7 A$ ^( h: W/ p
  32. * l  q2 E" c: V4 W6 L
  33.                                 //      [30]: Value 2 MSB! ?8 S# ^" b0 {9 @
  34. 9 S1 d/ S6 d9 U' d
  35.                                 //      [31]: Toggle bit
    & ^) t, u5 S& d( G, R5 w
  36. ! i: C' X5 A3 \. p4 s* d
  37. } EventRecord_t;
复制代码

* P9 A, R5 g. y0 U& a: D+ R) j9 O

其中参数成员info最重要,也就是前面说的32位ID,这里的说明与前面的说明稍有不同。这里是经过处理后,实际存储到Event Recorder缓冲里面的数据。) \' c# G0 M0 n9 w

对于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功能的添加,效果如下:

添加完成后,还有非常重要的两点要特别注意:

  •   第1点:一定要使用当前最新的CMSIS软件包,当前是V5.4.0(随着时间的推移,如果升级了新版本,直接使用新版即可)。

    8 H" B6 S0 Z8 L* ]+ h  o) y5 w( ]

下载并导入到MDK后,需要大家更新自己现有工程CMSIS文件里面的头文件,可以直接将CMSIS文件夹中Include文件里面的所有文件全部删掉,替换为MDK安装目录如下路径里面的所有头文件:

ARM\PACK\ARM\CMSIS\5.4.0\CMSIS\Include。保证头文件都是最新的5.4.0版本。

  • 第2点:由于使能了printf重定向,大家的工程里面一定不要再做重定向了,比如fpuc,fgetc。另外当前选择了微库MicroLib:


    + S7 [6 a5 X1 C2 }' k5 N
    ( X. f, J* O9 t$ k, h

注意这两点后,就可以使用Event Recorder的功能了。

8.4   Event Recorder事件记录的实现

Event Recorder的使用也比较省事,这里也分步为大家进行说明:

  第1步:初始化,仅需添加如下两行代码即可。

  1. /* 初始化EventRecorder并开启 */! S, P8 g7 p' x/ j( q# b

  2. 6 }" h( u: f/ [. `
  3. EventRecorderInitialize(EventRecordAll, 1U);% N. K5 X5 B4 O- ~- [, W) A

  4. 1 {' v& T& H% \# {; [
  5. EventRecorderStart();
复制代码


1 f' I4 w% o& H  k3 J- s

  第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引脚。


9 w8 ~& E7 Q' N. ^" U0 o8.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调试组件里面,方便图形化展示。简单的说,这两个变量仅仅起到一个传递变量数值的作用。

    ) c. B: _' f: T# P) |; p
    , c. S, S  y6 L* v5 U3 {" _

    # x- ]4 H9 J4 q2 C7 M, ~

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调试组件里面,方便图形化展示。简单的说,这两个变量仅仅起到一个传递变量数值的作用。

    0 O4 K4 `. {+ Z5 S+ X$ y
    5 ~: c: @/ B8 o+ c! `& Z

这里也分步为大家说明Event Statistics时间测量功能的使用方法。

  第1步:初始化,仅需添加如下两行代码即可。


  • & [  b4 s: }' R4 `1 d( s
    $ N0 v5 ]: d2 |+ [6 z' ]% D4 {6 @+ [
    /* 初始化EventRecorder并开启 */
    2 R$ ^* V! u3 J% u
    ; Z6 I9 @+ W2 i; k7 V% ?

  • : H0 w/ J) c' m% V- H2 V) y
      w3 Q# L3 e- H9 Q% `5 r" Q

    & q. {# B9 i" R. `

    4 A7 [: L- _2 r2 A  O2 X) f! x

  • 7 t# J$ O3 ?* |6 V  E# K& J* P. T

    . Y: _$ K* h' R4 p+ f
    EventRecorderInitialize(EventRecordAll, 1U);. V4 |1 H; _. N

    + ?/ a0 i  c; l( y* h4 \5 S6 f; O( {0 E

  • , N" ~6 E! }+ @8 R5 }1 X. A
    1 ~5 K2 G7 m8 Y; u3 k  \7 k5 l
    & x# v/ m! [  \" z1 H
    ' B9 k4 y& V+ i7 _- J# N
  • , W; V/ n& Z" }+ s/ `; D6 M

    & U9 }. N& L  y! q5 @& N, y0 @
    EventRecorderStart();7 t. h: a0 l8 q" Q4 N1 p: `

    # ]& z, m9 }+ G+ N& H9 ]& S
    ; |0 `4 {0 S7 \- ]

    - w  `4 U/ z" @! P

  第2步:在要测量的代码前后加上起始和结束时间。


  • ! l* G; u+ P- t
    1 p* l4 y8 v' L, f$ a8 Z3 e; |
    EventStartA(0);
    7 K+ e& m) L0 p0 u3 N9 l! T! P* T
    % r7 A5 N- h8 @4 z

  • " h0 l: N; L) z7 |* P3 I

    # M9 w" P) j/ o8 g

    . T( r8 S: u! c9 H/ c& d

    ! Y  U8 I# V# X$ x" v

  •   _& W1 S  V' R' h& [7 |0 l' {

    9 r9 T2 z/ C/ }1 g# {% i
    //测量的代码部分
    * s  N: z- z0 a' R  v
    : Z* ^: |2 j$ a7 f

  • / w% C" z4 O4 J( F) Y

    ; \" }" d( K. R2 b( J" O

    & |9 {2 d% g% b) p1 ~  ^5 s- j* O. Y; ]

    : d' R  u5 \) ]5 O0 `# u: s6 |

  • % y$ W; K8 X& n% {: Y$ x

    8 W, Q/ N* e! W2 X0 f4 S# W
    EventStopA(0);
    # j) r* M$ Y6 ?- i) w/ s* L

    / o7 P- V. X/ _( E9 u  l( c) p. X) q
      V+ x# ~7 l  z! g+ Q' k
    & H( c3 x  F& @5 {7 k

这里是用分组A的测量通道0。

  第3步:跟本章8.4小节讲解的一样,点击调试,选择周期更新选项,然后全速运行。

  第4步:全速运行后,显示Event Statistics调试组件。

比如我这里简单的测试了一个5ms的延迟函数,效果如下(测量时间是动态更新的):

另外要注意一点,微秒的时间单位us可能无法正常显示,这个是没有关系的:

8.7   Event Statistics 功耗测量功能的实现

当前仅KEIL自家的ULINKplus支持功耗测量功能,这款下载器不便宜,一套5000多,大家有个了解即可,我们这里就不做讲解了。

8.8   Event Recorder对RTX5及其所有中间件的支持

后面做RTX5及其所有中间件的教程时会为大家做讲解,这里让大家看下效果:

  •   RTX5组件和使用Event Recoder的效果:

    # ?- U9 i7 Q0 e1 u3 \

    - ~( P0 M( B" ~/ n' m- x7 ~* Z, N

  •   网络调试组件效果展示:


    7 n" Q5 s) G3 }  G

    ' Q: W8 D  E. r# ~

  •   文件系统和USB协议栈的效果展示:

    9 B. z1 G' s8 B6 B" S
    & V) E- U* y' m$ s

8.9   JLINK配置说明

为了帮助大家更好的使用JLINK,这里将JLINK配置中关键的几个地方做个说明。

  •   下面这个地方最重要,一定要正确设置当前系统工作的主频,如果不正确,会导致Event Statistics的时间统计不正确(对于H7,Core部分要填400MHz)。


    : |3 P9 J( x7 w6 U2 @

    8 w% |( ~. c; }& s4 s) s- J4 j

注:如果大家调试状态弹出SWD配置时钟超出范围的问题,可以考虑将上面截图中的Enable选项的对勾取消掉即可,但内核时钟一定要修改为芯片的主频。

另外,进入调试状态后,右下角的时间是否正常更新都没有关系:

  •   其它选项配置如下(只要大家的工程能够正常调试,配置就是没问题的):


    , F' }  Y1 m$ a  ~2 I8 j9 Q

    0 A- E0 M" D2 k$ X- M  P! ~

8.10 STLINK配置说明

为了帮助大家更好的使用STLINK,这里将STLINK配置中关键的几个地方做个说明。

  •   下面这个地方最重要,一定要正确设置当前系统工作的主频,如果不正确,会导致Event Statistics的时间统计是不正确的(对于H7,Core部分要填400MHz)。


    8 L1 I9 [& U7 a6 B5 Z' {. U- S% w& ~

    8 i7 c' v" L1 {" B' Q% @1 ?/ }

另外注意,进入调试状态后,右下角的时间是否正常更新都没有关系:

  • 其它选项配置如下(只要大家的工程能够正常调试,配置就是没问题的):


    ; t* C. Q7 B+ K% Z

    ) h0 j0 F3 ~2 a  c( K3 y/ F) {

8.11 CMSIS-DAP配置说明

为了帮助大家更好的使用CMSIS-DAP,这里将CMSIS-DAP配置中关键的几个地方做个说明。

  •   下面这个地方最重要,一定要正确设置当前系统工作的主频,如果不正确,会导致Event Statistics的时间统计不正确(对于H7,Core部分要填400MHz)。


    9 _/ {! x$ u. z  j4 R
    - f- T0 B/ r% ^4 R& O) k" }6 ~

另外注意,进入调试状态后,右下角的时间是否正常更新都没有关系:

  •   其它选项配置如下(只要大家的工程能够正常调试,配置就是没问题的):


    ( M/ f3 R4 m! H
    4 K) }) n) \2 m; M# Y2 C4 u

8.12 ULINK配置说明

由于手头没有ULINK,这里就不做讲解了。如果大家需要相关配置,按照前面小节三款LINK的配置照葫芦画瓢搞一下即可,或者在MDK安装目录的路径ARM\Hlp下有对应的文档说明:

8.13 配套例子

本章节教程配套了如下例程,仅MDK版本。

  •   V7-008_终极调试组件EventRecoder的使用


    ' j: r* j/ K1 j
    4 h- P9 U& c1 J$ G' m  ]' y8 V

具体代码实现也比较简单,以V6开发板为例,定义一个TIM6的中断,中断频率是500Hz,通过Event Statistics测量中断的执行频率。代码如下:

  1. #include "bsp.h"3 D' q! y9 P4 @3 @7 x

  2. 1 J# q4 B9 v" \2 Q# c
  3. #include "EventRecorder.h"
    3 P5 m+ i; _1 i0 O! S
  4. /* 定时器频率,500Hz */% K1 M6 F3 G0 r9 o
  5. #define  timerINTERRUPT_FREQUENCY    5001 i5 O2 u/ ^/ ]# a
  6. /* 中断优先级 */+ k/ U$ K1 O5 l  z, {* x1 S) M8 R
  7. #define  timerHIGHEST_PRIORITY       10
    4 l1 C: \5 w+ }/ i) F
  8. /*' B+ b9 t3 @7 E0 b0 D4 Z6 ^( l
  9. *********************************************************************************************************
    & h! y' j6 e; o" v5 R; q( j8 @% `7 o% f
  10. *    函 数 名: vEventRecorderTest$ s! T$ i5 D, @! M2 t  ]
  11. *    功能说明: 创建定时器
    - f" g" [/ G; k9 z0 r6 ]0 @1 W) f
  12. *    形    参: 无
    ; a' i& @# J3 M" R& O
  13. *    返 回 值: 无
    ) S8 k9 e. Q" P7 c
  14. *********************************************************************************************************6 M8 A2 n# _2 y6 ~0 B6 f' N
  15. *// W" Y2 t+ g. Q. O: {- x% C
  16. void vEventRecorderTest(void)
    + S( P* K4 [4 z6 R; ^
  17. {
    ! h% S5 O9 x; T4 T4 @8 X4 J
  18.      bsp_SetTIMforInt(TIM6, timerINTERRUPT_FREQUENCY, timerHIGHEST_PRIORITY, 0);
      E8 b) B  d8 D# I9 I% N
  19.      EventStartB(0);   
    ! c: |* h: u4 i* g
  20. }7 z* [5 m2 |. H* h
  21. ; Q' \) R- e5 ?( B
  22. /*
    + O6 x5 d3 [% E! U
  23. *********************************************************************************************************+ w' u' A3 Q6 B
  24. *    函 数 名: TIM6_DAC_IRQHandler8 r) T9 X4 k; B" p
  25. *    功能说明: TIM6中断服务程序。
    ( h' {+ j5 O7 T3 w! ?& M4 b6 Y
  26. *    形    参: 无
    " ?9 [* x* ~; \8 D% {
  27. *    返 回 值: 无
    ' O) H# c6 m! y+ ~
  28. *********************************************************************************************************6 L9 C. }- [% M! G
  29. */. h' ]" @& E- J2 Y
  30. void TIM6_DAC_IRQHandler( void )
    * C, S& _% M4 Y' M  a, S/ k2 Q
  31. {
    4 W7 J( G# H2 `) t9 q
  32.      if((TIM6->SR & TIM_FLAG_UPDATE) != RESET)0 r% {: }5 {1 n& Y* ?0 [0 _% e, `
  33.      {. ]# U+ h" t5 ^' r
  34.          EventStopB(0);    $ N# J* g9 v  i2 e5 M
  35.          EventStartB(0);   ! p' M; E5 }: M: x' B! I
  36.          /* 清除更新标志 */, q# e  Z- Q% _% }# a
  37.          TIM6->SR = ~ TIM_FLAG_UPDATE;" x% w! V/ G2 {4 |: R* _
  38.      }
    : i2 X# `: s; o, {
  39. }
复制代码

7 U0 L. D- D9 {5 _1 g. s, y3 s

效果如下,测量的平均频率是1.98ms,与我们设计的500Hz基本符合:

应用程序的设计如下:

  1. #include "bsp.h"            /* 底层硬件驱动 */
    ( t4 q' B/ ~7 x5 [6 T; C" a5 f
  2. #include "EventRecorder.h"$ A' a( H: l4 c

  3. " d8 A; V5 P4 s& X2 W  X
  4. /*
    8 r9 o7 ~9 q# B, Z4 f
  5. *********************************************************************************************************- X; X; z9 n; T) F! c- \, c4 g
  6. *                                              函数和变量
    / g4 O2 h$ m8 }  l* G7 t
  7. *********************************************************************************************************
    / h8 F% G5 x& B  S5 C
  8. */
    . A1 {, c( r, z% {7 ^
  9. extern void vEventRecorderTest(void);
    4 A( o: P7 [" B# V/ c- J. i- b- {
  10. uint8_t s_ucBuf[10] = "armfly";5 k& S: m. w) w9 q
  11. ; N. ^2 B" K" I1 c0 m7 L4 ?$ T
  12. /*3 T2 A% [6 E( H( K- `
  13. *********************************************************************************************************
    9 n8 R& G2 j$ C% j' N
  14. *    函 数 名: main
    ! {9 z$ k8 }# k# Z& X! ?) |
  15. *    功能说明: c程序入口/ |4 r: T5 y6 U9 m9 t. o' l7 v9 c
  16. *    形    参:无% S5 Z) n7 l1 e9 D1 o4 v
  17. *    返 回 值: 错误代码(无需处理)
    0 }$ D, }# n9 ]  d5 L$ r) @: w) m
  18. *********************************************************************************************************  J. O0 X+ q' m1 b6 T# P
  19. */
    - v3 f0 I" {8 O2 V% _6 T8 ?
  20. % u: D: \, p' x, H2 [8 C( g
  21. int main(void)
    9 C, ^! [# l& c5 F' q
  22. {  k1 j# H* F* j7 Y, a
  23.      uint8_t ucKeyCode;     /* 按键代码 */& [* S% `. X- Q  c* v4 b

  24. * ]2 p2 f# t, z8 J' E8 i6 V
  25.      uint32_t t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0;
    9 j: A# V6 u+ U7 l
  26. - ^$ J# ~- E* q* {
  27.      /* 初始化EventRecorder并开启 */8 f8 J; {- e+ c& d# a  H& J
  28. $ t! r& W; H3 j/ [
  29.      EventRecorderInitialize(EventRecordAll, 1U);
    # x0 F  f  L; Q! P4 A" r

  30. # Y$ Z# `# m9 _4 ]; s. ]0 Z$ j1 V# t+ S
  31.      EventRecorderStart();8 G: Z# b# t- M6 _& j& L  R

  32. : O6 a2 D" P9 c  n, q  W9 `( ~
  33.      bsp_Init();        /* 硬件初始化 */
    6 o. d' R  o2 c" i9 s4 l  r
  34. : ?% s$ Y, t" s6 ]: v- ]- p
  35.      bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器 */5 T2 }! ]) `4 k- L$ X

  36. , F. s" D4 Z1 d( }9 d
  37.      /* 测量中断周期 */
    # Y" r7 @# q- o1 }
  38.      vEventRecorderTest();% Q+ K8 J+ A' o
  39. 9 ~' D. L* `2 e# M
  40.      /* 进入主程序循环体 */3 [2 ~7 U$ Y2 b8 V  T( |+ l4 v
  41.      while (1)
    9 l) [& w1 ]" \- [
  42.      {
    * o0 ~1 H& P) }7 C
  43.          bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    4 Z% I7 F: y4 G  l2 q) W

  44. & [0 h5 [, h6 R$ B; a2 w
  45.          /* 判断定时器超时时间 */; C! w9 ?: O+ S* e
  46.          if (bsp_CheckTimer(0))5 M, |+ o2 K. R& P9 _
  47.          {. W7 n3 W: T; b, w
  48.               EventStartA(0);  
    , }7 Z# G) Y1 e
  49.               EventStopA(0);- m3 f' q0 C& |' L
  50.               EventStartA(1);
    1 I& N. N! E$ s; c$ g* w
  51.               bsp_DelayMS(5);
    8 a7 l4 W7 P! g' V) M: j- i
  52.               EventStopA(1);
    & ^) u5 S" s8 ?% s
  53.               EventStartA(2);
    * X0 t$ ~4 i' E0 |
  54.               bsp_DelayMS(30);) [- @1 u1 O' y' g: e; v
  55.               EventStopA(2);* r/ g$ s  @/ {1 v

  56. : V2 W, K6 m$ M6 [. {: y) {; ?
  57.               t0++;
    $ X( u  B7 ~: X0 \. ]" }  z
  58.               EventStartAv(3, t0, t0);( P5 }2 @# D- W# [0 t4 ]+ L
  59.               bsp_DelayMS(30);
    $ D4 o/ O" X2 N/ v2 z5 G
  60.               EventStopAv(3, t0, t0);
    3 ]( [, D+ ~3 q5 ]4 Z0 C1 ~
  61.          }
    ; ^9 K8 `/ g/ {8 e' I. k1 z
  62. * x. e2 N9 [6 U$ A+ ~1 }  j8 e% f& @
  63.          /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */& {" ]* W+ O* Q5 C
  64.          ucKeyCode = bsp_GetKey();   /* 读取键值, 无键按下时返回 KEY_NONE = 0 */9 Y' C5 G' K, c* T8 N4 k
  65.          if (ucKeyCode != KEY_NONE)
    4 Q/ U: A% {  V5 @8 M
  66.          {3 {  K' |9 J6 ?. X7 [3 q, t
  67.               switch (ucKeyCode)* P. y' L3 o% U# }; @) |
  68.               {
    : }$ |& R, i7 H
  69.                    case KEY_DOWN_K1:           /* K1键按下 */# e, e: O. s( l: X/ y1 L( S
  70.                        t1 += 1;
    * y0 P# y' k  J: G! B" n6 c, o
  71.                        t2 += 2;% @9 Q8 k* D7 Q9 x" W7 Y& L4 c" z
  72.                        EventRecord2(1+EventLevelAPI, t1, t2);" ?1 d( S5 e& x6 |
  73.                        t3 += 3;
    " d3 A- n& T" o  [) k  Q- T
  74.                        t4 += 4;$ t2 c" u* I+ ?
  75.                        EventRecord4(2+EventLevelOp, t1, t2, t3, t4);) Z: P; P' ?1 B5 ~6 z; o
  76.                        EventRecordData(3+EventLevelOp, s_ucBuf, sizeof(s_ucBuf));
    5 U0 M5 E( A% p! i$ @
  77.                        break;
    : N  i% x* u+ L
  78. - P' Y$ C$ b$ H4 P
  79.                    case KEY_DOWN_K2:           /* K2键按下 */. y% I7 q# l3 h% M. T
  80.                        printf("K2按键按下\r\n");7 E. d+ Q( r6 I  I
  81.                        break;
    ; {& z7 h- B# j5 Y1 d# U
  82. 2 |$ L- M8 L; v
  83.                    case KEY_DOWN_K3:           /* K3键按下 */  d# [- }+ p2 [) C( _$ p7 J7 b
  84.                        printf("K3按键按下\r\n");
    7 ^+ i3 x; S/ g5 @- _
  85.                        break;
    * p0 ^' |, m/ z4 Z
  86. # M+ l: r: T, d& F0 ?! W3 r
  87.                    default:7 @8 c+ @" `8 v4 V$ b7 p
  88.                         /* 其它的键值不处理 */" w+ ?0 \( N1 Z- d
  89.                        break;
    , F1 a2 H+ n2 s2 H; Q$ l" s% f! H- `
  90.               }! M, T  y% E2 n  ?" m0 k- V9 h# A
  91.          }) r, ~6 A) \* P& T0 e, c, L# s/ Z" Y, Y
  92.      }
    0 a3 g- P; L0 k) J3 s7 @/ V- H( L8 j
  93. }
复制代码


! \) E* B# g( J' L: t+ r8 E* k

应用程序里面主要实现了三个功能:

  1、利用测量分组A实现4路时间的测量(第1路什么也没有测量,可以用来表示这两个函数本身执行占用的时间)。每100ms测量一次时间,效果如下:

  2、利用函数EventRecord2,EventRecord4和EventRecordData发送消息事件。按下按键K1进行更新,效果如下:

  3、基于Event Recorder的printf重定向。按下按键K2或者K3会打印消息,效果如下:

8.14 总结

Event Recoder还是非常实用的,建议大家多使用几次,熟练掌握。基本用上几次就上瘾,离不开了,的确是工程调试的利器。


: d( D# Y. s& g* Y
收藏 评论0 发布时间:2022-1-1 18:00

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32Cube扩展软件包
意法半导体边缘AI套件
ST - 理想汽车豪华SUV案例
ST意法半导体智能家居案例
STM32 ARM Cortex 32位微控制器
关注我们
st-img 微信公众号
st-img 手机版