浅谈嵌入式软件设计

浅谈嵌入式软件设计

本文在21IC的公众号文章《多年嵌入式编程工程师经验分享:换个角度来编程》基础上结合自己理解而写,部分图片以及文字说明均来自互联网。

前后台模型

模型介绍

当开发过程中不使用OS时,几乎所有的嵌入式程序归根结底都是一个由无法停止的循环为结构构成的,即常见的while(1)for(;;),用流程图表示就是这样:

graph TD stop[结束] start[查询IO或外设状态] --> section1[执行相关业务逻辑] section1 --> conditionA{退出无限循环?} conditionA -- YES --> stop conditionA -- NO --> section1 start_sub[业务逻辑]-->subconditonA{执行业务逻辑A?} subconditonA{执行业务逻辑A?}--YES-->subsection1[子功能A] subconditonA{执行业务逻辑A?}--NO-->subconditonB{执行业务逻辑B?} subconditonB--YES-->subsection2[子功能B] subconditonB--NO-->stop_b[结束] interp[触发中断]-->baoliu[保护现场] baoliu-->interp2[执行中断服务函数] interp2-->tuichuzhongduan[退出中断] tuichuzhongduan-->huifu[恢复现场]

这个过程中,软件处在两种状态下:

  • 在无外设或IO请求时处于逻辑工作模式,顺序并循环执行相关逻辑程序
  • 在有中断触发时中断当前业务流程,执行中断服务程序。

这种软件设计即前后台设计,用图像表示即:

这种软件设计下可以将设备需要的功能根据实时性进行分类,将实时性要求不高但需要持续刷新的功能作为一个子函数在主循环中顺序执行,而将需要快速响应的功能作为中断函数进行处理。

存在的问题

这种方法存在几个问题:

  • 当高实时性任务的工作量过大时,中断占用时间过久,使后台需要刷新的任务无法得到及时更新,即中断函数过于臃肿,影响了业务逻辑。
  • 后台任务是顺序执行的,在上一个任务无法结束前下一个任务无法开启,这使的所有任务的更新时间均一致,逻辑代码越多,更新时间越久,进而影响某些任务的实现。

解决方法

上面的问题当然有一些解决的办法,但是都不是十分完美:

中断臃肿问题

将中断任务作为一个任务标记函数,并将中断任务进行拆分,中断来临时仅进行任务工作状态标记,如按键等,在主程序中对每个子任务执行前进行状态标记检查:


int main()
{
    while (1)
    {
        if (interflag == 1)
        {
            subinterfuncs(); //执行中断子程序
        }
        else
        {
            backgroundfunc1(); //后台任务1
        }
        if (interflag == 2)
        {
            subinterfuncs(); //执行中断子程序
        }
        else
        {
            backgroundfunc2(); //后台任务1
        }
        //后续其他任务等等
    }
    return 0;
}

subinterfuncs()
{
    switch (subinterflag)
    {
    case 1:
        interfunc1(); //中断子任务1
        break;
    case 2:
        interfunc2(); //中断子任务2
        break;
    default:
        break;
    }
    return;
}

这个方法虽然解决了中断逻辑臃肿的问题,但是只适合于那些触发动作式的场景,例如当按键按下时可以先快速的在屏幕或其他人机交互界面上响应触发的动作,但是该动作并未完全执行,比如在液晶屏上显示“执行中”等,可以先解决响应速度问题,但是对于需要显示完整动作结果的场景依然无法解决。这种情况下可以设计交互流程,提高用户体验。

后台臃肿问题

解决方法跟上面类似,将子任务继续拆分,将长耗时任务拆分,将周期性的时间要求高的任务穿插在长耗时任务中,即在一个循环内高耗时任务总体执行一次,时间要求高的任务执行多次。这种解决方法增加了程序维护成本,并且不是所有的任务都可以被有效拆分,高时效任务的执行周期不固定,因此并不是一个很有效的解决手段。

时间片模型

模型介绍

由于前后台模型在周期性任务上存在上面提到的问题,因此出现了时间片模型来解决这个问题,时间片模型简单地说就是通过时间分割调整不同任务的执行顺序,保证周期性任务时间上的满足:

程序上只需要在循环中执行调度器即可:

/*--------------------主函数-----------------------*/
void main(void)
{
    SCH_Init();//设置调度器
    SCH_Add_Task(任务函数名,任务调度延迟,任务调度周期);//将任务加入调度器的任务队列
    SCH_Start();//刷新任务队列
    while(1)
    {
        SCH_Dispatch_Tasks();  //执行任务调度器
    }
}
/*-------------------定时中断函数---------------------*/
Void SCH_Update(void)   interrupt
{
    //刷新任务队列
}

例如5ms一个周期的任务和100ms一个周期的任务可以通过一个5ms的定时器,通过计数的周期进行判断应该执行哪个任务,这里类似RTOS的任务设计,可以解决前后台模型中任务的时间要求问题。

存在的问题

时间片模型的问题十分明显:

  • 一个任务只有被执行完后才可以执行下一个,这意味着当前任务的执行时间不能超过分配的时间片段,如果超过了时间范围还未退出,则会被定时器中断所打断,并执行下一个任务,同时当前任务被中断,直到下次释放,执行周期出现混乱

在这种情况下只有将任务进行合理的拆分,设计好程序的执行周期,才可以解决这个问题。

一种通用的处理模型

从任务的时效性上可以将所有可执行的任务分为以下三种:

  • 及时型任务:当输入来临时必须快速响应,例如交互界面
  • 定时型任务:一般无外部输入,但是对时间的周期性较为敏感,例如数据采集等
  • 后台型任务:对时间周期性不敏感,能够定期刷新即可,如日志记录,状态上报等

这三类任务中,可以将及时型任务处理放在中断中,并赋予最高的中断优先级,即当及时型任务触发时,其他任务必须暂停。

这里存在一个优先级的过程,如果外部响应的优先级没有定时任务的优先级高,则也可以考虑在执行定时任务时将中断关闭,有限保证定时任务。总之根据实际场景决定优先级

定时型和后台任务由时间片进行调度,定时型任务自己单独享用一个定时器,由该定时器触发时执行定时任务,在执行定时任务时关闭后台型任务的中断,不响应外界输入,当定时任务执行完毕后开启中断,即及时型任务可打断后台型任务。

后台任务由另一个定时器控制时间调度,中断优先级最低,执行过程中中断并不关闭,即可被及时型任务和定时型任务抢断,抢断的优先级取决于场景,如及时任务的优先级高则可在及时任务中将总中断关闭,退出时打开总中断。而当定时任务执行时则将定时器中断关闭,避免后台任务抢占定时任务。

posted @ 2019-11-22 00:08  回归的世界线  阅读(901)  评论(0编辑  收藏  举报