嵌入式软件开发之程序架构设计-任务调度

1 前言

在嵌入式MCU软件开发过程中,程序任务调度架构的搭建尤为重要,直接关系到该程序能支持多少功能(随着功能越多系统响应能力越弱,好的任务调度架构能够在保持相同的系统响应能力前提下支持更多的功能),下面介绍三种常用的程序任务调度框架设计方案:

  • 前后台顺序执行法
  • 时间片论法
  • 操作系统

2 程序框架设计

2.1 前后台顺序执行法

这是初学者们常用的程序框架设计方案,不用考虑太多东西,代码简单,或者对系统的整体实时性和并发性要求不高;初始化后通过 while(1){ } 或 for(;;){ } 循环不断调用自己编写完成的函数,也基本不考虑每个函数执行所需要的时间,大部分情况下函数中或多或少都存在毫秒级别的延时等待。

  • 优点:对于初学者来说,这是最容易也是最直观的程序架构,逻辑简单明了,适用于逻辑简单,复杂度比较低的软件开发。
  • 缺点:实时性低,由于每个函数或多或少存在毫秒级别的延时,即使是1ms,也会造成其他函数间隔执行时间的不同,虽然可通过定时器中断的方式,但是前提是中断执行函数花的时间必须短。当程序逻辑复杂度提升时,会导致后来维护人员的大脑混乱,很难理清楚该程序的运行状态。

以下是我在学校期间做的寝室防盗系统的主函数代码(当时也存在部分BUG,没有解决。现在再看,其实很多问题,而且比较严重,比如中断服务函数内竟然有3000ms延时,这太可怕了,还有串口发送等等;由于实时性要求不算太高,因此主函数中的毫秒级别延时对系统运行没有多大影响,当然除BUG外;若是后期需要维护,那就是一个大工程,还不如推翻重写):

int main(void)
 {
    u8 temperature;
    u8 humidity;
    int a;
    delay_init();
    uart2_Init(9600);
    TIM3_Int_Init(4999,7199);
    ds1302_init();
    while(DHT11_Init());    //DHT11初始化
    a1602_init();
    lcd12864_INIT();
    LcdInit();

    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_ms(10); red_scan(); ds1302readtime(); 读取ds1302的日期时间 shi="At24c02Read(0);" 读取闹钟保存的数据 fen="At24c02Read(1);" usart2_scan(); 蓝牙数据扫描 usart2_bian(); 蓝牙处理数据 nao_scan(); k++; if(k<20) if(k="=1)" lcdwritecom(0x01); 清屏 lcddisplay(); 显示日期时间 if(red="=0)">=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 lcdwritedata('0'+humidity%10); } 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;" else 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);//&#x7B49;&#x5F85;&#x53D1;&#x9001;&#x7ED3;&#x675F;" usart_rx_sta="0;" delay_ms(3000); num1[i]); }< code></30)></11;a++)>

2.2 时间片论法

介于”前后台顺序执行法”和”操作系统”之间的一种程序架构设计方案。该设计方案需能帮助嵌入式软件开发者更上一层楼,在嵌入式软件开发过程中,若遇到以下几点,那么该设计方案可以说是最优选择,适用于程序较复杂的嵌入式系统;

  1. 目前的需求设计需要完全没有必要上操作系统
  2. 任务函数无需时刻执行,存在间隔时间(比如按键,一般情况下,都需要软件防抖,初学者的做法通常是延时10ms左右再去判断,但10ms极大浪费了CPU的资源,在这段时间内CPU完全可以处理很多其他事情)
  3. 实时性有一定的要求

该设计方案需要使用一个定时器,一般情况下定时1ms即可(定时时间可随意定,但中断过于频繁效率就低,中断太长,实时性差),因此需要考虑到每个任务函数的执行时间,建议不能超过1ms(能通过程序优化缩短执行时间则最好优化,如果不能优化的,则必须保证该任务的执行周期必须远大于任务所执行的耗时时间),同时要求主循环或任务函数中不能存在毫秒级别的延时。

