简析如何获取单片机代码运行时间的方法

前言
单片机编程者需要知道自己的程序需要花费多长时间、while周期是多少、delay延时是否真如函数功能描述那样精确延时。
很多时候,我们想知道这些参数,但是由于懒惰或者没有简单的办法,将这件事推到“明天”。笔者提出了一种简便的测试方法,可以解决这些问题。
测试代码的运行时间的两种方法:
使用单片机内部定时器,在待测程序段的开始启动定时器,在待测程序段的结尾关闭定时器。为了测量的准确性,要进行多次测量,并进行平均取值。
借助示波器的方法是:在待测程序段的开始阶段使单片机的一个gpio输出高电平,在待测程序段的结尾阶段再令这个gpio输出低电平。用示波器通过检查高电平的时间长度,就知道了这段代码的运行时间。显然,借助于示波器的方法更为简便。
以下内容为这两种方案的实例,以stm32为测试平台。如果读者是在另外的硬件平台上测试,实际也不难,思路都是一样的,自己可以编写对应的测试代码。
借助示波器方法的实例
delay_us函数使用stm32系统滴答定时器实现:
#include “systick.h”
/* systemfrequency / 1000 1ms中断一次
* systemfrequency / 100000 10us中断一次
* systemfrequency / 1000000 1us中断一次
*/
#define systickperiod 0.000001
#define systickfrequency (1/systickperiod)
/**
* @brief 读取systick的状态位countflag
* @param 无
* @retval the new state of usart_flag (set or reset)。
*/
static flagstatus systick_getflagstatus(void)
{
if(systick-》ctrl&systick_ctrl_countflag_msk)
{
return set;
}
else
{
return reset;
}
}
/**
* @brief 配置系统滴答定时器 systick
* @param 无
* @retval 1 = failed, 0 = successful
*/
uint32_t systick_init(void)
{
/* 设置定时周期为1us */
if (systick_config(systemcoreclock / systickfrequency))
{
/* capture error */
return (1);
}
/* 关闭滴答定时器且禁止中断 */
systick-》ctrl &= ~ (systick_ctrl_enable_msk | systick_ctrl_tickint_msk);
return (0);
}
/**
* @brief us延时程序,10us为一个单位
* @param
* @arg ntime: delay_us( 10 ) 则实现的延时为 10 * 1us = 10us
* @retval 无
*/
void delay_us(__io uint32_t ntime)
{
/* 清零计数器并使能滴答定时器 */
systick-》val = 0;
systick-》ctrl |= systick_ctrl_enable_msk;
for( ; ntime 》 0 ; ntime--)
{
/* 等待一个延时单位的结束 */
while(systick_getflagstatus() != set);
}
/* 关闭滴答定时器 */
systick-》ctrl &= ~ systick_ctrl_enable_msk;
}
检验delay_us执行时间中用到的gpio(gpio.h、gpio.c)的配置:
#ifndef __gpio_h
#define __gpio_h
#include “stm32f10x.h”
#define low 0
#define high 1
/* 带参宏,可以像内联函数一样使用 */
#define tx(a) if (a)
gpio_setbits(gpiob,gpio_pin_0);
else
gpio_resetbits(gpiob,gpio_pin_0)
void gpio_config(void);
#endif
#include “gpio.h”
/**
* @brief 初始化gpio
* @param 无
* @retval 无
*/
void gpio_config(void)
{
/*定义一个gpio_inittypedef类型的结构体*/
gpio_inittypedef gpio_initstructure;
/*开启led的外设时钟*/
rcc_apb2periphclockcmd( rcc_apb2periph_gpiob, enable);
gpio_initstructure.gpio_pin = gpio_pin_0;
gpio_initstructure.gpio_mode = gpio_mode_out_pp;
gpio_initstructure.gpio_speed = gpio_speed_50mhz;
gpio_init(gpiob, &gpio_initstructure);
}
在main函数中检验delay_us的执行时间:
#include “systick.h”
#include “gpio.h”
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
gpio_config();
/* 配置systick定时周期为1us */
systick_init();
for(;;)
{
tx(high);
delay_us(1);
tx(low);
delay_us(100);
}
}
示波器的观察结果:
可见delay_us(100),执行了大概102us,而delay_us(1)执行了2.2us。
更改一下main函数的延时参数:
int main(void)
{
/* led 端口初始化 */
gpio_config();
/* 配置systick定时周期为1us */
systick_init();
for(;;)
{
tx(high);
delay_us(10);
tx(low);
delay_us(100);
}
}
示波器的观察结果:
可见delay_us(100),执行了大概101us,而delay_us(10)执行了11.4us。
结论:此延时函数基本上还是可靠的。
使用定时器方法的实例
至于使用定时器方法,软件检测程序段的执行时间,程序实现思路见stm32之系统滴答定时器:
http://www.cnblogs.com/amanlikethis/p/3730205.html
笔者已经将检查软件的使用封装成库,使用方法在链接文章中也有介绍。我们这里只做一下简要的实践活动。
delay_us函数使用stm32定时器2实现:
#include “timer.h”
/* systemfrequency / 1000 1ms中断一次
* systemfrequency / 100000 10us中断一次
* systemfrequency / 1000000 1us中断一次
*/
#define systickperiod 0.000001
#define systickfrequency (1/systickperiod)
/**
* @brief 定时器2的初始化,,定时周期1us
* @param 无
* @retval 无
*/
void tim2_init(void)
{
tim_timebaseinittypedef tim_timebasestructure;
/*ahb = 72mhz,rcc_cfgr的ppre1 = 2,所以apb1 = 36mhz,tim2clk = apb1*2 = 72mhz */
rcc_apb1periphclockcmd(rcc_apb1periph_tim2, enable);
/* time base configuration */
tim_timebasestructure.tim_period = systemcoreclock/systickfrequency -1;
tim_timebasestructure.tim_prescaler = 0;
tim_timebasestructure.tim_countermode = tim_countermode_up;
tim_timebaseinit(tim2, &tim_timebasestructure);
tim_arrpreloadconfig(tim2, enable);
/* 设置更新请求源只在计数器上溢或下溢时产生中断 */
tim_updaterequestconfig(tim2,tim_updatesource_global);
tim_clearflag(tim2, tim_flag_update);
}
/**
* @brief us延时程序,10us为一个单位
* @param
* @arg ntime: delay_us( 10 ) 则实现的延时为 10 * 1us = 10us
* @retval 无
*/
void delay_us(__io uint32_t ntime)
{
/* 清零计数器并使能滴答定时器 */
tim2-》cnt = 0;
tim_cmd(tim2, enable);
for( ; ntime 》 0 ; ntime--)
{
/* 等待一个延时单位的结束 */
while(tim_getflagstatus(tim2, tim_flag_update) != set);
tim_clearflag(tim2, tim_flag_update);
}
tim_cmd(tim2, disable);
}
在main函数中检验delay_us的执行时间:
#include “stm32f10x.h”
#include “timer_drive.h”
#include “gpio.h”
#include “systick.h”
timingvartypedef time;
int main(void)
{
tim2_init();
systick_init();
systick_time_init(&time);
for(;;)
{
systick_time_start();
delay_us(1000);
systick_time_stop();
}
}
怎么去看检测结果呢?用调试的办法,打开调试界面后,将time变量添加到watch一栏中。然后全速运行程序,既可以看到time中保存变量的变化情况,其中timewidthavrage就是最终的结果。
可以看到timewidthavrage的值等于0x119b8,十进制数对应72120,滴答定时器的一个滴答为1/72m(s),所以delay_us(1000)的执行时间就是72120*1/72m (s) = 0.001001s,也就是1ms。验证成功。
备注:定时器方法输出检测结果有待改善,你可以把得到的timewidthavrage转换成时间(以us、ms、s)为单位,然后通过串口打印出来,不过这部分工作对于经常使用调试的人员来说也可有可无。
两种方法对比
软件测试方法:
操作起来复杂,由于在原代码基础上增加了测试代码,可能会影响到原代码的工作,测试可靠性相对较低。由于使用32位的变量保存systick的计数次数,计时的最大长度可以达到2^32/72m = 59.65 s。
示波器方法
操作简单,在原代码基础上几乎没有增加代码,测试可靠性很高。由于示波器的显示能力有限,超过1s以上的程序段,计时效果不是很理想。但是,通常的单片机程序实时性要求很高,一般不会出现程序段时间超过秒级的情况。
综合对比,推荐使用示波器方法。


一款能监测血糖的隐形眼镜
基于NB-iot技术实现财物跟踪的EA01-SG定位模块方案
实用USB术语
浅谈机场能源管理系统改造项目设计思路
一二次融合柱上断路器如何避免过电压的现象
简析如何获取单片机代码运行时间的方法
魅族Pro7将发布搭载联发科X30 高通呢?
九联科技携手惠州学院共建OpenHarmony创新实验室共筑OpenHarmony人才生态
华为荣耀终会回归
再创高速处理器传说 IBM推出全新zNext系列处理器
无线动能开关在大型商业综合体电气设计中如何体现优势
基于国家人工智能战略的解析
离线语音在吸顶灯上的应用
氢气检测仪在H2检测中有什么应用?
5G给显示产业带来新的机遇,显示应用场景将向“多点开花”转变
5W可调光非隔离式LED驱动电路设计
人工智能与教育行业的结合将成为潮流趋势
揭秘pcb是什么物质:你不知道的“化学战士”
一加3T的colette的特别定制版:外观配色惊艳,与法国品牌联合定制
高德智感携多款新品亮相第134届中国进出口商品交易会