什么是任务
在 FreeRTOS 中,任务就是一个函数,原型如下:
void ATaskFunction( void *pvParameters );
这个函数不能返回
同一个函数,可以用来创建多个任务;换句话说,多个任务可以运行同一
个函数
函数内部,尽量使用局部变量:
每个任务都有自己的栈
每个任务运行这个函数时
任务 A 的局部变量放在任务 A 的栈里、任务 B 的局部变量放在任务 B
的栈里
不同任务的局部变量,有自己的副本
函数使用全局变量、静态变量的话
只有一个副本:多个任务使用的是同一个副本
要防止冲突
任务栈
在一个裸机系统中, 如果有全局变量, 有子函数调用, 有中断发生。 那么系统在运行的时候, 全局变量放在哪里, 子函数调用时, 局部变量放在哪里, 中断发生时, 函数返回地址放哪里。 如果只是单纯的裸机编程, 它们放哪里我们不用管, 但是如果要写一个 RTOS, 这些种种环境参数, 我们必须弄清楚他们是如何存储的。 在裸机系统中, 他们统统放在一个叫栈的地方, 栈是单
片机 RAM 里面一段连续的内存空间, 栈的大小一般在启动文件或者链接脚本里面指定, 最后由 C 库函数 main 进行初始化。
但是, 在多任务系统中, 每个任务都是独立的, 互不干扰的, 所以要为每个任务都分配独立的栈空间, 这个栈空间通常是一个预先定义好的全局数组, 也可以是动态分配的一段内存空间, 但它们都存在于 RAM 中。
在多任务系统中, 有多少个任务就需要定义多少个任务栈
#define TASK1_STACK_SIZE 128
StackType_t Task1Stack[TASK1_STACK_SIZE];
#define TASK2_STACK_SIZE 128
StackType_t Task2Stack[TASK2_STACK_SIZE];
任务栈其实就是一个预先定义好的全局数组, 数据类型为StackType_t, 大小由 TASK1_STACK_SIZE 这个宏来定义, 默认为 128, 单位为字, 即 512 字节, 这也是 FreeRTOS 推荐的最小的任务栈。
任务控制块
在多任务系统中,
任务的执行是由系统调度的。 系统为了顺利的调度任务, 为每个任务都额外定义了一个任务控制块, 这个任务控制块就相当于任务的身份证, 里面存有任务的所有信息, 比如任务的栈指针, 任务名称, 任务的形参等。 有了这个任务控制块之后, 以后系统对任务的全部操作都可以通过这个任务控制块来实现。
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /* 栈顶 */ (1)
ListItem_t xStateListItem; /* 任务节点 */ (2)
StackType_t *pxStack; /* 任务栈起始地址 */ (3)
/* 任务名称, 字符串形式 */(4)
char pcTaskName[ configMAX_TASK_NAME_LEN ];
} tskTCB;
typedef tskTCB TCB_t; (5)
代码(1): 栈顶指针, 作为 TCB 的第一个成员。
代码(2): 任务节点, 这是一个内置在 TCB 控制块中的链表节点, 通过这个节点, 可以将任务控制块挂接到各种链表中。 这个节点就类似晾衣架的钩子, TCB就是衣服。
代码(3): 任务栈起始地址。
代码(4): 任务名称, 字符串形式, 长度由宏 configMAX_TASK_NAME_LEN 来控制, 该宏在 FreeRTOSConfig.h 中定义, 默认为 16。
代码(5): 数据类型重定义。
调度器
调度器的启动由 vTaskStartScheduler()函数来完成
调度器是操作系统的核心, 其主要功能就是实现任务的切换, 即从就绪列表里面找到优先级最高的任务, 然后去执行该任务。
创建任务
动态方法
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数
const char * const pcName, // 任务的名字
const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为 word,10 表示
40 字节
void * const pvParameters, // 调用任务函数时传入的参数
UBaseType_t uxPriority, // 优先级
TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任
务
静态方法
//创建开始任务
StartTask_Handler=xTaskCreateStatic((TaskFunction_t )start_task, //任务函数 (1)
(const char* )"start_task",//任务名称 (2)
(uint32_t )START_STK_SIZE,//任务堆栈大小(3)
(void* )NULL,//传递给任务函数的参数 (4)
(UBaseType_t )START_TASK_PRIO, //任务优先级 (5)
(StackType_t* )StartTaskStack,//任务堆栈 (6)
(StaticTask_t* )&StartTaskTCB);//任务控制块 (7)
代码(1): 任务入口函数, 即任务函数的名称, 需要我们自己定义并且实现。
代码(2): 任务名字, 字符串形式, 最大长度由 FreeRTOSConfig.h 中定义的configMAX_TASK_NAME_LEN 宏指定, 多余部分会被自动截掉, 这里任务名字最好要与任务函数入口名字一致, 方便进行调试。
代码(3): 任务堆栈大小, 单位为字, 在 32 位的处理器下(STM32) , 一个字等于 4 个字节, 那么任务大小就为 128 * 4 字节。
代码(4): 任务入口函数形参, 不用的时候配置为 0 或者 NULL 即可。
代码(5): 任务的优先级。 优先级范围根据 FreeRTOSConfig.h 中的宏configMAX_PRIORITIES 决定, 如果使能configUSE_PORT_OPTIMISED_TASK_SELECTION, 这个宏定义, 则最多支持 32 个优先级; 如果不用特殊方法查找下一个运行的任务, 那么则不强制要求限制最大可用优先级数目。 在 FreeRTOS 中, 数值越大优先级越高, 0 代表最低优先级。
代码(6): 任务栈起始地址, 只有在使用静态内存的时候才需要提供, 在使用动态内存的时候会根据提供的任务栈大小自动创建。
代码(7): 任务控制块指针, 在使用静态内存的时候, 需要给任务初始化函数 xTaskCreateStatic()传递预先定义好的任务控制块的指针。 在使用动态内存的时候, 任务创建函数 xTaskCreate()会返回一个指针指向任务控制块, 该任务控制块是 xTaskCreate()函数里面动态分配的一块内存。
编程(以动态方法创建)
创建两个LED灯,分别控制其闪烁
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 2
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
//任务优先级
#define LED2_TASK_PRIO 3
//任务堆栈大小
#define LED2_STK_SIZE 50
//任务句柄
TaskHandle_t LED2Task_Handler;
//任务函数
void led2_task(void *pvParameters);
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
LED_Init();
USART1_Init(115200);
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
//创建LED2任务
xTaskCreate((TaskFunction_t )led2_task,
(const char* )"led2_task",
(uint16_t )LED2_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED2_TASK_PRIO,
(TaskHandle_t* )&LED2Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
//LED2任务函数
void led2_task(void *pvParameters)
{
while(1)
{
LED2=0;
vTaskDelay(800);
LED2=1;
vTaskDelay(200);
}
}