雷猴啊~我是无际。

我还记得当年刚踏入嵌入式开发领域的时候,对软件架构完全没有概念。写代码想到哪写到哪,最后拼凑成一个能跑的程序。

但随着项目越来越复杂,代码也越来越臃肿,维护起来简直就是一场噩梦。改动一个小功能,都要提心吊胆,生怕把其他地方搞崩了。

工作大概4年左右,有幸接接手了大牛做的项目维护,感觉他的功底深不可测,能把复杂的系统拆解成一个个清晰的模块,而且封装得也挺复杂,当时看还挺复杂,挺不解,写这么复杂干吊?看起来头疼。

后面自己独立做复杂点的项目,才发现他的架构是真香,因此吸收了很多他的思路和架构。

如果你也想提升自己的代码质量,那么这篇文章就是为你准备的!我会用最通俗易懂的语言,带你了解嵌入式软件开发中常见的几种架构模式,让你摆脱“面向过程编程”的原始状态,掌握“面向架构编程”的高级技巧。

无论你是初入茅庐的新手,还是想进阶提升的老鸟,这篇文章都能帮你打造更清晰、更稳定、更易维护的嵌入式系统。

好了,废话不多说,我们直接进入主题。

1.轮询(Polling)架构:简单直接,但容易阻塞

轮询架构是最简单、最基础的架构模式,就像一个不知疲倦的“巡逻员”,不停地循环检查各个任务的状态,哪个任务需要执行就执行哪个。

基本原理:

轮询架构的核心是一个无限循环(while(1)),在循环中按照一定的顺序依次执行各个任务模块。每个任务模块通常是一个函数,执行完成后立即返回,继续执行下一个任务。

代码示例:

void task1()

{

// 任务1的代码

}

void task2()

{

// 任务2的代码

}

int main()

{

while (1)

{

task1();

task2();

// ... 其他任务

}

return 0;

}

适用场景:

任务比较简单,实时性要求不高。

系统资源非常有限,无法支持更复杂的架构。

初学者学习和理解嵌入式编程的入门架构。

优缺点:

优点: 简单易懂,易于实现,代码量少。

缺点: 响应速度慢,所有任务共享CPU时间,容易出现“任务饥饿”现象(某个任务一直得不到执行);如果某个任务执行时间过长,会阻塞其他任务的执行。

实战避坑

尽量让每个任务的执行时间短,避免长时间占用CPU。

可以调整任务的执行顺序,将优先级较高的任务放在前面执行,以提高响应速度。

轮询架构适合简单的控制逻辑和数据采集,但不适合复杂的实时系统。

2. 中断(Interrupt)架构:快速响应,但需要谨慎管理

中断架构是一种基于事件驱动的简易架构模式,当外部事件发生时,会触发中断,CPU暂停当前任务,立即执行中断服务程序(ISR)。

基本原理:

中断架构将任务分为“前台任务”和“后台任务”。后台任务在主循环中执行,前台任务(即中断服务程序)在中断发生时执行。

代码示例:

// 中断服务程序

void EXTI0_IRQHandler()

{

if (EXTI_GetITStatus(EXTI_Line0) != RESET)

{

// 处理中断事件

EXTI_ClearITPendingBit(EXTI_Line0);

}

}

int main()

{

// 初始化中断

// ...

while (1)

{

// 后台任务

}

return 0;

}

适用场景:

需要快速响应外部事件,如按键按下、传感器数据变化等。

系统有严格的实时性要求。

外部设备需要实时监控,例如串口接收数据。

优缺点:

优点: 响应速度快,能及时处理紧急事件;CPU利用率高,可以在后台执行其他任务。

缺点: 中断服务程序应该尽可能短,避免长时间占用CPU;中断嵌套管理复杂,容易导致中断优先级反转等问题。

实战避坑:

中断服务程序要“短小精悍”,只做最必要的工作,其他耗时操作交给后台任务处理。

