浅谈架构方法之时间片轮询

发布于:2024-04-29 ⋅ 阅读:(20) ⋅ 点赞:(0)

PS:最近在逛CSDN的时候偶然发现了一篇文章讲到了这个架构,发现之前做过一个项目就用了这个东西,于是我搜了一下,感觉挺多文章都不好理解,由于我也是最近才接触到这个东西,所以我决定自己也写一篇,加深印象。

架构的类型

        首先,在嵌入式中有三种架构,除了我们熟知的裸机系统和操作系统,第三种就是这个时间片轮询。我认为这种架构是介于二者之间的过渡。

        裸机系统能够处理一些相对简单的任务,但是功能单一,只会一直按顺序执行死循环里的任务,CPU只会全力做完一件事才会进行下一项任务,延时的时候也只会等待,浪费资源。

        操作系统的实时性和可靠性高还能同时执行多个任务,但是操作系统较为复杂,需要额外的学习成本,而且其对内存空间的要求需要我们注意内存的大小。

        而时间片轮询不仅能够实现多任务,保证实时性,而且结构简单,占用内存小,在实际开发中也节省时间和精力。

        对于简单且实时性强的简单任务可以使用时间片轮询,但是当任务数较多的时候还是老实用操作系统更好。

代码实现

        这里我只叙述原理,不设定具体的任务,将整个代码拆分来看,防止看的太乱,整体代码放在最后。

        我设置了三个任务,分别是5ms任务、10ms任务和20ms任务。

        在开始之前,我们需要在主函数里进行一个延时。这个延时一般是最大的时间片时间,像这个例子里,我可以延时20ms。

        首先,我们定义一个最大任务数为3的宏:

#define TASKS_MAX   (3)

         接下来定义一个结构体,其内成员含义如下:

                程序运行标志位。

                倒计时时间。

                重装载倒计时。

                任务指针。

        它们的含义会在后面讲。

typedef struct _TASK_COMPONENTS
{
    uint8_t Run;            
    uint16_t Timer;            
    uint16_t ItvTime;              
    void (*TaskHook)(void);   
} TASK_COMPONENTS; 

        接下来我们创建一个结构体数组用来存放我们定义的结构体成员变量。

        数组中每个元素都对应着我们定义的结构体成员。

/* 任务数组 */
static TASK_COMPONENTS TaskComps[] = 
{
    {0, 5, 5, task_5ms},            // 5ms任务
    {0, 10, 10, task_10ms},         // 10ms任务
    {0, 20, 20, task_20ms},         // 20ms任务
};

        接下来是重点了。

        这个函数需要放到系统定时器中断中执行,同时对所有任务进行计时。

        当有任务的值为0时,将其代入for()循环执行。

        当每个任务的Timer不为0时,进行减减操作,即倒计时。当其减到0的时候,将ItvTime的值赋给Timer,以方便下次计时。同时将程序运行标志位Run置1.

/* 任务启动倒计时 */
void TaskRemarks(void)
{
    uint8_t i;

    for (i=0; i<TASKS_MAX; i++)          
    {
         if (TaskComps[i].Timer)        
        {
           TaskComps[i].Timer--;         
           if (TaskComps[i].Timer == 0)       
           {
             TaskComps[i].Timer = TaskComps[i].ItvTime; 
             TaskComps[i].Run = 1;           
           }
        }
   }
}

        下面这个函数依然是从0开始遍历每个任务,判断它们的Run是否为1,如果是1则执行对应的任务,然后将标志位置0。

void TaskProcess(void)
{
    uint8_t i;

    for (i=0; i<TASKS_MAX; i++)           
    {
         if (TaskComps[i].Run)           
        {
             TaskComps[i].TaskHook();         
             TaskComps[i].Run = 0;          
        }
    }   
}

         至于任务里面实现什么功能,就任由我们按实际需求了。

        当20ms任务执行过一次之后,一个周期就结束了。如果我们把TaskRemarks()放到SysTick的中断服务函数中,那么当这个中断执行的时候,滴答计时器的计数就会重置,同时TaskRemarks()函数也会重新执行,从而达到时间片轮询的效果。

完整代码

#define TASKS_MAX   (3)

typedef struct _TASK_COMPONENTS
{
    uint8_t Run;            
    uint16_t Timer;            
    uint16_t ItvTime;              
    void (*TaskHook)(void);   
} TASK_COMPONENTS; 

/* 任务数组 */
static TASK_COMPONENTS TaskComps[] = 
{
    {0, 5, 5, task_5ms},            // 5ms任务
    {0, 10, 10, task_10ms},         // 10ms任务
    {0, 20, 20, task_20ms},         // 20ms任务
};

/* 任务启动倒计时 */
void TaskRemarks(void)
{
    uint8_t i;

    for (i=0; i<TASKS_MAX; i++)          
    {
         if (TaskComps[i].Timer)        
        {
           TaskComps[i].Timer--;         
           if (TaskComps[i].Timer == 0)       
           {
             TaskComps[i].Timer = TaskComps[i].ItvTime; 
             TaskComps[i].Run = 1;           
           }
        }
   }
}

void TaskProcess(void)
{
    uint8_t i;

    for (i=0; i<TASKS_MAX; i++)           
    {
         if (TaskComps[i].Run)           
        {
             TaskComps[i].TaskHook();         
             TaskComps[i].Run = 0;          
        }
    }   
}

void task_5ms(void)
{ }
void task_10ms(void)
{ }
void task_20ms(void)
{ }