单片机测量代码运行时间方法-STM32

在实际程序的编写中,我们经常会对程序进行一个整体的复盘、优化,或者对算法的运行时间进行测量等等,那么怎么精确的测量我们程序的运行时间呢?

下面我们给出几种方法:

  • 1 使用Keil Debug功能
  • 2 使用逻辑分析仪或示波器等设备测量
  • 3 使用STM32自带定时器进行测量

  • 1 使用Keil Debug功能

    我们可以使用J-LINK或者ST-link 等仿真器,实现对代码运行时间的测量,首先要设置仿真器仿真的实际频率

    首先点击Settings设置

    然后点击Trace 设置我们芯片的系统频率,点击Teace Enable 使能

    如果工作频率设置不正确,则会造成测量的时间不正确。

    仿真器默认采用的是10MHz的工作频率

    首先我们点击DEBUG模式

    1.然后会跳转到main.c

    我们可以看到从系统启动到main.c所需要的时间是0.00000367秒

    2.我们在需要测量的程序段开始和结束的地方设置断点

    3 通过起始断点和结束断点处读取到的时间,结束时间-起始时间,即可判断代码段执行的时间。


    我们用500ms延时的流水灯来举例:

    测量代码:

    		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);	//PB0置0
    		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET); //PB1置0
    		delay_ms(500);
    


    点击RUN 运行代码 到起始断点处停止

    记录下此时时间 t1=0.00008590s

    再次点击RUN 运行代码 到结束断点处停止

    记录下此时时间 t2=0.50014558s

    代码运行时间是: t2-t1=0.50014558-0.00008590=0.50005968s≈500ms

    最后的0.00005968s 是运行上面两个LED高电平函数,所需要的CPU指令时间以及误差时间

    总结:

    使用keil的debug模式进行测量,非常方便,可以很随意的对任意段代码进行测量,并且误差很小,操作方便。

    2 使用逻辑分析仪或示波器等设备测量

    在待测程序段的开始阶段使单片机的一个GPIO输出高电平,在待测程序段的结尾阶段再令这个GPIO输出低电平。用示波器或者逻辑分析仪通过检查高电平的时间长度,就知道了这段代码的运行时间。

    while(1){
    		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET); //PB1置1
    		delay_ms(500);
    		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);	//PB1置0
    		delay_ms(500);
    	}
    

    延时500ms时波形如下:

    修改延时为100ms:

    while(1){
    		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET); //PB1置1
    		delay_ms(100);
    		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);	//PB1置0
    		delay_ms(100);
    	}
    

    波形如下

    总结:

    使用示波器测量较为准确,缺点是需要单独的示波器或者逻辑分析仪等,示波器一般体积较大的还需要供电,并且还要连接GPIO口,还是有点麻烦的

    3 使用STM32自带定时器进行测量

    定时器本质上就是向上累加的计数器(如果配置成向上计数时),所以我们在测量开始的代码前面读取定时器的计数器,在结束测量的位置再读取定时器的计数器,获得两次的差值,这样就可以计算出这段代码的运行的时间。 这就是其原理

    这里我们使用TIM3定时器

    定时器初始化:

  • 定时器3初始化,我们使用STM32H7,定时器时钟为200M,分频系数为20000-1,
  • 所以定时器3的频率为200M/20000=10KHz 周期是T=1/F=1/10000=100us ,自动重装载为65535-1,
  • 那么定时器周期就是T=65535*100us=6.5535s
  • void TIM3_Init(u16 arr,u16 psc)
    {  
        TIM3_Handler.Instance=TIM3;                          //通用定时器3
        TIM3_Handler.Init.Prescaler=psc;                     //分频
        TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;    //向上计数器
        TIM3_Handler.Init.Period=arr;                        //自动装载值
        TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频因子
        HAL_TIM_Base_Init(&TIM3_Handler);
        
        HAL_TIM_Base_Start_IT(&TIM3_Handler); //使能定时器3和定时器3更新中断:TIM_IT_UPDATE    
    }
    
    
     TIM3_Init(65535-1,20000-1); //定时器3初始化,定时器时钟为200M,分频系数为20000-1,
    //所以定时器3的频率为200M/20000=10KHz  周期是T=1/F=1/10000=100us ,自动重装载为65535-1,
    //那么定时器周期就是T=65535*100us=6.5535s
    
    
    

    代码测试:

    我们还是以LED流水灯翻转200ms为例,看下定时器测得的时间:

    
    int  Start_count=0;  //开始计时定时器值
    int  End_count=0;   //结束计时定时器值
    int  Time;  //代码运行时间
    
    
    int main(void)
    {	
    
    	Cache_Enable();                		//打开L1-Cache
    	HAL_Init();				        	//初始化HAL库
    	Stm32_Clock_Init(160,5,2,4);  	    //设置时钟,400Mhz 
    
    	LED_Init();							//初始化LED
        TIM3_Init(65535-1,20000-1);      	//定时器3初始化,定时器时钟为200M,分频系数为20000-1,
    	//所以定时器3的频率为200M/20000=10KHz  周期是T=1/F=1/10000=100us ,
    	//自动重装载为65535-1,那么定时器周期就是6.5535S
    	
    	//计数1次等于100us  单位0.1ms 
    	
        while(1)
        {
    		//开始计时
    		Start_count=__HAL_TIM_GET_COUNTER(&TIM3_Handler); //获取定时器的值,开始计时
    		
           	HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET); //PB1置1
    		delay_ms(100);
    		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);	//PB1置0
    		delay_ms(100);
    		
    		//结束计时
    		End_count=__HAL_TIM_GET_COUNTER(&TIM3_Handler);  //获取定时器的值,结束计时	
    		Time=(End_count-Start_count)/10;  //时间(ms)=结束值-开始值+定时器溢出次数*65535
    		
    	} 
    }
    
    
    

    上面代码的思想就是在代码开始运行前,测得定时器的初始值,然后在代码结束的位置,再测得定时器的值,最终的代码运行时间:

    Time=(End_count-Start_count)/10; //时间(ms)=结束值-开始值

    测得时间为200ms

    当然,如果我们的代码运行时间超过了6.5535秒,就要考虑定时器中断溢出的情况,我们的代码如下:

    
    
    int  COUNT=0;  //定时器溢出次数
    int  Start_count=0;  //开始计时定时器值
    int  End_count=0;   //结束计时定时器值
    int  Time;
    
    
    int main(void)
    {	
    
    	Cache_Enable();                		//打开L1-Cache
    	HAL_Init();				        	//初始化HAL库
    	Stm32_Clock_Init(160,5,2,4);  	    //设置时钟,400Mhz 
    	delay_init(400);				//延时初始化
    	LED_Init();							//初始化LED
        TIM3_Init(65535-1,20000-1);      	//定时器3初始化,定时器时钟为200M,分频系数为20000-1,
    	//所以定时器3的频率为200M/20000=10KHz  周期是T=1/F=1/10000=100us ,
    	//自动重装载为65535-1,那么定时器周期就是6.5535S
    	
    	//计数1次等于100us  单位0.1ms 
    	
        while(1)
        {
    		//开始计时
    		COUNT=0;  //每次开始计时将COUNT清零
    		__HAL_TIM_SET_COUNTER(&TIM3_Handler,0); //获取定时器的值,开始计时
    		
           	HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET); //PB1置1
    		delay_ms(100);
    		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);	//PB1置0
    		delay_ms(100);
    		
    		//结束计时
    		End_count=__HAL_TIM_GET_COUNTER(&TIM3_Handler);  //获取定时器的值,结束计时	
    		Time=(End_count+COUNT*65535)/10;  //时间(ms)=结束值-开始值+定时器溢出次数*65535
    		
    	} 
    }
    //定时器3中断服务函数调用
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
        if(htim==(&TIM3_Handler))
        {
           COUNT++;
        }
    }
    
    

    上面代码的思想就是在代码开始运行前,定时器的计数值设置为0,然后在代码结束的位置,再测得定时器的值,如果代码运行时间过长,定时器中断中COUNT就加1,最终的代码运行时间:

    Time=(End_count+COUNT65535)/10; //时间(ms)=结束值+定时器溢出次数65535

    测得时间为200ms


    总结:
    这种方法比较方便的是代码的运行时间是一个变量,我们可以在程序中使用这个变量,这是很方便的,但是要占用一个定时器。

    请添加图片描述
    请添加图片描述

    物联沃分享整理
    物联沃-IOTWORD物联网 » 单片机测量代码运行时间方法-STM32

    发表评论