如何确定每个函数的任务周期呢?根据任务的耗时和效果决定、如按键扫描任务周期为 10ms(为了提高响应),指示灯控制任务周期为 100ms(通常情况下最高100ms的闪烁频率正好,特殊需求除外),LCD/OLED 显示周期为 100ms(通过这种通过SPI/IIC等接口的方式耗时大约在 1~10ms,甚至更长,所以任务周期必须远大于耗时,同时为了满足人眼所能接受的刷屏效果,也不能太长,100ms 的任务周期比较合适)等

以下介绍两种不同的实现方案,分别针对无函数指针概念的朋友和想进一步学习的朋友。

2.2.1 无函数指针的设计方式

/**
  * @brief      &#x4E3B;&#x51FD;&#x6570;.

  * @param      None.

  * @return     None.

  */
int main(void)
{
    System_Init();

    while (1)
    {

        if (TIM_1msFlag)                            // 1ms
        {
            CAN_CommTask();                         // CAN&#x53D1;&#x9001;/&#x63A5;&#x6536;&#x901A;&#x4FE1;&#x4EFB;&#x52A1;

            TIM_1msFlag = 0;
        }

        if (TIM_10msFlag)                           // 10ms
        {
            KEY_ScanTask();                         // &#x6309;&#x952E;&#x626B;&#x63CF;&#x5904;&#x7406;&#x4EFB;&#x52A1;

            TIM_10msFlag = 0;
        }

        if (TIM_20msFlag)                           // 20ms
        {
            LOGIC_HandleTask();                     // &#x903B;&#x8F91;&#x5904;&#x7406;&#x4EFB;&#x52A1;

            TIM_20msFlag = 0;
        }

        if (TIM_100msFlag)                          // 100ms
        {
            LED_CtrlTask();                         // &#x6307;&#x793A;&#x706F;&#x63A7;&#x5236;&#x4EFB;&#x52A1;

            TIM_100msFlag = 0;
        }

        if (TIM_500msFlag)                          // 500ms
        {
            TIM_500msFlag = 0;
        }

        if (TIM_1secFlag)                           // 1s
        {
            WDog_Task();                            // &#x5582;&#x72D7;&#x4EFB;&#x52A1;

            TIM_1secFlag = 0;
        }
    }
}

/**
  * @brief      &#x5B9A;&#x65F6;&#x5668;3&#x4E2D;&#x65AD;&#x670D;&#x52A1;&#x51FD;&#x6570;.

  * @param      None.

  * @return     None.

  */
void TIM3_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET) // &#x6EA2;&#x51FA;&#x4E2D;&#x65AD;
    {
        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);  // &#x6E05;&#x9664;&#x4E2D;&#x65AD;&#x6807;&#x5FD7;&#x4F4D;
}

2.2.2 含函数指针的设计方式

/**
  * @brief &#x4EFB;&#x52A1;&#x51FD;&#x6570;&#x76F8;&#x5173;&#x4FE1;&#x606F;&#x7ED3;&#x6784;&#x4F53;&#x5B9A;&#x4E49;.

  */
typedef struct{
    uint8 m_runFlag;                 /*!< &#x7A0B;&#x5E8F;&#x8FD0;&#x884C;&#x6807;&#x8BB0;&#xFF1A;0-&#x4E0D;&#x8FD0;&#x884C;&#xFF0C;1&#x8FD0;&#x884C; */
    uint16 m_timer;                  /*!< &#x8BA1;&#x65F6;&#x5668; */
    uint16 m_itvTime;                /*!< &#x4EFB;&#x52A1;&#x8FD0;&#x884C;&#x95F4;&#x9694;&#x65F6;&#x95F4; */
    void (*m_pTaskHook)(void);       /*!< &#x8981;&#x8FD0;&#x884C;&#x7684;&#x4EFB;&#x52A1;&#x51FD;&#x6570; */
} TASK_InfoType;

#define TASKS_MAX     5              // &#x5B9A;&#x4E49;&#x4EFB;&#x52A1;&#x6570;&#x76EE;

