一、什么是任务
在裸机系统中,系统的主体就是 main 函数里面顺序执行的无限循环,这个无限循环里面 CPU 按照顺序完成各种事情。在多任务系统中,我们根据功能的不同,把整个系统分割成一个个独立的且无法返回的函数,这个函数我们称为任务。任务的大概形式具体代码如下所示:
void task_entry (void *parg)
{
/* 任务主体,无限循环且不能返回 */
for (;;)
{
/* 任务主体代码 */
}
}
定义任务栈
我们先回想下,在一个裸机系统中,如果有全局变量,有子函数调用,有中断发生。那么系统在运行的时候,全局变量放在哪里,子函数调用时,局部变量放在哪里,中断发生时,函数返回地址放哪里。如果只是单纯的裸机编程,它们放哪里我们不用管,但是如果要写一个 RTOS,这些种种环境参数,我们必须弄清楚他们是如何存储的。在裸机系统中,他们统统放在一个叫栈的地方,栈是单片机 RAM 里面一段连续的内存空间,栈的大小一般在启动文件或者链接脚本里
面指定,最后由 C 库函数 main 进行初始化。
但是,在多任务系统中,每个任务都是独立的,互不干扰的,所以要为每个任务都分配独立的栈空间,这个栈空间通常是一个预先定义好的全局数组,也可以是动态分配的一段内存空间,但它们都存在于 RAM 中。
本小节我们要实现两个 LED 按照一定的频率轮流的翻转,每个 LED 对应一个任务,那么就需要定义两个任务栈,具体代码如下所示。在多任务系统中,有多少个任务就需要定义多少个任务栈。
#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 推荐的最小的任务栈。在 FreeRTOS 中,凡是涉及到数据类型的地方,FreeRTOS 都会将标准的 C 数据类型用 typedef重新取一个类型名。这些经过重定义的数据类型放在 portmacro.h 这个头文件。
定义任务函数
任务是一个独立的函数,函数主体无限循环且不能返回。本章我们在 main.c中定义的两个任务具体代码如下所示:
//任务 1 函数
void task1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200); //代表延时200个节拍,前面FreeRTOSConfig.h中有配置一个节拍占多少秒(这里配置的1MS)
LED1=1;
vTaskDelay(800);
}
}
//任务 2 函数
void task2_task(void *pvParameters)
{
while(1)
{
LED2=0;
vTaskDelay(800);
LED2=1;
vTaskDelay(200);
}
}
正如我们所说的那样,任务是一个独立的、无限循环且不能返回的函数。
定义任务控制块(TCB)
在裸机系统中,程序的主体是 CPU 按照顺序执行的。而在多任务系统中,任务的执行是由系统调度的。系统为了顺利的调度任务,为每个任务都额外定义了一个任务控制块,这个任务控制块就相当于任务的身份证,里面存有任务的所有信息,比如任务的栈指针,任务名称,任务的形参等。有了这个任务控制块之后,以后系统对任务的全部操作都可以通过这个任务控制块来实现。定义一个任务控制块需要一个新的数据类型,该数据类型在 task.c 这 C 文件中声明,具体代码如下所示,使用它可以为每个任务都定义一个任务控制块实体。
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就是衣服。有关链表的 C 知识可以百度了解下,这里不再赘述。ListItem_t就是freeRTOS非常重要的列表项,下面会重点讲这一块 (列表项)
代码(3):任务栈起始地址。
代码(4):任务名称,字符串形式,长度由宏 configMAX_TASK_NAME_LEN 来控制,该宏在 FreeRTOSConfig.h 中定义,默认为 16。
代码(5):数据类型重定义。
在本章实验中,我们在 main.c 文件中为两个任务定义的任务控制块,具体代码如下所示:
StaticTask_t Task1TaskTCB;
StaticTask_t Task2TaskTCB;
任务创建-静态方法
1 定义任务函数
2 空闲任务与定时器任务堆栈函数3 定义任务栈和任务控制块
4 静态创建任务函数
5 启动任务
6 主函数
定义任务函数
任务实际上就是一个无限循环且不带返回值的 C 函数。我们创建 2 个这样的任务,让开发板上面的 LED 灯以 1S 的频率闪烁,具体实现代码如下。
//任务 1 函数
void task1_task(void *pvParameters)
{
while(1) //(1)
{
LED1=0;
vTaskDelay(200); //(2)
LED1=1;
vTaskDelay(800);
}
}
//任务 2 函数
void task2_task(void *pvParameters)
{
while(1)
{
LED2=0;
vTaskDelay(800);
LED2=1;
vTaskDelay(200);
}
}
代码(1):任务必须是一个死循环,否则任务将通过 LR 返回,如果 LR 指向了非法的内存就会产生 HardFault_Handler,而 FreeRTOS 指向一个死循环,那么任务返回之后就在死循环中执行,这样子的任务是不安全的,所以为避免这种情况,任务一般都是死循环并且无返回值的。
代码(2):任务里面的延时函数必须使用 FreeRTOS 里面提供的延时函数,并不能使用我们裸机编程中的那种延时。这两种的延时的区别是 FreeRTOS 里面的延时是阻塞延时,即调用 vTaskDelay()函数的时候,当前任务会被挂起,调度器会切换到其它就绪的任务,从而实现多任务。如果还是使用裸机编程中的那种延时,那么整个任务就成为了一个死循环,如果恰好该任务的优先级是最高的,那么系统永远都是在这个任务中运行,比它优先级更低的任务无法运行,根本无法实现多任务。但 FreeRTOS 中的延时最小值只能是 ms,对于 us 级别的延时怎么办呢,这个在前面移植操作系统时我们介绍过,使用 SysTick.c 文件中的延时函数亦可,包括 ms 和 us 级延时,这两个延时函数与裸机实验中是不一样的,所以不要搞混。
定义任务栈和任务控制块
目前我们只创建了 2 个任务,当任务进入延时的时候,因为没有另外就绪的用户任务,那么系统就会进入空闲任务,空闲任务是 FreeRTOS 系统自己启动的一个任务,优先级最低。当整个系统都没有就绪任务的时候,系统必须保证有一个任务在运行,空闲任务就是为这个设计的。当用户任务延时到期,又会从空闲任务切换回用户任务。
在 FreeRTOS 系统中,每一个任务都是独立的,他们的运行环境都单独的保存在他们的栈空间当中。那么在定义好任务函数之后,我们还要为任务定义一个栈,目前我们使用的是静态内存,所以任务栈是一个独立的全局变量,具体见代码。任务的栈占用的是 MCU 内部的 RAM,当任务越多的时候,需要使用的栈空间就越大,即需要使用的 RAM 空间就越多。一个MCU 能够支持多少任务,就得看芯片的 RAM 空间有多少。
//任务堆栈大小
#define TASK1_STK_SIZE 128
//任务堆栈
StackType_t Task1TaskStack[TASK1_STK_SIZE];
//任务堆栈大小
#define TASK2_STK_SIZE 128
//任务堆栈
StackType_t Task2TaskStack[TASK2_STK_SIZE];
在大多数系统中需要做栈空间地址对齐,在 FreeRTOS 中是以 8 字节大小对齐,并且会检查堆栈是否已经对齐,其中 portBYTE_ALIGNMENT 是在portmacro.h 里面定义的一个宏,其值为 8,就是配置为按 8 字节对齐,当然用户可以选择按 1、2、4、8、16、32 等字节对齐,目前默认为 8。定义好任务函数和任务栈之后,我们还需要为任务定义一个任务控制块,通常我们称这个任务控制块为任务的身份证。在 C 代码上,任务控制块就是一个结构体,里面有非常多的成员,这些成员共同描述了任务的全部信息。
//任务控制块
StaticTask_t Task1TaskTCB;
//任务控制块
StaticTask_t Task2TaskTCB;
静态创建任务函数
一个任务的三要素是任务主体函数,任务栈,任务控制块,那么怎么样把这三个要素联合在一起?FreeRTOS 里面有一个叫静态任务创建函数xTaskCreateStatic(),它就可以实现。它将任务主体函数,任务栈(静态的)和任务控制块(静态的)这三者联系在一起,让任务可以随时被系统启动,具体如下。
//创建开始任务
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()函数里面动态分配的一块内存。
启动任务
当任务创建好后,是处于任务就绪(Ready),在就绪态的任务可以参与操作系统的调度。但是此时任务仅仅是创建了,还未开启任务调度器,也没创建空闲任务与定时器任务(如果使能了 configUSE_TIMERS 这个宏定义),那这两个任务就是在启动任务调度器中实现,每个操作系统,任务调度器只启动一次,之后就不会再次执行了,FreeRTOS 中启动任务调度器的函数是vTaskStartScheduler(),并且启动任务调度器的时候就不会返回,从此任务管理都由 FreeRTOS 管理,此时才是真正进入实时操作系统中的第一步。
主函数文件
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务堆栈
StackType_t StartTaskStack[START_STK_SIZE];
//任务控制块
StaticTask_t StartTaskTCB;
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define TASK1_TASK_PRIO 2
//任务堆栈大小
#define TASK1_STK_SIZE 128
//任务堆栈
StackType_t Task1TaskStack[TASK1_STK_SIZE];
//任务控制块
StaticTask_t Task1TaskTCB;
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);
//任务优先级
#define TASK2_TASK_PRIO 3
//任务堆栈大小
#define TASK2_STK_SIZE 128
//任务堆栈
StackType_t Task2TaskStack[TASK2_STK_SIZE];
//任务控制块
StaticTask_t Task2TaskTCB;
//任务句柄
TaskHandle_t Task2Task_Handler;
//任务函数
void task2_task(void *pvParameters);
/* 空闲任务任务堆栈 */
static StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE];
/* 定时器任务堆栈 */
static StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH];
/* 空闲任务控制块 */
static StaticTask_t Idle_Task_TCB;
/* 定时器任务控制块 */
static StaticTask_t Timer_Task_TCB;
//获取空闲任务地任务堆栈和任务控制块内存,因为本例程使用的
//静态内存,因此空闲任务的任务堆栈和任务控制块的内存就应该
//有用户来提供,FreeRTOS 提供了接口函数 vApplicationGetIdleTaskMemory()
//实现此函数即可。
//ppxIdleTaskTCBBuffer:任务控制块内存
//ppxIdleTaskStackBuffer:任务堆栈内存
//pulIdleTaskStackSize:任务堆栈大小
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer=&Idle_Task_TCB;
*ppxIdleTaskStackBuffer=Idle_Task_Stack;
*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}
//获取定时器任务的任务堆栈和任务控制块内存
//ppxTimerTaskTCBBuffer : 任务控制块内存
//ppxTimerTaskStackBuffer: 任务堆栈内存
//pulTimerTaskStackSize : 任务堆栈大小
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize)
{
*ppxTimerTaskTCBBuffer=&Timer_Task_TCB;/* 任务控制块内存 */
*ppxTimerTaskStackBuffer=Timer_Task_Stack;/* 任务堆栈内存 */
*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;/* 任务堆栈大小
}
/********************************************************************
***********
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*********************************************************************
**********/
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组 4
LED_Init();
USART1_Init(115200);
//创建开始任务
StartTask_Handler=xTaskCreateStatic((TaskFunction_t )start_task,//任务函数
(const char* )"start_task",//任务名称
(uint32_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO,//任务优先级
(StackType_t* )StartTaskStack,//任务堆栈
(StaticTask_t* )&StartTaskTCB);//任务控制块
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建 TASK1 任务
Task1Task_Handler=xTaskCreateStatic((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint32_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(StackType_t* )Task1TaskStack,
(StaticTask_t* )&Task1TaskTCB);
//创建 TASK2 任务
Task2Task_Handler=xTaskCreateStatic((TaskFunction_t )task2_task,
(const char* )"task2_task",
(uint32_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(StackType_t* )Task2TaskStack,
(StaticTask_t* )&Task2TaskTCB);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//任务 1 函数
void task1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
//任务 2 函数
void task2_task(void *pvParameters)
{
while(1)
{
LED2=0;
vTaskDelay(800);
LED2=1;
vTaskDelay(200);
}
}
*/
将工程程序编译下载到开发板内,可以看到 DS0 和 DS1 指示灯间隔 1 秒闪烁一次。
任务创建-动态方法
在前面实验中,我们使用静态内存方法创建了 2 个任务,任务的栈,任务的控制块用的都是静态内存,必须由用户预先定义,所以静态内存这种方法我们在使用 FreeRTOS的时候用的比较少,通常的方法是使用动态内存方法创建任务,由系统自动分配任务栈和控制块。
动态内存分配
在使用静态方法创建任务的例程中,任务控制块和任务栈的内存空间都是从内部的 SRAM 里面分配的,具体分配到哪个地址由编译器决定。现在我们开始使用动态内存,即堆,其实堆也是内存,也属于 SRAM。FreeRTOS 做法是在 SRAM 里面定义一个大数组,也就是堆内存,供 FreeRTOS 的动态内存分配函数使用,在第一次使用的时候,系统会将定义的堆内存进行初始化,这些代码在 FreeRTOS提供的内存管理方案中实现(heap_1.c、heap_2.c、heap_4.c 等,具体的内存管理方案后面详细讲解),具体见代码。
//系统所有总的堆大小
#define configTOTAL_HEAP_SIZE ((size_t)(36*1024)) //(1)
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; //(2)
/* 如果这是第一次调用 malloc 那么堆将需要初始化,以设置空闲块列表。*/
if ( pxEnd == NULL )
{
prvHeapInit(); //(3)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
代码(1):堆内存的大小为 configTOTAL_HEAP_SIZE ,在 FreeRTOSConfig.h中由我们自己定义,configSUPPORT_DYNAMIC_ALLOCATION 这个宏定义在使用FreeRTOS 操作系统的时候必须开启,且关闭configSUPPORT_STATIC_ALLOCATION 这个宏。
代码(2):从内部 SRAMM 里面定义一个静态数组 ucHeap,大小由configTOTAL_HEAP_SIZE 这个宏决定,目前定义为 36KB。定义的堆大小不能超过内部 SRAM 的总大小。
代码(3):如果这是第一次调用 malloc 那么需要将堆进行初始化,以设置空闲块列表,方便以后分配内存,初始化完成之后会取得堆的结束地址,在MemMang 中的 5 个内存分配 heap_x.c 文件中实现。
动态创建任务函数
使用动态内存的时,使用 xTaskCreate()函数来创建任务。如下:
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数 (1)
(const char* )"start_task", //任务名称 (2)
(uint16_t )START_STK_SIZE, //任务堆栈大小 (3)
(void* )NULL, //传递给任务函数的参数 (4)
(UBaseType_t )START_TASK_PRIO, //任务优先级 (5)
(TaskHandle_t* )&StartTask_Handler); //任务句柄 (6)
代码(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):任务控制块指针,在使用内存的时候,需要给任务初始化函数xTaskCreateStatic()传递预先定义好的任务控制块的指针。在使用动态内存的时候,任务创建函数 xTaskCreate()会返回一个指针指向任务控制块,该任务控制块是 xTaskCreate()函数里面动态分配的一块内存。
主函数文件
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
//任务优先级
#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);
}
}
从代码可以看出,动态方法创建的任务与静态方法创建的差别很小。
实验现象
将工程程序编译下载到开发板内,可以看到 DS0 和 DS1 指示灯间隔 1 秒闪烁一次。
动态方法同时创建三个任务的main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "FreeRTOS.h"
#include "task.h"
#include "LED.h"
#include "Serial.h"
#include "OLED.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小,字,128*4 = 512字节
#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);
void vUart1Task( void *pvParameters );
void vOLEDTask( void *pvParameters );
int main(void)
{
//初始化滴答时钟,FreeRTOS使用
SysTick_Init(72);
//FreeRTOS中有15个中断优先级,也就是4位,抢占式优先级就占据了这4位
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
LED_Init();
Serial_Init();
OLED_Init();
//创建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);
xTaskCreate(vUart1Task, "vUart1Task", 1000, NULL, 1, NULL);
xTaskCreate(vOLEDTask, "vOLEDTask", 1000, NULL, 2, NULL);
vTaskStartScheduler(); //开启任务调度
}
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
LED1_ON();
vTaskDelay(200); //延时200ms
LED1_OFF();
vTaskDelay(800);
}
}
void vUart1Task( void *pvParameters )
{
int iiii = 0;
for( ;; )
{
Serial_Printf("serial:%d\r\n",iiii);
vTaskDelay(1000); //延时1s
iiii++;
}
}
void vOLEDTask( void *pvParameters )
{
int iiii = 0;
for( ;; )
{
OLED_ShowString(1, 1, "Count:"); //1行1列显示字符串Count:
OLED_ShowNum(1, 7, iiii, 5); //OLED不断刷新显示CountSensor_Get的返回值
iiii++;
vTaskDelay(1000); //延时1s
}
}