本帖最后由 zexin 于 2024-10-21 15:20 编辑
#申请原创# 代码运行时间的三种测量方法 一、基本介绍
在软件调试过程中,测量代码片段的运行时间对理解程序执行过程和优化程序执行效率有重要意义。本文以测量ADC转换时间为例,介绍以下三种方法。
①GPIO翻转法:在ADC转换前后,将引脚电平的高低状态作为时间测量的起止点,利用逻辑分析仪或示波器等设备读取GPIO状态变化的时间差。
②调试器跟踪法:以AT-link为例,在ADC转换开始和结束设置断点,读取断点间代码运行的时间差。
③DWT(DataWatchpoint and Trace)测量法:在ADC转换开始前后,利用DWT的计数器记录起止点之间执行的指令数,根据指令数和系统时钟计算时间差。
二、案例搭建
1.简介
使用PA0和PA1两个通道采集外部信号,用不同方法测试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
|
使用三种常用方法测量代码运行时间,三种方式均描述的较为详细,也是常用的可靠方法,调试细节介绍清晰,文章完整。