发新帖本帖赏金 50.00元(功能说明)我要提问
12下一页
返回列表
打印
[AT32F403/403A]

测量代码片段运行时间

[复制链接]
2386|29
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
zexin|  楼主 | 2024-10-21 10:41 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 zexin 于 2024-10-21 15:20 编辑

#申请原创#
代码运行时间的三种测量方法
一、基本介绍
在软件调试过程中,测量代码片段的运行时间对理解程序执行过程和优化程序执行效率有重要意义。本文以测量ADC转换时间为例,介绍以下三种方法。
①GPIO翻转法:在ADC转换前后,将引脚电平的高低状态作为时间测量的起止点,利用逻辑分析仪或示波器等设备读取GPIO状态变化的时间差
②调试器跟踪法:以AT-link为例,在ADC转换开始和结束设置断点,读取断点间代码运行的时间差
③DWT(DataWatchpoint and Trace)测量法:在ADC转换开始前后,利用DWT的计数器记录起止点之间执行的指令数,根据指令数和系统时钟计算时间差
二、案例搭建
1.简介
使用PA0PA1两个通道采集外部信号,用不同方法测试ADC的转换时间。
2.步骤
①开启ADC1的通道0和通道1;
②开启普通通道的序列模式,设置通道数目以及采样时间
③添加DMA请求;
④开启DMA中断;
⑤如果使用“GPIO电平翻转法”来测试,还需要配置一个GPIO作为输出(以PA4为例);
⑥生成代码。
三、主要代码
main.c
#include "at32f403a_407_wk_config.h"
#include "wk_system.h"
//#include "timecalculate.h" /*(DWT测量法)*/

uint16_t adc_value[2] = {0}; //用于存放外部信号的adc数组
uint16_t dma_trans_complete_flag = 0; //dma转换完成标志
//uint16_t start = 0, stop = 0; /*(DWT测量法)*/
/*(调试器跟踪法)——基本配置*/
/*
void disable_swo_debug_config(void)
{
  crm_periph_clock_enable(CRM_IOMUX_PERIPH_CLOCK, TRUE);
  gpio_pin_remap_config(SWJTAG_GMUX_010, TRUE);

//  DEBUGMCU->ctrl_bit.trace_ioen = FALSE;
  DEBUGMCU->ctrl_bit.trace_ioen = TRUE;
}
*/

