嵌入式软件开发常用的三种架构你知道吗?
ccwgpt 2024-10-08 01:08 29 浏览 0 评论
摘要:对于单片机程序来说,大家都不陌生,但是真正使用架构,考虑架构的恐怕并不多,随着程序开发的不断增多,架构是非常必要的。
应用程序的架构大致有三种:
1、简单的前后台顺序执行程序,这类写法是大多数人使用的方法,不需用思考程序的具体架构,直接通过执行顺序编写应用程序即可。
2、时间片轮询法,此方法是介于顺序执行与操作系统之间的一种方法。
3、操作系统,此法应该是应用程序编写的最高境界。
一、程序框架设计
1、前后台顺序执行法
这是初学者们常用的程序框架设计方案,不用考虑太多东西,代码简单,或者对系统的整体实时性和并发性要求不高;初始化后通过while(1){}或for(;;){}`循环不断调用自己编写完成的函数,也基本不考虑每个函数执行所需要的时间,大部分情况下函数中或多或少都存在毫秒级别的延时等待。
- 优点:对于初学者来说,这是最容易也是最直观的程序架构,逻辑简单明了,适用于逻辑简单,复杂度比较低的软件开发。
- 缺点:实时性低,由于每个函数或多或少存在毫秒级别的延时,即使是1ms,也会造成其他函数间隔执行时间的不同,虽然可通过定时器中断的方式,但是前提是中断执行函数花的时间必须短。当程序逻辑复杂度提升时,会导致后来维护人员的大脑混乱,很难理清楚该程序的运行状态。
以下是在校期间做的寝室防盗系统的部分代码(当时也存在部分BUG,没有解决。现在再看,其实很多问题,而且比较严重,比如中断服务函数内竟然有3000ms延时,这太可怕了,还有串口发送等等;由于实时性要求不算太高,因此主函数中的毫秒级别延时对系统运行没有多大影响,当然除BUG外;若是后期需要维护,那就是一个大工程,还不如推翻重写):
int main(void)
{
u8 temperature;
u8 humidity;
int a;
delay_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
I2c_init();
uart2_Init(9600);
uart_init(9600);//串口初始化为115200
TIM3_Int_Init(4999,7199);
ds1302_init();
while(DHT11_Init())//DHT11初始化
{
led2=0;
}
a1602_init();
Ds1302Init();
EXTIX_Init();
GPIOX_Init();
lcd12864_INIT();
LcdInit();
beep_init();
RED_Init();
led1=1;
beep=0;
while(1)
{
for(a=0;a<11;a++)
{
num[a+3]=At24c02Read(a+2)-208;
delay_us(10);
}
for(a=0;a<6;a++)
{
shuru[a]=At24c02Read(a+13)-208;
delay_us(10);
}
delay_ms(10);
RED_Scan();
Ds1302ReadTime(); //读取ds1302的日期时间
shi=At24c02Read(0); //读取闹钟保存的数据
delay_ms(10);
fen=At24c02Read(1); //读取闹钟保存的数据
usart2_scan(); //蓝牙数据扫描
usart2_bian(); //蓝牙处理数据
usart2_gai();
nao_scan();
k++;
if(k<20)
{
if(k==1)
LcdWriteCom(0x01);//清屏
LcdDisplay(); //显示日期时间
}
if(RED==0)
RED_Scan();
if(k>=20&&k<30)
{
if(k==20)
LcdWriteCom(0x01); //清屏
Lcddisplay(); //显示温湿度
LcdWriteCom(0x80+6);
DHT11_Read_Data(&temperature,&humidity); //读取温湿度值
Temp=temperature;Humi=humidity;
LcdWriteData('0'+temperature/10);
LcdWriteData('0'+temperature%10);
LcdWriteCom(0x80+0X40+6);
LcdWriteData('0'+humidity/10);
LcdWriteData('0'+humidity%10);
}
if(k==30)
k=0;
lcd12864(); //显示防盗闹钟状态
}
}
//定时器3中断服务程序
void TIM3_IRQHandler(void)//TIM3中断
{
int i;
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);//清除TIMx更新中断标志
if(key1==1&&FEN-fen==0&&SHI-shi==0)//时间一到闹钟响起
{
f=1;
}
if(key1==0||FEN-fen!=0||SHI-shi!=0)
else
{
f=0;
}
if(USART_RX_BUF[0]=='R'&&USART_RX_BUF[1]=='I'&&USART_RX_BUF[2]=='N'&&USART_RX_BUF[3]=='G')
{
key0=1;
for(i=0;i<17;i++)
{
USART_SendData(USART1, num[i]);//向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
USART_RX_STA=0;
}
delay_ms(3000);
for(i=0;i<3;i++)
{
USART_SendData(USART1, num1[i]);//向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
USART_RX_STA=0;
}
}
}
}
嵌入式物联网需要学的东西真的非常多,千万不要学错了路线和内容,导致工资要不上去!
无偿分享大家一个资料包,差不多150多G。里面学习内容、面经、项目都比较新也比较全!某鱼上买估计至少要好几十。
点击这里找小助理0元领取:加微信领取资料
2、时间片论法
介于前后台顺序执行法和操作系统之间的一种程序架构设计方案。该设计方案需能帮助嵌入式软件开发者更上一层楼,在嵌入式软件开发过程中,若遇到以下几点,那么该设计方案可以说是最优选择,适用于程序较复杂的嵌入式系统;
- 目前的需求设计需要完全没有必要上操作系统。
- 任务函数无需时刻执行,存在间隔时间(比如按键,一般情况下,都需要软件防抖,初学者的做法通常是延时10ms左右再去判断,但10ms极大浪费了CPU的资源,在这段时间内CPU完全可以处理很多其他事情)
- 实时性有一定的要求。
该设计方案需要使用一个定时器,一般情况下定时1ms即可(定时时间可随意定,但中断过于频繁效率就低,中断太长,实时性差),因此需要考虑到每个任务函数的执行时间,建议不能超过1ms(能通过程序优化缩短执行时间则最好优化,如果不能优化的,则必须保证该任务的执行周期必须远大于任务所执行的耗时时间),同时要求主循环或任务函数中不能存在毫秒级别的延时。
“
如何确定每个函数的任务周期呢?根据任务的耗时和效果决定、如按键扫描任务周期为 10ms(为了提高响应),指示灯控制任务周期为 100ms(通常情况下最高100ms的闪烁频率正好,特殊需求除外),LCD/OLED 显示周期为 100ms(通过这种通过SPI/IIC等接口的方式耗时大约在 1~10ms,甚至更长,所以任务周期必须远大于耗时,同时为了满足人眼所能接受的刷屏效果,也不能太长,100ms 的任务周期比较合适)等。
”
以下介绍两种不同的实现方案,分别针对无函数指针概念的朋友和想进一步学习的朋友。
1、无函数指针的设计方式
/**
* @brief 主函数.
* @param None.
* @return None.
*/
int main(void)
{
System_Init();
while (1)
{
if (TIM_1msFlag)// 1ms
{
CAN_CommTask(); // CAN发送/接收通信任务
TIM_1msFlag = 0;
}
if (TIM_10msFlag) // 10ms
{
KEY_ScanTask(); // 按键扫描处理任务
TIM_10msFlag = 0;
}
if (TIM_20msFlag) // 20ms
{
LOGIC_HandleTask();// 逻辑处理任务
TIM_20msFlag = 0;
}
if (TIM_100msFlag) // 100ms
{
LED_CtrlTask(); // 指示灯控制任务
TIM_100msFlag = 0;
}
if (TIM_500msFlag)// 500ms
{
TIM_500msFlag = 0;
}
if (TIM_1secFlag) // 1s
{
WDog_Task(); // 喂狗任务
TIM_1secFlag = 0;
}
}
}
/**
* @brief 定时器3中断服务函数.
* @param None.
* @return None.
*/
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET) //溢出中断
{
sg_1msTic++;
sg_1msTic % 1 == 0 ? TIM_1msFlag = 1 : 0;
sg_1msTic % 10 == 0 ? TIM_10msFlag = 1 : 0;
sg_1msTic % 20 == 0 ? TIM_20msFlag = 1 : 0;
sg_1msTic % 100 == 0 ? TIM_100msFlag = 1 : 0;
sg_1msTic % 500 == 0 ? TIM_500msFlag = 1 : 0;
sg_1msTic % 1000 == 0 ? (TIM_1secFlag = 1, sg_1msTic = 0) : 0;
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); // 清除中断标志位
}
2、含函数指针的设计方式
/**
* @brief 任务函数相关信息结构体定义.
*/
typedef struct{
uint8 m_runFlag; /*!< 程序运行标记:0-不运行,1运行 */
uint16 m_timer; /*!< 计时器 */
uint16 m_itvTime; /*!< 任务运行间隔时间 */
void (*m_pTaskHook)(void); /*!< 要运行的任务函数 */
} TASK_InfoType;
#define TASKS_MAX 5 // 定义任务数目
/** 任务函数相关信息 */
static TASK_InfoType sg_tTaskInfo[TASKS_MAX] = {
{0, 1, 1, CAN_CommTask}, // CAN通信任务
{0, 10, 10, KEY_ScanTask}, // 按键扫描任务
{0, 20, 20, LOGIC_HandleTask}, // 逻辑处理任务
{0, 100, 100, LED_CtrlTask}, // 指示灯控制任务
{0, 1000, 1000, WDog_Task}, // 喂狗任务
};
/**
* @brief 任务函数运行标志处理.
* @note 该函数由1ms定时器中断调用
* @param None.
* @return None.
*/
void TASK_Remarks(void)
{
uint8 i;
for (i = 0; i < TASKS_MAX; i++)
{
if (sg_tTaskInfo[i].m_timer)
{
sg_tTaskInfo[i].m_timer--;
if (0 == sg_tTaskInfo[i].m_timer)
{
sg_tTaskInfo[i].m_timer = sg_tTaskInfo[i].m_itvTime;
sg_tTaskInfo[i].m_runFlag = 1;
}
}
}
}
/**
* @brief 任务函数运行处理.
* @note 该函数由主循环调用
* @param None.
* @return None.
*/
void TASK_Process(void)
{
uint8 i;
for (i = 0; i < TASKS_MAX; i++)
{
if (sg_tTaskInfo[i].m_runFlag)
{
sg_tTaskInfo[i].m_pTaskHook(); // 运行任务
sg_tTaskInfo[i].m_runFlag = 0; // 标志清0
}
}
}
/**
* @brief 主函数.
* @param None.
* @return None.
*/
int main(void)
{
System_Init();
while (1)
{
TASK_Process();
}
}
/**
* @brief 定时器3中断服务函数.
* @param None.
* @return None.
*/
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET) //溢出中断
{
TASK_Remarks();
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);// 清除中断标志位
}
三、操作系统
嵌入式操作系统EOS(Embedded OperatingSystem)是一种用途广泛的系统软件,过去它主要应用于工业控制和国防系统领域,而对于单片机来说,比较常用的有UCOS、FreeRTOS、RT-Thread Nano和RTX 等多种抢占式操作系统(其他如Linux等操作系统不适用于单片机)
操作系统和“时间片论法”,在任务执行方面来说,操作系统对每个任务的耗时没有过多的要求,需要通过设置每个任务的优先级,在高优先级的任务就绪时,会抢占低优先级的任务;操作系统相对复杂,因此这里没有详细介绍了。
关于如何选择合适的操作系统(uCOS、FreeRTOS、RTThread、RTX等RTOS的对比之特点:
- uCOS:网上资料丰富,非常适合学习,但是在产品上使用则需要收费。
- FreeRTOS:使用免费,因此很多产品都在用。
- RT-Thread:国产物联网操作系统,有着十分丰富的组件,也免费,资料:RT-Thread文档中心。
- RTX:为ARM和Cortex-M设备设计的免版税,确定性的实时操作系统。
借网上一张对比图:
四、总结
从上述的对比中可以看出,时间片轮询法的优势还是比较大的,它既有前后台顺序执行法的优点,也有操作系统的优点。结构清晰,简单,非常容易理解,所以这种是比较常用的单片机设计框架。
原文链接:https://blog.csdn.net/qq_24130227/article/details/87537036
版权声明:本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。
文章链接:
https://mp.weixin.qq.com/s/vEBGeqK0BAAyvN3_XxmWgg
转载自:果果小师弟 ,作者智果君
文章链接:嵌入式软件开发常用的三种架构你知道吗?
相关推荐
- 一个基于.Net Core遵循Clean Architecture原则开源架构
-
今天给大家推荐一个遵循CleanArchitecture原则开源架构。项目简介这是基于Asp.netCore6开发的,遵循CleanArchitecture原则,可以高效、快速地构建基于Ra...
- AI写代码翻车无数次,我发现只要提前做好这3步,bug立减80%
-
写十万行全是bug之后终于找到方法了开发"提示词管理助手"新版本那会儿,我差点被bug整崩溃。刚开始两周,全靠AI改代码架构,结果十万行程序漏洞百出。本来以为AI说没问题就稳了,结果...
- OneCode低代码平台的事件驱动设计:架构解析与实践
-
引言:低代码平台的事件驱动范式在现代软件开发中,事件驱动架构(EDA)已成为构建灵活、松耦合系统的核心范式。OneCode低代码平台通过创新性的注解驱动设计,将事件驱动理念深度融入平台架构,实现了业务...
- 国内大厂AI插件评测:根据UI图生成Vue前端代码
-
在IDEA中安装大厂的AI插件,打开ruoyi增强项目:yudao-ui-admin-vue31.CodeBuddy插件登录腾讯的CodeBuddy后,大模型选择deepseek-v3,输入提示语:...
- AI+低代码技术揭秘(二):核心架构
-
本文档介绍了为VTJ低代码平台提供支持的基本架构组件,包括Engine编排层、Provider服务系统、数据模型和代码生成管道。有关UI组件库和widget系统的信息,请参阅UI...
- GitDiagram用AI把代码库变成可视化架构图
-
这是一个名为gitdiagram的开源工具,可将GitHub仓库实时转换为交互式架构图,帮助开发者快速理解代码结构。核心功能一键可视化:替换GitHubURL中的"hub...
- 30天自制操作系统:第六天:代码架构整理与中断处理
-
1.拆开bootpack.c文件。根据设计模式将对应的功能封装成独立的文件。2.初始化pic:pic(可编程中断控制器):在设计上,cpu单独只能处理一个中断。而pic是将8个中断信号集合成一个中断...
- AI写代码越帮越忙?2025年研究揭露惊人真相
-
近年来,AI工具如雨后春笋般涌现,许多人开始幻想程序员的未来就是“对着AI说几句话”,就能轻松写出完美的代码。然而,2025年的一项最新研究却颠覆了这一期待,揭示了一个令人意外的结果。研究邀请了16位...
- 一键理解开源项目:两个自动生成GitHub代码架构图与说明书工具
-
一、GitDiagram可以一键生成github代码仓库的架构图如果想要可视化github开源项目:https://github.com/luler/reflex_ai_fast,也可以直接把域名替换...
- 5分钟掌握 c# 网络通讯架构及代码示例
-
以下是C#网络通讯架构的核心要点及代码示例,按协议类型分类整理:一、TCP协议(可靠连接)1.同步通信//服务器端usingSystem.Net.Sockets;usingTcpListene...
- 从复杂到优雅:用建造者和责任链重塑代码架构
-
引用设计模式是软件开发中的重要工具,它为解决常见问题提供了标准化的解决方案,提高了代码的可维护性和可扩展性,提升了开发效率,促进了团队协作,提高了软件质量,并帮助开发者更好地适应需求变化。通过学习和应...
- 低代码开发当道,我还需要学习LangChain这些框架吗?| IT杂谈
-
专注LLM深度应用,关注我不迷路前两天有位兄弟问了个问题:当然我很能理解这位朋友的担忧:期望效率最大化,时间用在刀刃上,“不要重新发明轮子”嘛。铺天盖地的AI信息轰炸与概念炒作,很容易让人浮躁与迷茫。...
- 框架设计并不是简单粗暴地写代码,而是要先弄清逻辑
-
3.框架设计3.框架设计本节我们要开发一个UI框架,底层以白鹭引擎为例。框架设计的第一步并不是直接撸代码,而是先想清楚设计思想,抽象。一个一个的UI窗口是独立的吗?不是的,...
- 大佬用 Avalonia 框架开发的 C# 代码 IDE
-
AvalonStudioAvalonStudio是一个开源的跨平台的开发编辑器(IDE),AvalonStudio的目标是成为一个功能齐全,并且可以让开发者快速使用的IDE,提高开发的生产力。A...
- 轻量级框架Lagent 仅需20行代码即可构建自己的智能代理
-
站长之家(ChinaZ.com)8月30日消息:Lagent是一个专注于基于LLM模型的代理开发的轻量级框架。它的设计旨在简化和提高这种模型下代理的开发效率。LLM模型是一种强大的工具,可以...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- mfc框架 (52)
- abb框架断路器 (48)
- beego框架 (52)
- java框架spring (58)
- grpc框架 (65)
- tornado框架 (48)
- 前端框架bootstrap (54)
- orm框架有哪些 (51)
- 知识框架图 (52)
- ppt框架 (55)
- 框架图模板 (59)
- 内联框架 (52)
- cad怎么画框架 (58)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)