控制/MCU
引言
目前,RTOS特别是抢先式RTOS在嵌入式系统中的应用越来越广泛,但是还有很大一部分产品使用是小型单片机。这些系统由于成本的限制,通常资源非常有限,比如ROM往往小丁32 KB,RAM小于2 KB,由于RTOS对每个任务都要开辟单独内存区域,存放任务的上下文和各任务独立的堆栈,所以在这种系统中使用RTOS非常勉强。对于这些低成本资源受限系统通常采用“前后台”(或者叫“超级循环”)结构进行编程,这实际上是一种事件触发的编程模式,当中断数目较多且系统完成的功能相对复杂时,就会使系统的程序编写变得非常复杂并使系统运行的可预测性迅速下降。
针对这个问题,Michael J.Pont提出了一种“基于时间触发的编程模式”,这种方法有助于降低CPU的负荷并减少存储器的使用量,提高系统行为的可预测性,并使程序的结构变得简洁。但是在实际使用中,当系统中不同的任务对时间要求差异较大时,“基于时间触发的编程模式”难以给出简单有效的解决方案。为此,对“基于时间触发的编程模式”进行了改进,使之适应性更强,可以为成本和资源受限的小型嵌入式系统提供统一且有效的编程模式。
1 传统编程结构的局限性
当不使用RTOS时,嵌入式软件通常采用两种传统的编程结构进行编程,一种叫“前后台厅式”或者叫“超级循环结构”,本质上是事件触发的编程方式;另一种叫时间触发编程模式,Michael J.Pont的“基于时间触发的编程模式”即属于此。
在实际工作中,当系统稍微复杂时,会发现这两种方式都有一定局限性,下面以一个实际产品设计中遇到的问题为例来说明。在设计一个用于配电柜的壁装式智能配电仪表时,CPU的程序设计需完成以下任务:
①每半秒对前显示屏的显示数据进行一次刷新。
②每0.1 s对DI/DO进行一次刷新。
③每0.2 s对键盘进行一次扫描。
④每半秒对测量数据进行一次重新采集和计算。
⑤异步串行口与上位机使用Modhus通信,速率最高1 9 200 bps。
⑥CPU通过I2C总线与时钟芯片和EEPROM通信。
⑦CPU通过SPI总线与LED数码管及采集芯片通信。
⑧CPU要对所采集的6路信号进行FFT变换。
⑨当系统掉电时,CPU要能快速响应,把当前的电度底数写入EEPROM中。
上述任务中,任务⑤和任务⑨是强实时性的,如果对串口的收发事件得不到及时响应,接收时会导致字节丢失,发送时会导致字节间时间间隔太大,造成接收方的Modbus帧定界错误,对系统掉电事件如果不能及时响应会造成EEPROM的写入失败。其他任务只要在指定的周期内能得到执行就行,但是任务⑧比较特殊,使用通常的8位CPU进行6种信号的FFT变换,哪怕每种信号只做128点的FFT,运算一次也要好几秒。下面来看用传统编程结构实现上述设计时遇到的困扰。
1.1 使用“前后台方式”进行编程
使用“前后台方式”进行编程时,为保证任务⑤的及时性,使用了UART中断,当UART完成一个字节的收发后产生中断,在中断程序中将接收到的字符保存在接收缓冲区或从发送缓冲区取下一个待发字符装入UART进行发送,对Modbus协议的处理可以单独用一个任务在中断外处理,这保证了巾断程序的简短。为保证任务⑨响应的及时性,也必须为它安排一个中断。因为当系统掉电时,系统只有不到10 ms的过渡时间,系统如果不能在这个时间内完成相关的操作,系统电压将跌落至有效电压以下而丧失工作能力。
安排好了后台的中断任务后再来看看前台的任务如何完成。这里遇到的最大的挑战是对任务⑧的处理,因为任务⑧需要的执行时间太长了,简单的把它当成一个任务处理将影响系统对其他任务的响应,在超级循环中的代码结构如下:
while(1){
任务①;
任务②;
……
任务⑧;
}
由于任务⑧执行一次要几秒钟的时间,整个超级循环执行一次至少大于任务⑧需要的时间,也就是说这个超级循环循环一次要几秒钟时间,将满足不了各任务响应时间的要求。
要解决这个问题,只有把任务⑧拆分成很多个子任务,将每个子任务的耗时压缩到10 ms左右,并定义好各个子任务完成后的状态,在超级大循环中每次根据状态只执行一个子任务,程序结构如下:
while(1){
任务①;
任务②;
……
switch(子任务状态){
case 子任务状态①:
子任务①;
break;
……
case 子任务状态②:
子任务②;
break;
……
case 子任务状态:
子任务;
break;
}
}
这样,就需要把一个耗时几秒的FFT运算任务拆分成几百个耗时10 ms左有的子任务,这显然是不可接受的。除此之外,超级大循环结构隐含的一个缺点就是随着任务的增加,循环体的执行时间是线性增加的,在实际设计中即使没有像任务⑧那样的高耗时任务,当系统功能增加时要保证系统响应的及时性也是一个不小的挑战。
1.2 使用“时间触发编程模式”进行编程
“时间触发编程模式”的核心是建立一个基丁时间触发的合作式的任务调度器,在系统中尽量减少事件触发(减少中断的使用),系统通过任务调度器完成各任务的调度执行,下面是“时间触发编程模式”的典型程序结构:
2 对“时间触发编程模式”的改进
根据多年嵌入式系统编程的经验,通常嵌入系统的任务可以划分成3种类型:
①及时型任务。这类任务是事件触发型的,一旦事件发生,系统必须在限定的时间内进行响应,对这类任务,最自然的方法就是使用中断来完成,即定义成“前后台方式”中的后台任务。
②周期型任务。这类任务是时间触发式周期型的,系统必须保证在指定的周期内执行任务,“时间触发编程模式”可以很好地满足这类任务的需求。
③背景型任务。这类任务是非实时型的,实时性不是非常重要,系统在运行过程中可随时中断这类任务以便执行前两类任务,系统只要能充分利用资源尽最大可能快速完成这类任务即可,这类任务最适合定义成“前后台方式”中的前台任务。
根据以上任务分类,对“时间触发编程模式”的改进可概括成以下需求:
◆任务分3类,1类任务优先级最高,3类任务优先级最低;
◆高优先级的任务可中断低优先级任务的执行,同级的任务之间不可相互剥夺;
◆实际没计中为提高系统的可预测性,应尽量减少1类任务的数量及1类任务的执行时间;
◆为降低系统资源的占用,系统不给任务划分单独的堆栈空间。
以上改进的本质是设计3个优先级的简单的任务调度机制,高优先级的任务可中断低优先级的任务,同优先级的任务之间不能相互剥夺,该调度机制不为每个单独的任务保存任务上下文和单独的堆栈,这样可以减少该编程模式对系统资源的需求。
可剥夺式RTOS中的一个高优先级任务中断一个低优先级的任务时,会保存好低优先级任务的上下文并把该低优先级任务的局部变量保存在本任务单独的堆栈中,如果系统不给任务分配单独的堆栈,如何保证高优先级任务退出后,低优先级任务执行环境的恢复呢?
对这个问题,可以借鉴中断的处理机制用以下办法予以解决:
①在系统中设计一个定时中断函数,该函数的功能就是执行周期性任务的调度,该定时中断在所有中断中优先级最低。
②在系统中设计另一个定时中断函数,该函数的功能是刷新周期型任务的任务管理队列,为任务调度提供支持,本定时中断函数的优先级在系统中次低。
③周期型任务就是一个函数,该函数入口的第一个操作是开中断,允许任务执行期间被中断以便响应及时型任务。
④背景型任务就是在主函数超级循环中执行的代码,该代码可随时被及时型和周期型任务中断,当系统没有及时型任务和周期型任务时才循环执行背景型任务的代码。
通过以上措施,“改进型时间触发编程模式”的程序结构如下:
STM32/STM8
意法半导体/ST/STM
全部0条评论
快来发表一下你的评论吧 !