int main(void)
{
  wk_system_clock_config();
  wk_periph_clock_config();
//disable_swo_debug_config();/*(调试器跟踪法)——使能swo功能*/
  wk_nvic_config();
  wk_timebase_init();
  wk_dma1_channel1_init();
  wk_dma_channel_config(DMA1_CHANNEL1,
                        (uint32_t)&ADC1->odt,
                        DMA1_CHANNEL1_MEMORY_BASE_ADDR,
                        DMA1_CHANNEL1_BUFFER_SIZE);
  dma_channel_enable(DMA1_CHANNEL1, TRUE);
  wk_adc1_init();
  wk_gpio_config(); /*GPIO翻转法*/
  adc_ordinary_software_trigger_enable(ADC1, TRUE);
  //start = ARM_CM_DWT_CYCCNT; /*(DWT测量法)*/
  //gpio_bits_set(GPIOA, GPIO_PINS_4); /*(GPIO翻转法)*/
  while(dma_trans_complete_flag == 0); //等待DMA传输完成

  while(1)
  {
   
  }
}
at32f403a_407_wk_config.c
#include "at32f403a_407_wk_config.h"
void wk_system_clock_config(void)
{
crm_reset();
crm_clock_source_enable(CRM_CLOCK_SOURCE_LICK, TRUE);
while(crm_flag_get(CRM_LICK_STABLE_FLAG) != SET)
{
}
crm_clock_source_enable(CRM_CLOCK_SOURCE_HICK, TRUE);
while(crm_flag_get(CRM_HICK_STABLE_FLAG) != SET)
{
}
crm_pll_config(CRM_PLL_SOURCE_HICK, CRM_PLL_MULT_60, CRM_PLL_OUTPUT_RANGE_GT72MHZ);
crm_clock_source_enable(CRM_CLOCK_SOURCE_PLL, TRUE);
while(crm_flag_get(CRM_PLL_STABLE_FLAG) != SET)
{
}
crm_ahb_div_set(CRM_AHB_DIV_1);
crm_apb2_div_set(CRM_APB2_DIV_2);
crm_apb1_div_set(CRM_APB1_DIV_2);
crm_auto_step_mode_enable(TRUE);
crm_sysclk_switch(CRM_SCLK_PLL);
while(crm_sysclk_switch_status_get() != CRM_SCLK_PLL)
{
}
crm_auto_step_mode_enable(FALSE);
system_core_clock_update();
}
void wk_periph_clock_config(void)
{
crm_periph_clock_enable(CRM_DMA1_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_IOMUX_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_ADC1_PERIPH_CLOCK, TRUE);
}
void wk_nvic_config(void)
{
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 15, 0));
nvic_irq_enable(DMA1_Channel1_IRQn, 0, 0);
}
void wk_gpio_config(void)
{
gpio_init_type gpio_init_struct;
gpio_default_para_init(&gpio_init_struct);
gpio_bits_reset(GPIOA, GPIO_PINS_4);
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_pins = GPIO_PINS_4;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(GPIOA, &gpio_init_struct);
}
void wk_adc1_init(void)
{
gpio_init_type gpio_init_struct;
adc_base_config_type adc_base_struct;
gpio_default_para_init(&gpio_init_struct);
gpio_init_struct.gpio_mode = GPIO_MODE_ANALOG;
gpio_init_struct.gpio_pins = GPIO_PINS_0;
gpio_init(GPIOA, &gpio_init_struct);
gpio_init_struct.gpio_mode = GPIO_MODE_ANALOG;
gpio_init_struct.gpio_pins = GPIO_PINS_1;
gpio_init(GPIOA, &gpio_init_struct);
crm_adc_clock_div_set(CRM_ADC_DIV_6);
adc_combine_mode_select(ADC_INDEPENDENT_MODE);
adc_base_default_para_init(&adc_base_struct);
adc_base_struct.sequence_mode = TRUE;
adc_base_struct.repeat_mode = FALSE;
adc_base_struct.data_align = ADC_RIGHT_ALIGNMENT;
adc_base_struct.ordinary_channel_length = 2;
adc_base_config(ADC1, &adc_base_struct);
adc_ordinary_channel_set(ADC1, ADC_CHANNEL_0, 1, ADC_SAMPLETIME_239_5);
adc_ordinary_channel_set(ADC1, ADC_CHANNEL_1, 2, ADC_SAMPLETIME_239_5);
adc_ordinary_conversion_trigger_set(ADC1, ADC12_ORDINARY_TRIG_SOFTWARE, TRUE);
adc_ordinary_part_mode_enable(ADC1, FALSE);
adc_dma_mode_enable(ADC1, TRUE);
adc_enable(ADC1, TRUE);
adc_calibration_init(ADC1);
while(adc_calibration_init_status_get(ADC1));
adc_calibration_start(ADC1);
while(adc_calibration_status_get(ADC1));
}
void wk_dma1_channel1_init(void)
{
dma_init_type dma_init_struct;
dma_reset(DMA1_CHANNEL1);
dma_default_para_init(&dma_init_struct);
dma_init_struct.direction = DMA_DIR_PERIPHERAL_TO_MEMORY;
dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_HALFWORD;
dma_init_struct.memory_inc_enable = TRUE;
dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_HALFWORD;
dma_init_struct.peripheral_inc_enable = FALSE;
dma_init_struct.priority = DMA_PRIORITY_LOW;
dma_init_struct.loop_mode_enable = FALSE;
dma_init(DMA1_CHANNEL1, &dma_init_struct);
dma_flexible_config(DMA1, FLEX_CHANNEL1, DMA_FLEXIBLE_ADC1);
dma_interrupt_enable(DMA1_CHANNEL1, DMA_FDT_INT, TRUE);
}
void wk_dma_channel_config(dma_channel_type* dmax_channely, uint32_t peripheral_base_addr, uint32_t memory_base_addr, uint16_t buffer_size)
{
dmax_channely->dtcnt = buffer_size;
dmax_channely->paddr = peripheral_base_addr;
dmax_channely->maddr = memory_base_addr;
}
at32f403a_407_wk_config.h
#ifndef __AT32F403A_407_WK_CONFIG_H
#define __AT32F403A_407_WK_CONFIG_H
#ifdef __cplusplus
extern "C" {
#endif
#include "at32f403a_407.h"
#define DMA1_CHANNEL1_BUFFER_SIZE 2 //DMA传输数据个数
#define DMA1_CHANNEL1_MEMORY_BASE_ADDR (uint32_t)adc_value //DMA传输目标地址
void wk_system_clock_config(void);
void wk_periph_clock_config(void);
void wk_nvic_config(void);
void wk_gpio_config(void);
void wk_adc1_init(void);
void wk_dma1_channel1_init(void);
void wk_dma_channel_config(dma_channel_type* dmax_channely, uint32_t peripheral_base_addr, uint32_t memory_base_addr, uint16_t buffer_size);
#ifdef __cplusplus
}
#endif
#endif
  
