从流水灯到工程实践:STM32开发板的进阶开发指南
2026/9/5 18:17:44 网站建设 项目流程

有人买的 STM32 开发板,从开箱到吃灰只隔了一个工程:流水灯。买的时候有多兴奋,点完灯之后就有多迷茫。看着 LED 按照数组里的顺序一轮一轮亮灭,那一刻确实觉得自己“入门了”,可是第二天换一个稍带业务逻辑的题目,就不知道代码究竟该从哪里写起。

这里要先把判断说清楚:三百块买到的不只是一块能闪灯的电路板,而是一整套可以跑系统、调外设、做产品的硬件平台。流水灯作为第一个实验没有任何问题,哪怕工业级的开发套件,工程师拿到手通常也从“点亮一颗 LED”开始。真正的问题在于,很多学习者把“复制别人写好的流水灯例程并烧录成功”当成了“学会 STM32”,随即就停在原地,后面再也没往前走过。

本文不是让你把流水灯扔掉。恰恰相反,我会基于流水灯这个起点,把它一步步改造成更有工程感的代码:先解决阻塞延时带来的问题,再引入定时器中断、外部中断和 PWM 调光,最后补上串口调试、启动模式、存储器映射等新手容易踩坑的底层知识。希望你看完之后,能对自己手中的那块开发板有一个新的判断:它不是一块“会闪灯的演示板”,而是可以继续挖很久的嵌入式学习平台。

1. 流水灯不是原罪,停在流水灯才是

很多人会问:STM32 开发板的第一课难道不是点灯吗?是,但点灯和点灯之间差别很大。

第一种点灯,是打开别人的工程文件,找到 main.c,修改几个 GPIO 引脚编号,下载,看灯亮。整个过程里,你没有处理时钟使能,没有配置 GPIO 模式,没有思考过为什么 LED 要串联电阻,也没有查看板子原理图上的引脚连接。灯亮了,知识却没有留下多少。

第二种点灯,是自己打开参考手册,找到芯片引脚定义,打开芯片厂商提供的固件库或 HAL 文档,确认 GPIO 的推挽输出、上拉下拉、输出速度这些参数分别影响什么,然后从空工程开始一步步把代码写出来。这种点灯虽然结果一样,但它让你理解了 MCU 内部的工作方式,后面换成按键、串口、PWM,也能快速迁移。

“三百块就点亮个流水灯”这句话之所以扎心,不是因为流水灯太简单,而是因为停留在流水灯意味着你还没有开始接触真正的嵌入式系统问题:

  • 系统里同时有多个任务,能不能不靠HAL_Delay阻塞方式来切换任务?
  • 外部事件来了,CPU 怎么知道该去处理?
  • 串口想打印日志,USB 转串口和 MCU 之间是什么关系?
  • 要让 LED 亮度渐变,为什么 GPIO 很难做到,而定时器 PWM 可以?

解决这些问题,恰恰是 STM32 工程和 Arduino 类玩具项目拉开差距的地方。对大多数嵌入式岗位来说,招聘要求里写的“熟悉 ARM 体系结构、熟悉常用外设、有独立调试能力”,靠流水灯是无法证明的。

2. STM32 开发板到底买了什么:概念分层

在继续动手之前,有必要先梳理几个很容易被混在一起的概念。

2.1 MCU、开发板、调试器的关系

单片机(MCU)和开发板不是一回事。STM32 本身是意法半导体推出的一个 MCU 系列,内部有 CPU 内核、Flash、SRAM,以及 GPIO、定时器、UART、I2C、SPI、ADC 等外设。你买的“板子”,是把某颗 STM32 芯片焊在一块 PCB 上,再补上晶振、复位电路、电源管理、USB 转串口芯片、LED、按键、排针引出等外围资源。所以严格来说,你写的是“运行在开发板上 MCU 内部 Flash 里的程序”,而不是“写在开发板里的程序”。

开发板和调试器也经常被搞混。很多人说的“用开发板下载程序”,实际流程是:电脑通过 USB 连接板载的 ST-Link 或 DAP-Link 调试器,调试器再通过 SWD 或 JTAG 接口把编译生成的 bin/hex 文件写入 MCU 的 Flash。调试器不仅负责下载,还承担断点调试、变量查看、寄存器读取这些工作。如果开发板上没有板载调试器,就需要单独接一个“仿真器”,很多初学者在设备管理器里找不到串口或者找不到下载设备,原因往往是上面这两个连接路径没有理顺。

