查看原文
其他

嵌入式编程必备之多方法测定程序运行时间(经验篇)

bug菌 最后一个bug 2021-01-31


1. 为什么需要测定程序运行时间?

    与其说是测量程序运行时间,不如叫测量任务运行时间可能更加贴切,我们所做的项目都是为了达到一定的项目目的从而完成相应的任务,简单一点的项目基本上就是专注于一个任务,复杂一点的涉及到多个任务相互交互共同达到项目目的,那么当我们设计一项产品功能的时候,势必需要考虑其系统的反应速度然而在硬件足够迅速的情况下,软件的每个任务的反应速度就决定了产品功能上的实效性

    软件的时效性决定着系统的快速性,这一句话的前提是在不考虑硬件的限制下符合的。不过对于我们大部分项目而言,而是真正限制系统反应速度的还是硬件资源等。比如说我们真实世界与数字世界的窗口-AD采样芯片,我们都需要通过感知外界的一些反馈来对我们的系统做出正确的决策,那么AD芯片的转化速度就成了决定该反馈的速度,进而影响着我们的系统。(不包括一些预测算法从而影响着系统提前进行判断和动作)

    那么排除硬件限制,程序的运行时间就成了影响系统速度的关键因素了,因为我们大部分的任务都基本采用一种轮训或者是一种监控的状态,如果我们所要处理的任务占据了太长的CPU时间,带来的就是我们整个系统的延时特别是在多任务调度系统的RTOS中,我们有时候执行的任务时间太长我们却忽略了导致系统总是达不到我们预期的效果,那么我们就需要非常清楚的知道我们的任务实际到底运行了多长的时间。

    那么小伙伴们会问了该如何测定运行时间呢?如果从程序运行的本质上来测定就需要:(程序所执行的最长指令个数*芯片的指令执行周期 = 实际我们程序运行的最长时间),可是我们一般很少需要这么精确的数据(可能还不一定精确毕竟时钟会有差异),至少对于大部分项目而言意义不大,并且费时费力。我们一般都是采用测量从开始到结束这样一个整个的时间段,这样更加具有可测性和使用性。下面我为大家总结了在平时工作和研究过程中使用到的测量方法,供大家参考学习。

2. 工程师必备之IO翻转示波器测量法

    该方法应该是大部分工程师常用的方法了,在大部分嵌入式研发公司的实验室都有非常精密的示波器,我们通常只需要在所测量程序的起始位置置位IO口为高电平,在程序结束的位置重置IO口为低电平,那么在示波器上显示的一段高电平时间就是我们的程序运行时间

    参考代码如下:

  1. /*******************************************

  2. * Fucion : sControlTask

  3. * Author: (公众号:最后一个bug)

  4. *******************************************/

  5. void sControlTask(void)

  6. {

  7. //1、设置Test引脚为高电平

  8. SetTEST_IO_HIGTH();

  9. //2、进行ADC采样

  10. sADC_Sample();

  11. //3、获得传感器值

  12. sGetAnalogVal();

  13. //4、控制器计算处理

  14. sContrlCal();

  15. //5、设置Test引脚为低电平

  16. SetTEST_IO_LOW();

  17. }

    注意:示波器法是我们常用的一种方法,有些小伙伴可能会觉得IO口翻转不会有延时吗?其实IO口的动作延时是不影响我们测量程序时间的,更何况IO的动作所需的时间对于任务时间是微乎其微的,如下图你就可以理解为什么不会影响测量:

    虽然说示波器测量不会带来严重的延时,不过我们的任务是动态的,可能我们所测试的高低电平宽度在不断的发生变化,这样在示波器上无法非常直观的看出最长的运行时间,这是唯一的缺陷,不过我们大部分项目都不需要那么分别的精确,该指标仅仅作为程序设计中的一个参考。