at32f403a_407_int.c
#include "at32f403a_407_int.h"
#include "wk_system.h"
//#include "timecalculate.h" /*(DWT测量法)*/
extern uint16_t dma_trans_complete_flag;
//extern uint16_t stop; /*(DWT测量法)*/
void SysTick_Handler(void)
{
wk_timebase_handler();
}
void DMA1_Channel1_IRQHandler(void)
{
//stop = ARM_CM_DWT_CYCCNT; /*(DWT测量法)*/
//gpio_bits_reset(GPIOA, GPIO_PINS_4); /*(GPIO翻转法)*/
if(dma_interrupt_flag_get(DMA1_FDT1_FLAG) != RESET)
{
dma_flag_clear(DMA1_FDT1_FLAG);
dma_trans_complete_flag = 1;
}
}
  
四、测试方法
不同测试方法的main.c和at32f403a_407_int.c略有不同,其他基本一致。
1.GPIO翻转法
介绍
GPIO在ADC转换前为低电平,转换开始时为高电平,转换结束后为低电平
代码
main.c
#include "at32f403a_407_wk_config.h"
#include "wk_system.h"
uint16_t adc_value[2] = {0}; //用于存放外部信号的adc数组
uint16_t dma_trans_complete_flag = 0; //dma转换完成标志
int main(void)
{
wk_system_clock_config();
wk_periph_clock_config();
wk_nvic_config();
wk_timebase_init();
wk_dma1_channel1_init();
wk_dma_channel_config(DMA1_CHANNEL1,
(uint32_t)&ADC1->odt,
DMA1_CHANNEL1_MEMORY_BASE_ADDR,
DMA1_CHANNEL1_BUFFER_SIZE);
dma_channel_enable(DMA1_CHANNEL1, TRUE);
wk_adc1_init();
wk_gpio_config(); /*GPIO翻转法——默认PA4为低电平*/
adc_ordinary_software_trigger_enable(ADC1, TRUE);
gpio_bits_set(GPIOA, GPIO_PINS_4); /*(GPIO翻转法)——ADC开始转换后PA4设为高电平*/
while(dma_trans_complete_flag == 0); //等待DMA传输完成
while(1)
{
}
}
at32f403a_407_int.c
#include "at32f403a_407_int.h"
#include "wk_system.h"
extern uint16_t dma_trans_complete_flag;
void SysTick_Handler(void)
{
wk_timebase_handler();
}
void DMA1_Channel1_IRQHandler(void)
{
gpio_bits_reset(GPIOA, GPIO_PINS_4); /*(GPIO翻转法)——ADC转换结束后PA4设为低电平*/
if(dma_interrupt_flag_get(DMA1_FDT1_FLAG) != RESET)
{
dma_flag_clear(DMA1_FDT1_FLAG);
dma_trans_complete_flag = 1;
}
}
  