/** &#x4EFB;&#x52A1;&#x51FD;&#x6570;&#x76F8;&#x5173;&#x4FE1;&#x606F; */
static TASK_InfoType sg_tTaskInfo[TASKS_MAX] = {
    {0, 1, 1, CAN_CommTask},         // CAN&#x901A;&#x4FE1;&#x4EFB;&#x52A1;
    {0, 10, 10, KEY_ScanTask},       // &#x6309;&#x952E;&#x626B;&#x63CF;&#x4EFB;&#x52A1;
    {0, 20, 20, LOGIC_HandleTask},   // &#x903B;&#x8F91;&#x5904;&#x7406;&#x4EFB;&#x52A1;
    {0, 100, 100, LED_CtrlTask},     // &#x6307;&#x793A;&#x706F;&#x63A7;&#x5236;&#x4EFB;&#x52A1;
    {0, 1000, 1000, WDog_Task},      // &#x5582;&#x72D7;&#x4EFB;&#x52A1;
};

/**
  * @brief      &#x4EFB;&#x52A1;&#x51FD;&#x6570;&#x8FD0;&#x884C;&#x6807;&#x5FD7;&#x5904;&#x7406;.

  * @note       &#x8BE5;&#x51FD;&#x6570;&#x7531;1ms&#x5B9A;&#x65F6;&#x5668;&#x4E2D;&#x65AD;&#x8C03;&#x7528;
  * @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      &#x4EFB;&#x52A1;&#x51FD;&#x6570;&#x8FD0;&#x884C;&#x5904;&#x7406;.

  * @note       &#x8BE5;&#x51FD;&#x6570;&#x7531;&#x4E3B;&#x5FAA;&#x73AF;&#x8C03;&#x7528;
  * @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();         // &#x8FD0;&#x884C;&#x4EFB;&#x52A1;
             sg_tTaskInfo[i].m_runFlag = 0;         // &#x6807;&#x5FD7;&#x6E05;0
        }
    }
}

/**
  * @brief      &#x4E3B;&#x51FD;&#x6570;.

  * @param      None.

  * @return     None.

  */
int main(void)
{
    System_Init();

    while (1)
    {
        TASK_Process();
    }
}

/**
  * @brief      &#x5B9A;&#x65F6;&#x5668;3&#x4E2D;&#x65AD;&#x670D;&#x52A1;&#x51FD;&#x6570;.

  * @param      None.

  * @return     None.

  */
void TIM3_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET) // &#x6EA2;&#x51FA;&#x4E2D;&#x65AD;
    {
        TASK_Remarks();
    }

    TIM_ClearITPendingBit(TIM3,TIM_IT_Update);     // &#x6E05;&#x9664;&#x4E2D;&#x65AD;&#x6807;&#x5FD7;&#x4F4D;
}

2.3 操作系统

嵌入式操作系统EOS(Embedded OperatingSystem)是一种用途广泛的系统软件,过去它主要应用于工业控制和国防系统领域,而对于单片机来说,比较常用的有 UCOS、FreeRTOS、RT-Thread Nano 和 RTX 等多种抢占式操作系统(其他如 Linux 等操作系统不适用于单片机)

操作系统和”时间片论法”,在任务执行方面来说,操作系统对每个任务的耗时没有过多的要求,需要通过设置每个任务的优先级,在高优先级的任务就绪时,会抢占低优先级的任务;操作系统相对复杂,因此这里没有详细介绍了。
关于如何选择合适的操作系统([RTOS]uCOS、FreeRTOS、RTThread、RTX等RTOS的对比之特点):

  • UCOS:网上资料丰富,非常适合学习,但是在产品上使用则需要收费
  • FreeRTOS:使用免费,因此很多产品都在用
  • RT-Thread:国产物联网操作系统,有着十分丰富的组件,也免费,资料:RT-Thread 文档中心
  • RTX:为ARM和Cortex-M设备设计的免版税,确定性的实时操作系统

借网上一张对比图:

嵌入式软件开发之程序架构设计-任务调度

总结

从上述的对比中可以看出,时间片轮询法的优势还是比较大的,它既有前后台顺序执行法的优点,也有操作系统的优点。结构清晰,简单,非常容易理解,所以这种是比较常用的单片机设计框架。

下篇:嵌入式软件开发之程序分层(二)

