通常在STM32上面进行裸机开发时,有的需要进行延时操作,常用的办法是使用systick计时并使CPU空转。当延时时间达到毫秒级的时候,这种方式会大幅度导致程序运行效率下降。本文采用一种仿照OSAL的任务队列的方法,当一个任务延时的时候,不耽误其他任务的运行。
通常裸机程序没有内存管理,文章中的程序没有使用malloc函数。
一、任务的数据类型与数据结构
通常来说,一个任务就是一个函数,在设定好的时间调用该函数。函数的无返回值。任务之间可能需要传递消息,可以通过任务函数的参数传递。以下是一个任务函数的模板:
void Func1(uint32_t para) {
uint8_t a = (uint8_t)para;
uint8_t b = (uint8_t)(para >> 8);
uint8_t c = (uint8_t)(para >> 16);
uint8_t d = (uint8_t)(para >> 24);
// Todo ……
}
这样可以一次传递4个u8类型的消息。
如果传递的消息比较多,类型比较复杂,可以采用下列形式:
void Func1(void* para) {
func1msg* msg = (func1msg*)para;
// Todo ……
}
其中func1msg的定义为
typedef struct func1msg {
uint8_t a,b;
uint16_t c;
uint32_t d;
char* e;
}
可以定义一个func1msg类型的全局变量,然后在消息间传递变量的指针,实现消息传递。
所有可能执行的任务函数放在一个数组中,数组的类型如下:
typedef void (*pTaskFn)(void* para);
假设程序中有三个任务函数,分别为func1, func2, func3,则任务函数数组定义如下:
pTaskFn taskFnArr[] = {Func1, Func2, Func3};
任务函数数组长度为:
uint8_t taskNumber = sizeof(taskFnArr) / sizeof(pTaskFn);
实际上一个任务不仅仅包括任务函数,还有任务函数所需的参数(消息),任务的执行时间,任务是否被激活等信息,任务的数据结构如下:
typedef enum {
TASK_DISABLE = 0,
TASK_ENABLE
} TASK_STATUS;
typedef struct {
TASK_STATUS taskEn;
void* parameter;
uint32_t delayMs;
pTaskFn func;
} TASK;
任务队列的数组定义如下:
TASK taskArr[sizeof(taskFnArr) / sizeof(pTaskFn)] = {0};
二、任务的初始化
任务的初始化包含两部分:系统时钟的初始化和任务队列的初始化。
系统时钟采用systick,使用以下函数初始化systick:
void SystickInit(void) {
SysTick_Config(SystemCoreClock / 1000);
}
实现了每隔1ms中断一次的功能。
程序中需要利用systick中断实现计时功能,代码如下:
uint32_t uwTick = 0;
void IncTick(void) {
uwTick++;
}
uint32_t GetTick(void) {
return uwTick;
}
在systick中断函数中调用IncTick函数:
void SysTick_Handler(void)
{
IncTick();
}
即可实现系统时钟的初始化。
任务队列的初始化如下:
void TaskInit(void) {
int i = 0;
for(i=0;i<taskNumber;i+) {
taskArr[i].func = taskFnArr[i];
taskArr[i].parameter = 0;
taskArr[i].delayMs = 0;
taskArr[i].taskEn = TASK_DISABLE;
}
}
三、任务的运行与激活
任务的运行函数如下:
uint8_t taskIndex = 0;
void TaskRun(void) {
for(;;) {
if(taskArr[taskIndex].taskEn == TASK_ENABLE && taskArr[taskIndex].delayMs <= GetTick()) {
taskArr[taskIndex].taskEn = TASK_DISABLE;
taskArr[taskIndex].func(taskArr[taskIndex].parameter);
}
if(taskIndex < (taskNumber-1)) {
++taskIndex;
} else {
taskIndex = 0;
}
}
}
函数循环的遍历每个任务,当任务处于激活状态,且到达定时的时间,则先将激活标志位置为DISABLE,然后执行该任务中的函数。之所以需要先改变激活标志位,是因为在任务中可能会再次激活该任务。
任务的激活函数如下:
void SetTask(pTaskFn taskFn, uint32_t delayMs, void* para) {
int i=0;
for(i=0;i<taskNumber;i++) {
if(taskArr[i].func == taskFn)
break;
}
if(i >= TASK_NUMBER) return;
taskArr[i].delayMs = GetTick() + delayMs;
taskArr[i].taskEn = TASK_ENABLE;
taskArr[i].parameter = para;
}
遍历任务队列,通过设定的任务的函数指针找到目标任务,并将激活标准设定为ENABLE,定时时间设定为当前时间+延时时间,并保存参数指针,即可实现任务的定时激活。
四、完整代码
task.c
#include "task.h"
#include "systick.h"
#include "func.h"
pTaskFn taskFnArr[] = {Func1, Func2, Func3};
uint8_t taskNumber = sizeof(taskFnArr) / sizeof(pTaskFn);
TASK taskArr[sizeof(taskFnArr) / sizeof(pTaskFn)] = {0};
uint8_t taskIndex = 0;
void TaskInit(void) {
for(int i=0;i<taskNumber ;++i) {
taskArr[i].func = taskFnArr[i];
taskArr[i].parameter = 0;
taskArr[i].delayMs = 0;
taskArr[i].taskEn = TASK_DISABLE;
}
}
void TaskRun(void) {
for(;;) {
if(taskArr[taskIndex].taskEn == TASK_ENABLE && taskArr[taskIndex].delayMs <= GetTick()) {
taskArr[taskIndex].taskEn = TASK_DISABLE;
taskArr[taskIndex].func(taskArr[taskIndex].parameter);
}
if(taskIndex < (taskNumber-1)) {
++taskIndex;
} else {
taskIndex = 0;
}
}
}
void SetTask(pTaskFn taskFn, uint32_t delayMs, void* para) {
int i=0;
for(i=0;i<taskNumber;i++) {
if(taskArr[i].func == taskFn)
break;
}
if(i >= TASK_NUMBER) return;
taskArr[i].delayMs = GetTick() + delayMs;
taskArr[i].taskEn = TASK_ENABLE;
taskArr[i].parameter = para;
}
task.h
#ifndef __TASK_H
#define __TASK_H
#include "main.h"
typedef void (*pTaskFn)(void* para);
typedef enum {
TASK_DISABLE = 0,
TASK_ENABLE
} TASK_STATUS;
typedef struct {
TASK_STATUS taskEn;
void* parameter;
uint32_t delayMs;
pTaskFn func;
} TASK;
void TaskInit(void);
void TaskRun(void);
void SetTask(pTaskFn taskFn, uint32_t delayMs, void* para);
#endif
systick.c
#include "systick.h"
uint32_t uwTick = 0;
void SystickInit(void) {
SysTick_Config(SystemCoreClock / 1000);
}
void IncTick(void) {
uwTick++;
}
uint32_t GetTick(void) {
return uwTick;
}
systick.h
#ifndef __SYSTICK_H
#define __SYSTICK_H
#include "main.h"
void SystickInit(void);
void IncTick(void);
uint32_t GetTick(void);
#endif
stm32f10x_it.c
#include "stm32f10x_it.h"
#include "systick.h"
void SysTick_Handler(void)
{
IncTick();
}
func.c
#include "func.h"
#include "task.h"
func1msg f1msg;
func2msg f2msg;
func3msg f3msg;
void Func1(void* para) {
func1msg* msg = (func1msg*)para;
// TODO…
SetTask(Func2, 10, &f2msg);
}
void Func2(void* para) {
func2msg* msg = (func2msg*)para;
// TODO…
SetTask(Func3, 10, &f3msg);
}
void func3(void* para) {
func3msg* msg = (func3msg*)para;
// TODO…
SetTask(Func1, 10, &f1msg);
}
func.h
#ifndef __FUNC_H
#define __FUNC_H
#include "main.h"
typedef struct {
//data
} func1msg;
typedef struct {
//data
} func2msg;
typedef struct {
//data
} func3msg;
extern func1msg f1msg;
extern func2msg f2msg;
extern func3msg f3msg;
void Func1(void* para);
void Func2(void* para);
void Func3(void* para);
#endif
main.c
#include "main.h"
#include "task.h"
#include "systick.h"
#include "func.h"
int main() {
//TODO……
SystickInit();
TaskInit();
SetTask(Func1, 20, &f1msg);
TaskRun();
}