时间片轮询法
时间片轮询法是一种比较简单易用的系统架构之一,它对于系统中的任务调度算法是分时处理。核心思路是把 CPU 的时间分时给各个任务使用。我们常用的定时方法是定时器,把调度器放在定时中,可以简单的实现时间片轮询法。
需要注意的是,这种方法的前提是执行的 每个任务都是短小精悍的,要不然一个任务执行的时间过长,大于其它任务设置的时间片值,那其它任务就无法保证按它预设的时间片来执行。
尤其需要注意任务中延时的使用,可能会产生不可预料的结果。如果任务内部需要延时的时候,或者说单个任务过长,需要保存任务执行到一半的状态,建议使用状态机切割长任务。
时间片轮询法架构
一个时间片轮询应用程序的架构是非常简单的,包括一个任务结构体,一个中断处理函数,一个轮询执行任务函数。
设计一个结构体:
// 任务结构
typedef struct {
uint8_t task_id; // 任务 ID
uint16_t task_interval; // 任务运行间隔时间
void (*task_entry)(void); // 要运行的任务
volatile uint16_t task_tick_ms; // 计时器
}task_info_t;
定时器复用和中断处理
定时器可以是任意的定时器,这里采用系统滴答定时器 (systick) 来定时。systick 的配置就不细讲,假设定时器的定时中断为 10ms(可以自行设定,中断过于频繁效率就低,中断太长,实时性差)。
timing_task_tick
函数就相当于中断服务函数,需要在定时器的中断服务函数中调用此函数。
// 为每个任务计时,每次中断加 10ms
void timing_task_tick(void)
{
uint8_t task_index = 0;
while (task_index < ARRAY_SIZE(timing_task_array))
{
timing_task_array[task_index].task_tick_ms += CONFIG_SYSTEM_TICK_PERIOD_MS; // 每次加一个 systick 周期,即 10ms
task_index++;
}
}
时间片轮询实例
下面我就就说说怎样应用吧,假设我们有三个任务:时钟显示,按键扫描,和工作状态显示。
定义一个上面定义的那种结构体数组
// 计算任务个数
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
// 定义了 3 个任务
static task_info_t timing_task_array[] =
{
{0, 100, task_disp_clock_running, 0}, // 显示时钟
{1, 20, task_scan_key_running, 0}, // 按键扫描
{2, 30, task_disp_ws_running, 0}, // 工作状态显示
};
在定义变量时,我们已经初始化了值,这些值的初始化,非常重要,跟具体的执行时间优先级等都有关系,这个需要自己掌握。
大概意思是,我们有三个任务,每 1s 执行一下时钟显示,因为我们的时钟最小单位是 1s,所以在秒变化后才显示一次就够了。
由于按键在按下时会参数抖动,而我们知道一般按键的抖动大概是 20ms,那么我们在顺序执行的函数中一般是延伸 20ms,而这里我们每 20ms 扫描一次,是非常不错的出来,即达到了消抖的目的,也不会漏掉按键输入。
为了能够显示按键后的其他提示和工作界面,我们这里设计每 30ms 显示一次,如果你觉得反应慢了,你可以让这些值小一点。后面的名称是对应的函数名,你必须在应用程序中编写这函数名称和这三个一样的任务。
编写任务函数
//Description : 显示任务
void task_disp_clock_running(void)
{
}
//Description : 扫描任务
void task_scan_key_running(void)
{
}
//Description : 工作状态显示
void task_disp_ws_running(void)
{
}
任务处理
// 任务计划表,轮询执行任务
void timing_task_scheduler(void)
{
uint8_t task_index = 0;
while (1)
{
for (task_index = 0 ; task_index < ARRAY_SIZE(timing_task_array); task_index++)
{
if (timing_task_array[task_index].task_tick_ms >= timing_task_array[task_index].task_interval)
{
timing_task_array[task_index].task_tick_ms = 0;
timing_task_array[task_index].task_entry();
}
}
}
}
程序陷入死循环,依次判断每个任务是否符合执行要求。如果是,则执行相应的任务函数;否则等待计时。