2.2 不同价位板子的差异与选型逻辑

STM32 开发板的价位跨度很大。二三十块钱可以买到 STM32F103C8T6 最小系统板,几十块钱到一两百块钱可以买到资料较完善的核心板,到了三百元乃至更高价位,通常就是正点原子、野火等厂商推出的全功能开发板。这类板子一般集成大量外设模块、传感器接口、屏幕接口、音频接口,并且配套文档、视频、例程完善。

价格差异对应的是三类成本:

  • 硬件成本:更多的外设、更复杂的 PCB、更好的电源和接口保护;
  • 资料成本:成熟的厂家会提供从入门到进阶的例程、原理图、用户手册,这一部分对新手价值很大;
  • 学习路径成本:同一块板子,如果按键、LED、USB 转串口、屏幕都集成好了,你可以把精力集中在写程序上,不用一上来就自己做硬件。

所以在选开发板时,不要只问“这块板子贵不贵”,还要问“这块板子配套的例程是否覆盖中断、定时器、串口、ADC 这些外设”。对入门者来说,资料体系的完整程度,往往比硬件堆料更重要。

3. 流水灯的例子,为什么撑不起一个单片机学习路径

如果把一个典型的流水灯程序拆解开来,你会发现核心代码非常短。以 HAL 库为例,常见的结构大概是:

// 主循环流水灯示例,演示用 while (1) { for (int i = 0; i < 8; i++) { HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin << i, GPIO_PIN_RESET); HAL_Delay(200); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin << i, GPIO_PIN_SET); } }

这段代码的问题不在于点灯本身,而在于它只展示了一种最简单的“输出”能力。程序从头到尾都在做同一件事,不需要处理外部输入,不需要和外设通信,也没有时间上的精确性要求。等你真正要做一个产品级别的功能时,会发现现实世界的需求往往是这样:

按一下按键,LED 要切换亮灭状态;同时系统还要用串口把运行状态上报给上位机;还要用 ADC 采集温度,温度超过阈值时通过 PWM 调节风扇转速。如果继续用HAL_Delay串行阻塞的方式写,程序会越来越乱。因为延时期间 CPU 全程被占着,按键扫描和串口发送都无法及时响应,这就是典型的“点灯思维”不适合复杂系统的原因。

流水灯能教会你的,是最基础的 GPIO 输出、电平概念、循环结构和简单的延时控制。这些能力是必要的,但不足以支撑你理解定时器、中断、状态机、实时任务调度这些更核心的嵌入式思维方式。

4. 环境准备与工程组成:别在老例程里“考古”

从流水灯走向真正的外设开发,环境准备非常关键。很多新手会卡在“开发环境选哪个”这个问题上,今天听说 Keil 好,明天又听说 VSCode 配合插件更现代,后天看到教程用 STM32CubeIDE,结果在一堆工具里反复折腾,始终没有跑通一个属于自己的工程。

我的建议是:先选一套能闭环的工具链,集中精力做项目,不要在工具选择上过度消耗。

目前常见组合大致有:

开发方式说明适合人群
Keil MDK + STM32CubeMX最经典的组合,教程多,企业中使用比例高入门阶段推荐,遇到问题容易搜到资料
STM32CubeIDE官方免费 IDE,集成 CubeMX 和编译调试不喜欢破解或者不想装太多软件的人
VSCode + EIDE/CMake轻量、现代,适合代码编辑和 Git 管理熟悉命令行和 CMake 的进阶用户
寄存器开发不依赖库,直接操作寄存器想深入理解 MCU 底层逻辑

这里真正容易踩坑的,不是选哪个 IDE,而是工程内部的库版本。STM32 有标准外设库、HAL 库、LL 库,很多老教程还在用标准库写 STM32F103,而新出的芯片或新版工具链可能已经默认 HAL 工程。如果你把标准库的代码硬塞进 HAL 工程,编译会报大量接口不匹配的错误。

