|

关注公众号,回复“入门资料”获取单片机入门到高级开挂教程
开发板带你入门,我们带你飞
文 | 无际(微信:2777492857)
全文约4501字,阅读大约需要 15 分钟
雷猴啊~我是无际。我还记得当年刚踏入嵌入式开发领域的时候,对软件架构完全没有概念。写代码想到哪写到哪,最后拼凑成一个能跑的程序。
但随着项目越来越复杂,代码也越来越臃肿,维护起来简直就是一场噩梦。改动一个小功能,都要提心吊胆,生怕把其他地方搞崩了。
工作大概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!
"); } else if (temperature 10.0) { currentTemperatureState = STATE_LOW_TEMP_ALARM; // 启动加热器 GPIO_SetBits(HEATER_PORT, HEATER_PIN); printf("Low Temperature Alarm!
"); } break; case STATE_HIGH_TEMP_ALARM: if (temperature 30.0) { currentTemperatureState = STATE_NORMAL; // 关闭风扇 GPIO_ResetBits(FAN_PORT, FAN_PIN); printf("Normal Temperature.
"); } break; case STATE_LOW_TEMP_ALARM: if (temperature >= 15.0) { currentTemperatureState = STATE_NORMAL; // 关闭加热器 GPIO_ResetBits(HEATER_PORT, HEATER_PIN); printf("Normal Temperature.
"); } 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
", 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,就是把轮询架构加了一层封装,让它们在管理任务时更加灵活方便,以轮询作为主框架,其余有状态机、表驱动之类的架构配合,具体可以看我前面几篇文章。
0tpkoozwvja64029034935.png
关于这个轮询架构我也在2018年录了个全面的教程,目前开源,无际粉丝可找我安排。
mwixgdxh52r64029035035.png
?实战避坑:
在设计混合架构之前,先分析系统需求,明确各个模块的功能和性能要求。
确定核心架构,例如使用RTOS作为主框架,然后根据需要添加其他架构模块。
定义清晰的接口规范,确保各个模块之间的通信和数据交换顺畅。
6. 选择架构:没有最好的,只有最合适的
选择哪种架构模式,并没有绝对的答案,关键是要根据具体的项目需求和资源情况进行选择。
?如果任务简单,实时性要求不高,资源有限,那么轮询架构是一个不错的选择。
?如果需要快速响应外部事件,或者有严格的实时性要求,那么中断架构是必不可少的。
?如果系统逻辑复杂,有明显的状态概念,那么状态机架构可以帮助你更好地管理代码。
?如果系统功能复杂,需要多个任务并行执行,那么RTOS架构可以提高系统效率和响应速度。
?如果以上几种架构都不能满足你的需求,那么可以考虑采用混合架构,将多种架构模式结合起来使用。
7. 总结
嵌入式软件架构是单片机开发的“灵魂”,它决定了你的代码是“豆腐渣工程”还是“艺术品”。
掌握这些常见的架构模式,你就能更好地组织你的代码,构建更清晰、更稳定、更易维护的嵌入式系统。
希望这篇文章能帮助你摆脱“代码搬运工”的身份,起到抛砖引玉的作用,助你成为一名真正的嵌入式软件架构师!好的架构不仅能提高开发效率,还能让你在开发复杂项目时游刃有余。
end
xgkonqdxcya64029035135.jpg
下面是更多无际原创的个人成长经历、行业经验、技术干货。
1.电子工程师是怎样的成长之路?10年5000字总结
2.如何快速看懂别人的代码和思维
3.单片机开发项目全局变量太多怎么管理?
4.C语言开发单片机为什么大多数都采用全局变量的形式?
5.单片机怎么实现模块化编程?实用程度让人发指!
6.c语言回调函数的使用及实际作用详解
7.手把手教你c语言队列实现代码,通俗易懂超详细!
8.c语言指针用法详解,通俗易懂超详细! |
|