1. 课程导论与开发环境搭建:为什么需要软件架构?
各位同学,大家好。我是你们这门课的老朋友。说实话,做了十几年嵌入式,踩过的坑比吃过的盐还多。今天咱们开篇第一讲,不急着敲代码,先聊聊一个看似“虚”但实则非常要命的问题——为什么我们需要软件架构?
很多人觉得,STM32裸机开发嘛,不就是写个while(1)循环,加点中断,调调外设就完事了?我以前也这么想。直到有一次,我接手一个同事离职留下的项目——一个智能家居网关。好家伙,main函数里两千多行,全局变量满天飞,中断服务函数里还调延时。改一个功能,三个地方出bug。那段时间我天天加班到凌晨,心里就一个念头:这代码要是有点架构,至于吗?
1.1 STM32裸机开发的痛点分析
咱们先来盘点一下,裸机开发到底有哪些“坑”。你想想看,是不是经常遇到这些问题?
| 痛点 | 具体表现 | 后果 |
|---|---|---|
| 代码耦合严重 | LED闪烁逻辑写在按键中断里,定时器回调里又去操作串口 | 改一处,崩一片 |
| 全局变量泛滥 | extern uint8_t flag; 满文件飞,不知道谁改的 | 调试像破案,心累 |
| 模块复用性差 | 这个项目的I2C驱动,换个芯片引脚就得重写 | 重复造轮子,效率低 |
| 可读性灾难 | main函数里直接操作寄存器,没有函数封装 | 自己写的代码,两周后也看不懂 |
| 调试困难 | 逻辑和硬件初始化混在一起,不知道是配置错了还是逻辑错了 | 定位问题耗时占开发60% |
说白了,裸机开发不是不能做,而是没有章法地做,项目一复杂就崩。我见过太多人,写个流水灯没问题,但一做多任务、多外设协同,代码就成了一团乱麻。
1.2 为什么需要软件架构?
软件架构不是什么高大上的东西。它其实就是一套约定和规则,告诉你的代码该怎么组织、模块之间怎么通信、数据怎么流动。
我个人习惯把架构比作“盖房子的脚手架”。没有脚手架,你也能砌墙,但砌不高、砌不稳,风一吹就倒。有了架构,你才能一层一层往上盖,出了问题也知道是哪根柱子歪了。
咱们这门课要讲的模块化编程架构,核心就三个原则:
- 高内聚:一个模块只做一件事,并且把它做好。比如LED模块只管亮灭,别管按键。
- 低耦合:模块之间通过接口通信,不直接访问对方的内部变量。
- 分层设计:硬件驱动层、中间件层、应用逻辑层,各司其职。
你想想看,如果代码按这个思路写,是不是改一个功能,其他模块不用动?是不是换个芯片平台,只需要重写底层驱动,上层逻辑直接复用?
1.3 裸机模块化架构的核心逻辑
为了让大家更直观地理解,我画了一张图。这张图就是咱们整个课程要实现的目标架构。
这张图就是咱们这门课的终极目标。从下往上,每一层都只依赖它的下一层。应用层不知道你用的是STM32F103还是F407,它只管调用中间件接口。中间件也不知道GPIO是怎么配置的,它只管调用HAL层的API。
这样做的好处,我在项目里体会太深了。有一次客户要求把主控从STM32F103换成GD32F303,我只需要重写最底层的HAL驱动,上面的中间件和应用层一行代码没改,两天就搞定了。要是以前那种“大泥球”代码,估计得折腾两周。
1.4 开发环境搭建与工程模板创建
好了,理念讲完了,咱们来点实际的。工欲善其事,必先利其器。咱们得先把开发环境搭好。
市面上主流的STM32开发环境有三个:Keil MDK、IAR EWARM 和 STM32CubeIDE。我个人习惯用Keil,因为生态成熟,第三方库支持好。但STM32CubeIDE免费且内置了CubeMX,对新手很友好。IAR的编译优化确实强,但价格劝退。
| 特性 | Keil MDK | IAR EWARM | STM32CubeIDE |
|---|---|---|---|
| 价格 | 收费(有免费版限制32KB) | 收费(较贵) | 免费 |
| 编译效率 | 中等 | 高 | 中等(基于GCC) |
| 调试器支持 | J-Link / ST-Link / ULINK | J-Link / I-jet | ST-Link(原生支持) |
| CubeMX集成 | 需手动导入 | 需手动导入 | 原生集成 |
| 适合人群 | 企业项目、老手 | 对代码大小/速度有极致要求 | 新手、学生、预算有限 |
1.5 创建你的第一个模块化工程模板
咱们不搞复杂的,先创建一个最简的工程模板。这个模板会贯穿整个课程,后续所有章节的代码都基于这个模板扩展。
步骤很简单:
- 新建文件夹结构:按架构分层创建目录
- 配置启动文件和链接脚本:从STM32Cube固件包复制
- 编写main.c框架:只包含初始化循环和主循环骨架
- 添加一个简单的LED模块:验证编译通过
文件夹结构我建议这样组织:
Project/
├── App/ # 应用逻辑层
│ └── app_main.c
├── Middleware/ # 中间件层
│ └── led/ # LED模块(后续会扩展)
│ ├── led.c
│ └── led.h
├── Hal/ # 硬件抽象层
│ ├── gpio/
│ │ ├── hal_gpio.c
│ │ └── hal_gpio.h
│ └── timer/
│ ├── hal_timer.c
│ └── hal_timer.h
├── Mcu/ # MCU底层
│ ├── startup_stm32f103xe.s
│ └── system_stm32f1xx.c
├── Drivers/ # 官方标准外设库或HAL库
│ └── CMSIS/
└── Project.uvprojx # Keil工程文件
嗯,这里要注意:不要把所有的.c文件都扔在一个文件夹里。我见过太多人,一个Src文件夹里塞了50多个文件,找起来眼睛都花了。按模块分文件夹,这是架构的第一步。
main.c的骨架也很简单:
#include "hal_gpio.h"
#include "led.h"
int main(void)
{
/* 硬件初始化 */
HAL_GPIO_Init();
/* 模块初始化 */
LED_Init();
/* 主循环 */
while (1)
{
LED_Toggle();
Delay_Ms(500);
}
}
你看,main函数里只有三部分:初始化、主循环。具体怎么闪烁,那是LED模块内部的事。main函数只负责“调度”,不负责“实现”。这就是架构的雏形。
1.6 本章小结
这一章咱们聊了不少。从裸机开发的痛点,到为什么需要架构,再到环境搭建和工程模板。说白了,就是让你明白一个道理:写代码和写“好”代码,中间差的就是一个架构。
我刚开始做嵌入式的时候,也觉得架构是浪费时间。直到被项目毒打了几次,才明白“磨刀不误砍柴工”的道理。这门课,我会把我这些年积累的模块化架构经验,一点一点拆给你看。