如果只选一条路,我更推荐“STM32CubeMX 生成底层代码 + HAL 库 + Keil/VSCode 编写业务层”的组合。HAL 库虽然封装度较高,但可读性好,外设初始化由 CubeMX 图形化完成,不容易漏掉时钟、GPIO 复用等配置。等以后对时序要求苛刻时,再配合 LL 库或寄存器直接操作也不迟。

拿到开发板后,不要上来就复制例程。先把这几件事做完:

  1. 阅读开发板原理图,找到板载 LED、按键、串口、调试器对应引脚;
  2. 确认板子上的外部晶振频率,因为工程里的时钟树配置必须和实际晶振一致;
  3. 在 CubeMX 中新建工程,选择正确芯片型号,用图形化方式把串口、GPIO、定时器配置好;
  4. 生成工程后先编译一次空工程,再开始写逻辑。

这样做的意义是:你不再被例程绑架,而是能理解“为什么这个工程里需要开这个时钟,为什么这个引脚要配成复用模式”。

5. 动手改造第一个“非阻塞点灯”程序

现在回到 LED。我们用同一个 LED,做一次从“阻塞点灯”到“非阻塞点灯”的改造。这个改造看似简单,却是从流水灯思维走向系统思维的关键一步。

5.1 阻塞 delay 版本

很多流水灯例程里会写类似下面的代码:

// 文件路径:Core/Src/main.c(CubeMX 生成的工程,示例片段) int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); while (1) { HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); // LED 亮 HAL_Delay(500); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); // LED 灭 HAL_Delay(500); } }

这段代码能跑,但HAL_Delay(500)是一个阻塞延时函数。在延时期间,CPU 一直在空转等待,它不会去处理按键,也不会去接收串口数据。如果系统里只有一个 LED 任务,这完全没问题;一旦任务变多,立刻会暴露出响应不及时的问题。

5.2 用 HAL_GetTick 改成非阻塞

HAL 库内部基于 SysTick 维护了一个单调递增的毫秒计数器uwTickHAL_GetTick()就是返回这个值。利用它,我们可以把“延时等待”改成“按时间戳判断”,让 CPU 在等待期间继续执行其他代码。

// 文件路径:Core/Src/main.c(示例片段) int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); uint32_t lastToggle = 0; while (1) { // 每 500ms 翻转一次 LED if (HAL_GetTick() - lastToggle >= 500) { lastToggle = HAL_GetTick(); HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } // 主循环内可以继续处理按键扫描、串口消息等其他任务 // 这里不会因为点灯而被阻塞 500ms } }

这段代码的巧妙之处在于:HAL_GetTick() - lastToggle这个减法会自动处理 SysTick 计数回绕的情况,因为 uint32_t 无符号数的回绕特性,可以保证在一定时间跨度内计算结果仍然正确。

运行起来后,LED 的亮灭表现和阻塞版本看起来一样,但内部逻辑已经不同。如果串口收到一条命令,你可以在同一个 while 循环里及时处理,而不用担心它要等 500ms 延时结束才能响应。

5.3 从 SysTick 到硬件定时器

HAL_GetTick()适合做毫秒级任务调度,但它本质上仍然依赖 SysTick 中断,精度有限。如果要做更精确的定时,比如 PWM 输出、捕获外部信号频率,就需要用到 STM32 里的通用定时器 TIM。

TIM 定时器是 STM32 里功能非常丰富的外设。它内部有一个计数器,可以按照你设定的预分频和自动重装载值递增或递减,计数溢出时可以触发中断,也可以直接驱动引脚输出 PWM 波形。

先看一个用 TIM2 定时中断翻转 LED 的例子:

// 文件路径:Core/Src/main.c(示例片段,具体引脚以你的板子为准) int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); // CubeMX 生成:定时器初始化 HAL_TIM_Base_Start_IT(&htim2); // 启动定时器中断 while (1) { // 主循环空着,或者处理其他低优先级任务 } } // 定时器溢出中断回调。HAL 库在溢出后会调用这个函数。 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } }

用定时器中断的好处是:LED 的翻转由硬件定时器自动触发,主循环不需要每次去查询时间。这种方式更接近真实嵌入式系统的处理模型——需要精确周期执行的任务,用硬件外设触发,而不是靠 CPU 频繁轮询。

