从延时到定时器:我对 STM32 程序设计的理解又往前走了一步
前言最近继续跟着铁头山羊学习 STM32。前面从 GPIO、输入输出、按键检测到中断响应整体上已经慢慢建立起一种认识STM32 程序不是单纯从上到下执行几行代码而是在不断和外部硬件、外部事件打交道。而到了定时器这一部分我感觉整个理解又往前迈了一步。因为中断更多是在解决“事件来了怎么办”而定时器开始让我认真思考另一个问题程序里的很多事情到底应该在什么时间发生、以什么节奏发生、由谁来负责这个节奏。以前刚开始学的时候对“时间”这个概念其实理解得很粗糙。无非就是加一个延时现象慢一点再加一个延时现象明显一点。但学到现在越来越能感觉到在 STM32 里“时间”不是一个附属条件而是程序设计里非常核心的一部分。所以这一篇主要就记录一下最近学定时器时的一些理解包括延时和定时器的区别阻塞式写法的问题定时器为什么比想象中更重要程序结构为什么会因为定时器而发生变化这一阶段我对 STM32 编程思路的变化这篇会尽量写得更偏笔记化一些方便后面自己复习。一、为什么学到定时器时会明显感觉程序思路变了我最近最大的感受就是定时器不是简单多学一个外设而是它会逼着你重新理解程序是怎么运行的。前面学 GPIO、按键、中断时重点还是在如何读取外部状态如何控制输出结果如何在事件发生时快速响应这些内容当然都很重要但整体还更多是在研究“功能怎么实现”。而学到定时器之后关注点开始变化了。因为定时器会让你不得不去想这个动作多久执行一次这个状态多久更新一次这个任务能不能按固定周期运行这个程序能不能在不阻塞主流程的情况下维持某种节奏也就是说程序不再只是“做不做某件事”还多了一层“什么时候做”的要求。这个变化看起来不起眼但我觉得非常关键。因为很多程序真正难的地方都不是功能本身而是多个功能放到一起之后时间关系怎么处理。二、先重新整理一下延时到底帮了什么又限制了什么在学定时器之前延时函数几乎是前期实验里最常见、也最好用的工具之一。所以我觉得有必要先把自己对延时的理解重新整理一下。延时在入门阶段为什么这么好用这一点必须承认延时函数在前期真的很有帮助。因为很多实验现象如果没有延时程序执行太快肉眼根本看不出来。比如某个状态切换太快看起来像没变化某个输出变化瞬间完成不容易观察某个按键响应过程太快难以分辨程序逻辑而加了延时之后整个实验会立刻变得清晰很多。所以从学习角度看延时最大的价值就是让现象可观察让节奏可感知让程序执行过程更容易理解这也是为什么前期很多实验都离不开延时。2. 延时本质上做了什么虽然前面一直在用延时但最近我才开始认真想这个问题。延时本质上并不是“让程序聪明地等一下”而是“让程序停在这里先别做别的事”。也就是说在延时这段时间里主程序往往是被卡住的。它不会继续执行别的逻辑也不会主动去处理新的事情。这个理解特别重要。因为它意味着延时并不是一种真正意义上的时间管理而更像是一种最直接的阻塞手段。延时的问题为什么前期不明显后期却会越来越严重前期实验一般都比较简单功能单一所以就算程序被卡一下也没什么大影响。比如只做一个输出翻转实验延时完全够用。但一旦程序开始复杂起来比如要同时处理多个输入要维持某个固定频率要及时响应外部事件要让不同功能彼此不影响这时候延时的问题就会迅速暴露出来。因为一旦主程序在某个地方被延时卡住其他逻辑就只能等某些事件可能处理不及时主循环节奏会被打乱程序可扩展性会变差所以我现在越来越觉得延时是入门期非常有价值的工具但它更适合帮助理解现象不适合长期承担程序节奏控制的核心任务。三、定时器的意义不只是计时而是把“时间控制”从主流程里独立出来这是我最近理解上很大的一个变化。以前一提到定时器第一反应是“计时”“定时中断”“周期触发”。这些当然没错但现在感觉这种理解还是太表面了。定时器真正厉害的地方是什么我现在的理解是定时器最重要的意义是把原本由主程序硬扛的时间控制任务独立交给一个专门模块去处理。换句话说前面用延时的时候是主程序自己负责“等时间过去”而有了定时器之后主程序不需要亲自站在原地等而是由定时器来负责“到点通知”。这个区别特别关键。因为它意味着时间控制不再和主程序死绑在一起。主程序可以继续干别的事而定时器在后台按照设定好的节奏运行。为什么这会直接改变程序结构因为一旦“时间控制”独立了出来很多代码的组织方式就会变。以前可能是这样执行任务 A延时执行任务 B延时执行任务 C这种写法本质上是把“业务逻辑”和“等待时间”强行揉在一起。而当定时器进来之后思路就可能慢慢变成主循环负责整体逻辑推进定时器负责周期性产生节奏某些任务由定时信号触发某些状态由周期事件更新这样程序就不再依赖大量阻塞等待而开始形成一种“按节奏驱动逻辑”的模式。这也是为什么我最近越来越觉得定时器是 STM32 学习里一个非常关键的节点。因为从这里开始程序真的开始有点“系统运行”的味道了。四、重新理解“阻塞”和“非阻塞”以前只是知道这两个词现在终于有点感觉了以前总会听到一句话写嵌入式程序尽量避免阻塞。但说实话在没真正接触到定时器、没亲身体会到主循环被延时拖住之前我对这句话理解得很浅。最近这部分内容学下来才算真正有点实感。什么叫阻塞我现在给自己的定义很朴素阻塞就是程序为了等某件事停下当前流程不去处理其他事情。延时就是最典型的阻塞。因为程序在延时期间基本上处于“先站着别动”的状态。阻塞最麻烦的地方不只是浪费时间以前总觉得阻塞的主要问题是“慢”。但现在越来越觉得它更大的问题是会破坏程序整体的灵活性。比如主程序正在某个延时里卡着这时候如果有按键输入变化有新的外部事件到来有状态需要更新有通信任务需要处理那这些事情很可能都只能被拖后。所以阻塞的问题不只是“等了一会儿”而是程序在这段时间里失去了同时处理其他事情的能力。什么叫非阻塞我现在对非阻塞的理解是程序不需要傻等某个时间点到来而是可以继续执行其他逻辑等条件真正满足时再处理。而定时器的价值之一就是帮助程序实现这种“先继续跑等时机到了再触发”的思路。这样一来程序就不再是“做一件事卡一次”而是慢慢开始具备同时兼顾多个逻辑节奏的能力。为什么这一步特别重要因为嵌入式程序和普通单线程顺序程序最大的不同之一就在这里。它往往不是只处理一条线上的事情而是要面对外部输入在变化内部状态在更新输出行为要控制时间节奏要维持事件响应还得及时如果一直靠阻塞式写法撑着程序后面会越来越笨重。而一旦开始理解非阻塞就会慢慢知道程序不是只能“做完这件事再做下一件事”而是可以“边推进当前逻辑边等别的时机成熟”。五、定时器让我第一次认真思考程序里的“节奏”到底该由谁来管理这部分是我最近最想记下来的内容之一。以前做实验时节奏这件事几乎都是靠延时直接写出来的。比如想让某个现象 1 秒变化一次就手动加一个差不多的延时。但现在开始觉得这种方式其实很粗糙。因为它把“节奏控制”硬塞到了业务逻辑里面。节奏控制和业务逻辑混在一起会有什么问题如果程序越来越复杂这种写法会导致几个明显问题代码可读性变差修改时间参数时容易影响业务逻辑一个功能的等待时间可能拖慢其他功能程序整体结构会越来越乱也就是说本来“多久执行一次”应该是一个独立维度但如果直接靠延时写在流程里它就会和“做什么事”这件事纠缠不清。定时器其实是在帮程序建立“统一节奏源”我现在的理解是定时器的一个核心价值就在于它可以为程序提供相对稳定、可管理的时间基准。有了这个时间基准之后程序里的很多行为就可以不再依赖“每次都手工卡住等”而是改成“按某个周期自然触发”。这种变化特别像是从“人肉打拍子”变成“系统自己有节拍器”。而一旦程序有了节拍器整体组织方式就会清楚很多。六、把最近这部分内容整理成一个更清晰的理解框架为了后面复习方便我想把最近学到的内容按自己的理解整理成一个框架。前期写法的核心特点前期更多是主循环承担大部分逻辑延时负责控制现象快慢输入靠轮询或简单响应程序结构偏直接、偏线性这种方式适合快速入门也适合观察现象。继续往后学中断带来了什么中断主要让我理解了程序不需要一直主动盯着事件外部事件可以主动触发程序响应主循环和事件处理开始出现分工程序第一次有了“正常运行”和“突发处理”两层结构3. 定时器又带来了什么定时器则进一步让我理解了程序里的时间控制不该全靠延时时间节奏应该独立管理周期任务可以交给定时器驱动主流程不应该被大量等待逻辑占死程序开始从“顺着执行”往“按节奏调度”变化4. 这样串起来之后我现在对 STM32 程序的理解到目前为止我会觉得一个越来越像样的 STM32 程序应该慢慢朝下面这个方向靠初始化部分配置硬件和运行条件主循环负责整体逻辑推进中断负责处理突发事件定时器负责提供时间节奏状态变量/标志位负责连接不同模块之间的信息流虽然现在我离真正把这套东西运用得很熟还差很远但至少思路已经开始有点清楚了。七、学到这里我对“主循环”这件事的理解也变了这一点其实是前几篇就已经慢慢有感觉的但最近学定时器之后更明显了。以前会下意识觉得while(1) 就是程序的核心什么都往里面写。但现在越来越觉得主循环不是垃圾桶它更像是一个“总调度框架”。主循环不该继续承担所有时间等待工作如果主循环里塞满延时那主循环就会变成一个到处停顿的流程。它看起来在执行程序实际上经常被各种等待动作打断。这会导致主循环越来越不像“控制中心”而更像“被绑住手脚的执行器”。主循环更合理的角色是什么我现在理解的更合理写法应该是主循环负责检查状态主循环负责推进逻辑主循环负责调用相应处理流程时间节奏尽量交给定时器突发事件尽量交给中断这样主循环才不会被大量细节拖死。这个理解虽然还只是初步的但我觉得很重要。因为后面功能一多主循环设计得好不好会直接决定程序是不是还能继续扩展。八、这部分内容带给我最大的收获不是“我学会了定时器”而是我终于开始更认真地看程序设计了说实话最近最明显的变化并不是我已经把定时器学得多深入而是我开始不再满足于“功能能实现”这件事了。以前更关注这个现象能不能做出来这个实验能不能跑通这个函数怎么调用这个配置写在哪里现在则开始更多关注为什么这里不用延时了为什么这里更适合定时器触发为什么主循环不该被阻塞为什么时间控制应该独立出来这段代码如果继续扩展会不会马上变乱我觉得这是一个挺关键的变化。因为 STM32 这种东西真正难的地方从来都不只是“知识点多”而是知识点一多之后程序还能不能保持清楚。而最近这部分内容正好就在逼着我从“会写几段代码”往“开始考虑程序设计”这个方向走。九、这一阶段我觉得最容易忽略、但其实很关键的几个点这里单独记一下后面复习时应该会很有用。不要把定时器简单理解成“替代延时”这是最容易表面化理解的地方。定时器当然可以在很多场景下替代延时但它更重要的价值不是单纯“换一个计时方式”而是它在改变程序的时间组织方式。程序设计里“时间”本身就是一类要管理的资源以前更关注功能现在越来越觉得时间也得单独看。多久做一次、何时触发、周期是否稳定这些都不是附属问题。阻塞式写法前期很好用但一定要知道它的边界延时不是不能用而是要明白它适合什么阶段、适合什么场景。如果一直依赖它后面程序复杂度一上来会很吃力。主循环、中断、定时器之间是分工关系不是互相替代关系这一点也很关键。不是说学了中断就不用主循环学了定时器就什么都丢给它。而是不同机制各自负责不同类型的问题。以后看代码不能只看“做了什么”还要看“为什么这样安排时间”我觉得这一点是最近最重要的收获之一。STM32 程序很多时候不只是逻辑问题还是时间组织问题。十、总结最近继续跟着铁头山羊学习 STM32学到定时器这一部分之后我最大的感受就是程序终于不只是“做什么”而开始认真处理“什么时候做、按什么节奏做”。前面的延时让我理解了现象中断让我理解了响应而定时器则让我开始理解调度和节奏。如果把这几个阶段连起来看会发现自己的思路确实在变化从只关注现象到开始关注逻辑再到开始关注结构现在又开始关注时间管理我觉得这才是 STM32 学习里真正有意思的地方。因为你不是在机械学几个模块而是在一点点建立嵌入式程序设计的思维。现阶段我对自己的要求也更清楚了以后不只满足于把实验做出来还要尽量搞清楚这个程序为什么要这样安排逻辑、这样安排时间。因为真正能拉开差距的往往不是“会不会用某个模块”而是“会不会把这些模块组织成一个清楚、稳定、可扩展的程序”。