Arm Cortex-M1平台下函数执行时钟周期测量方法咨询
在Cortex-M1上测量函数执行时钟周期的替代方案
嗨,我之前做Cortex-M1相关的FPGA项目时也碰到过这个问题——没错,Cortex-M1作为面向FPGA的轻量级内核,确实没有实现DWT(数据观察点和跟踪)单元,官方手册里找不到相关寄存器就是最好的证明。下面是几个我亲测有效的替代方法,按精度和实现复杂度排序:
1. 利用通用定时器(精度高,纯软件实现)
这是最常用的方案,几乎所有Cortex-M1的硬件平台都会配备至少一个通用定时器,我们可以把它配置成和系统时钟同步的计数器:
实现步骤:
- 初始化定时器:将时钟源设置为系统时钟(这样每个计数tick就是一个时钟周期),配置为自由递增模式,不启用自动重载。
- 测量逻辑:调用目标函数前读取定时器当前计数值;函数执行完成后再次读取,两者的差值就是函数执行的时钟周期数。
- 注意事项:如果函数执行时间较长,要提前计算定时器溢出时间,避免计数溢出导致结果错误;测量过程建议用
__disable_irq()和__enable_irq()包裹,关闭全局中断防止中断抢占周期影响精度。
示例代码片段:
#include "your_platform_timer.h" // 替换为实际平台的定时器头文件 void cycle_timer_init(void) { // 使能定时器时钟 TIMER_CLK_ENABLE(); // 关闭定时器 TIMER->CR1 &= ~TIM_CR1_CEN; // 预分频器设为0,直接使用系统时钟 TIMER->PSC = 0; // 自动重载值设为最大值,避免快速溢出 TIMER->ARR = 0xFFFFFFFF; // 重置计数器 TIMER->CNT = 0; // 启动定时器 TIMER->CR1 |= TIM_CR1_CEN; } uint32_t measure_func_cycles(void (*target_func)(void)) { uint32_t start, end; __disable_irq(); start = TIMER->CNT; target_func(); end = TIMER->CNT; __enable_irq(); // 处理计数器溢出的情况 return (end >= start) ? (end - start) : (0xFFFFFFFF - start + end + 1); }
2. 软件循环计数(精度一般,快速验证)
如果暂时不想折腾定时器,可以用软件循环的方式粗略估计,但精度会受编译器优化和指令流水线影响:
- 实现思路:
- 先校准空循环的时钟周期:比如执行1000次固定空循环,用定时器(或示波器)测出总周期,算出单次循环的周期数。
- 在目标函数前后分别执行这个空循环,统计循环次数,用次数乘以单次循环周期得到大致执行时间。
- 注意事项:必须关闭编译器优化(编译时加
-O0参数),并且把计数循环用汇编实现,避免编译器优化掉空循环。
3. FPGA硬件计数(精度最高,需修改硬件)
因为Cortex-M1通常在FPGA中实现,如果你有权限修改FPGA的RTL代码,可以添加专用硬件计数器:
- 实现方式:
- 在FPGA中设计计数器模块,时钟源连接到Cortex-M1的系统时钟。
- 通过检测Cortex-M1的指令地址范围(比如目标函数的起止地址),或者用GPIO引脚触发,控制计数器启停。
- 函数执行完成后,通过内存映射或IO口读取计数器数值,就是精确的时钟周期数。
额外注意事项
- 编译器优化:测量前务必关闭优化,或给被测函数添加
__attribute__((optimize("-O0")))属性,防止编译器对函数指令重排、删除,导致测量结果失真。 - 缓存影响:如果你的Cortex-M1带指令/数据缓存,第一次执行函数可能有缓存未命中,周期数偏高;建议多次测量取平均值,或提前调用一次函数预热缓存。
- 中断干扰:即使关闭全局中断,也要注意不可屏蔽中断(NMI)的影响,必要时可暂时禁用NMI(如果硬件支持)。
内容的提问来源于stack exchange,提问作者Terra
相关产品推荐
相关产品推荐