2. 工程师必备之捕获IO测量法

    该方法其实和示波器法类似,都属于外部测量方法,不过该方法的好处是能够弥补任务运行时间变化较大导致示波器测量显示难以辨识最长时间的问题

    大体实现思路:我们可以把IO翻转信号看成一个脉冲宽度来进行测量,只需要通过编程在用另外一款尽量高端的或者是主频更高的芯片通过捕获功能结合定时器进行时间记录即可,这样我们既可以获得程序运行的时间,还可以通过编程的思路,进行一段长时间内的脉宽的最大值、平均值等来更好的表征程序运行的状况。

    该方法的缺点:该办法的精度等得由外部芯片来决定,不过对于大部分应用是满足的。

    下面伪代码供大家参考:

  1. /*******************************************

  2. * Fucion : EcapInterrupt

  3. * Descri : 捕获中断触发

  4. * Author : (公众号:最后一个bug)

  5. *******************************************/

  6. void EcapInterrupt(void)

  7. {

  8. //捕获到上升沿

  9. if(GET_Ecap_Statue(ECAP1) == ECAP_RISE)

  10. {

  11. //1)启动记录脉宽标记

  12. //2)获得定时器计数时间

  13. //3)设置ECAP进入捕获下降沿模式

  14. }

  15. //捕获到下降沿

  16. else if(GET_Ecap_Statue(ECAP1) == ECAP_FALL)

  17. {

  18. //1)结束记录脉宽标记

  19. //2)获得定时器计数时间

  20. //3)进行脉宽时间计算或者最终结果

  21. //4)对相关数据进行置位等处理

  22. //5)设置ECAP进入捕获下降沿模式


  23. }

  24. }

  25. /*******************************************

  26. * Fucion : TimerInterrupt

  27. * Descri : 上升计数定时器周期触发

  28. * Author : (公众号:最后一个bug)

  29. *******************************************/

  30. void TimerInterrupt(void)

  31. {

  32. //如果已经进入脉宽检测,则对时间进行累加处理;

  33. //如果没有进入脉宽检测,则不做处理。

  34. }

通过定时器计算脉冲:

如下图所示获得脉冲(即程序运行时间)宽度公式:

脉宽 = 定时器时间*触发次数 + (下降沿定时器计数 - 上升沿定时器计数)*定时器时基

3. 工程师必备之内部定时器法

    该方法与上面的捕获IO方法是类似的,在嵌入式系统中经常会遇到时间戳的使用,同时还能获得CPU的利用率,其实方法都是一样的,系统中会开启一个核心定时器,该定时器一般不会受到外部的干扰等,通过该定时器在任务的开头和结尾分别安插标志,从而获得这段时间内的定时器计数,从而计算出程序运行时间。

    该方法在我们的小型或者资源不足的芯片上使用得不多,因为我们很少有多余的定时器来进行额外的处理,同时由于定时器处理需要一定的时间,以及一些额外的计算等都会导致测量时间上的误差,如果该误差在能够接受的范围还是可以采用该方法的。

4. 工程师必备之仿真器法

    目前许多芯片的调试仿真器都会具备测量仿真程序的多方面性能的功能,比如说KEIL或者CCS集成开发环境也都基本支持测量程序运行时间的,一般都是测量断点之间的程序所运行时间,用户需要在开发环境中进行芯片当前的晶振、主频等等方面参数的设置以后基本能够获得一个运行时间结果。

    不过根据大部分工程师的反馈来看,有部分仿真器测量会有较大误差,该部分的误差可能一方面来自于用户配置不正确等使用问题,可能一部分来自于仿真器本身的机制算法所带来的误差,该部分需要具体了解仿真器计算方法,这里就不再展开了优先使用方法1吧,如果资源优先可以采用方法2

    好了,今天就总结到这里,这里是公众号:“最后一个bug”,感谢大家的关注,后续我会有更加精美的文章供大家阅读!也希望大家多分享转发!

推荐阅读

单片机常用程序框架之分时轮询(详注代码)

嵌入式编程之动态接口技术(经验干货)

看门狗你确定会用了?(经验干货满满)

单片机开发之节省内存大法(C语言版本)

一种 “ 超强 ” 队列的C语言实现(附代码)

一文搞定通信协议中CRC校验(附代码)

【连载】嵌入式测试驱动开发(9)


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存