4 Star 47 Fork 18

ECBM工作室/时间片轮询框架

Create your Gitee Account
Explore and code with more than 12 million developers,Free private repositories !:)
Sign up
Clone or Download
contribute
Sync branch
Cancel
Notice: Creating folder will generate an empty file .keep, because not support in Git
Loading...
README
MIT

ETP简介

ETP是Ecbm-Timeslice-Polling的缩写,是ECBM工作室推出的时间片轮询程序框架。具有如下几个特点:

  • 任务的优先级固定。所有任务按安装时的顺序执行,不可在运行时更改。
  • 任务是静态的,不能在运行时创建、删除。但是可以开启和停止任务。
  • 代码不和硬件关联,方便移植。
  • 任务之间不会抢占CPU,如果某个任务阻塞,那么整体都会卡主。
  • 由于任务按顺序执行,但如果某一任务的执行时间太长,会影响到其他任务的正常运行。

时间片轮询是一种基础框架,可以通过合理地分割任务来充分利用CPU。但ETP并不是实时操作系统,在实际应用中也会出现阻塞,请根据实际需求来决定如何应用。

如何移植

ETP是硬件无关的框架,但因为是基于时间片的框架,所以移植的目标平台至少要有一个定时器用来产生时间片。

1.确认数据类型

为了实现跨平台,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位无符号变量

2.设置固定周期的中断

时间片的概念和定时器并不挂钩,理论上一串外来的脉冲+外部中断也能实现时间片,但是大多数情况下还是用定时器产生一个固定周期的中断。比如51单片机的定时器中断。

3.加入工程

以keil为例,在Project窗口选择合适地方,比如DEVICE。

双击DEVICE,弹出添加文件的窗口,添加etp.c到工程中。

接下来在main.c中加载头文件:

#include "etp.h"

至此,ETP已经完整的移植到工程中了,就差一步就可以开始使用了。

4.添加代码

以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框架来安排任务。

函数API

etp_install

函数原型:void etp_install(etp16 tim,void (* fun)(void));

描述

时间片任务的安装函数。

输入

  • tim:该任务的执行周期,单位是“时间片个数”。
  • fun:任务函数。

输出

返回值

  • 0~254:该任务的编号。
  • 255:任务列表已满,任务安装失败。

参数设置

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的执行函数。
    }
}

注意事项

  1. 安装的函数一定得先定义再用etp_install安装。如果只声明没有定义的话,在执行的时候一定会跑飞。
  2. 假如一个时间片是1mS,100个时间片就是100mS。同理一个时间片是10mS,100个时间片就是1S。
  3. 本函数仅将任务函数排放进任务列表内,此时任务还不会执行,还需要配合etp_start来启动任务。
  4. 先安装的任务的优先级比后安装的任务优先级高。每次轮询都是先执行优先级高的任务。
  5. 本函数会返回任务编号,只要不是255就说明任务安装成功。

etp_main

函数原型:void etp_main(void);

描述

时间片轮询的主函数。

输入

输出

返回值

调用例程

...//其他无关代码
void main(void){
    ...//其他无关代码
    while(1){
        etp_main();//ETP的执行函数。
    }
}

注意事项

  1. 为了方便管理任务,建议主循环只存放etp_main一个函数就行。其他需要执行的工作打包成任务函数来让ETP来执行。

etp_time_tick

函数原型:void etp_time_tick(void);

描述

任务时间函数。

输入

输出

返回值

调用例程

void fun1(void)TIMER0_IT_NUM{//这里是定时器0的中断函数。
    etp_time_tick();//每次中断执行一次。
}

注意事项

  1. 把本函数放入定时器中断或者其他周期性的中断中。

etp_stop

函数原型:void etp_stop(etp8 id);

描述

任务停止函数。

输入

  • id:需要停止的任务的编号。

输出

返回值

调用例程

void key_scan(void){//这是按键扫描函数。
    if(key_stop==0){//当停止键按下时,
        etp_stop(0);//停止编号为0的任务。
    }
}

注意事项

  1. 任务的编号由etp_install函数在安装该任务的时候生成。可以用一个变量储存起来:

    u8 led_task_id;
    ...//其他无关代码
    void main(void){
        ...//其他无关代码
        led_task_id=etp_install(100,test);//test函数每100个时间片执行一次,并把编号给变量led_task_id。
        while(1){
            ...//其他无关代码
        }
    }

etp_start

函数原型:void etp_start(etp8 id);

描述

任务开启函数。

输入

  • 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){
        ...//其他无关代码
    }
}

注意事项

  1. 由于所有任务在安装的时候都不会自动开启,所以在安装之后需要执行本函数来开启任务。当然也可以在其他时机开启,或者在某种条件触发后开启任务。

etp_get_status

函数原型:etp8 etp_get_status(etp8 id);

描述

任务状态获取函数,用于获取某个任务的状态,通过状态可知道任务是否正常运行。

输入

  • 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群提出。

1.任务阻塞

假如有一个任务内部使用了while(1)来阻塞进程的话,那么其他任务将无法再获得运行的机会。因此严禁单个任务阻塞。

2.任务延后

假如一个任务在运行的时候,超出了它的时间片,那么会导致所有任务失去预设的周期。下面用例子进行说明。

正常情况

#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了。

出现了以上的现象的时候,受影响的任务都会被标记上“延时”标志位。可以通过读取任务状态得知当前任务有没有延时。

3.任务执行卡点

代码就不列举了。任务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的优化等级和编译器版本有关,上表仅做参考。

MIT License Copyright (c) 2021 奈特 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

About

这是一个跨平台的ETP(Ecbm-Timeslice-Polling)框架。本框架基于时间片轮询法,任务之间不具备有抢占性,优先级由安装任务的顺序决定。 expand collapse
C
MIT
Cancel

Releases

No release

Contributors

All

Activities

Load More
can not load any more
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
C
1
https://gitee.com/ecbm/timeslice-polling.git
git@gitee.com:ecbm/timeslice-polling.git
ecbm
timeslice-polling
时间片轮询框架
master

Search

344bd9b3 5694891 D2dac590 5694891