百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

嵌入式软件开发常用的三种架构你知道吗?

ccwgpt 2024-10-08 01:08 21 浏览 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等操作系统不适用于单片机)

操作系统和“时间片论法”,在任务执行方面来说,操作系统对每个任务的耗时没有过多的要求,需要通过设置每个任务的优先级,在高优先级的任务就绪时,会抢占低优先级的任务;操作系统相对复杂,因此这里没有详细介绍了。

关于如何选择合适的操作系统(uCOSFreeRTOSRTThreadRTX等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

转载自:果果小师弟 ,作者智果君

文章链接:嵌入式软件开发常用的三种架构你知道吗?

相关推荐

2025南通中考作文解读之四:结构框架

文题《继续走,迈向远方》结构框架:清晰叙事,层层递进示例结构:1.开头(点题):用环境描写或比喻引出“走”与“远方”,如“人生如一条长路,每一次驻足后,都需要继续走,才能看见更美的风景”。2.中间...

高中数学的知识框架(高中数学知识框架图第三章)

高中数学的知识框架可以划分为多个核心板块,每个板块包含具体的知识点与内容,以下为详细的知识框架结构:基础知识1.集合与逻辑用语:涵盖集合的概念、表示方式、性质、运算,以及命题、四种命题关系、充分条件...

决定人生的六大框架(决定人生的要素)

45岁的自己混到今天,其实是失败的,要是早点意识到影响人生的六大框架,也不至于今天的模样啊!排第一的是环境,不是有句话叫人是环境的产物,身边的环境包括身边的人和事,这些都会对一个人产生深远的影响。其次...

2023年想考过一级造价师土建计量,看这30个知识点(三)

第二章工程构造考点一:工业建筑分类[考频分析]★★★1.按厂房层数分:(1)单层厂房;(2)多层厂房;(3)混合层数厂房。2.按工业建筑用途分:(1)生产厂房;(2)生产辅助厂房;(3)动力用厂房;(...

一级建造师习题集-建筑工程实务(第一章-第二节-2)

建筑工程管理与实务题库(章节练习)第一章建筑工程技术第二节结构设计与构造二、结构设计1.常见建筑结构体系中,适用建筑高度最小的是()。A.框架结构体系B.剪力墙结构体系C.框架-剪力墙结构体系D...

冷眼读书丨多塔斜拉桥,这么美又这么牛

”重大交通基础设施的建设是国民经济和社会发展的先导,是交通运输行业新技术集中应用与创新的综合体现。多塔斜拉桥因跨越能力强、地形适应性强、造型优美等特点,备受桥梁设计者的青睐,在未来跨越海峡工程中将得...

2021一级造价师土建计量知识点:民用建筑分类

2021造价考试备考开始了,学霸君为大家整理了一级造价师备考所用的知识点,希望对大家的备考道路上有所帮助。  民用建筑分类  一、按层数和高度分  1.住宅建筑按层数分类:1~3层为低层住宅,4~6层...

6个建筑结构常见类型,你都知道吗?

建筑结构是建筑物中支承荷载(作用)起骨架作用的体系。结构是由构件组成的。构件有拉(压)杆、梁、板、柱、拱、壳、薄膜、索、基础等。常见的建筑结构类型有6种:砖混结构、砖木结构、框架结构、钢筋混凝土结构、...

框架结构设计经验总结(框架结构设计应注意哪些问题)

1.结构设计说明主要是设计依据,抗震等级,人防等级,地基情况及承载力,防潮抗渗做法,活荷载值,材料等级,施工中的注意事项,选用详图,通用详图或节点,以及在施工图中未画出而通过说明来表达的信息。2.各...

浅谈混凝土框架结构设计(混凝土框架结构设计主要内容)

浅谈混凝土框架结构设计 摘要:结构设计是个系统的全面的工作,需要扎实的理论知识功底,灵活创新的思维和严肃认真负责的工作态度。钢筋混凝土框架结构虽然相对简单,但设计中仍有很多需要注意的问题。本文针...

2022一级建造师《建筑实务》1A412020 结构设计 精细考点整理

历年真题分布统计1A412021常用建筑结构体系和应用一、混合结构体系【2012-3】指楼盖和屋盖采用钢筋混凝土或钢木结构,而墙和柱采用砌体结构建造的房屋,大多用在住宅、办公楼、教学楼建筑中。优点:...

破土动工!这个故宫“分院”科技含量有点儿高

故宫“分院”设计图。受访者供图近日,位于北京海淀区西北旺镇的故宫北院区项目已开始破土动工,该项目也被称作故宫“分院”,筹备近十年之久。据悉,故宫本院每年展览文物的数量不到1万件,但是“分院”建成后,预...

装配式结构体系介绍(上)(装配式结构如何设计)

PC构件深化、构件之间连接节点做法等与相应装配式结构体系密切相关。本节列举目前常见的几种装配式结构体系:装配整体式混凝土剪力墙结构体系、装配整体式混凝土框架结构体系、装配整体式混凝土空腔结构体系(S...

这些不是双向抗侧结构体系(这些不是双向抗侧结构体系的特点)

双向抗侧土木吧规范对双向抗恻力结构有何规定?为何不应采用单向有墙的结构?双向抗侧土木吧1.规范对双向抗侧力结构体系的要求抗侧力体系是指抵抗水平地震作用及风荷载的结构体系。对于结构体系的布置,规范针对...

2022一级建造师《建筑实务》1A412020 结构设计 精细化考点整理

1A412021常用建筑结构体系和应用一、混合结构体系【2012-3】指楼盖和屋盖采用钢筋混凝土或钢木结构,而墙和柱采用砌体结构建造的房屋,大多用在住宅、办公楼、教学楼建筑中。优点:抗压强度高,造价...

取消回复欢迎 发表评论: