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

【经验分享】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。

    1 [# M) [. _  d5 `' H( U. d

    $ A) A5 C, {' q

    & i1 l* M* x. x( x7 {
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接口区别

    / X  ?% s) h( E

    3 `( i1 m/ Y' D% l+ o

    ! E3 M+ ?* D; K- q( D

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

  •   词条 SWV(Serial Wire Viewer)


    3 S8 c3 a8 h0 D: i" |  I5 w. L
    $ P3 s/ J2 \: Z8 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 就会把一个差分的时间戳数据包插入到跟踪数据流中。跟踪捕获设备在得到了这些时间戳后,就可以找出各跟踪数据之间的时间相关信息。另外,在时间戳计数器溢出时也会发送时间戳数据包。

    * L) ^2 G8 D0 X" P4 M4 ]% d+ j

    5 o: `/ g7 O5 W3 H( E

    , u# n' J  M- e( C9 g7 N
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调试。


    5 m& K6 G' I" G/ W3 [

    8 M% O, X( L( q* |4 F
    - c; V0 j3 Q5 w+ O0 R
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 {0 I1 `1 R2 N  J( t

  2. # _& X4 T2 {$ t* H; I
  3.   uint32_t ts;                  // Timestamp (32-bit, Toggle bit instead of MSB)# E3 C5 m3 ], R9 f0 f3 S
  4. 3 {$ h3 v0 v: x# Q: z
  5.   uint32_t val1;                // Value 1   (32-bit, Toggle bit instead of MSB)
    ! N# s8 x7 K6 i  e8 x
  6. 3 }' Q! p1 k- c1 \
  7.   uint32_t val2;                // Value 2   (32-bit, Toggle bit instead of MSB)
    6 v! ?* f" F/ R6 n  o

  8. ' P. j9 ?+ Z4 B" C, A- p6 l# K6 D) n6 e
  9.   uint32_t info;                // Record Information3 Z1 x" Q9 }3 f. s1 x+ |' h3 m

  10. 4 C! H# f7 X0 ?! M+ ~
  11.                                 //  [ 7.. 0]: Message ID (8-bit)
    5 i1 F5 m0 d. o$ W

  12. # ?* Z5 e$ z& f$ ?5 v2 b
  13.                                 //  [15.. 8]: Component ID (8-bit)6 Q$ h8 B2 I% k- G9 u0 _$ I* P

  14. 6 [0 _2 \- p+ Y) W( i
  15.                                 //  [18..16]: Data Length (1..8) / Event Context
    $ x6 I2 L" [" Y1 p) ~% g" ?! R% _

  16. : k- C1 W- x; l
  17.                                 //      [19]: IRQ Flag; Z5 z4 Y1 E+ h+ T& y4 a9 H# h

  18. 2 y' p& D% j7 `. g
  19.                                 //  [23..20]: Sequence Number) f% c" h5 F& A! O6 ]+ w1 |! u; J
  20. ( `3 h5 ~' o9 O) k1 c
  21.                                 //      [24]: First Record
    ! @8 D* n# t' G" h  i9 D4 j

  22. . u: s, W/ ^6 L# `5 v! q% y
  23.                                 //      [25]: Last Record
    + e, E! M' i! }5 |# }" N

  24. $ o1 s9 H3 Z" q. d
  25.                                 //      [26]: Locked Record
    ! h3 c6 {' P+ f6 [( V

  26. , e8 B; n' @: x
  27.                                 //      [27]: Valid Record/ R* Q6 Q9 f* b. n
  28. ! V" }; h! `0 I8 i
  29.                                 //      [28]: Timestamp MSB
    5 b% l- i0 I6 r, K( f

  30. - O3 K* t" }: x
  31.                                 //      [29]: Value 1 MSB
    * [9 c/ [. R9 {5 x1 c5 l
  32. + `9 H, w( {% `; O* E1 R6 @/ u
  33.                                 //      [30]: Value 2 MSB
    ! Q4 ?* n' M8 u$ ^% T
  34. + X$ i5 V) o& @% O
  35.                                 //      [31]: Toggle bit* x! V4 L4 L" G& A5 b1 _! z/ I/ j0 ?
  36. 1 J& }4 A! H# i  n
  37. } EventRecord_t;
复制代码

( L/ s0 y4 u; j" w2 @5 G, F. C

其中参数成员info最重要,也就是前面说的32位ID,这里的说明与前面的说明稍有不同。这里是经过处理后,实际存储到Event Recorder缓冲里面的数据。
' b" J+ X, g) b: i

对于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(随着时间的推移,如果升级了新版本,直接使用新版即可)。

    5 g: S0 |" r- t& j8 H1 N

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

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

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

    3 [0 k# Z" k3 j, W: i9 D, w

    2 p- e/ f& b% b1 o. p' D

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

8.4   Event Recorder事件记录的实现

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

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

  1. /* 初始化EventRecorder并开启 */8 `7 L4 B# v* \# G& r

  2. 1 i7 H) l6 p: B/ \! m
  3. EventRecorderInitialize(EventRecordAll, 1U);
    9 V# U# M: D1 n- q
  4. , L8 L: ~! u  F+ w  K- r4 H) X1 H
  5. EventRecorderStart();
复制代码

: L. f. z  f& r, }. E$ k, a" O% q

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


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

    % Z/ w: q/ v7 @$ [
    ) w! p9 Y# m2 t# e7 M

    7 ?  Y" U5 H* y" }2 [4 V5 a& }

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

    4 I6 j! X  N3 o1 @6 A: b; k' U) _( G

    ; n9 ?6 {: R% B  n: }4 Q7 `

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

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


  • ' K8 a5 i& m3 ?( C8 H

    & C/ r1 m# y7 o2 c$ I% a, p1 R
    /* 初始化EventRecorder并开启 */& q6 p2 }/ F+ Y( Q& k
    ' ?3 O. L! {' l7 a# f( [# N4 s# e

  • 8 N6 D, r3 v9 d- W& Y$ p) i: Y( o# y
    ( e( M" f2 ?8 l
    6 a7 v: m5 e, {( g+ o
    & l% ]# h$ y% Y* @

  • 9 V$ R/ q6 R4 ]% J) |5 ]4 N
    - O# g3 h5 ^: ]1 _" }  V6 D& F
    EventRecorderInitialize(EventRecordAll, 1U);: B1 {) K- K7 ]5 s! X

    - J7 Q- \$ o4 h$ W; U: ]

  • : Y, A1 o7 A2 k& h. |: A( k" q
    4 G+ d4 h. C$ F+ M1 U! I

    9 i9 w! z$ Y  }
    " ^, Q* Y% r+ e$ N8 E2 l
  • 2 `. d4 }# d; a8 t
    , g+ |; `* x) K; {6 a& `( k: S: ?
    EventRecorderStart();
    9 t# r; F# m* S5 I1 a

    - Q  u7 G3 Z" N5 `/ y, Y! C- K0 ?9 S' K- d. @8 w! Q$ Z& p# ]

    9 W, Z+ V  b* \$ e- L2 L- z* k

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


  • $ {& ^) ^  k, e3 h. B. f

    # b5 ^5 }. w. I! \. |
    EventStartA(0);& |* a# [9 ]- Z8 `7 s/ i) H: l

    ! t# z  s* v; X

  • 4 U9 \% Z3 t3 h/ ?

    ( k3 V+ Q6 ^  G( [8 U

    ; D' T+ o9 u; T7 l. y
    8 `) X( |2 q. r7 y7 P4 C

  • 8 Q" A& r( `: `- u  n  V& H' e
      q3 H% M, [5 M2 t
    //测量的代码部分
    2 k# G4 x9 t" A0 X( S6 _% h

    - `: x5 B6 V7 v3 k
  • # N1 F. _0 S, D% M3 c

    ( ]+ ?0 ]- t1 g. }; t

    $ e$ B) o% d7 L' F; D
    # A% B2 q* N8 f% v4 t

  • 5 L* h1 L7 C9 `% a6 e7 t  m& Q4 |

    , F, @9 ^6 u' E, P/ N5 J
    EventStopA(0);
    - r- |; T# r" D1 p/ }* I2 S

    * |5 F! ?8 s# u( m& o7 a
    & d# b, C" Y3 K, I# g& J4 m& B

    " ~% Y3 S4 D5 y

这里是用分组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的效果:

    ' E8 E( Y7 Q3 Q6 F
    * G) a* n9 A; n- g5 F

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

    ( _& [( S) r# H2 N0 q' d9 _

    / ~( g7 s7 h( e+ j" _. {

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


    ; s7 V' v& A9 b9 q
    / x  U+ d/ z2 O: X3 |  p, O% i" O

8.9   JLINK配置说明

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

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

    2 W2 J' g; A  U9 N$ q- Z% Q5 O
    3 w- O: s- O* q( t' u3 W0 D) R) h

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

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

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

    ! a' D, ^' X5 A9 K" H
    $ s% A, A; m- b" m) _) W

8.10 STLINK配置说明

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

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

    ' N8 h7 T) \9 A. n: D" x2 q
    ( D# D% N* x0 E8 @4 G7 x6 q

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

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

    ; b, N/ Q$ M% ~! K& x5 N5 Z6 A' ^

    4 S$ ^4 V# x4 I9 K5 y. n+ L

8.11 CMSIS-DAP配置说明

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

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


    5 e  p' J: a$ d: N2 L
    : b3 G8 y$ o$ t# `, Q

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

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

    + n+ V+ I' W2 d' m: L
    6 k$ c9 g. m5 v! U0 i

8.12 ULINK配置说明

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

8.13 配套例子

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

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


    1 O3 ?% Z1 v6 ?7 ]- L

    & V0 J7 B) T6 F' R

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

  1. #include "bsp.h"
    ) Q: Q+ A! u# c3 \- T
  2. / y' P- Z& n# m! X6 d' F
  3. #include "EventRecorder.h"+ S) B! i; U5 n" i, s/ a: Y
  4. /* 定时器频率,500Hz */) w- Y2 H% f- }# r5 ], v, v$ [. U6 D2 c
  5. #define  timerINTERRUPT_FREQUENCY    500
      k( g# b: e+ \# x, u0 V! I5 i
  6. /* 中断优先级 */
    ' f2 r; T0 ~, w6 Z) l2 _4 @" U
  7. #define  timerHIGHEST_PRIORITY       10
    ( V# q# _8 P" z6 \! [2 n* j) X
  8. /*" I, t: E: S; a6 c& o* c
  9. *********************************************************************************************************
    8 t+ g5 i1 h  e! a/ X- i
  10. *    函 数 名: vEventRecorderTest
    7 }2 [: N$ `1 }' @& `3 v  l4 c
  11. *    功能说明: 创建定时器- v+ C: O* s. L! z5 x
  12. *    形    参: 无
    $ |6 ?. |+ e8 D5 M# c
  13. *    返 回 值: 无' d0 n. c% g- X: e0 r4 R: l- ^
  14. *********************************************************************************************************/ `7 N( O1 A3 i3 V0 K: C" K
  15. */
    " c3 }7 ]" y, b& u: A/ w. w7 J
  16. void vEventRecorderTest(void)9 z  y- H1 T7 W; o3 W/ U
  17. {
    / }2 e1 q: m) Z/ y/ H$ v
  18.      bsp_SetTIMforInt(TIM6, timerINTERRUPT_FREQUENCY, timerHIGHEST_PRIORITY, 0);( J- J: S1 i! N0 W7 t. Q2 q6 _
  19.      EventStartB(0);   9 Q2 s2 }7 f# Z6 ~' B0 o
  20. }: z# t$ ~  O' Y  T: x
  21. 2 x. L& u0 L. I% l9 {% ~6 N
  22. /*8 m% ~* Y: T8 z5 F$ e' U$ h6 d6 N
  23. *********************************************************************************************************$ M0 t, A% L3 u& `5 C
  24. *    函 数 名: TIM6_DAC_IRQHandler
    ! d0 q8 r; v' r* W  C( h3 o4 ?7 D3 \
  25. *    功能说明: TIM6中断服务程序。* T3 Q( S" [  v
  26. *    形    参: 无- w0 `' _* j! ^
  27. *    返 回 值: 无
    $ s" ?7 S6 Q5 h7 Q, X0 @1 S
  28. *********************************************************************************************************
    # \) b, g) `9 o2 |2 k+ q; @  k; A1 y
  29. */. N8 Z3 Z: p/ G2 d" m5 I0 J
  30. void TIM6_DAC_IRQHandler( void )! ?" u# x0 o( D. o9 \( \, v$ N; K
  31. {2 p8 n+ ]' n8 @) E! @3 v2 l: }$ F: J
  32.      if((TIM6->SR & TIM_FLAG_UPDATE) != RESET)4 Q4 b* I' `3 T$ d9 _
  33.      {
    , q* b; N, A& E2 k; T" V5 i
  34.          EventStopB(0);    # z+ v* Y% k( H1 r& w7 v
  35.          EventStartB(0);   8 @" Z, M9 h+ l
  36.          /* 清除更新标志 *// _' y, s. s2 ~# L
  37.          TIM6->SR = ~ TIM_FLAG_UPDATE;
    3 F9 I! {! `' _/ ^
  38.      }9 y5 j" G& M' b7 X
  39. }
复制代码
) Z' q& z' S6 r9 E# G, t: W! z

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

应用程序的设计如下:

  1. #include "bsp.h"            /* 底层硬件驱动 */
    2 i4 ~" V# H6 y
  2. #include "EventRecorder.h"' b1 S# B$ i) ^4 P7 H
  3. + L$ t5 ~0 A' c6 z" i
  4. /*
    " p& ^1 \3 T- ~3 d5 ?  b/ A, L
  5. *********************************************************************************************************
    3 G- B; [' f5 w5 @( h
  6. *                                              函数和变量
    5 K6 m) P' _. O  P! l- o2 }
  7. *********************************************************************************************************1 u" E4 m8 @- U+ d" {6 K
  8. */4 l& \0 Q/ n8 P$ }8 h: ]% b( \
  9. extern void vEventRecorderTest(void);4 I" ~5 ?% ]% L6 Q) N" l
  10. uint8_t s_ucBuf[10] = "armfly";
    . S9 B$ ~7 e% Q7 z% Z$ \

  11. 9 |$ s3 O# r) q; P+ b: L: E5 ~
  12. /*
    4 y2 A' u: c( T/ ]0 y
  13. *********************************************************************************************************
    ) J4 a0 Q9 |8 Z0 {* }& u7 O
  14. *    函 数 名: main
    ( q6 [7 S8 k  X, K# R3 y0 O! r
  15. *    功能说明: c程序入口
    0 @9 d- R, e4 m+ A; z- T) j  ]
  16. *    形    参:无
    * g) P0 r0 K- o9 Q' q  x+ T
  17. *    返 回 值: 错误代码(无需处理)
    ; N) X% T  z% C
  18. *********************************************************************************************************
    $ s' V& T  O  i
  19. */8 w% U' \" I! M! ^6 Q

  20. . Z% X) z' H# S8 C; m+ p1 [
  21. int main(void)
      A( t, ^/ ^, Q1 @) N
  22. {7 S8 Y" p- c3 Q
  23.      uint8_t ucKeyCode;     /* 按键代码 */
    1 w& ?7 q  t, s5 C' H" ]2 ~" B  V
  24. * J9 x) s: n' s; R1 t' c
  25.      uint32_t t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0;
    7 U  |, @) K3 x% y

  26. ! V2 N# F& o/ M  W, K8 Z: g& `$ e* i, q
  27.      /* 初始化EventRecorder并开启 */
    ' b  [& _5 A8 N" A7 _& X  ]
  28. 5 t3 v6 l) q, f. K
  29.      EventRecorderInitialize(EventRecordAll, 1U);( g8 @3 g& V/ G9 i$ C
  30.   s$ V' L5 H# @0 i0 e! a% W
  31.      EventRecorderStart();2 p: d7 J' w* X9 i8 L3 z0 V
  32. ) [  w. H9 p' z* J1 H
  33.      bsp_Init();        /* 硬件初始化 */* @( D4 N% W; s" G) T' f7 C  N. @
  34. 9 s8 t+ B9 d9 U" @' `1 Z$ |
  35.      bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器 */
    & ]! C& l7 E9 \, @* k" S

  36. 1 T- d# t6 b0 ~* y1 a0 }! M
  37.      /* 测量中断周期 */
    0 M* x" f4 M3 M2 p2 O# b/ Y
  38.      vEventRecorderTest();. G6 d" Y. ?! I( z4 x$ ?( Z% p
  39. $ E' s3 R2 o% P1 I0 B
  40.      /* 进入主程序循环体 */
    / w9 ~5 V9 M, _& ^
  41.      while (1)2 V8 T1 f& I* A- u1 w5 w& _% ^3 s
  42.      {5 W. u  E6 S$ e( D
  43.          bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */" ]! q4 A2 Q, F6 I( @, s6 U9 i( Y
  44. , _8 n/ X! B. m, H3 M+ X7 T) V+ G' {
  45.          /* 判断定时器超时时间 */- X* s7 ?4 r, F2 ^4 U6 f  W5 }
  46.          if (bsp_CheckTimer(0))
    * L) J; G4 P# b6 e- S
  47.          {
    6 u3 t5 }4 _) X' w( U9 l
  48.               EventStartA(0);  
    - {3 _$ L8 M0 B5 X7 k( e4 A" a
  49.               EventStopA(0);# P1 u2 e9 m, \  O* l: J
  50.               EventStartA(1);
    / V/ ^0 D) u8 {: n
  51.               bsp_DelayMS(5);
    9 r# b8 Z1 c7 m1 h$ [: ?
  52.               EventStopA(1);& F  g2 a) }& J* k# ?" X8 v
  53.               EventStartA(2);8 L7 g+ p' \- X) Y# A3 o+ f
  54.               bsp_DelayMS(30);
    / G. ~1 Y$ D+ b% Y, J8 `
  55.               EventStopA(2);
    4 m( }: M( }* N& u

  56. ! n# v0 f$ e! N% R
  57.               t0++;
    3 i# F& a9 v, j! y
  58.               EventStartAv(3, t0, t0);
    2 t: @5 m6 i- b/ t8 q: f; Q
  59.               bsp_DelayMS(30);
    ( ^+ K* O$ `3 y2 [" M
  60.               EventStopAv(3, t0, t0);1 ^3 {9 c+ s: D/ v: d& J
  61.          }
    + _0 t' Q$ G9 C/ n2 T$ N4 ?7 `
  62. 9 m7 q3 L2 z& r8 i1 V0 s# R& W" G
  63.          /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
    6 W1 A2 `# ~8 P& {
  64.          ucKeyCode = bsp_GetKey();   /* 读取键值, 无键按下时返回 KEY_NONE = 0 */+ ]9 @3 N2 o$ y# }1 x+ F
  65.          if (ucKeyCode != KEY_NONE)
    ; e+ c% P5 {, X7 J$ c, k8 Z9 ^7 J
  66.          {$ O- d9 r/ [0 M7 J
  67.               switch (ucKeyCode)
    + h7 e  t5 m6 o8 x; m1 W2 g: G
  68.               {% S4 ~- i( V3 H2 w
  69.                    case KEY_DOWN_K1:           /* K1键按下 */
    ) `3 {1 b: g% B8 S; i
  70.                        t1 += 1;
    , I) }! F/ \- A9 b
  71.                        t2 += 2;
    5 I6 z: s7 K$ v
  72.                        EventRecord2(1+EventLevelAPI, t1, t2);
    1 ~7 ~) }4 W" v+ f' r; @
  73.                        t3 += 3;
      v  Q$ j9 A+ l& r
  74.                        t4 += 4;
    8 T7 _+ r$ I+ ^" V, ^+ p
  75.                        EventRecord4(2+EventLevelOp, t1, t2, t3, t4);
    7 m& g2 A  A/ S$ f- p
  76.                        EventRecordData(3+EventLevelOp, s_ucBuf, sizeof(s_ucBuf));5 ^  S7 ]: ?. M$ C
  77.                        break;
    & |% n6 k. t: T  p7 z8 W' ~. ^. K
  78. 2 K7 w+ l5 ?" _7 r
  79.                    case KEY_DOWN_K2:           /* K2键按下 */
    $ A9 b+ T1 Z; e0 c& [9 y5 S
  80.                        printf("K2按键按下\r\n");, x9 T0 D. o# p6 Y3 S
  81.                        break;7 h5 o. n9 O% b/ m7 n% c7 \8 C

  82. 1 H6 b4 j9 ?9 q2 {5 J
  83.                    case KEY_DOWN_K3:           /* K3键按下 */
    ! [: B) f. u4 P! j1 ]
  84.                        printf("K3按键按下\r\n");! T" i/ f% H4 ^: F$ N
  85.                        break;
    $ X% A  ?6 L( T! }/ a

  86. 0 V5 `  c/ D  k0 X( k5 ?0 z0 m3 X
  87.                    default:
    # ^6 i  l- c# I$ z- W
  88.                         /* 其它的键值不处理 */
      P2 R8 R$ @$ E% Q3 P6 B$ W0 |
  89.                        break;" h( w$ _! ?9 e9 W- T6 T
  90.               }2 Q5 ]1 `5 h7 Z7 `
  91.          }" a  j6 V5 x0 M3 J. X& ?5 Q/ I5 f
  92.      }; L0 p1 _- }6 ]/ q& _3 n7 E5 C
  93. }
复制代码


5 H. n3 H4 _. @8 E3 }0 w$ y( J

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

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

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

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

8.14 总结

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


4 p- G6 `, T" F, J
收藏 评论0 发布时间:2022-1-1 18:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版