杨一萌,杨勇,杨远聪
(中国地质大学 数学与物理学院,武汉 430074)
Protothreads在提高系统响应方面的应用*
杨一萌,杨勇,杨远聪
(中国地质大学 数学与物理学院,武汉 430074)
采用基于Protothreads的轻量灵活的多任务编程方式,使资源紧张的小型微控制器可支持多任务,改善了系统综合性能。测试证明,运用该技术后C51系统对按键响应的速度最高提高了10倍。该方法为在小型微控制器上运行多任务系统提供了一种新的思路。
Protothreads;多任务;低延迟;STC90C516RD+
基于微控制器的仪器设备在进行数据采集时,往往还需要支持通信、显示和按键等,这要求微控制器能够在短时间内处理多个任务,并且具有较好的实时性。Free RTOS和μC/OS II等完整的嵌入式操作系统,往往需要较多的硬件资源,对于小型微控制器来说,其过于庞大。许多应用既希望采用轻巧价廉的微控制器,又希望响应迅速,尤其是航天和交通等系统更是如此[1]。这种情况下,需根据任务和微控制器的特点来设计编写控制程序,才能充分发挥微控制器的性能。在轻量级多任务框架中,由Protothreads衍生出了各具特点的修改版本[2],但对某些微控制器来说仍然有些复杂,使用受限。本文结合已有的采集系统,尝试使用简单的Protothreads框架来提升系统实时性能,并评估Protothreads框架在资源紧张的多任务嵌入式系统中的可行性。
Protothreads由瑞典皇家理工学院的Adam Dunkels博士开发,在BSD许可证下发行[3]。它是一个非常轻量的协程库,被应用到很多开源软件中,例如嵌入式网络操作系统Contiki、TCP/IP协议栈μIP和LwIP等。
1.1Protothreads特点及基本函数
Protothreads具有如下优点:①完全由C语言实现,没有汇编代码,不依赖任何库和系统特性,在任何平台都可移植;②极少的资源需求,每个Protothreads函数仅需要2个字节;③支持条件阻塞机制和协程间通信等功能;④不存在调用开销,没有栈切换,系统资源占用极少。
Protothreads十分轻量的特点使它非常适合用于内存受限系统、事件驱动协议栈、深度嵌入式系统和传感器网络节点等场景[4]。Protothreads进入阻塞状态时,不保存堆栈和局部变量,这意味着,在使用Protothreads的系统中要谨慎对待本地变量,如果不确定是否可行,就使用全局变量[5]。
1.2用于定义协程的函数主体
Protothreads协程由4种基本操作组成,初始化:PT_INIT();执行:PT_BEGIN();条件阻塞:PT_WAIT_UNTIL(),PT_WAIT_WHILE();退出:PT_END()。
这4个基本操作函数均是宏,在代码预编译阶段会展开为实际代码。每一次发生调用时,Protothreads将一直运行,直到它主动进入阻塞状态或退出。因此,由使用Protothreads的应用程序完成Protothreads的调度[5]。
Protothreads使用pt.lc记录阻塞位置,协程刚初始化和任务结束时,pt.lc均等于0。
该数据采集系统主要用于电流检测,结构图如图1所示。
图1 采样系统基本结构
微控制器使用STC90C516RD+,系统主要任务有A/D转换和数据处理、结果显示、量程切换、PC通信、菜单处理、快捷按键处理和数据记录。A/D转换使用AD7710芯片,可以设置10 Hz或50 Hz陷波频率。不同陷波频率下的转换速度不同,单片机对转换结果进一步处理。菜单键按下会触发外部中断,中断函数置位标志位,然后在主函数中处理该事件。
2.1存在的问题
经过后期测试发现,单片机大部分时间花费在A/D获取数据及处理这个部分。在不同的设置下,花费的时间在20~4 000 ms。由于系统任务是顺序执行,导致其他任务响应很慢,最坏的情况下,按下菜单键后几秒钟后屏幕才会显示出来。
如图2描述的情况,在任务A执行过程中,黑色标记处相继发生了任务B、C对应的事件,发生事件后进入相应中断,置位相应标志位,但是要等到任务A结束之后才会执行任务B、C。
图2 CPU使用情况
2.2解决方法
A/D转换和数据处理任务使用Protothreads协程,其他任务不变。A/D转换和数据处理任务,下文均称作任务A。任务A每运行200 ms就会主动进入阻塞状态,让出CPU使用权,然后检查是否有事件需要处理,相关任务处理完成后,任务A获得CPU使用权,继续执行。任务A每次停留在阻塞状态的时间是不固定的,例如,当把保存的数据从仪器发送到上位机时,任务A在发送完毕前将一直处于阻塞状态。改善后的CPU的使用情况如图3所示。
图3 改善后CPU使用情况
图4描述了任务A的内部流程。任务A主要由采集数据和处理数据两个子任务构成。这两个子任务都不是一次性完成的,每次运行一小部分,然后阻塞任务A,让出CPU使用权。如图4所示,任务A运行后,首先根据pt.lc的值判断运行状态,如果是从阻塞态恢复,将回到上次结束位置继续运行。当pt.lc值对应采集任务程序块(行号在采集任务程序块)时,进入采集任务;当pt.lc值对应数据处理程序块(行号在数据处理程序块)时,进入数据处理任务。采集数据和处理数据两个子任务按顺序先后运行,子任务运行超过200 ms后,阻塞任务A。当数据处理结束后,任务A结束。
图4 任务A流程图
系统中使用定时器计时,每10 ms触发定时中断,作为系统的TickClock,变量sclk记录中断次数。当sclk等于20时,表示至少已经过去了200 ms。程序如下:
void Timer0_ISR() interrupt 1{
sclk++;
}
使用PT_THREAD()宏声明任务A的主体函数。当从A/D转换器中读出一个转换结果后检测sclk的值,当sclk大于等于20时,主动阻塞自己,让出CPU使用权。程序如下:
PT_THREAD(dataobj_acquire(dataobj *dataobj, struct pt *pt)){
//变量声明及初始化…
sclk=0;//重置计时
PT_BEGIN(pt);
if (dataobj->number_to_average >= 4){
for (i = dataobj_raw_data_start_addr; i < dataobj->raw_data_end_addr;){
AD7710Read();
/*如果已经运行至少200 ms,阻塞自己,退出本函数。当再进入本函数时,从当前位置继续执行*/
PT_WAIT_WHILE(pt, (sclk > 20));
}
数据处理……
PT_END(pt);
}
在主函数中,通过pt.lc的值是否为0来判断任务A是否已经执行完成。某些任务,比如显示数据只有在任务A正常结束后才能刷新。某些任务执行后会改变系统当前设置,已经采集到的缓存数据需要重新开始采集,可以通过PT_INIT()宏重置任务A。程序如下:
void main(){
变量声明及初始化…
PT_INIT(&data_acquire_pt);
模块初始化…
while (1){
if (deviceobj->key_isr_happen){
KeyInterruptProc(dataobj, deviceobj, uartobj);
deviceobj->key_isr_happen = 0;
/*重置任务A,使其从头开始执行*/
PT_INIT(&data_acquire_pt);
}
dataobj_acquire (dataobj, deviceobj, &data_acquire_pt);
if (data_acquire_pt.lc == 0){
RangeCheckProc(dataobj, deviceobj, uartobj);
}
if (deviceobj->range_changed){
deviceobj->range_changed = 0;
/*重置任务A,使其从头开始执行*/
PT_INIT(&data_acquire_pt);
dataobj_acquire (dataobj, deviceobj, &data_acquire_pt);
}
if (deviceobj->over_range == 0 && data_acquire_pt.lc == 0){
LCDDisplay (dataobj);
}
if (deviceobj->autotest_run && data_acquire_pt.lc == 0){
AutoTestProc(&tmp_addr, &toi2c_num, dataobj, deviceobj, uartobj);
}
uart_loop:
if (uartobj->command_received){
EX0=0;
UARTProcessComm(dataobj, deviceobj, uartobj, &data_acquire_pt);
EX0=1;
}
if (uartobj->uartloop_flag){ goto uart_loop;}
if (QuicKeyProc(dataobj, deviceobj, uartobj)){
/*重置任务A,使其从头开始执行*/
PT_INIT(&data_acquire_pt);
}
}
}
根据不同的设置,在没有事件发生时,任务A总的执行时间在20~4 000 ms之间,使用Protothreads之后总的执行时间并没有明显增加。在没有使用Protothreads协程框架之前,按下按键使按键标志位置位后,要等待较长时间才能获得响应。使用框架后,从最严重的延迟3 060 ms下降到270 ms。比较结果见表1。
表1 使用Protothreads前后结果比较
每个Protothreads协程仅需要2字节的存储空间,使用协程的函数内的局部变量全部要改为静态变量。任务A相比之前增加20字节的内存占用,程序大小增加1 KB左右。
本文分析了数据采集系统中存在任务响应不及时的问题,并根据其使用的微控制器资源紧缺和采集系统的特点,提出了使用Protothreads来实现多任务的编程方式;简要介绍了Protothreads基本功能,详细阐述了改进系统响应性能的实现方法。结合改进前后的数据,经过对比发现,该方法可以明显提升系统性能,并且没有明显增加内存和程序空间占用,对于更复杂的系统需求,可以根据情况设计一个调度程序。本文对于其他嵌入式软件开发具有较高的参考价值。
[1] 荣国平,刘天宇,谢明娟,等.嵌入式系统开发中敏捷方法的应用研究综述[J].软件学报,2014(2):267-283.
[2] 楼亮亮,周苗,鲍星合.一种适用于物联网节点的高效轻量级嵌入式系统设计[J].单片机与嵌入式系统应用,2014(11):67-70.
[3] Dunkels A,Schmidt O,Voigt T,et al.Protothreads: Simplifying event-driven programming of memory-constrained embedded systems[C]//Proceedings of the Fourth ACM Conference on Embedded Networked Sensor Systems,2006:29-42.
[4] Dunkels A,Schmidt O.Protothreads-lightweight stackless threads in C [EB/OL].[2016-03].http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.60.2455&rep=repl&type=pdf.
[5] 闫石,马潮.时间触发模式下的Protothreads设计应用[J].单片机与嵌入式系统应用,2009(1):15-17.
杨一萌(研究生),研究方向为光电信号检测与嵌入式系统应用;杨勇(教授),主要从事微弱信号检测相关工作;杨远聪(研究生),研究方向为光电信号检测。
(责任编辑:薛士然收修改稿日期:2016-03-26)
Protothreads Application in Terms of Improving System Response
Yang Yimeng,Yang Yong,Yang Yuancong
(China University of Geosciences,Wuhan 430074,China)
The resource intensive small microcontroller can support multitasking by using the lightweight flexible multitask programming based on Protothreads,that improves the performance of the system.The experiment results show that the method can speed up 10 times maximum for the button response on a C51 system obviously.The method provides a new idea for running multitask on small microcontroller.
Protothreads;multitask;low-latency;STC90C516RD+
TP311
A