复旦微FM33G0定时器实战:从Systick到ET计数器的延时优化

张开发
2026/4/5 12:29:36 15 分钟阅读

分享文章

复旦微FM33G0定时器实战:从Systick到ET计数器的延时优化
1. 为什么需要优化FM33G0的定时器延时最近在做一个嵌入式项目时遇到了一个典型的定时问题。我需要控制一个引脚在上电后保持低电平3秒等待设备充电完成后再拉高。听起来很简单对吧但实际调试时却发现使用Systick定时器实现的延时函数总是导致系统复位。这个问题其实很典型。FM33G0作为一款国产MCU其Systick定时器是24位的在使用16MHz高频RC振荡器时最大只能实现约1秒的延时。更麻烦的是看门狗定时器的溢出周期设置为2秒这意味着如果连续调用三次1秒延时系统就会因为看门狗超时而复位。我在调试时发现很多开发者都会遇到类似的定时器使用问题。特别是当项目需要较长时间的精确延时时Systick的局限性就显现出来了。这时候就需要考虑使用ET扩展定时器计数器来实现更灵活的定时功能。2. Systick定时器的使用与局限2.1 Systick的基本工作原理Systick是Cortex-M内核提供的一个系统定时器主要用途是提供操作系统所需的时间基准。在FM33G0上它是一个24位递减计数器时钟源可以选择系统时钟或系统时钟的1/8。以16MHz系统时钟为例Systick的最大计数值是2^24-116777215对应的最大延时时间为 16777215 / 16000000 ≈ 1.048秒这就是为什么直接使用Systick无法实现3秒延时的根本原因。虽然可以通过多次调用1秒延时来实现但这会带来其他问题。2.2 看门狗与延时的冲突在实际项目中看门狗定时器(WDT)是保证系统可靠性的重要机制。FM33G0的看门狗默认溢出周期是2秒这意味着如果程序在2秒内没有喂狗系统就会自动复位。考虑以下代码Delay_ms(1000); Delay_ms(1000); Delay_ms(1000);虽然每个延时只有1秒但三个延时连续执行时中间的喂狗操作可能被错过导致看门狗超时。这就是为什么我们需要更可靠的延时方案。2.3 改进的Systick延时实现虽然Systick有局限性但通过优化实现方式还是可以满足一些基本需求的。下面是我改进后的us级和ms级延时函数void Delay_us(uint32_t nus) { uint32_t tnow, tcnt 0; uint32_t reload SysTick-LOAD; // 获取重装载值 uint32_t told SysTick-VAL; // 获取当前计数值 uint32_t ticks nus * 16; // 计算需要的节拍数(16MHz/1000000) while(1) { tnow SysTick-VAL; if(tnow ! told) { if(tnow told) { tcnt told - tnow; // 递减计数 } else { tcnt reload - tnow told; } told tnow; if(tcnt ticks) { break; // 延时完成 } } } } void Delay_ms(uint32_t nms) { for(uint32_t i0; inms; i) { Delay_us(1000); IWDT_Clr(); // 每次ms延时后喂狗 } }这种实现方式虽然解决了喂狗问题但仍然受限于Systick的最大延时能力。对于需要更长延时的场景我们需要考虑使用ET计数器。3. ET计数器的优势与应用3.1 ET计数器简介FM33G0的ET(Extended Timer)计数器是芯片提供的扩展定时器资源相比Systick具有更多优势32位计数器支持更长延时更灵活的时钟源选择支持中断和DMA多个独立通道ET计数器特别适合需要精确长延时的应用场景。在我的项目中使用ETIM2实现了可靠的3秒延时功能。3.2 ET计数器的两种使用方式3.2.1 轮询方式实现延时volatile uint8_t delay3sFlag 0; void Delay_LD3s(void) { PA11_ON(); // 拉低引脚 ETIMx_ETxCR_CEN_Setable(ETIM2, ENABLE); // 启动定时器 while(!delay3sFlag) { IWDT_Clr(); // 循环中定期喂狗 } delay3sFlag 0; PA11_OFF(); // 拉高引脚 }这种方式简单直接在等待延时完成的过程中可以定期喂狗避免系统复位。delay3sFlag会在ET计数器中断中置位。3.2.2 中断方式实现延时volatile uint8_t delay3sFlag 0; void Delay_LD3s_Init(void) { PA11_ON(); // 拉低引脚 ETIMx_ETxCR_CEN_Setable(ETIM2, ENABLE); // 启动定时器 } int main(void) { // 初始化代码... while(1) { if(delay3sFlag 1) { delay3sFlag 0; PA11_OFF(); // 延时完成拉高引脚 } // 其他任务... IWDT_Clr(); // 主循环中喂狗 } }中断方式更加灵活不会阻塞主程序执行。定时器到期后通过中断置位标志位主程序检测到标志位变化后执行相应操作。4. 实际项目中的定时器优化建议4.1 定时器选择策略根据项目需求选择合适的定时器短延时(us~ms级)使用Systick中长延时(ms~s级)使用ET计数器周期性任务使用ET计数器中断精确时间测量使用ET计数器输入捕获4.2 看门狗协同设计使用长延时时必须考虑看门狗的影响合理设置看门狗超时时间留出足够余量在延时循环中加入喂狗操作避免在中断服务程序中执行过长操作考虑使用独立看门狗(IWDT)和窗口看门狗(WWDT)的组合4.3 低功耗场景下的定时器使用FM33G0支持多种低功耗模式定时器的使用也需要相应调整在睡眠模式下只有特定定时器可以继续工作考虑使用RTC或低功耗定时器(LPTIM)实现唤醒调整定时器时钟源以降低功耗4.4 调试技巧与常见问题在调试定时器相关功能时我总结了一些实用技巧使用GPIO引脚输出脉冲来测量实际延时时间在中断服务程序中设置标志位方便调试注意定时器时钟源的配置避免分频错误32位计数器的读写需要特殊处理防止原子性问题遇到最多的问题是定时器不工作通常检查以下方面定时器时钟是否使能计数器是否启动中断是否配置正确优先级设置是否合理在实际项目中我通常会先验证定时器的基本功能再逐步添加复杂逻辑。FM33G0的定时器资源虽然丰富但合理规划和使用才能发挥最大效益。

更多文章