为什么需要更少侵入的耗时统计
在STM32等Cortex-M单片机开发中,我们常需要回答:这个函数执行了多久?中断延迟多大?优化后性能提升多少?
常见方法有两种:
- GPIO翻转 + 示波器/逻辑分析仪:需要占用引脚,每次测量要改硬件连接,且受限于仪器采样率(通常几十MHz),对百纳秒级函数误差大。
- 系统滴答定时器(SysTick):分辨率通常为1ms,只能测长耗时函数。
而Cortex-M内核内置的DWT(Data Watchpoint and Trace) 单元中的周期计数器(CYCCNT),能以CPU时钟周期为分辨率(例如168MHz下约5.95ns),且不占用任何外部引脚,只需几条指令即可读取,侵入性极低。
DWT周期计数器原理
DWT是ARM CoreSight调试组件的一部分,其中CYCCNT寄存器是一个32位向上计数器,每个内核时钟周期递增1。当计数溢出(2^32个周期)后自动回绕。
关键点:
- 需要使能DWT的CYCCNT功能(通过
DEMCR寄存器中的TRCENA位和DWT->CTRL的CYCCNTENA位)。 - 读取
DWT->CYCCNT仅需一条LDR指令,开销约1~2个周期。 - 计数器在调试器暂停时通常也停止,不影响测量。
- 不同Cortex-M内核(M3/M4/M7/M33等)均支持,但M0/M0+无DWT。
配置步骤
以STM32 HAL库环境为例(标准外设库同理):
-
使能DWT访问:设置
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; -
清零并启动周期计数器:
DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; -
读取计数:直接读
DWT->CYCCNT。 -
换算时间:
周期数 / SystemCoreClock得到秒数。
注意:某些芯片需要先解锁DWT(如STM32F7/H7需设置DWT->LAR = 0xC5ACCE55),但多数F1/F4系列不需要。
完整代码示例
以下代码提供可移植的接口,支持嵌套测量(通过保存进入时的计数值)。
#include "stm32f4xx.h" // 根据实际芯片替换
// 初始化DWT周期计数器
void DWT_Init(void)
{
// 使能DWT
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
// 清零计数器
DWT->CYCCNT = 0;
// 使能周期计数器
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
// 获取当前周期计数
static inline uint32_t DWT_GetCycles(void)
{
return DWT->CYCCNT;
}
// 计算两次计数之间的周期差(处理回绕)
static inline uint32_t DWT_CycleDiff(uint32_t start, uint32_t end)
{
return (end >= start) ? (end - start) : (0xFFFFFFFF - start + end + 1);
}
// 将周期数转换为微秒(浮点)
float DWT_CyclesToUs(uint32_t cycles)
{
return (float)cycles * 1000000.0f / (float)SystemCoreClock;
}
// 使用示例:测量一个函数的耗时
void Measure_Function(void)
{
uint32_t start, end, cycles;
start = DWT_GetCycles();
// 被测函数
SomeFunction();
end = DWT_GetCycles();
cycles = DWT_CycleDiff(start, end);
float us = DWT_CyclesToUs(cycles);
printf("SomeFunction took %lu cycles, %.2f us\n", cycles, us);
}
对于需要多次测量取平均的场景,可封装成宏:
#define DWT_MEASURE_BEGIN() uint32_t _dwt_start = DWT_GetCycles()
#define DWT_MEASURE_END() (DWT_CycleDiff(_dwt_start, DWT_GetCycles()))
注意事项与进阶技巧
-
中断干扰:测量期间若发生中断,中断服务程序的时间会被计入。建议在测量关键函数时临时关闭全局中断(
__disable_irq()),但会改变实时性,需权衡。 -
编译器优化:确保被测函数不会被优化掉(可加
__attribute__((noinline))或使用volatile变量)。 - 多任务环境:在RTOS中,任务切换会导致测量值偏大。可在测量前后记录任务切换计数,或仅在单任务上下文中使用。
- 计数器回绕:32位计数器在168MHz下约25.5秒回绕一次,短时间测量无需担心,但长时间测量需用差值法处理。
- 调试器影响:连接调试器时,DWT可能被调试器占用或复位,建议在独立运行模式下验证。
- 低功耗模式:进入睡眠/停止模式后,DWT计数器可能停止,测量低功耗唤醒时间需改用其他方法。
- 精度验证:可用GPIO翻转配合示波器对比,确认DWT测量误差在1%以内。
总结
DWT周期计数器为嵌入式开发者提供了一种近乎零成本、高分辨率、无引脚占用的函数级耗时统计手段。通过简单的初始化和几行读取代码,即可在开发阶段快速定位性能瓶颈。结合本文的代码和注意事项,你可以将其集成到自己的调试框架中,实现更高效的性能分析。
暂无评论,来抢沙发~