ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ARM Cortex-M DWT CYCCNT 高精度代码执行时间测量实战指南

ARM Cortex-M DWT CYCCNT 高精度代码执行时间测量实战指南 1. 项目缘起为什么需要精确测量代码执行时间在嵌入式开发尤其是基于ARM Cortex-M这类微控制器的项目中我们常常会遇到一个看似简单却至关重要的问题这段代码到底跑了多久你可能在优化一个算法想知道优化后节省了多少个时钟周期也可能在调试一个实时性要求苛刻的中断服务程序需要确保它没有超时或者你只是想验证一下某个外设的配置时序是否满足数据手册的要求。传统的做法是什么很多人会想到用GPIO翻转配合示波器测量。比如在代码段开始前拉高一个引脚结束后拉低然后用示波器测量高电平的脉宽。这个方法直观但有几个明显的痛点首先它需要额外的硬件示波器和引脚资源其次GPIO操作本身会引入几个时钟周期的开销对于微秒甚至纳秒级的测量这个误差不可忽视再者它无法方便地测量代码内部多个片段的执行时间或者进行多次统计。更“软件”一点的方法可能是使用系统滴答定时器SysTick。通过读取SysTick的当前值来计算时间差。这确实是个进步但SysTick的精度受其重装载值和时钟源限制通常用于毫秒级调度对于需要更高分辨率比如时钟周期级别的测量就显得力不从心了。而且在测量非常短的代码片段时读取定时器寄存器、做减法这些操作本身的时间可能已经和被测代码的执行时间处于同一量级严重干扰了测量结果。这就是DWTData Watchpoint and Trace单元大显身手的地方。作为ARM Cortex-M内核从M3开始内置的一个强大调试组件DWT提供了一个名为CYCCNTCycle Counter的32位计数器。这个计数器随着内核时钟HCLK的每一个周期自动递增永不停止除非被手动禁止。它的存在就是为了让我们能够以内核时钟周期的精度无侵入地测量代码执行时间。说“无侵入”是因为我们只需要在代码前后读取两次CYCCNT寄存器的值做一次减法这个操作本身的耗时相对于被测代码通常是微不足道的并且是确定可预估的。本次实验我们就基于英飞凌的XMC4500 Relax Kit一颗Cortex-M4内核的MCU来深入探讨如何利用DWT的CYCCNT实现高精度、低开销的代码执行时间测量。你会发现这不仅仅是调用一个API那么简单里面涉及到初始化、时钟确认、编译器屏障、测量误差分析等一系列值得深究的细节。2. DWT_CYCCNT 原理与启用打开内核的“高精度秒表”要使用DWT我们首先得理解它在Cortex-M内核调试架构中的位置。DWT是CoreSight调试系统的一部分它提供了数据观察点、PC采样跟踪、以及对我们至关重要的——周期计数CYCCNT功能。CYCCNT是一个32位的向上计数器直接由处理器时钟HCLK驱动。这意味着如果你的系统主频是120MHz那么这个计数器每秒钟就会累加120,000,000次。理论上它可以测量的最大时间跨度是(2^32 - 1) / HCLK_Frequency。以120MHz计算大约是35.8秒。超过这个时间计数器会从0重新开始溢出。对于测量代码片段来说这个范围通常绰绰有余。启用CYCCNT的步骤非常直接主要涉及两个寄存器解锁调试功能在Cortex-M中有些调试功能默认是禁用的需要通过内核的调试寄存器来启用。具体是设置Core Debug模块中的DEMCR(Debug Exception and Monitor Control Register) 寄存器的TRCENA位第24位。这个位是跟踪组件包括DWT、ITM等的总开关。启用CYCCNT计数器在DWT模块中有一个控制寄存器DWT_CTRL(DWT Control Register)。我们需要将其CYCCNTENA位第0位置1以启动周期计数器。在代码上这通常如下所示以CMSIS-Core标准接口为例这是ARM为Cortex-M处理器提供的通用硬件抽象层#include “core_cm4.h” // 包含CMSIS Core for M4定义 void DWT_Init(void) { // 1. 启用CoreSight DWT跟踪组件 CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; // 2. 清除CYCCNT计数器可选但建议从0开始 DWT-CYCCNT 0; // 3. 启用周期计数器 DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; }这里有几个关键的实操细节和“为什么”为什么需要先设置DEMCRTRCENA位是一个安全特性防止在非调试状态下意外启用功耗较高的跟踪功能。在正常应用程序中启用DWT必须显式地打开这个总开关。DWT-CYCCNT的复位值根据ARM手册该计数器在上电或系统复位后是未定义的。虽然启用后它会开始计数但为了获得一个干净的起始点特别是在多次测量求平均时手动清零是一个好习惯。但请注意清零操作本身需要几个时钟周期。检查是否启用成功不是所有Cortex-M实现都支持DWT尽管M3/M4/M7基本都支持。更稳健的做法是在启用后稍作延迟执行几条NOP指令然后检查DWT_CTRL寄存器的NOCYCCNT位第25位。如果该位为1则表示此设备不支持CYCCNT。但通常对于XMC4500这类标准的M4我们可以放心。注意在启用DWT功能后内核的功耗会有微小的增加因为计数器电路在持续运行。在极端追求低功耗的休眠模式下可能需要考虑禁用DWT。对于XMC4500我们还需要确认一点它的HCLK供给内核的时钟是否就是我们以为的系统主频在XMC4000系列中系统时钟配置相对灵活但通常CYCCNT的时钟源就是CPU_CLK即内核时钟。在默认的时钟树配置下比如从出厂设置的12MHz内部RC振荡器倍频到120MHz这个关系是直接的。但如果你使用了复杂的时钟分频需要查阅数据手册中关于调试模块时钟源的部分以确保CYCCNT的计数频率与你想要测量的“CPU执行周期”是一致的。3. 测量实践从基础用法到规避陷阱有了启用的CYCCNT测量代码执行时间在形式上变得极其简单。基本范式如下uint32_t start, end, cycles_elapsed; start DWT-CYCCNT; // 这里放置你要测量的代码段 My_Function_To_Measure(); end DWT-CYCCNT; cycles_elapsed end - start;cycles_elapsed就是你的代码执行所消耗的处理器时钟周期数。将其除以系统主频单位Hz就得到了以秒为单位的执行时间。例如cycles_elapsed 1200HCLK 120MHz 则执行时间t 1200 / 120e6 10us。然而在实际操作中如果只是这样写你可能会得到一些令人困惑甚至错误的结果。下面我们来拆解几个必须注意的陷阱和优化点。3.1 编译器优化带来的“测量失真”这是最隐蔽也最常见的问题。现代编译器如GCC、ARMCC、IAR的优化器非常强大。它会分析你的代码进行各种优化例如删除死代码如果被测函数的结果没有被使用且没有可观察的副作用比如只做了些内部计算没有修改全局变量或访问外设编译器可能会直接把这个函数调用给优化掉内联展开对于小型函数编译器可能将其内联到调用处这改变了代码的布局可能影响测量尤其是测量“函数调用开销”时。指令重排为了效率编译器可能会调整start DWT-CYCCNT;这条语句与被测代码的相对顺序。为了解决这个问题我们必须使用编译器屏障Compiler Barrier。它告诉编译器“不要跨过这个点移动指令。”最常用的方法是使用volatile关键字和__asm内联汇编。方案一将DWT-CYCCNT的读取声明为易变volatile访问。在CMSIS定义中DWT-CYCCNT通常已经被定义为volatile类型这能防止编译器对连续读取进行优化。但为了确保start和end的赋值顺序不被重排我们可以#define DWT_CYCCNT (*((volatile uint32_t *)0xE0001004)) start DWT_CYCCNT; __asm volatile ( : : : memory); // 编译器内存屏障 My_Function_To_Measure(); __asm volatile ( : : : memory); // 编译器内存屏障 end DWT_CYCCNT;__asm volatile ( : : : memory)是一个通用的GCC/Clang编译器内存屏障它告诉编译器内存可能被更改因此之前的所有内存操作必须完成之后的所有内存操作不能提前。方案二确保被测代码有“可观察的副作用”。这是更根本的方法。如果你测量的是一个纯计算函数可以将其结果赋值给一个volatile变量或者让函数去操作一个全局的volatile变量。这样编译器就无法消除它了。volatile uint32_t dummy_result; start DWT-CYCCNT; dummy_result My_Pure_Calculation_Function(); // 结果被volatile变量使用 end DWT-CYCCNT;3.2 中断干扰与测量环境CYCCNT测量的是实际流逝的处理器周期。这意味着如果在你测量的代码段执行过程中发生了一个中断并且CPU去处理了这个中断那么中断服务程序ISR所花费的周期数也会被计入cycles_elapsed中。这显然会污染你的测量结果使得测量值远大于代码本身在“单线程”下的执行时间。因此在进行高精度、可重复的基准测试时禁用全局中断是一个常见的做法。uint32_t primask_bit; uint32_t start, end; primask_bit __get_PRIMASK(); // 保存当前中断状态 __disable_irq(); // 禁用全局中断 start DWT-CYCCNT; My_Function_To_Measure(); end DWT-CYCCNT; if (!primask_bit) { __enable_irq(); // 如果之前中断是开启的则恢复 }这里使用了__get_PRIMASK()和__disable_irq()/__enable_irq()这些CMSIS内置函数。PRIMASK是Cortex-M的一个特殊寄存器将其置1即可屏蔽所有可配置优先级的中断除了NMI和HardFault。我们保存旧状态测量完成后恢复这样不会破坏系统原有的中断环境。重要提示禁用中断的时间必须非常短长时间关闭中断会导致系统实时性丧失可能引发看门狗复位或其他故障。因此这只适用于测量极短的、确定性的代码片段。对于测量包含任务调度、外设等待等可能主动阻塞的代码则需要不同的策略。3.3 测量开销的校准与消除即使我们禁用了中断start DWT-CYCCNT;和end DWT-CYCCNT;这两条读取指令本身也需要消耗时钟周期。这个开销会叠加到测量结果中。对于执行时间很长的代码比如毫秒级这点开销可以忽略。但对于测量一个只有几十个时钟周期的小循环或单条指令这个开销就必须被扣除。校准方法进行“空测量”。我们可以测量一个“空操作”段的周期数这个值就是测量框架本身的开销。uint32_t measure_overhead(void) { uint32_t start, end; __asm volatile ( : : : memory); start DWT-CYCCNT; __asm volatile ( : : : memory); end DWT-CYCCNT; return (end - start); } uint32_t overhead measure_overhead(); start DWT-CYCCNT; My_Function_To_Measure(); end DWT-CYCCNT; uint32_t net_cycles (end - start) - overhead;注意我们在start和end赋值前后都加了内存屏障以确保overhead测量的是与真实测量完全相同的指令序列开销。在实际应用中这个开销值通常是常数可以在系统初始化时测量一次并保存起来。3.4 32位计数器的溢出处理如前所述CYCCNT是32位计数器在120MHz下约35.8秒溢出一次。在测量长时间运行的任务或函数时必须考虑溢出问题。处理溢出的逻辑并不复杂uint32_t start, end, cycles_elapsed; start DWT-CYCCNT; // ... 执行一段可能很长的代码 ... end DWT-CYCCNT; if (end start) { cycles_elapsed end - start; // 没有溢出 } else { // 发生了溢出 end start cycles_elapsed (0xFFFFFFFF - start) end 1; // 等价于 end (0xFFFFFFFF - start 1) }这段代码可以正确处理一次溢出。如果被测代码执行时间可能超过一个完整的计数器周期即超过35.8秒那么你需要引入额外的软件计数器来记录溢出次数。但对于绝大多数嵌入式应用场景测量单次执行超过30秒的代码段并不常见通常我们更关注循环内或高频调用的短小代码块。4. 进阶应用与场景分析掌握了基础测量和避坑方法后我们可以将DWTCYCCNT应用到更复杂的场景中解决实际开发中的痛点。4.1 测量中断服务程序ISR的执行时间这是DWT的一个典型应用。ISR的实时性至关重要你需要知道最坏情况下它执行需要多久以确保不会错过下一个中断或影响其他高优先级任务。测量ISR的挑战在于ISR内部无法轻易地禁用中断除非处理嵌套中断但通常不推荐在ISR内禁用全局中断。而且ISR可能被更高优先级的中断抢占。策略在ISR入口和出口处读取CYCCNT。我们可以定义一个全局变量来记录ISR的最大执行周期数。volatile uint32_t isr_max_cycles 0; uint32_t isr_start_cycle; void TIMER_IRQHandler(void) { uint32_t start, end, cycles; start DWT-CYCCNT; // ... ISR实际的处理代码 ... end DWT-CYCCNT; // 处理溢出 if (end start) { cycles end - start; } else { cycles (0xFFFFFFFF - start) end 1; } // 更新最大执行时间记录 if (cycles isr_max_cycles) { isr_max_cycles cycles; } // ... 清除中断标志等 ... }这样isr_max_cycles就会记录下该ISR自系统运行以来所经历过的最长一次执行时间。这对于评估系统实时性非常有价值。需要注意的是这个测量值包含了被更高优先级中断抢占的时间如果发生了抢占。如果你想测量ISR本身的“纯净”执行时间就需要在更复杂的多任务或中断嵌套环境中进行更精细的控制。4.2 性能剖析与热点查找你可以用DWT来剖析一个复杂函数找出其中的“热点”最耗时的部分。方法是在函数内部的多个关键点插入测量点。void Complex_Algorithm(void) { uint32_t point1, point2, point3; uint32_t total_start, total_end; total_start DWT-CYCCNT; // 阶段A point1 DWT-CYCCNT; Function_Stage_A(); point2 DWT-CYCCNT; // 阶段B Function_Stage_B(); point3 DWT-CYCCNT; // 阶段C Function_Stage_C(); total_end DWT-CYCCNT; uint32_t time_stage_a point2 - point1; uint32_t time_stage_b point3 - point2; uint32_t time_stage_c total_end - point3; uint32_t time_total total_end - total_start; // 可以通过串口打印或存储在全局数组中进行分析 printf(“Stage A: %lu cycles, Stage B: %lu, Stage C: %lu, Total: %lu\n”, time_stage_a, time_stage_b, time_stage_c, time_total); }通过这种方式你可以量化地看到算法各个部分的耗时占比从而有针对性地进行优化。例如你可能会发现Function_Stage_B()占用了70%的时间那么优化重点就应该放在这里。4.3 验证外设时序与软件延时在驱动开发中经常需要满足特定的时序要求比如I2C的SCL高低电平时间、SPI的时钟极性与相位、或是给某个传感器一个毫秒级的复位脉冲。使用DWT可以实现非常精确的软件延时。void dwt_delay_us(uint32_t us) { uint32_t start_ticks, target_ticks; uint32_t cycles_per_us SystemCoreClock / 1000000; // 计算每微秒对应的周期数 if (cycles_per_us 0) cycles_per_us 1; // 防止除零错误 target_ticks us * cycles_per_us; start_ticks DWT-CYCCNT; // 注意处理溢出 while ((DWT-CYCCNT - start_ticks) target_ticks) { // 空循环等待 } }这个dwt_delay_us函数比普通的基于SysTick的延时函数精度高得多因为它以CPU周期为单位进行等待。SystemCoreClock是CMSIS定义的全局变量表示系统内核时钟频率HCLK。同样循环等待条件(DWT-CYCCNT - start_ticks) target_ticks在32位无符号整数运算下即使发生单次溢出也能正确工作前提是target_ticks小于2^31个周期即约17.9秒120MHz对于微秒级延时这完全满足。你可以用这个高精度延时函数去生成精确的脉冲然后用逻辑分析仪或示波器验证会发现其误差通常在几个时钟周期内远优于基于SysTick或普通循环计数的延时。5. 实测案例在XMC4500上测量不同内存访问的耗时差异理论说再多不如一个实际案例。让我们在XMC4500 Relax Kit上设计一个小实验来直观感受DWT测量的威力。XMC4500的Flash访问速度与RAM不同我们可以通过测量来验证这一点。实验设计我们编写两个简单的函数一个在Flash中执行一段密集的计算循环另一个将同样的代码复制到RAM中执行通过链接器脚本或__attribute__((section(“.ram_code”)))实现。然后用DWT分别测量它们的执行时间。// 假设这是我们要测试的“工作负载”一个简单的内存填充操作 void workload_flash(void) { volatile uint32_t buffer[128]; for (int i 0; i 128; i) { buffer[i] i * i; // 一些计算避免被编译器完全优化掉 } } // 通过编译器属性将函数定位到RAM中执行 void __attribute__((section(“.ram_code”))) workload_ram(void) { volatile uint32_t buffer[128]; for (int i 0; i 128; i) { buffer[i] i * i; } } void run_memory_speed_test(void) { uint32_t cycles_flash, cycles_ram; uint32_t start, end; const uint32_t overhead measure_overhead(); // 假设已校准 // 测量Flash中执行的耗时 __disable_irq(); start DWT-CYCCNT; workload_flash(); end DWT-CYCCNT; __enable_irq(); cycles_flash (end - start) - overhead; // 测量RAM中执行的耗时 __disable_irq(); start DWT-CYCCNT; workload_ram(); end DWT-CYCCNT; __enable_irq(); cycles_ram (end - start) - overhead; printf(“[Memory Test] Flash: %lu cycles, RAM: %lu cycles, Ratio: %.2f\n”, cycles_flash, cycles_ram, (float)cycles_flash / cycles_ram); }实测结果分析在XMC4500上假设主频120MHzFlash可能开启了预取和加速你可能会发现workload_ram()的执行周期数显著少于workload_flash()。这个差异主要来自于Flash存储器的访问延迟。即使有预取缓冲区顺序访问大量数据时RAM通常是紧耦合的SRAM的速度优势依然明显。这个实验清晰地展示了将性能关键的代码或数据放到RAM中可以带来可观的性能提升而DWT测量给了我们量化的依据。注意事项为了公平比较两个函数体内的代码必须完全一致唯一的区别是链接地址。需要确保链接器脚本正确地将.ram_code段分配到了RAM区域并且启动代码在初始化时可能需要对这部分代码进行从Flash到RAM的拷贝如果函数本身存储在Flash中。编译器优化等级需要保持一致。高优化等级下编译器可能将循环展开或进行其他激进优化这会影响绝对周期数但不影响Flash与RAM的相对性能趋势。6. 常见问题排查与测量误差分析即使按照上述步骤操作你可能还是会遇到一些奇怪的现象。这里汇总一些常见问题及其排查思路。问题一测量结果波动很大每次都不一样。可能原因1中断干扰。这是最常见的原因。即使你测量的是很短的一段代码如果中断频率很高还是有概率在测量窗口内被中断。解决方案在测量关键短代码时务必使用__disable_irq()和__enable_irq()包裹。可能原因2缓存效应。Cortex-M4内核有可选的指令缓存I-Cache和数据缓存D-Cache。XMC4500没有硬件缓存但如果有比如在M7内核上第一次执行某段代码和后续执行因为缓存命中的关系时间会差很多。解决方案进行“预热”即在正式测量前先执行几次被测代码让缓存处于稳定状态然后测量多次取平均值。可能原因3动态频率调整。有些MCU有动态电压频率调整DVFS功能。确保在测量期间CPU主频是固定不变的。XMC4500在正常运行模式下频率通常是固定的。可能原因4编译器优化导致代码被消除或重构。再次检查是否使用了volatile和编译器屏障来防止优化。问题二测量结果看起来是0或者非常小个位数。可能原因1编译器将函数调用优化掉了。确保被测函数有“可观察的副作用”比如操作volatile变量、访问外设寄存器等。可能原因2DWTCYCCNT没有成功启用。检查DEMCR和DWT_CTRL寄存器的值确认TRCENA和CYCCNTENA位确实被置1。可以在调试器中查看这些寄存器的值。可能原因3start和end的读取被编译器重排到了被测代码的同一侧。确保使用了足够强的内存屏障__asm volatile ( : : : memory)。问题三测量结果比预期大一个数量级。可能原因1CYCCNT的时钟源不是HCLK。查阅XMC4500的参考手册确认调试模块的时钟源。在绝大多数标准配置下它就是HCLK。但如果你修改了复杂的时钟树需要再次确认。可能原因2测量代码本身开销巨大且未校准。记得减去测量框架的开销measure_overhead。可能原因3被测代码中包含了隐性的等待或延迟。例如如果代码中访问了一个速度很慢的外设如外部QSPI Flash或者包含了一个基于循环的软件延时。你需要区分这是CPU执行时间还是包含等待状态的“墙上时钟”时间。DWT测量的是前者。关于测量误差的终极思考没有任何测量是绝对精确的。DWTCYCCNT的误差主要来源于读取指令开销通过校准可以基本消除。流水线效应读取CYCCNT的指令本身需要几个周期执行并且受处理器流水线影响它反映的“时刻”与指令执行流存在细微的相位差。但对于代码段测量几十个周期以上这个误差通常小于1%可以接受。中断延迟即使禁用中断从执行__disable_irq()指令到中断真正被屏蔽也有几个周期的延迟。同样对于较长的被测代码这个影响很小。因此DWTCYCCNT是嵌入式领域进行相对比较和趋势分析的绝佳工具。比如比较算法A和算法B哪个更快优化后性能提升了百分之多少它给出的结果是非常可靠的。对于需要绝对纳秒级精度的场合如验证高速通信协议则需要结合更高精度的硬件定时器或分析仪器。7. 工具化封装与在项目中的集成建议为了在项目中方便地使用DWT进行测量建议对其进行简单的工具化封装。这里提供一个参考实现// dwt_delay.h #ifndef DWT_DELAY_H #define DWT_DELAY_H #include stdint.h #ifdef __cplusplus extern “C” { #endif /** * brief 初始化DWT Cycle Counter * retval 0: 成功 -1: 失败可能该内核不支持DWT */ int dwt_init(void); /** * brief 获取当前的DWT周期计数值 * return 当前的CYCCNT值 */ static inline uint32_t dwt_read_cycle_counter(void) { return DWT-CYCCNT; } /** * brief 计算两个cycle计数之间的差值自动处理32位溢出 * param start 起始cycle计数 * param end 结束cycle计数 * return 经历的周期数 (end - start) 正确处理了一次溢出 */ static inline uint32_t dwt_cycle_diff(uint32_t start, uint32_t end) { if (end start) { return end - start; } else { // 发生了一次溢出 return (UINT32_MAX - start) end 1; } } /** * brief 高精度微秒级延时 (阻塞式) * param us 要延时的微秒数 * note 调用前必须确保已成功调用 dwt_init() */ void dwt_delay_us(uint32_t us); /** * brief 测量一段代码的执行周期数 (简易版不处理中断) * param code_block 要测量的代码块通常用大括号{}包裹 * return 消耗的CPU周期数 (近似值未扣除测量开销) */ #define DWT_MEASURE_CYCLES(code_block) \ ({ \ uint32_t __dwt_start, __dwt_end; \ __dwt_start dwt_read_cycle_counter(); \ do { code_block } while(0); \ __dwt_end dwt_read_cycle_counter(); \ dwt_cycle_diff(__dwt_start, __dwt_end); \ }) #ifdef __cplusplus } #endif #endif // DWT_DELAY_H// dwt_delay.c #include “dwt_delay.h” #include “core_cm4.h” // 根据你的MCU内核选择 static uint32_t cycles_per_us 0; int dwt_init(void) { // 检查DWT是否可用 if ((CoreDebug-DEMCR CoreDebug_DEMCR_TRCENA_Msk) 0) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; } // 有些实现需要检查DWT是否存在 if ((DWT-CTRL DWT_CTRL_NOCYCCNT_Msk) ! 0) { // 此设备不支持CYCCNT return -1; } // 启用CYCCNT DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; // 计算每微秒的周期数假设SystemCoreClock已正确设置 cycles_per_us SystemCoreClock / 1000000; if (cycles_per_us 0) cycles_per_us 1; return 0; } void dwt_delay_us(uint32_t us) { uint32_t start_ticks DWT-CYCCNT; uint32_t delay_ticks us * cycles_per_us; // 等待目标周期数到达处理单次溢出 while (dwt_cycle_diff(start_ticks, DWT-CYCCNT) delay_ticks) { __asm volatile (“nop”); } }在项目中的集成建议条件编译可以将DWT测量功能用宏如USE_DWT_PROFILING控制在发布版本中关闭避免不必要的功耗和代码体积。日志输出将测量结果通过串口、SWOSerial Wire Output或Segger RTT输出便于实时分析。统计功能扩展上述工具增加对多次测量求平均、求最大值/最小值、计算标准差的功能以获得更稳定的性能数据。与RTOS集成如果你使用FreeRTOS、µC/OS等RTOS可以考虑在任务切换钩子函数中读取CYCCNT来统计每个任务的CPU占用率。这是一个非常强大的性能剖析工具。电源管理在进入低功耗模式如Sleep, Stop前可以考虑禁用DWT (DWT-CTRL ~DWT_CTRL_CYCCNTENA_Msk) 以节省功耗唤醒后再重新启用。但要注意禁用后CYCCNT的值会停止重新启用后可能不会从之前的值继续根据实现而定通常需要重新清零。DWTCYCCNT是一个被许多开发者低估的调试利器。它成本低廉几乎零额外硬件精度极高并且是内核标准功能移植性好。花一点时间掌握它能让你在嵌入式性能优化和调试的道路上从“凭感觉”进化到“拿数据说话”极大地提升开发效率和代码质量。在XMC4500或其他Cortex-M芯片上下次当你再疑惑“这段代码到底有多快”时不妨首先想到打开这个内置的“高精度秒表”。
返回列表