需要注意的是,中断回调函数里不能做耗时太长的工作。像HAL_Delay这种阻塞函数绝不能写在回调里,否则会阻塞中断处理,严重时会导致系统卡死。

6. 进阶实例:按键控制呼吸灯 + 状态标记

有了定时器和非阻塞思路,就可以做一个比流水灯更有价值的综合实验:按键控制呼吸灯。按一下按键,LED 做呼吸渐变;再按一下,LED 熄灭。

这个实验虽然不复杂,但它覆盖了 GPIO 输入、外部中断、PWM 定时器输出、中断回调标志位、主循环状态机等多个知识点,非常适合作从流水灯到系统开发之间的过渡。

6.1 CubeMX 配置思路

在 CubeMX 里,通常需要做以下配置:

  • 把 LED 所在引脚配置为定时器 PWM 输出通道,而不是普通的 GPIO 输出;
  • 把按键所在引脚配置为 GPIO 外部中断模式,同时根据原理图选择上拉或下拉;
  • 确定 PWM 定时器的工作频率和周期。比如想生成 1kHz 的 PWM,可以将定时器计数频率设为 1MHz,自动重装载值 ARR 设为 999,这样 PWM 周期就是 1ms,占空比调节精度为 1/1000;
  • 生成工程后,在 main.c 中调用HAL_TIM_PWM_Start,然后通过修改比较寄存器的值来改变占空比。

具体使用哪个 TIM 和哪个通道,需要看你的 LED 接在哪个引脚上,因为定时器输出通道是对应固定引脚的。开发板原理图里通常会有说明,不要在不知道引脚的情况下直接照抄别人的代码。

6.2 代码实现与回调逻辑

按键中断的回调适合用来置一个全局标志,而不是直接在里面做 LED 开关。因为真正控制呼吸灯渐变的逻辑比较耗时,如果放在中断回调里,会拖慢整个系统。

// 文件路径:Core/Src/main.c(示例片段) volatile uint8_t led_enabled = 0; // 外部中断回调,由按键触发 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == KEY_Pin) { led_enabled = !led_enabled; // 只翻转标志,业务处理放主循环 } }

主循环里,根据标志位决定是否执行呼吸渐变:

// 文件路径:Core/Src/main.c(示例片段) #define PWM_PERIOD 999 // 和 CubeMX 中 ARR 保持一致 int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM3_Init(); HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); uint16_t brightness = 0; int8_t step_dir = 1; while (1) { if (led_enabled) { // 修改占空比,控制 LED 亮度 __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, brightness); brightness += step_dir; // 到达最亮或最暗时,反转渐变方向 if (brightness >= PWM_PERIOD || brightness == 0) { step_dir = -step_dir; } // 这个延时只影响亮度变化的步进速度,不像 HAL_Delay 那样阻塞整个系统 HAL_Delay(2); } else { // 关闭呼吸灯:直接把占空比清零 __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 0); } } }

很多人第一次写 PWM 程序时会困惑:为什么 LED 已经配成输出模式,但看不到亮度变化?常见原因是没有调用HAL_TIM_PWM_Start,或者 GPIO 没有配置成复用功能模式。在 STM32 中,定时器输出波形并不是直接出现在普通 GPIO 上的,而是通过引脚复用功能映射出去的。所以引脚模式必须设为复用推挽输出,而不是普通推挽输出。

真正容易踩坑的地方还有按键消抖。机械按键在按下和释放的瞬间会产生抖动,如果不处理,一次按击可能触发多次外部中断。简单做法是在中断里检测到按键后设置一个“待处理标志”,然后在主循环里延时 10ms 到 20ms 后重新读取按键电平,确认确实按下后再处理业务;或者直接使用定时器扫描的方式做消抖。

6.3 运行结果与验证

编译烧录后,预期行为是:

  1. 按下按键,LED 开始以呼吸方式渐亮渐灭;
  2. 再次按下按键,LED 熄灭;
  3. 按键响应基本及时,呼吸渐变不会导致系统卡死。

如果按按键没有反应,先检查按键引脚配置。很多开发板上的按键需要外部上拉,按下时引脚接地;也有些板子是下拉结构,按下时为高电平。GPIO 外部中断的触发沿配置错了,回调函数就不会被调用。