合理设置中断优先级,避免高优先级的中断被低优先级的中断阻塞。

避免在中断服务程序中使用阻塞函数,如delay(),printf()等。

3. 状态机(State Machine)架构:逻辑清晰,但容易“状态爆炸”

状态机架构是一种将系统划分为多个状态,并根据输入事件在不同状态之间进行转换的架构模式。

基本原理:

状态机将系统抽象成有限个状态,每个状态下有特定的行为和转移条件。通过状态转移图或表来管理状态间的转换。

代码示例:

typedef enum

{

STATE_IDLE,

STATE_RUNNING,

STATE_ERROR

} State_t;

State_t currentState = STATE_IDLE;

void stateMachine()

{

switch (currentState)

{

case STATE_IDLE:

if (startCondition)

{

currentState = STATE_RUNNING;

}

break;

case STATE_RUNNING:

if (errorCondition)

{

currentState = STATE_ERROR;

}

break;

case STATE_ERROR:

// 处理错误

break;

}

}

int main()

{

while (1)

{

stateMachine();

// ... 其他任务

}

return 0;

}

适用场景:

系统有明显的“状态”概念,如通信协议、控制系统、用户界面、不同的模式等。

逻辑比较复杂,需要清晰的状态管理。

需要明确的流程控制,例如按键检测,电梯运行等。

优缺点:

优点: 逻辑清晰,易于维护和扩展;能有效管理复杂的状态转移。

缺点: 状态过多时,代码量会急剧增加,维护成本高;状态转移逻辑错误容易导致“死循环”等问题。

实战避坑:

在编写代码之前,先画好状态转移图,明确每个状态的行为和转移条件。

尽量减少状态的数量,避免“状态爆炸”。

可以使用状态机框架或工具来简化开发,例如UML状态机图。

4. 实时操作系统(RTOS)架构:多任务并行,但需要更多资源

实时操作系统(RTOS)是一种专门用于嵌入式系统的操作系统,它提供了任务调度、任务间通信、资源管理等功能,让开发者可以像编写PC程序一样编写嵌入式软件。

基本原理:

RTOS提供任务调度器,可以将系统划分为多个独立的任务(线程),每个任务都有自己的优先级和栈空间。RTOS负责调度这些任务,让它们并行执行。

代码示例(以FreeRTOS为例):

#include "FreeRTOS.h"

#include "task.h"

void task1(void *pvParameters)

{

while (1)

{

// 任务1的代码

vTaskDelay(100 / portTICK_PERIOD_MS); // 延时100ms

}

}

void task2(void *pvParameters)

{

while (1)

{

// 任务2的代码

vTaskDelay(200 / portTICK_PERIOD_MS); // 延时200ms

}

}

int main() {

xTaskCreate(task1, "Task1", 128, NULL, 1, NULL);

xTaskCreate(task2, "Task2", 128, NULL, 2, NULL);

vTaskStartScheduler();

return 0;

}

适用场景:

系统功能复杂,需要多个任务并行执行。

对实时性和响应速度有较高要求。

需要复杂的任务管理和资源管理。

优缺点:

优点: 任务管理灵活,易于实现复杂功能;提高系统响应速度;可以有效利用CPU资源。

缺点: 资源消耗较大,不适合资源受限的单片机;学习成本高;需要考虑任务同步和互斥等问题。

实战避坑:

根据项目和应用场景选择合适的RTOS,如FreeRTOS、uC/OS等。

合理分配任务优先级和栈空间,避免任务冲突和栈溢出。

使用信号量、互斥锁等机制进行任务间通信和资源保护。

避免在中断服务程序中调用RTOS API。

5. 混合架构:灵活应对,但需要精心设计

在实际开发中,单一的架构模式往往难以满足所有需求,因此经常会采用混合架构,将多种架构模式结合起来使用。例如,“轮询 + 中断”、“状态机 + RTOS”等。