结果
①理论时间
单个通道:(采样+转换)时间= (采样时间+12.5)个ADCCLK周期;
两个通道:T_calculate = (239.5+12.5) ÷ 20M × 2 = 25.2us
②实测时间
逻辑分析仪测量得ADC转换(两个通道)时间T_test = 25.75us
2.调试器跟踪法(AT-Link)
介绍
使用AT-link进入调试,测量两个断点之间的时间差代码
main.c
#include "at32f403a_407_wk_config.h"
#include "wk_system.h"
uint16_t adc_value[2] = {0}; //用于存放外部信号的adc数组
uint16_t dma_trans_complete_flag = 0; //dma转换完成标志
/*(调试器跟踪法)——基本配置*/
void disable_swo_debug_config(void)
{
  crm_periph_clock_enable(CRM_IOMUX_PERIPH_CLOCK, TRUE);
  gpio_pin_remap_config(SWJTAG_GMUX_010, TRUE);

//DEBUGMCU->ctrl_bit.trace_ioen = FALSE;
  DEBUGMCU->ctrl_bit.trace_ioen = TRUE;
}

int main(void)
{
  wk_system_clock_config();
  wk_periph_clock_config();
  disable_swo_debug_config();/*(调试器跟踪法)——使能swo功能*/
  wk_nvic_config();
  wk_timebase_init();
  wk_dma1_channel1_init();
  wk_dma_channel_config(DMA1_CHANNEL1,
                        (uint32_t)&ADC1->odt,
                        DMA1_CHANNEL1_MEMORY_BASE_ADDR,
                        DMA1_CHANNEL1_BUFFER_SIZE);
  dma_channel_enable(DMA1_CHANNEL1, TRUE);
  wk_adc1_init();
  adc_ordinary_software_trigger_enable(ADC1, TRUE);
  while(dma_trans_complete_flag == 0); //等待DMA传输完成

  while(1)
  {
   
  }
}
at32f403a_407_int.c
#include "at32f403a_407_int.h"
#include "wk_system.h"

extern uint16_t dma_trans_complete_flag;

void SysTick_Handler(void)
{
wk_timebase_handler();
}
void DMA1_Channel1_IRQHandler(void)
{
if(dma_interrupt_flag_get(DMA1_FDT1_FLAG) != RESET)
{
dma_flag_clear(DMA1_FDT1_FLAG);
dma_trans_complete_flag = 1;
}
}
步骤
①设置调试器
②启用变量跟踪
③进入DUBUG模式,先设置断点(如下图),再全速运行代码;
右击右下角的运行时间t1,将其复位为0
⑤在中断设置下一个断点,再全速运行
结果
①理论时间
单个通道:(采样+转换)时间= (采样时间+12.5)个ADCCLK周期;
两个通道:T_calculate = (239.5+12.5) ÷ 20M × 2 = 25.2us
②实测时间
观察右下角t1得ADC转换(两个通道)时间T_test = 25.7us
3.DWT测量法
介绍
添加DWT测量的C文件和H文件,在DEBUG下观察计数差值,利用差值计算得ADC转换时间。
步骤
①将“cpu_sysclk_time_calculate”文件夹(详见附件)移植至工程文件下;
②新建Group(Calculate)并添加现有文件进来;
③选择步骤①路径下“cpu_sysclk_time_calculate”的c文件;
④添加文件路径;
⑤添加步骤①路径下的“cpu_sysclk_time_calculate”文件夹;
代码
main.c
#include "at32f403a_407_wk_config.h"
#include "wk_system.h"
#include "timecalculate.h" /*(DWT测量法)——包含相关头文件*/
uint16_t adc_value[2] = {0}; //用于存放外部信号的adc数组
uint16_t dma_trans_complete_flag = 0; //dma转换完成标志
uint16_t start = 0, stop = 0; /*(DWT测量法)——计数的起始点和终止点*/
int main(void)
{
wk_system_clock_config();
wk_periph_clock_config();
wk_nvic_config();
wk_timebase_init();
wk_dma1_channel1_init();
wk_dma_channel_config(DMA1_CHANNEL1,
(uint32_t)&ADC1->odt,
DMA1_CHANNEL1_MEMORY_BASE_ADDR,
DMA1_CHANNEL1_BUFFER_SIZE);
dma_channel_enable(DMA1_CHANNEL1, TRUE);
wk_adc1_init();
adc_ordinary_software_trigger_enable(ADC1, TRUE);
start = ARM_CM_DWT_CYCCNT; /*(DWT测量法)——记录起始计数值*/
while(dma_trans_complete_flag == 0); //等待DMA传输完成
while(1)
{
}
}
timecalculate.c
#include "timecalculate.h"
#include "at32f403a_407_wk_config.h"