如果 LED 不呼吸而是一直微亮或一直不亮,重点检查 PWM 定时器对应通道是否正确,以及比较值的修改是否写到了正确的句柄上。

7. STM32 进阶学习必须知道的底层概念

当你发现自己不再满足于点灯,开始用外部中断、定时器、PWM 做项目时,新的问题会接踵而至。有不少概念是看例程永远看不懂,但一旦解决了,就会对嵌入式开发有质的理解。

7.1 GPIO 之外的门道

很多单片机初学者把 GPIO 理解为“可以输出高低电平的引脚”。这个理解不能算错,但对 STM32 来说不够。STM32 的 GPIO 有很多工作模式:

  • 推挽输出:既能输出高电平也能输出低电平,驱动能力较强;
  • 开漏输出:只能主动拉低,高电平靠外部上拉电阻实现,常用于电平转换和多设备共享总线;
  • 浮空输入:引脚既不接地也不接电源,外部状态完全由外部电路决定,容易受干扰;
  • 上拉输入/下拉输入:内部有弱上拉或弱下拉电阻,适合接按键等场景。

为什么这点重要?因为在流水灯里你只需要推挽输出,但在接 I2C、接开漏设备、接按键时,如果选错模式,设备要么读写失败,要么状态不稳定。STM32 的每个外设并不是简单“启用”就结束,多数外设引脚在输出波形前还要配置成复用功能模式。

7.2 启动模式与存储器重映射

很多新手会在“下载成功后板子没反应”这个问题上卡很久。程序明明编译成功,下载也显示成功,但复位后 LED 就是不亮。其中有一种原因就和启动模式有关。

STM32 芯片内部有 Flash、SRAM,也可能有系统存储器。系统存储器中预置了出厂 BootLoader。芯片上电复位后,从哪里开始执行程序,取决于 BOOT0 和 BOOT1 引脚的电平状态。以常见的 STM32F1 系列为例,典型配置是:

  • BOOT0 = 0,从主 Flash 启动,执行你下载到 0x08000000 地址的程序;
  • BOOT0 = 1,BOOT1 = 0,从系统存储器启动,进入出厂 BootLoader;
  • BOOT0 = 1,BOOT1 = 1,从 SRAM 启动。

如果你把程序下载到了 Flash,但 BOOT0 被跳线接到高电平,芯片上电后进入的是系统存储器 BootLoader,而不会执行 Flash 里的用户程序。这不是程序代码的问题,而是启动配置的问题。

存储器重映射则和“启动地址怎么等于访问地址”有关系。Cortex-M 内核复位后需要从地址 0x00000000 读取初始栈指针和复位向量,但 STM32 的主 Flash 实际上位于地址 0x08000000。芯片通过启动方式决定了 0x00000000 这个地址如何映射到不同物理存储器。理解这一点后,你再回头看调试器里的“下载地址”“PC 值”“向量表偏移”,就不会觉得它们只是抽象概念了。

7.3 时钟系统

STM32 内部是一个复杂的多总线系统,CPU 通过 AHB、APB1、APB2 等多条总线访问内存和外设。每条总线上的外设都需要对应的时钟使能后才能正常工作。这也是很多新手从 51 单片机转过来后不习惯的地方:在 STM32 中,没有打开某个外设的时钟,直接操作它的寄存器通常无效。

更隐蔽的问题在于时钟源配置。STM32 通常使用外部高速晶振 HSE 作为参考时钟源,经过 PLL 倍频得到系统时钟 SYSCLK。如果你的开发板实际焊接的晶振频率是 12MHz,但工程里 CubeMX 配置成 8MHz,系统时钟就会算错,串口波特率也会跟着错乱。这就是“同一个串口例程,在别人板上正常,在自己板上乱码”的经典原因。

8. STM32 开发中的常见问题与排查清单

自己写完代码后,编译报错和运行异常一定会出现。遇到问题不要急着换板子,先按现象分类,再逐步缩小排查范围。