这次我们以一个简单的温度监控系统为例,该系统需要:

实时监控温度传感器: 需要中断来快速响应温度变化。

根据温度进行状态切换: 需要状态机来管理不同的工作模式(例如:正常、高温报警、低温报警)。

定期进行数据记录: 需要轮询来定期保存温度数据到存储器。

// 1. 定义状态

typedef enum

{

STATE_NORMAL,

STATE_HIGH_TEMP_ALARM,

STATE_LOW_TEMP_ALARM

} TemperatureState_t;

TemperatureState_t currentTemperatureState = STATE_NORMAL;

// 2. 定义全局变量

volatile float temperature = 25.0; // 当前温度,volatile 确保中断和主循环都能访问

bool newDataAvailable = false; // 新数据标志

// 3. 温度传感器中断服务程序 (假设通过ADC读取)

void ADC_IRQHandler()

{

if (ADC_GetITStatus(ADC1, ADC_IT_EOC) != RESET)

{

// 读取ADC值

uint16_t adcValue = ADC_GetConversionValue(ADC1);

// 转换成温度值 (简化计算)

temperature = (float)adcValue * 0.1; // 假设每0.1代表1摄氏度

newDataAvailable = true; // 设置新数据标志

// 清除中断标志位

ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);

}

}

// 4. 状态机函数

void temperatureStateMachine()

{

switch (currentTemperatureState)

{

case STATE_NORMAL:

if (temperature > 35.0)

{

currentTemperatureState = STATE_HIGH_TEMP_ALARM;

// 启动风扇

GPIO_SetBits(FAN_PORT, FAN_PIN);

printf("High Temperature Alarm!\n");

}

else if (temperature < 10.0)

{

currentTemperatureState = STATE_LOW_TEMP_ALARM;

// 启动加热器

GPIO_SetBits(HEATER_PORT, HEATER_PIN);

printf("Low Temperature Alarm!\n");

}

break;

case STATE_HIGH_TEMP_ALARM:

if (temperature <= 30.0)

{

currentTemperatureState = STATE_NORMAL;

// 关闭风扇

GPIO_ResetBits(FAN_PORT, FAN_PIN);

printf("Normal Temperature.\n");

}

break;

case STATE_LOW_TEMP_ALARM:

if (temperature >= 15.0)

{

currentTemperatureState = STATE_NORMAL;

// 关闭加热器

GPIO_ResetBits(HEATER_PORT, HEATER_PIN);

printf("Normal Temperature.\n");

}

break;

}

}

// 5. 数据记录任务 (轮询方式)

void dataLoggingTask()

{

static uint32_t lastLogTime = 0;

uint32_t currentTime = HAL_GetTick(); // 获取当前时间 (需要HAL库支持)

if (currentTime - lastLogTime >= 5000) // 每5秒记录一次

{

lastLogTime = currentTime;

// 将温度数据记录到存储器 (这里简化成打印)

printf("Logging: Temperature = %.2f, State = %d\n", temperature, currentTemperatureState);

// 实际应用中,需要写入Flash或者SD卡

}

}

// 6. 主函数

int main()

{

// 初始化 ADC,GPIO,中断

// ... (初始化代码略)

HAL_Init();

SystemClock_Config();

MX_GPIO_Init();

MX_ADC1_Init();

// 启动ADC转换并使能中断

HAL_ADC_Start_IT(&hadc1);

// 主循环

while (1)

{

// 1. 状态机处理

temperatureStateMachine();

// 2. 数据记录 (轮询)

dataLoggingTask();

// 3. 其他任务 (可以添加更多任务)

// ...

// 4. 降低功耗 (可选)

// HAL_PWR_EnterSleepMode(PWR_LOWPOWERREGULATOR_ON, ADC_IRQn);

}

}

代码说明:

温度传感器中断: 使用ADC读取温度,并在每次读取后设置newDataAvailable标志。volatile关键字确保主循环能及时读取到最新的温度数据。

状态机: temperatureStateMachine() 函数根据温度值,切换系统状态,并控制风扇或加热器的开启/关闭。

数据记录任务: dataLoggingTask() 函数每隔5秒记录一次温度数据和系统状态。

混合架构的优势:

实时响应: 中断确保系统能及时响应温度变化。

状态管理: 状态机负责管理系统的工作模式,并根据温度进行切换。

数据记录: 轮询确保数据能够被定期记录。

这个例子虽然简单,但已经展示了混合架构的基本思想:将不同的架构模式结合起来,充分发挥各自的优势,以满足复杂的系统需求。

适用场景:

系统既有实时性要求,又有复杂的逻辑。

资源有限,需要在性能和复杂度之间进行平衡。

各个模块的功能特性不同,需要采用不同的架构模式。

优缺点:

优点: 灵活性高,可以针对具体问题选择最佳解决方案。

缺点: 架构设计难度较大,需要经验丰富的工程师;架构间的接口和通信需要精心设计,避免出现“缝合怪”现象。

我们从事了10年,就喜欢用自己设计的架构,采用了多种混合架构,缝合了不同框架的优势,相对RTOS更精简更节省资源,很多51单片机也能用。

比如我们无际单片机的项目3和项目6,就是把轮询架构加了一层封装,让它们在管理任务时更加灵活方便,以轮询作为主框架,其余有状态机、表驱动之类的架构配合,具体可以看我前面几篇文章。

添加图片注释,不超过 140 字(可选)

关于这个轮询架构我也在2018年录了个全面的教程,目前开源,无际粉丝可找我安排。

添加图片注释,不超过 140 字(可选)

实战避坑:

在设计混合架构之前,先分析系统需求,明确各个模块的功能和性能要求。

确定核心架构,例如使用RTOS作为主框架,然后根据需要添加其他架构模块。

定义清晰的接口规范,确保各个模块之间的通信和数据交换顺畅。

6. 选择架构:没有最好的,只有最合适的

选择哪种架构模式,并没有绝对的答案,关键是要根据具体的项目需求和资源情况进行选择。

如果任务简单,实时性要求不高,资源有限,那么轮询架构是一个不错的选择。

如果需要快速响应外部事件,或者有严格的实时性要求,那么中断架构是必不可少的。

如果系统逻辑复杂,有明显的状态概念,那么状态机架构可以帮助你更好地管理代码。

如果系统功能复杂,需要多个任务并行执行,那么RTOS架构可以提高系统效率和响应速度。

如果以上几种架构都不能满足你的需求,那么可以考虑采用混合架构,将多种架构模式结合起来使用。

7. 总结

嵌入式软件架构是单片机开发的“灵魂”,它决定了你的代码是“豆腐渣工程”还是“艺术品”。

掌握这些常见的架构模式,你就能更好地组织你的代码,构建更清晰、更稳定、更易维护的嵌入式系统。

希望这篇文章能帮助你摆脱“代码搬运工”的身份,起到抛砖引玉的作用,助你成为一名真正的嵌入式软件架构师!好的架构不仅能提高开发效率,还能让你在开发复杂项目时游刃有余。

最近很多粉丝问我单片机怎么学,我根据自己从业十年经验,累积耗时一个月,精心整理一份「单

片机最佳学习路径+单片机入门到高级教程+工具包」,全部无偿分享给铁粉!!!

除此以外,再含泪分享我压箱底的22个热门开源项目,包含源码+原理图+PCB+说明文档,让你迅速进阶成高手!

教程资料包和详细的学习路径可以看我下面这篇文章的开头。

《单片机入门到高级开挂学习路径(附教程+工具)》

《单片机入门到高级开挂学习路径(附教程+工具)》

《单片机入门到高级开挂学习路径(附教程+工具)》