单片机软件架构概述:什么是软件架构、为什么需要架构、常见架构模式对比
大家好,我是老李。做单片机开发十几年了,踩过的坑比吃过的盐还多。今天咱们聊聊软件架构——这个听起来有点虚,但实际上能救命的东西。
很多人觉得,单片机嘛,不就是写个循环,加点中断,完事了?嗯,我刚开始也是这么想的。直到有一次,一个智能家居项目,功能越加越多,代码改到后面我自己都看不懂了。客户要加个新功能,我愣是改了一个星期,还改出了三个bug。从那以后,我彻底明白了:没有架构的代码,就是给自己挖坑。
什么是软件架构?
说白了,软件架构就是代码的骨架。它决定了你的程序怎么组织、模块之间怎么通信、数据怎么流动。
我习惯把架构比作盖房子。你想想看,没有设计图纸,直接砌砖,能盖出高楼吗?能,但大概率是危房。软件架构就是那张图纸。
具体到单片机,架构要回答几个问题:
- 主循环里该放什么?
- 中断里该处理什么?
- 任务之间怎么同步?
- 数据怎么共享?
这些问题想清楚了,代码写起来就顺了。
为什么需要架构?
我遇到过不少工程师,上来就写代码,写到一半发现逻辑乱了。为什么会这样?因为没有架构。
架构的好处,我总结了几点:
- 可维护性:三个月后,你还能看懂自己的代码。我曾经接手过一个项目,前任工程师离职了,代码没有架构,我花了整整两周才理清楚逻辑。那两周,我每天都在骂人。
- 可扩展性:加功能不用大改。我记得有个项目,客户临时要加个蓝牙控制。因为有架构,我只加了一个模块,两天就搞定了。同事问我怎么这么快?我说,架构好,改起来就是爽。
- 可靠性:减少bug。架构清晰了,逻辑就不容易乱。我有个习惯,写代码前先画架构图。画清楚了再动手,bug率至少降一半。
- 团队协作:大家分工明确。你写驱动,我写应用,他写协议栈。各干各的,互不干扰。
核心观点:没有架构的项目,前期省时间,后期加倍还。我见过太多项目死在维护阶段。
常见架构模式对比
单片机开发中,最常见的架构模式有三种:前后台系统、RTOS(实时操作系统)、状态机。咱们一个一个说。
1. 前后台系统
这是最传统的架构。前台是中断,后台是主循环。中断处理紧急事件,主循环处理常规任务。
代码大概长这样:
void main(void)
{
// 初始化
SystemInit();
while(1)
{
// 后台任务
Task1();
Task2();
Task3();
}
}
// 中断服务函数
void TIM_IRQHandler(void)
{
// 前台处理
Flag_Tick = 1;
}
优点很明显:简单、资源占用少。我刚开始做开发时,用的就是这种架构。一个8位单片机,跑得飞起。
但缺点也很致命:实时性差。如果某个任务卡住了,后面的任务都得等。我曾经在一个项目中,因为一个延时函数没写好,导致整个系统响应慢了200毫秒。客户投诉说按键不灵敏,我查了三天才找到原因。
适用场景:功能简单、任务少、对实时性要求不高的项目。比如电子钟、温度计、简单的遥控器。
避坑指南:我曾经在前后台系统中,把大量计算放在中断里。结果中断时间太长,主循环几乎没机会跑。系统直接死机了。记住:中断里只做最紧急的事,别做复杂计算。
2. RTOS(实时操作系统)
RTOS就是给单片机装了个小系统。任务可以独立运行,互不干扰。常用的有FreeRTOS、uC/OS、RT-Thread等。
代码示例:
void Task_LED(void *pvParameters)
{
while(1)
{
LED_Toggle();
vTaskDelay(500); // 延时500ms
}
}
void Task_UART(void *pvParameters)
{
while(1)
{
UART_Send("Hello\n");
vTaskDelay(1000);
}
}
int main(void)
{
xTaskCreate(Task_LED, "LED", 128, NULL, 1, NULL);
xTaskCreate(Task_UART, "UART", 128, NULL, 1, NULL);
vTaskStartScheduler();
}
RTOS的好处是:任务独立、实时性好、代码清晰。我做过一个多传感器采集项目,用了RTOS后,每个传感器一个任务,互不干扰。调试起来特别方便。
但RTOS也有代价:占用资源多、学习成本高。我记得第一次用FreeRTOS时,光是理解任务调度就花了一周。而且,如果任务优先级设置不当,容易出现优先级反转的问题。
适用场景:功能复杂、任务多、对实时性要求高的项目。比如无人机、机器人、工业控制器。
个人经验:我建议初学者先从前后台系统开始,等遇到瓶颈了再上RTOS。别一上来就搞RTOS,容易把自己搞晕。
3. 状态机
状态机是一种思想,不是具体的系统。它把系统分成若干个状态,每个状态处理特定的事情。状态之间通过事件切换。
代码示例:
typedef enum
{
STATE_IDLE,
STATE_RUNNING,
STATE_ERROR
} SystemState_t;
SystemState_t currentState = STATE_IDLE;
void StateMachine_Run(void)
{
switch(currentState)
{
case STATE_IDLE:
// 空闲处理
if(Event_Start)
currentState = STATE_RUNNING;
break;
case STATE_RUNNING:
// 运行处理
if(Event_Error)
currentState = STATE_ERROR;
if(Event_Stop)
currentState = STATE_IDLE;
break;
case STATE_ERROR:
// 错误处理
if(Event_Reset)
currentState = STATE_IDLE;
break;
}
}
状态机的优点是:逻辑清晰、容易调试。我做过一个按键扫描程序,用状态机实现了单击、双击、长按的识别。代码写得很干净,后来同事接手,一看就懂。
缺点呢?状态多了,管理起来麻烦。我曾经做过一个通信协议解析,状态有20多个,画状态图都画了半天。
适用场景:逻辑复杂、有明确状态切换的项目。比如按键处理、通信协议解析、菜单系统。
三种架构对比
| 特性 | 前后台系统 | RTOS | 状态机 |
|---|---|---|---|
| 资源占用 | 极低 | 较高 | 低 |
| 实时性 | 差 | 好 | 中等 |
| 代码复杂度 | 低 | 高 | 中等 |
| 可维护性 | 差 | 好 | 好 |
| 学习成本 | 低 | 高 | 中等 |
| 适用场景 | 简单项目 | 复杂项目 | 逻辑控制 |
怎么选?
说实话,没有最好的架构,只有最合适的。我个人的选择原则是:
- 如果项目功能少于5个,用前后台系统。简单直接,不折腾。
- 如果项目功能多、实时性要求高,上RTOS。别犹豫,后期改架构更痛苦。
- 如果项目逻辑复杂、状态切换多,用状态机。哪怕配合前后台系统也行。
另外,这三种架构不是互斥的。我做过一个项目,主架构是RTOS,但某个任务内部用了状态机。两者结合,效果很好。
最后说一句:架构是工具,不是目的。别为了用架构而用架构。我见过有人用RTOS做电子钟,结果资源不够,还得换芯片。何必呢?
好了,这一章就聊到这里。下一章咱们聊聊怎么从零开始设计一个软件架构。到时候我会拿一个实际项目做例子,手把手教大家。