void start_Math(void)
{
  if (ARM_CM_DWT_CTRL != 0) {        // See if DWT is available
   ARM_CM_DEMCR      |= 1 << 24;  // Set bit 24
   ARM_CM_DWT_CYCCNT  = 0;
   ARM_CM_DWT_CTRL   |= 1 << 0;   // Set bit 0
   }
}
timecalculate.h
#ifndef __TIMECALCULATE_H
#define __TIMECALCULATE_H

#define  ARM_CM_DEMCR      (*(uint32_t *)0xE000EDFC)
#define  ARM_CM_DWT_CTRL   (*(uint32_t *)0xE0001000)
#define  ARM_CM_DWT_CYCCNT (*(uint32_t *)0xE0001004)

void start_Math(void);

#endif
at32f403a_407_int.c
#include "at32f403a_407_int.h"
#include "wk_system.h"
#include "timecalculate.h" /*(DWT测量法)——包含相关头文件*/
extern uint16_t dma_trans_complete_flag;
extern uint16_t stop; /*(DWT测量法)——声明外部变量*/
void SysTick_Handler(void)
{
wk_timebase_handler();
}
void DMA1_Channel1_IRQHandler(void)
{
stop = ARM_CM_DWT_CYCCNT; /*(DWT测量法)——记录终止点的计数值*/
if(dma_interrupt_flag_get(DMA1_FDT1_FLAG) != RESET)
{
dma_flag_clear(DMA1_FDT1_FLAG);
dma_trans_complete_flag = 1;
}
}
  
结果
①理论时间
单个通道:(采样+转换)时间= (采样时间+12.5)个ADCCLK周期;
两个通道:T_calculate = (239.5+12.5) ÷ 20M × 2 = 25.2us
②实测时间
DEBUG模式下,观察起始计数值终止计数值,根据二者的差值(指令数)计算得ADC转换(两个通道)时间T_test = (stop - start) ×指令周期= (18578 - 12436) ÷240M = 25.59us
五、总结


优点
缺点
GPIO翻转法
操作简单,通用性强
精度受限于GPIO切换速度
调试器跟踪法
无其他代码的干预
需特定的硬件支持
DWT测量法
无需外部测量工具
配置过程相对复杂
三种方法都可测量代码片段的运行时间(比如ADC转换时间、定时器的中断频率等),可根据不同场景灵活使用。

由于作者水平有限,文中如有错误之处,恳请读者批评指正。
参考资料:
《RM_AT32F403A_407_CH_V2.06》的19模拟数字转换(ADC)https://www.arterytek.com/file/download/1995
雅特力AT32F423开启FPU跟不开启FPU性能差异(使用DWT测量)https://blog.csdn.net/l18817813618/article/details/140275305





cpu_sysclk_time_calculate.rar

617 Bytes

DWT测量法的相关文件

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 50.00 元 2024-10-25
理由:恭喜通过原创审核!期待您更多的原创作品~

评论
21小跑堂 2024-10-25 17:44 回复TA
使用三种常用方法测量代码运行时间,三种方式均描述的较为详细,也是常用的可靠方法,调试细节介绍清晰,文章完整。 
沙发
zexin|  楼主 | 2024-10-21 10:45 | 只看该作者

使用特权

