一、任务调度
众所周知,实时多任务操作系统(RTOS)是嵌入式应用软件的基础和开发平台,因其简单易用、开源等优点被大多数嵌入式开发者使用,但是在一些简单、实时性要求比较强的情况下,RTOS系统往往由于一些本身的系统损耗而不能在程序中使用。
为了在程序中模仿这种任务调度系统,时间片轮询法逐渐进入大家的视野,它既可以实现简单的任务调度,又可以基于STM的裸机系统运行而备受大家青睐,在STM32裸机系统中,常常利用STM32的定时器系统,将需要执行的任务函数,根据执行周期的不同设定不同频率的任务,进而实现简单的任务调度。
二、实现方法
这次我们以STM32F4系列为例,其中主频168Mhz,对应的定时器时钟为84Mhz。
2.1 定时器
我们定义Tim2预分频系数为1,自动装载值为8400,经f=Tck/(psc+1)*(arr+1), 其中TCK为时钟频率,PSC为时钟预分频系数,arr为自动重装载值。计算频率为10Khz,相对应的周期为0.1ms,这样每计数值到10即为1ms。
/** 初始化任务管理定时器 **/
void task_timer_init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = (65535);
TIM_TimeBaseStructure.TIM_Prescaler = (8400 -1);
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_SetCounter(TIM2, 0);
/* TIM enable counter */
TIM_Cmd(TIM2, ENABLE);
}
初始完我们需要的定时器,接下来就是定义需要的变量
/** 任务调度相关参数 **/
volatile uint64_t TimingTick = 0;
volatile uint64_t TimingTickHold = 0;
然后就是获取我们任务调度时所需要的系统时间
/** 任务执行期间获取系统时间 **/
void task_systick(void)
{
u32 temp = TIM_GetCounter(TIM2);
if(temp > 10000)
{
TIM_SetCounter(TIM2,0);
TimingTickHold = TimingTickHold + temp;
temp = 0;
}
TimingTick = temp + TimingTickHold;
}
这个函数也没什么讲的,就是一直获取Tim2的计数值,根据计数值来判断是否到达执行某一周期任务的时间
/** 获取启动后时间 **/
uint64_t GetTimingTick(void)
{
return TimingTick;
}
到这里前期的准备工作就结束了,下一步就是我们如何在程序中具体使用这个任务调度的功能了。
2.2 任务调度结构体
typedef struct{
void(*fTask)(void); //任务指针,执行具体的任务
uint32_t uNextTick; //该任务下一次执行时间,默认为0
uint32_t uLenTick; //任务间隔周期,周期为0.1ms,自行计算需要的周期
}sTask;
接下来我们定义一个同结构体的数组(任务列表)
/** 任务列表 **/
static sTask mTaskTab[]=
{
{task_systick,0,0}, //计算系统时间,不可删除
{data_upload,0,100}, //10ms 数据发送
{key_control,0,300}, //30ms 按键按键检测及控制
{channel_scan,0,700}, //70ms 通道扫描
{ocp,0,1500}, //150ms 过流保护
};
这样,就可以对多个任务做一个简单的调度,以后添加任务时只需要在mTaskTab表中添加即可,需要强调的是,由于执行每个任务也需要耗费时间,就会导致一个任务的实际调度周期可能会比设定的调度周期要长,这样会存在时间不准的情况,当然这仅仅是适合于对轮询周期不是很严格的任务,这里建议不要在单个任务中增加延时类的函数操作。
接下来就是一个宏定义
#define ARRAYSIZE(a) (sizeof(a)/sizeof((a)[0]))
前面我们定义是一个未知大小的结构体,这个主要是自动计算我们刚才定义的结构体数组有多大,这样子也是为了我们后期操作时可以任意在任务列表中添加需要的任务。
2.3 main函数
// 任务调度
for(int i = 0;i < ARRAYSIZE(mTaskTab);i++ )
{
if(mTaskTab[i].uNextTick <= GetTimingTick())
{
mTaskTab[i].uNextTick += mTaskTab[i].uLenTick;
mTaskTab[i].fTask();
}
}
在主循环中遍历任务列表,然后将当前任务的下一次调用时间和当前时间比较,如果发现轮到该任务执行,就执行该任务,执行完成后,将该任务的下一次执行时间设为当前时间加任务的调度时间,然后按照此方法去执行下一个需要执行的任务。(注:我没太看懂如何实现按照任务周期来执行任务的,但是这个遍历确实能实现这种功能,如果有知道的还请不吝赐教~~~)