问题现象可能原因排查方式解决方案
下载成功但板子无反应BOOT 引脚配置错误;程序没下载到正确 Flash 地址;LED 引脚不对检查 BOOT0 电平;查看调试器下载地址;对照原理图确认引脚BOOT0 接到低电平从 Flash 启动;按实际原理图修改 GPIO 引脚
HAL_Delay 卡死或程序无法进入主循环SysTick 中断被关闭;中断优先级配置不当;HAL_Init 未调用在 HAL_Delay 处设置断点,查看 SysTick 是否还在递增确认 HAL_Init 已执行;检查中断优先级分组是否和外设冲突
加入中断后系统频繁死机在中断回调里调用阻塞函数;多个中断同时访问同一变量在回调入口打断点,观察执行轨迹;查看 HardFault_Handler 是否被触发中断回调里只置标志,耗时逻辑放主循环或用信号量机制处理
串口输出乱码波特率不匹配;系统时钟和外部晶振不一致;TX/RX 接反用示波器或逻辑分析仪看波形;确认 CubeMX 时钟树中 HSE 频率检查开发板晶振频率;统一两侧波特率;确认串口线交叉连接
PWM 没有输出没有调用 HAL_TIM_PWM_Start;GPIO 未配置为复用模式;比较值为 0检查初始化代码;查看 GPIO 模式寄存器调用 PWM 启动函数;把 LED 引脚模式改为复用推挽输出;修改比较值
按键中断触发不可控按键未消抖;上拉/下拉配置和电路不一致用调试器观察 GPIO 输入电平变化增加软件消抖;按原理图配置正确的内部上拉或下拉
程序运行一段时间后跑飞栈溢出;数组越界;中断处理时间过长打开调试器查看 PC 位置;检查 Keil 的栈大小设置增大启动文件中的栈空间;检查数组边界;优化中断回调逻辑

上面表格里的大部分问题,并不是芯片本身缺陷,而是工程配置或代码结构引入的。排查时有一个通用原则:先确认硬件连接和供电,再检查工程配置,最后看业务逻辑。不要一上来就怀疑寄存器写错,很多问题到最后都是引脚编号抄错或者电源没接好。

9. STM32 的学习建议与工程最佳实践

把定时器、中断、PWM 都跑通之后,你已经不是那个“只会点流水灯”的状态了。接下来要想的不是再买一块更贵的开发板,而是把已经学到的外设组合起来,做出一个完整的小系统。

9.1 别让外设例程停留在演示层

一个很常见的现象是:每学一个外设,就单独跑一个例程。GPIO 例程点亮 LED,定时器例程精确翻转电平,串口例程发送一串字符,ADC 例程读取电压。这些例程都跑通了,但合在一起时,却不知道代码该怎么组织。

更好的做法,是给自己设计一个综合小项目。比如做一个简单的温控风扇:

  • 用 ADC 采集电位器电压,模拟温度传感器输入;
  • 用定时器产生 PWM,控制风扇转速;
  • 用按键切换自动/手动模式;
  • 用串口把当前电压值、模式、PWM 占空比上报出来;
  • 用 LED 指示系统运行状态。

这样,流水灯里学到的 GPIO 变成了系统的一部分,定时器不再只是用来翻转 LED,而是真正承担了 PWM 输出任务,串口也能帮你定位程序运行到哪一步出了问题。完成这个小系统后,你的代码组织能力会明显超过“点灯时期”。

9.2 工程化习惯

嵌入式工程和单片机小实验之间,差的往往不只是代码量,还有工程习惯。下面几条是我认为值得一早就养成的习惯:

第一,主循环不要写成一个大杂烩。把按键扫描、状态更新、串口发送拆成独立函数,每一段只干一件事。流水灯的代码可以用 while 循环硬写,但一个温控系统如果还用硬写方式,过两周你自己都很难维护。

第二,中断里不要做复杂运算和阻塞延时。中断处理应当是短小、快速的。如果你发现中断回调里有超过几十行的处理逻辑,就应该考虑把处理降级到主循环或任务中。

第三,全局变量要尽量少。跨文件共享的状态可以使用明确命名的模块接口。你不需要一开始就引入复杂的架构,但至少要让自己的代码看起来不是随便堆出来的。

第四,修改外设初始化时,先看数据手册,再看厂家例程,不要盲改。原厂资料和开发板原理图是最可靠的信息源。

9.3 学习路径建议

如果你是从流水灯位置决定往前走的读者,可以参考下面的

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询