Original: https://www.cnblogs.com/const-zpc/p/16364444.html
Author: 大橙子疯
Title: 嵌入式软件开发之程序架构设计-任务调度

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/581965/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • vue3中iview框架初始值赋值问题

    问题:今天有个小伙伴说我们前端有个页面异常奇怪,没有显示数据。 开发小伙伴进行查询之后(非专业前端),明明进行了绑定,页面死活无法渲染,归属于灵异事件了,对于这种灵异事件,我总会想…

    Linux 2023年6月7日
    086
  • Shell脚本编程初体验

    通常,当人们提到”shell脚本语言”时,浮现在他们脑海中是bash,ksh,sh或者其它相类似的linux/unix脚本语言。脚本语言是与计算机交流的另外…

    Linux 2023年5月28日
    097
  • ASP.NET Core 2.2 : 二十. Action的多种数据返回格式处理机制

    上一章讲了系统如何将客户端提交的请求数据格式化处理成我们想要的格式并绑定到对应的参数,本章讲一下它的”逆过程”,如何将请求结果按照客户端想要的格式返回去。 …

    Linux 2023年6月7日
    0129
  • bash初始化文件详解

    本文使用的环境: Bash 4.2.46 bash启动时会执行一系列脚本, 具体要执行哪些启动文件, 这和bash的类型有关: 是否为交互式(interactive)的shell,…

    Linux 2023年6月7日
    074
  • Windows 下日志保存至Linux rsyslog日志服务器

    一、 下载安装 通过https://www.rsyslog.com/windows-agent/windows-agent-download/下载客户端后,按照默认安装完成后即进行…

    Linux 2023年6月6日
    095
  • DHCP超级作用域

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年6月7日
    084
  • 4.3 Linux压缩文件或目录为.zip格式(zip命令)

    我们经常会在 Windows 系统上使用 “.zip”格式压缩文件,其实”.zip”格式文件是 Windows 和 Linux 系统…

    Linux 2023年6月7日
    0102
  • 真·Android Zxing 扫码中文乱码解决

    Zxing3.2.1 之前遇到过Android Zxing扫码乱码的问题,网上搜了下解决了下。 今天又遇到了问题。 依然会乱码。 研究总结如下: Zxing 可以在Hints中添加…

    Linux 2023年6月13日
    076
  • Unity中Button的一些基础用法

    Button的功能探究 Interactable(是否可交互) 可交互简单来说所指的是按钮能否被点击,有true和false两种状态 public Button testButto…

    Linux 2023年6月7日
    0115
  • 在vue中使用websocket回调函数中调用其他methods函数报错Uncaught TypeError: this.getHistory is not a function at WebSocket.ws.onmess解决方案

    1、报错详情 在vue中,使用了websocket类,在websocket的onmessage回调函数中调用methods的 getHistory 函数,发现一调用就报错 Unca…

    Linux 2023年6月8日
    084
  • 一文入门Qt Quick

    以下内容为本人的著作,如需要转载,请声明原文链接微信公众号「englyf」 https://mp.weixin.qq.com/s/dvamU6q5lZQb5hztfD2zNg 初识…

    Linux 2023年6月6日
    0116
  • 阿拉德之怒手游超详细图文架设教程

    写在前面 你是否还记得DNF,一天你不小心救了赛丽亚,从此变成了拯救阿拉德大陆的勇士,从此开始冒险之旅,不管你的职业是亲儿子还是下水道,你一直对你玩的角色情有独钟,在一次次刷图PK…

    Linux 2023年6月7日
    082
  • 5.5 Vim移动光标命令汇总

    Vim 文本编辑器中,最简单的移动光标的方式是使用方向键,但这种方式的效率太低,更高效的方式使用快捷键。 Vim 移动光标常用的快捷键及其功能如下面各表所示,需要注意的是,表中所有…

    Linux 2023年6月7日
    0101
  • 正则表达式

    正则表达式:REGEXP,REGular EXPression。正则表达式分为两类: Basic REGEXP(基本正则表达式 Extended REGEXP(扩展正则表达式) 元…

    Linux 2023年6月7日
    0112
  • MariaDB 安装和配置

    一、MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可。 1、关闭selinux ①修改selinux的配置文件 [root@localh…

    Linux 2023年6月7日
    096
  • 无线配置多一个路由器作为家庭wifi的无线热点?

    以下内容为本人的著作,如需要转载,请声明原文链接微信公众号「englyf」 https://mp.weixin.qq.com/s/8OcDnY3O6ux41GntesHHcg 手头…

    Linux 2023年6月6日
    0108
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球