ARTICLE DETAIL

资讯详情

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

FreeRtos:SysTick的功能分析

FreeRtos:SysTick的功能分析 SysTick 是 Cortex-M 内核中一个仅有 24 位计数器的简单定时器几乎被所有 RTOS 用作调度心跳。但它的潜力远不止于此——从精准微秒延时到代码性能分析从软件定时器链到任务超时监控SysTick 是 Cortex-M 上最被低估的万能工具之一。01SysTick 寄存器全解SysTick 属于系统控制块SCB共 4 个寄存器// SysTick 寄存器地址CMSIS 标准定义 SysTick-CTRL // 0xE000E010 — 控制和状态 SysTick-LOAD // 0xE000E014 — 重装载值24位最大值 0xFFFFFF SysTick-VAL // 0xE000E018 — 当前值读返回计数器值写清零 SysTick-CALIB // 0xE000E01C — 校准值厂家预置CTRL 寄存器明细CTRL (0xE000E010) 位段 [16] COUNTFLAG — 计数器从 1→0 跳变时置 1读取后清零 [2] CLKSOURCE — 时钟源0外部参考时钟/1内核时钟 [1] TICKINT — 中断使能1 时递减到 0 触发异常 [0] ENABLE — 定时器使能// SysTick 的 CMSIS 封装函数 void SysTick_Config(uint32_t ticks) { if (ticks 0xFFFFFFUL) return; // 24 位计数器限制 // 设置重装载值 SysTick-LOAD ticks - 1; // 从 ticks 减到 0 // 使能 SysTick 中断使用内核时钟 NVIC_SetPriority(SysTick_IRQn, 0xFF); // 最低优先级 SysTick-VAL 0; // 清零计数器 SysTick-CTRL 0x07; // ENABLE | TICKINT | CLKSOURCE }时钟源选择的关键差异CLKSOURCE时钟源典型频率影响1内核时钟处理器时钟 HCLK168MHz (STM32F4)最高精度最快计数0外部参考AHB 分频后HCLK/8 21MHz (STM32F4)低频省电兼容 HALT 模式// 获取 SysTick 时钟频率 uint32_t get_systick_freq(void) { if (SysTick-CTRL (1 2)) { return SystemCoreClock; // CLKSOURCE 内核时钟 } else { return SystemCoreClock / 8; // CLKSOURCE 外部参考 } }02精准延时比 HAL_Delay 更好的选择HAL_Delay 依赖 SysTick 中断最小精度是 1ms且在中断中调用会产生递归锁死。// 基于 SysTick VAL 的微秒级延时阻塞式~1μs 精度 // ⚠️ 前提SysTick 已被初始化为 1ms 中断模式 void delay_us(uint32_t us) { uint32_t ticks_per_us get_systick_freq() / 1000000; uint32_t target ticks_per_us * us; uint32_t start SysTick-VAL; // 读取当前递减值 uint32_t elapsed 0; while (elapsed target) { uint32_t current SysTick-VAL; if (current start) { elapsed start - current; // 正常递减 } else { // 计数器绕回已溢出走过一次 LOAD→0 elapsed start; // 第一次递减到 0 elapsed (SysTick-LOAD - current); // 第二次递减 start SysTick-LOAD; // 重置参考点 } if (elapsed target) break; } }为什么不用 for(i0;in;i) 空循环— 编译器优化会改变循环行为且循环节拍随 CPU 频率变化移植性极差。利用 CALIB 校准值做跨芯片适配// CALIB 寄存器包含厂家预置的 10ms 校准值 uint32_t ten_ms_ticks SysTick-CALIB 0x00FFFFFF; if (ten_ms_ticks ! 0) { // 厂家提供了校准值 → 用于自适应延时 uint32_t ticks_per_us ten_ms_ticks / 10000; } else { // 无校准值 → 回退到计算 uint32_t ticks_per_us SystemCoreClock / 1000000; }03代码性能分析测量函数执行时间SysTick 的 VAL 寄存器保存当前计数器值递减通过读取差值可以精确测量一段代码的执行时长——比示波器打 GPIO 更方便。// 使用 SysTick 测量代码执行时间 // ⚠️ 确保测量期间无 SysTick 中断发生 static uint32_t perf_start_val; static uint32_t perf_start_ticks; void perf_start(void) { // 记录当前 SysTick 状态 perf_start_val SysTick-VAL; perf_start_ticks SysTick-LOAD - SysTick-VAL; // 已流逝的 ticks } uint32_t perf_stop(void) { uint32_t end_val SysTick-VAL; uint32_t load SysTick-LOAD; uint32_t elapsed; if (end_val perf_start_val) { elapsed perf_start_val - end_val; } else { // 发生了重装载测量跨越了 1ms 边界 // 简单场景加上一次重装载的 ticks elapsed (perf_start_val 1) (load - end_val); } return elapsed; // 返回 ticks 数 } // 使用示例 void measure_function(void) { perf_start(); critical_function(); // 被测量的函数 uint32_t ticks perf_stop(); uint32_t ns ticks * 1000000000 / get_systick_freq(); printf(critical_function 耗时: %u ns (%u ticks)\n, ns, ticks); }溢出处理是关键当测量跨越了 SysTick 重装周期比如超过 1msVAL 会从 0 跳回到 LOAD必须处理绕回。04软件定时器链单 SysTick 驱动多层定时器SysTick 每 1ms 产生一次中断但我们可以在这 1ms 基础上构建任意周期的软件定时器链。// 软件定时器数据结构 typedef struct { volatile uint32_t count; // 递减计数器 volatile uint32_t reload; // 重装值 void (*callback)(void *); // 超时回调 void *arg; // 回调参数 uint8_t repeat; // 0单次 1周期 uint8_t active; // 0未激活 1已激活 } soft_timer_t; #define MAX_SOFT_TIMERS 8 static soft_timer_t timers[MAX_SOFT_TIMERS]; // SysTick 中断中调用 void soft_timer_tick(void) { for (int i 0; i MAX_SOFT_TIMERS; i) { if (!timers[i].active) continue; if (--timers[i].count 0) { // 定时器到期 if (timers[i].callback) { timers[i].callback(timers[i].arg); } if (timers[i].repeat) { timers[i].count timers[i].reload; // 周期模式 } else { timers[i].active 0; // 单次模式 } } } } // 启动一个软件定时器 int soft_timer_start(uint32_t ms, int repeat, void (*cb)(void *), void *arg) { int slot -1; for (int i 0; i MAX_SOFT_TIMERS; i) { if (!timers[i].active) { slot i; break; } } if (slot 0) return -1; // 无可用槽位 timers[slot].count ms; timers[slot].reload ms; timers[slot].callback cb; timers[slot].arg arg; timers[slot].repeat repeat; timers[slot].active 1; return slot; } void soft_timer_stop(int id) { timers[id].active 0; }应用示例LED 闪烁500ms、按键消抖50ms、看门狗喂狗1000ms、温度采样周期2000ms——一个 SysTick 就足够。05任务超时监视在 RTOS 中可以用 SysTick 检测某个任务是否在规定时间内响应——在工业控制中这比硬件看门狗更精细。typedef struct { uint32_t task_id; volatile uint32_t last_heartbeat; // 任务最后一次报平安的时间戳 uint32_t timeout_ms; // 超时阈值 void (*timeout_cb)(uint32_t task_id); // 超时回调 } task_watchdog_t; task_watchdog_t watchdog_table[4]; // 监控 4 个关键任务 // 任务调用来报告自己还活着 void task_watchdog_feed(uint32_t task_id) { watchdog_table[task_id].last_heartbeat get_systick_ms(); } // 每秒调用一次由软件定时器或主循环触发 void task_watchdog_check(void) { uint32_t now get_systick_ms(); for (int i 0; i 4; i) { uint32_t elapsed now - watchdog_table[i].last_heartbeat; if (elapsed watchdog_table[i].timeout_ms) { // 任务超时 watchdog_table[i].timeout_cb(i); } } } uint32_t get_systick_ms(void) { // 全局 SysTick 计数器在 SysTick_Handler 中递增 return systick_ms_counter; }相比硬件独立看门狗IWDG这种软件方式的不同在于可以区分哪个任务出问题而硬件看门狗只知道整个系统挂了。06FreeRTOS 占用 SysTick 后的注意事项这是最常见的问题用户在 FreeRTOS 项目里想用 SysTick 做延时或测量发现不工作了。// FreeRTOSConfig.h 中的配置 #define configTICK_RATE_HZ ((TickType_t)1000) // SysTick 1ms 中断 // FreeRTOS 初始化时调用 // vTaskStartScheduler() 内部会配置 SysTick // 此后 SysTick 归 RTOS 内核管理FreeRTOS 占用了 SysTick 后用户不能做的事1.❌ 调用 SysTick_Config() 重新配置——会打乱 RTOS 的 tick 计数2.❌ 修改 SysTick-LOAD——导致 xTaskGetTickCount() 异常3.❌ 在 SysTick_Handler 中插入用户代码——FreeRTOS 已使用该入口正确的替代方案// ✅ 方案 1使用 vTaskDelay() 替代自旋延时 vTaskDelay(pdMS_TO_TICKS(100)); // 延时 100ms // ✅ 方案 2使用 xTaskGetTickCount() 获取时间戳 TickType_t start xTaskGetTickCount(); // ... 做点事 ... TickType_t elapsed xTaskGetTickCount() - start; if (elapsed pdMS_TO_TICKS(500)) { // 超时处理 } // ✅ 方案 3如果需要更高精度定时器用硬件定时器TIM // 而不是跟 SysTick 抢资源 HAL_TIM_Base_Start_IT(htim2); // 使用 TIM2 // ✅ 方案 4使用 DWT 周期计数器Cortex-M3 // DWT-CYCCNT 是 32 位 CPU 周期计数器不受 SysTick 影响 uint32_t cycles DWT-CYCCNT;DWT 周期计数器的使用Cortex-M3/M4/M7 的 DWTData Watchpoint and Trace模块提供了一个32 位的 CPU 周期计数器比 SysTick 更高精度且不互斥void dwt_init(void) { // 使能 DWT 模块 CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; // 使能周期计数器 DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; } uint32_t dwt_get_cycles(void) { return DWT-CYCCNT; } uint32_t dwt_measure(void (*func)(void)) { uint32_t start DWT-CYCCNT; func(); uint32_t end DWT-CYCCNT; return end - start; // 执行周期数 }07SysTick 的 HALT 模式行为调试时暂停 CPU断点命中SysTick 是否继续// SysTick-CTRL 的第 0 位控制 // 在大多数实现中SysTick 在 CPU HALT 时也会暂停 // 但需要具体验证——有些 Debugger 配置可能让 SysTick 继续运行这意味着用 SysTick 做延时断点后恢复执行延时时间可能不准。对于需要长时间精确延时的场景如 Bootloader 中的等待超时使用独立硬件定时器更可靠。EOF总结SysTick 远不止是 RTOS 的心跳。理解了它的四个寄存器、掌握 VAL 差值测量法和软件定时器链构建你就能在几乎不增加硬件成本的情况下实现精准延时~1μs、代码性能分析、多层软件定时器和任务超时监控。但也要记住它的边界FreeRTOS 下别跟它抢高精度场景用 DWT 或硬件定时器调试暂停时注意行为。
返回列表