评论回复
板凳
呐咯密密| | 2024-10-22 10:13 | 只看该作者
我基本都是用示波器测量GPIO翻转

使用特权

评论回复
地板
Chad1989| | 2024-10-22 11:52 | 只看该作者
为什么不用系统时钟去计算呢

使用特权

评论回复
5
zexin|  楼主 | 2024-10-22 13:58 | 只看该作者
Chad1989 发表于 2024-10-22 11:52
为什么不用系统时钟去计算呢

其实DWT测量法也是利用系统时钟计算的,当然您有其他更方便的方法也欢迎分享,这边只是在学习过程中做了一些整理

使用特权

评论回复
6
xionghaoyun| | 2024-10-23 08:51 | 只看该作者
开串口打印

使用特权

评论回复
7
febgxu| | 2024-11-7 22:28 | 只看该作者
测量单片机代码片段的运行时间通常需要使用一个高精度的计时器或定时器。

使用特权

评论回复
8
elsaflower| | 2024-11-8 09:16 | 只看该作者
通过设置一个快速循环,并在循环内部进行计数操作,利用循环执行的时间来近似估算代码片段的运行时间。这种方法相对简单,但精度较低,适用于对时间精度要求不高的情况。

使用特权

评论回复
9
xiaoyaodz| | 2024-11-8 10:07 | 只看该作者
配置定时器:首先,配置一个定时器,使其以一定的频率计数。
记录开始时间:在代码片段执行前,记录定时器的当前计数值。
记录结束时间:在代码片段执行后,再次记录定时器的当前计数值。
计算时间差:通过两次记录的计数值之差,计算出代码片段的运行时间。

使用特权

评论回复
10
alvpeg| | 2024-11-8 11:21 | 只看该作者
在待测代码段开始前启动单片机内部的定时器,并在代码段结束时停止定时器。通过读取定时器的计数值来计算代码段的运行时间。

使用特权

评论回复
11
backlugin| | 2024-11-8 12:43 | 只看该作者
可以使用外部工具如逻辑分析仪或示波器来测量代码片段的运行时间。

使用特权

评论回复
12
usysm| | 2024-11-8 14:31 | 只看该作者
首先计算出循环执行一次的平均时间,然后根据所记录的开始时间、结束时间以及代码片段执行期间循环执行的次数,计算出代码片段的运行时间。例如,如果循环执行一次的平均时间为 t 秒,代码片段执行期间循环执行了 n 次,那么运行时间 =(结束时间 - 开始时间)- n × t 秒。

使用特权

评论回复
13
earlmax| | 2024-11-8 16:15 | 只看该作者
使用定时器              

使用特权

评论回复
14
caigang13| | 2024-11-8 19:00 | 只看该作者
最简单的方式就是使用定时器

使用特权

评论回复
15
nomomy| | 2024-11-8 20:01 | 只看该作者
单片机通常内置有定时器,可以用来测量时间间隔。

使用特权

评论回复
16
sdlls| | 2024-11-8 20:48 | 只看该作者
定时器的精度取决于单片机的时钟频率。

使用特权

评论回复
17
albertaabbot| | 2024-11-8 21:22 | 只看该作者
SysTick定时器是ARM Cortex-M系列微控制器的一个特性,可以用来生成周期性的中断,也可以用来测量时间间隔

使用特权

评论回复
18
macpherson| | 2024-11-8 21:45 | 只看该作者
单片机内部通常都包含这样的硬件资源,可以用来精确地测量时间间隔。

使用特权

评论回复
19
beacherblack| | 2024-11-8 22:18 | 只看该作者
使用结束时间和开始时间计算代码片段的运行时间。

使用特权

评论回复
20
timfordlare| | 2024-11-8 23:00 | 只看该作者
利用单片机内部的定时器来计时。定时器是一种能够按照设定的时间间隔产生中断或进行计数操作的硬件模块。通过在代码片段开始和结束时分别对定时器进行相关操作,就可以计算出代码片段运行所占用的时间。

使用特权

评论回复
发新帖 本帖赏金 50.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

9

主题

17

帖子

1

粉丝