ETP是Ecbm-Timeslice-Polling的缩写,是ECBM工作室推出的时间片轮询程序框架。具有如下几个特点:
时间片轮询是一种基础框架,可以通过合理地分割任务来充分利用CPU。但ETP并不是实时操作系统,在实际应用中也会出现阻塞,请根据实际需求来决定如何应用。
ETP是硬件无关的框架,但因为是基于时间片的框架,所以移植的目标平台至少要有一个定时器用来产生时间片。
为了实现跨平台,ETP使用了自定义的数据类型,根据实际硬件的情况修改。在etp.h中的宏定义专栏:
#define etp_u1 bit //1位无符号变量
#define etp_u8 unsigned char //8位无符号变量
#define etp_u16 unsigned int //16位无符号变量
#define etp_u32 unsigned long //32位无符号变量
时间片的概念和定时器并不挂钩,理论上一串外来的脉冲+外部中断也能实现时间片,但是大多数情况下还是用定时器产生一个固定周期的中断。比如51单片机的定时器中断。
以keil为例,在Project窗口选择合适地方,比如DEVICE。
双击DEVICE,弹出添加文件的窗口,添加etp.c到工程中。
接下来在main.c中加载头文件:
#include "etp.h"
至此,ETP已经完整的移植到工程中了,就差一步就可以开始使用了。
以ECBM库的工程为例,在定时器0的中断中添加etp_time_tick函数。
void fun1(void)TIMER0_IT_NUM{
etp_time_tick();
}
在main函数里的主循环里添加etp_main函数。
void main(void){ //主函数。
system_init();//ECBM库的初始化。
timer_init(); //定时器初始化。
timer_set_timer_mode(0,1000);//设置定时器0为定时器模式,定时时间为1000uS。
timer_start(0);//打开定时器0。
while(1){
etp_main();//ETP的执行函数。
}
}
然后编译,不出错的话说明ETP已经移植成功。根据下面的例程来一起使用ETP吧。
通过上面的移植之后,ETP框架已经搭建好了。但是只有框架,没有内容的话是没有意义的。下面告诉大家如何来基于ETP框架来安排任务。
函数原型:void etp_install(etp16 tim,void (* fun)(void));
时间片任务的安装函数。
无
ETP框架的特点决定了任务的数量是静态的,因此需要手动设置任务列表的大小。
打开etp.h,在第38行的宏定义处填写一个1~254的值:
#define ETP_TASK_MAX 10
如上所示就是设置任务列表的容量为10,可以安装10个任务。
void test(void){//定义一个函数。
P11=!P11;//内容根据实际需要,这里只做测试,用LED闪烁来看效果。
}
...//其他无关代码
void main(void){
...//其他无关代码
etp_install(100,test);//test函数每100个时间片执行一次。
while(1){
etp_main();//ETP的执行函数。
}
}
函数原型:void etp_main(void);
时间片轮询的主函数。
无
无
无
...//其他无关代码
void main(void){
...//其他无关代码
while(1){
etp_main();//ETP的执行函数。
}
}
函数原型:void etp_time_tick(void);
任务时间函数。
无
无
无
void fun1(void)TIMER0_IT_NUM{//这里是定时器0的中断函数。
etp_time_tick();//每次中断执行一次。
}
函数原型:void etp_stop(etp8 id);
任务停止函数。
无
无
void key_scan(void){//这是按键扫描函数。
if(key_stop==0){//当停止键按下时,
etp_stop(0);//停止编号为0的任务。
}
}
任务的编号由etp_install函数在安装该任务的时候生成。可以用一个变量储存起来:
u8 led_task_id;
...//其他无关代码
void main(void){
...//其他无关代码
led_task_id=etp_install(100,test);//test函数每100个时间片执行一次,并把编号给变量led_task_id。
while(1){
...//其他无关代码
}
}
函数原型:void etp_start(etp8 id);
任务开启函数。
无
无
u8 led_task_id;
...//其他无关代码
void main(void){
...//其他无关代码
led_task_id=etp_install(100,test);//test函数每100个时间片执行一次,并把编号给变量led_task_id。
etp_start(led_task_id);//开启test任务。
while(1){
...//其他无关代码
}
}
函数原型:etp8 etp_get_status(etp8 id);
任务状态获取函数,用于获取某个任务的状态,通过状态可知道任务是否正常运行。
无
u8 led_task_id;
...//其他无关代码
void main(void){
...//其他无关代码
led_task_id=etp_install(100,test);//test函数每100个时间片执行一次,并把编号给变量led_task_id。
etp_start(led_task_id);//开启test任务。
while(1){
...//其他无关代码
etp_main();//主要执行函数。
if(etp_get_status(led_task_id)&ETP_TASK_DELAY){//检查任务的延时标志位,如果任务的执行时间片超过设定的100。该标志位会被置位。
uart_printf(1,"Task Delay!\r\n");//向串口发送任务执行时间过长的警告。
}
}
}
无
#include "ecbm_core.h"//加载库函数的头文件。
#include "etp.h"//加载ETP的头文件。
void tog_p11(void){//P1.1脚翻转函数。
P11=!P11;
}
void tog_p12(void){//P1.2脚翻转函数。
P12=!P12;
}
void main(void){ //主函数。
system_init();//ECBM库的初始化。
timer_init(); //定时器初始化。
timer_set_timer_mode(0,1000);//设置定时器0为定时器模式,定时时间为1000uS。
timer_start(0);//打开定时器0。
etp_install(10,tog_p11);//每10mS执行一次tog_p11。
etp_install(20,tog_p12);//每20mS执行一次tog_p12。
etp_start(0);//开启任务tog_p11。
etp_start(1);//开启任务tog_p12。
while(1){
etp_main();//ETP的执行函数。
}
}
void fun1(void)TIMER0_IT_NUM{//这里是定时器0的中断函数。
P10=1;//这句话只是为了逻辑分析仪看时序的。
etp_time_tick();//每次中断执行一次。
P10=0;//这句话只是为了逻辑分析仪看时序的。
}
运行之后,逻辑分析仪的波形图如下:
通道0是定时器的中断波形,上升沿意味着进入了定时器中断,下降沿意味着定时器中断结束。
通道1对应着任务tog_p11,所以P1.1脚每次翻转电平的间隔都是10mS。
通道2对应着任务tog_p12,所以P1.2脚每次翻转电平的间隔都是20mS。
为了更好的理解ETP,我选了几个比较常见的问题进行了测试。如有补充,欢迎加入778916610QQ群提出。
假如有一个任务内部使用了while(1)来阻塞进程的话,那么其他任务将无法再获得运行的机会。因此严禁单个任务阻塞。
假如一个任务在运行的时候,超出了它的时间片,那么会导致所有任务失去预设的周期。下面用例子进行说明。
#include "ecbm_core.h"//加载库函数的头文件。
#include "etp.h"//加载ETP的头文件。
void task1(void){//任务1。
P11=1;//置一代表任务开始。
delay_ms(5);//假设这个任务执行需要5mS。
P11=0;//置零代表任务结束。
}
void task2(void){//任务2。
P12=1;//置一代表任务开始。
delay_ms(3);//假设这个任务执行需要3mS。
P12=0;//置零代表任务结束。
}
void main(void){ //主函数。
system_init();//ECBM库的初始化。
timer_init(); //定时器初始化。
timer_set_timer_mode(0,1000);//设置定时器0为定时器模式,定时时间为1000uS。
timer_start(0);//打开定时器0。
etp_install(50,task1);//每50mS执行一次任务1。
etp_install(25,task2);//每25mS执行一次任务2。
etp_start(0);//开启任务1。
etp_start(1);//开启任务2。
while(1){
etp_main();//ETP的执行函数。
}
}
void fun1(void)TIMER0_IT_NUM{//这里是定时器0的中断函数。
P10=1;//这句话只是为了逻辑分析仪看时序的。
etp_time();//每次中断执行一次。
P10=0;//这句话只是为了逻辑分析仪看时序的。
}
假设每个任务的执行时间远小于任务间隔,那么两个任务的执行情况是符合预期的:
由上图可知,任务1每50mS执行一次,任务2每25mS执行一次。同时因为执行任务1需要5mS,所以当任务1和任务2需要同时执行的时候,任务2总会延后5mS(就是在等任务1执行结束)才能执行,这就是因为在ETP框架中任务不能抢占的结果。也正是因为任务执行需要时间,所以一部分间隔不是严格的50mS和25mS。
现在假定这种情况,任务1实际的执行时间是60mS,然而任务1的间隔时间却是50mS。其他条件不变。
#include "ecbm_core.h"//加载库函数的头文件。
#include "etp.h"//加载ETP的头文件。
void task1(void){//任务1。
P11=1;//置一代表任务开始。
delay_ms(60);//假设这个任务执行需要60mS。
P11=0;//置零代表任务结束。
}
void task2(void){//任务2。
P12=1;//置一代表任务开始。
delay_ms(3);//假设这个任务执行需要3mS。
P12=0;//置零代表任务结束。
}
void main(void){ //主函数。
system_init();//ECBM库的初始化。
timer_init(); //定时器初始化。
timer_set_timer_mode(0,1000);//设置定时器0为定时器模式,定时时间为1000uS。
timer_start(0);//打开定时器0。
etp_install(50,task1);//每50mS执行一次任务1。
etp_install(25,task2);//每25mS执行一次任务2。
etp_start(0);//开启任务1。
etp_start(1);//开启任务2。
while(1){
etp_main();//ETP的执行函数。
}
}
void fun1(void)TIMER0_IT_NUM{//这里是定时器0的中断函数。
P10=1;//这句话只是为了逻辑分析仪看时序的。
etp_time_tick();//每次中断执行一次。
P10=0;//这句话只是为了逻辑分析仪看时序的。
}
那么将会出现如下情况:
可以看到,因为任务1执行完消耗60mS,任务2为了等待任务1结束也必须等待60mS才能执行。然后因为任务1的执行标志位每50mS就置一,也就意味着任务1还没执行完就获得了下一次轮询的执行允许。于是两个任务的执行关系就完全脱离了设置的50mS和25mS了。
出现了以上的现象的时候,受影响的任务都会被标记上“延时”标志位。可以通过读取任务状态得知当前任务有没有延时。
代码就不列举了。任务1和任务2的执行间隔是一致的,但是执行的时间不一致。
按理来说,任务1和任务2的执行时间都小于其间隔,那么他们都应该是按优先级来执行的,且顺序不会变。但是实际情况确实任务1有时先执行,有时候后执行。这是因为在etp_time里置一任务1的运行状态时,etp_main恰好已经过了判断任务1的时机,于是本来应该先执行的任务1只能等到下一次轮询才能执行。
因此,如果两个任务要求连续执行且执行先后顺序固定的话,还是要把两个任务合并成一个才行。
内容 | 消耗空间 |
---|---|
任务消耗 | 每个任务增加 8 Byte |
框架消耗 | 3.1 Byte |
etp_install函数 | 99 Byte |
etp_main函数 | 118 Byte |
etp_time_tick函数 | 216 Byte |
etp_stop函数 | 17 Byte |
etp_start函数 | 24 Byte |
etp_get_status函数 | 26 Byte |
函数的占用空间和KEIL的优化等级和编译器版本有关,上表仅做参考。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。