第1章:课程导论与开发环境搭建
各位同学,欢迎来到《STM32裸机模块化编程与状态机设计实战》。我是你们这门课的主讲。在嵌入式这行摸爬滚打了十几年,踩过的坑、烧过的板子,说实话,真不少。今天这第一讲,我们不急着写代码,先把地基打牢。
你可能会问:为什么第一节课不讲点炫酷的?我的回答是:方向错了,跑得再快也没用。选型选错、工具链没配好、库选得不对,后面全是泪。我见过太多新手,甚至一些老手,因为一开始没想清楚,项目做到一半发现资源不够,或者换芯片重写驱动,那叫一个痛苦。
所以,这节课我们就把三件事聊透:选什么芯片、用什么思想写代码、搭什么环境。
1.1 STM32选型指南:别让芯片成为你的瓶颈
STM32家族庞大得像个动物园。从Cortex-M0到M7,从几十KB Flash到几MB,从几十脚到两百多脚。怎么选?我个人的习惯是,先看需求,再看预算,最后看生态。
| 系列 | 内核 | 主频 | 典型Flash | 适合场景 |
|---|---|---|---|---|
| STM32F0 | Cortex-M0 | 48 MHz | 16-256 KB | 简单控制、替代8位机 |
| STM32F1 | Cortex-M3 | 72 MHz | 32-512 KB | 通用工业控制、电机 |
| STM32F4 | Cortex-M4F | 168-180 MHz | 256-2048 KB | DSP、图形、复杂算法 |
| STM32G0 | Cortex-M0+ | 64 MHz | 16-512 KB | 低功耗、成本敏感 |
| STM32H7 | Cortex-M7 | 400-550 MHz | 1-2 MB | 高性能计算、AI边缘 |
选型时,还有几个关键点你要想清楚:
- 引脚数: 你需要的GPIO、外设接口(UART、SPI、I2C)够不够?别到时候发现少一个定时器通道,那就尴尬了。
- Flash和RAM: 代码体积、堆栈大小、缓冲区。我做过一个项目,用了F103的48KB RAM,结果一个FFT缓冲区就占了32KB,剩下的连个任务栈都分不出来。嗯,后来换了F4。
- 封装: LQFP好焊接,BGA需要打样。自己DIY就选LQFP。
1.2 模块化编程思想概述:让代码像乐高一样搭起来
很多新手写代码,喜欢把所有东西都塞进 main.c。一个文件几千行,全局变量满天飞。这种代码,我称之为“意大利面条式代码”。看着能跑,但改一个功能,可能牵出一堆bug。
模块化编程,说白了就是分而治之。把不同的功能拆成独立的模块,每个模块有自己的 .h 和 .c 文件。模块之间通过接口(函数、结构体)通信,不直接访问对方的内部变量。
- 高内聚: 一个模块只做一件事,并且把它做好。
- 低耦合: 模块之间依赖越少越好。改一个模块,不影响其他模块。
- 接口清晰: 公开的函数和数据结构要稳定,内部实现可以随便改。
举个例子。你写一个LED闪烁程序。非模块化写法:直接在main里操作GPIO寄存器。模块化写法:先写一个 led.c,提供 LED_Init()、LED_On()、LED_Off()、LED_Toggle() 四个函数。main里只管调用。以后你想把LED从PA0换到PB5,只需要改 led.c 里的配置,main里一行代码都不用动。
你想想看,如果项目里有几十个外设,每个都这么搞,维护起来是不是轻松多了?
1.3 状态机设计思想概述:让逻辑不再混乱
嵌入式系统里,大部分行为都是事件驱动的。按键按下、定时器溢出、数据接收完成……这些事件发生后,系统要做出不同的反应。如果用一堆 if-else 或者 switch-case 来写,逻辑一复杂,代码就变成一团乱麻。
状态机(State Machine)就是用来解决这个问题的。它把系统抽象成有限个状态,每个状态下,系统只对特定的事件做出响应,并可能跳转到下一个状态。
一个简单的状态机模型:
- 状态(State): 系统当前所处的模式。比如:空闲、运行、报警。
- 事件(Event): 触发状态转移的条件。比如:按键按下、温度超限。
- 动作(Action): 进入或离开状态时执行的操作。比如:点亮LED、启动电机。
- 转移(Transition): 从当前状态到下一个状态的路径。
状态机的好处是:逻辑可视化、可测试、易扩展。你甚至可以先把状态图画出来,再照着图写代码。这就是我们这门课的核心思想之一。
1.4 开发环境安装与配置:工欲善其事,必先利其器
环境搭建这块,我推荐两种选择:Keil MDK 和 STM32CubeIDE。各有优劣,看你的习惯和项目需求。
| 特性 | Keil MDK | STM32CubeIDE |
|---|---|---|
| 编译器 | ARMCC (v5/v6) | GCC (arm-none-eabi) |
| 调试器 | ULINK / J-Link / ST-Link | ST-Link (内置) |
| 代码生成 | 需配合CubeMX | 内置CubeMX |
| 许可证 | 商业收费(有代码大小限制的免费版) | 免费开源 |
| 生态 | 老牌、工业界广泛使用 | ST官方、更新快 |
安装步骤其实很简单,但有几个坑要注意:
- Keil安装: 先装Keil,再装对应的芯片包(Pack)。别装反了。我记得有一次帮同事装,他先装了Pack,结果Keil死活认不到芯片。嗯,顺序很重要。
- STM32CubeIDE: 直接从ST官网下载,解压即用。注意安装路径不要有中文和空格。我见过有人装在“D:\程序\STM32”下面,结果编译报错,找了一下午原因。
- 驱动: 如果你用ST-Link,记得装驱动。Win10/11一般自动识别,但Win7需要手动装。插上调试器,设备管理器里看到“STMicroelectronics STLink dongle”就对了。
- 第一个工程: 新建工程,选好芯片,配置时钟(一般先选外部晶振HSE,然后配PLL到最高主频),然后写一个简单的GPIO翻转程序,下载进去,看LED亮不亮。亮了,环境就通了。
1.5 标准外设库与HAL库的选择:别纠结,看场景
这是新手问得最多的问题。标准外设库(SPL)和HAL库,到底学哪个?
我的看法很直接:学HAL库,但理解SPL的思想。
- 标准外设库(SPL): 直接操作寄存器,代码执行效率高,体积小。但每个外设的初始化代码都要自己写,移植性差。ST已经停止更新了,只支持到F1/F4等老系列。
- HAL库: 抽象层封装,API统一,跨系列移植方便。CubeMX可以直接生成初始化代码。缺点是代码体积大,执行效率略低(但大部分场景下你感觉不到)。
为什么我推荐学HAL库?因为现在的趋势就是HAL。新出的G0、G4、H5、H7系列,ST只提供HAL库。你学SPL,以后换芯片还得重新学。而且HAL库的代码可读性好,配合CubeMX,开发效率高很多。
举个例子。你用HAL库的 HAL_GPIO_TogglePin() 翻转一个IO,频率可能只有几MHz。但如果你直接操作 GPIOA->ODR ^= (1 << 5),频率可以到几十MHz。这就是差距。但平时点个灯、读个按键,HAL库完全够用。