任务实现的例子:
我们会创建两个任务,并让这两个任务不断地切换,任务的主体都是让一 个变量按照一定的频率翻转,通过 KEIL 的软件仿真功能,在逻辑分析仪中观察变量的波 形变化,最终的波形图。
3.1为什么用任务
在裸机系统中,系统的主体就是 main 函数里面顺序执行的无限循环,这个无限循环里
面 CPU 按照顺序完成各种事情。在多任务系统中,我们根据功能的不同,把整个系统分割
成一个个独立的且无法返回的函数,这个函数我们称为任务。
在裸机系统中,两个变量轮流翻转只能用顺序执行的方式
int main()
{
for(;;)
{
flag1=1;
delay(100);
flag1=0;
delay(100);
flag2=1;
delay(100);
flag2=0;
delay(100);
}
}
在多任务系统中,两个变量的波形是完全一样的,就好像 CPU 在同时干两件事一样,这才是多任务的意义。虽然多任务系统和顺序执行的波形图一样,但是,代码的实现方式是完全不一样的,由原来的顺序执行变成了任务的主 动切换,这是根本区别。
3.2创建任务
3.2.1定义任务栈:要写一个RTOS必须将全局变量,局部变量,中断放回函数怎么存储的。在裸机系统 中,他们统统放在一个叫栈的地方,栈是单片机 RAM 里面一段连续的内存空间,栈的大小一般在启动文件或者链接脚本里面指定,最后由 C 库函数 main 进行初始化,在多任务系统中,每个任务都是独立的,互不干扰的,所以要为每个任务都分配独立的栈空间,这个栈空间通常是一个预先定义好的全局数组,也可以是动态分配的一段内存空间,但它们都存在于 RAM 中。
我们最终要实现两个变量按照一定的频率轮流的翻转,每个变量对应一个任务,那么 就需要定义两个任务栈
//任务1堆栈大小
#define TASK1_STACK_SIZE 128
StackType_t Task1Stack[TASK1_STACK_SIZE];
//任务2堆栈大小
#define TASK2_STACK_SIZE 128
StackType_t Task2Stack[TASK2_STACK_SIZE];
任务堆栈其实就是一个预先定义好的全局数据,数据类型为 StackType_t,大小由 TASK1_STACK_SIZE 这个宏来定义,默认为 128,单位为字,即 512 字节,这也是 FreeRTOS 推荐的最小的任务栈。
3.2.2定义任务控制块:在裸机系统中,程序的主体是 CPU 按照顺序执行的。而在多任务系统中,任务的执行 是由系统调度的。系统为了顺利的调度任务,为每个任务都额外定义了一个任务控制块, 这个任务控制块就相当于任务的身份证,里面存有任务的所有信息,比如任务的栈指针, 任务名称,任务的形参等。有了这个任务控制块之后,以后系统对任务的全部操作都可以 通过这个任务控制块来实现。
//定义任务控制块
TCB_t Task1TCB;
TCB_t Task2TCB;
3.2.3任务创建函数(重点)
任务的栈,任务的函数实体,任务的控制块最终需要联系起来才能由系统进行统一调度。那么这个联系的工作就由任务创建函数 xTaskCreateStatic()来实现,该函数在 task.c (task.c 第一次使用需要自行在文件夹 freertos 中新建并添加到工程的 freertos/source 组)中 定义,在 task.h 中声明,所有跟任务相关的函数都在这个文件定义。
FreeRTOS 中,任务的创建有两种方法,一种是使用动态创建,一 种是使用静态创建。动态创建时,任务控制块和栈的内存是创建任务时动态分配的,任务 删除时,内存可以释放。静态创建时,任务控制块和栈的内存需要事先定义好,是静态的 内存,任务删除时,内存不能释放。
/* 就绪列表初始化 */
prvInitialiseTaskLists();
Task1_Handle =xTaskCreateStatic(
(TaskFunction_t)Task1_Entry, /* 任务句柄 */
(char *)"Task1", /* 任务名称 */
(uint32_t)TASK1_STACK_SIZE , /* 任务栈大小,单位为字 */
(void *) NULL, /* 任务形参 */
(StackType_t *)Task1Stack, /* 任务栈起始地址 */
(TCB_t *)&Task1TCB ); /* 任务控制块 */
/* 将任务添加到就绪列表 */
vListInsertEnd( &( pxReadyTasksLists[1] ),
&( ((TCB_t *)(&Task1TCB))->xStateListItem ) );
3.3实现调度器
调度器是操作系统的核心,其主要功能就是实现任务的切换,即从就绪列表里面找到 优先级最高的任务,然后去执行该任务。从代码上来看,调度器无非也就是由几个全局变量和一些可以实现任务切换的函数组成,全部都在 task.c 文件中实现。
调度器的启动由 vTaskStartScheduler()函数来完成.
3.4任务切换
任务切换就是在就绪列表中寻找优先级最高的就绪任务,然后去执行该任务。但是目 前我们还不支持优先级,仅实现两个任务轮流切换。
//全局变量
POrtCRAR flagl;
portCHAR flag2;
extern List t pxReadyTasksListsl configMAX PRIORITIES ] ;
//任务句柄
TaskHandle t Task1 Handle;
#define TASK1 STACK SIZE 128
StackType t TaskIstack[TASK1 STACK SIZE];
TCB_t Task1TCB;
TaskHandle_ t Task2_ Handle;
#define TASK2 STACK SIZE 128
StackType t Task2Stack[TASK2 STACK SIZE];
TCB_t Task2TCB;
void delay (uint32_t count);
void Taskl_Entry(void *P_arg);
void Task2_Entry(void *p_arg);
int main(void)
{
prvInitialiseTaskLists();
Task1_Handle = xTaskCreateStatic((TaskFunction t)Task1_Entry,
(char *)"Taskl",
(uint32 t)TASK1 STACK SIZE,
(void *) NULL,
(StackType_ t *)Task1Stack,
TCB_t *)&Task1TCB ); /* 任务控制块 */
/* 将任务添加到就绪列表 */
vListInsertEnd( &( pxReadyTasksLists[1] ),
&( ((TCB_t *)(&Task1TCB))->xStateListItem ) );
Task2_Handle = xTaskCreateStatic((TaskFunction t)Task2_Entry,
(char *)"Task2",
(uint32 t)TASK2_STACK_SIZE,
(void *) NULL,
(StackType_ t *)Task2Stack,
TCB_t *)&Task2TCB ); /* 任务控制块 */
/* 将任务添加到就绪列表 */
vListInsertEnd( &( pxReadyTasksLists[2] ),
&( ((TCB_t *)(&Task2TCB))->xStateListItem ) );
vTaskStartScheduler();
for(;;)
{
}
}
void delay (uint32_ t count)
{
for (; count!=0; count--);
}
void Task1_Entry(void *P_arg)
{
for(;;)
{
flag1=1;
delay(100);
flag1=0;
delay(100);
taskYIELD();
}
}
void Task2_Entry(void *P_arg)
{
for(;;)
{
flag2=1;
delay(100);
flag2=0;
delay(100);
taskYIELD();
}
}