早期的嵌入式开发没有嵌入式操作系统的概念,直接操作裸机,再裸机上写程序没有操作系统的概念,通常把程序分为前台系统和后台系统两部分。简单的小系统通常是前后台系统,这样的程序包括一个死循环和若干个中断服务程序:应用程序是一个无限循环,循环中调用API函数完成所需要的操作,这个大循环叫做后台系统。中断服务程序用于处理系统用的异步事件,也就是前端系统,前台是中断级,后台是任务级别。
这样就会出现一个问题就是任务的实时性很差,对于简单的系统已经够了,但是对于复杂应用比如说用网络或者界面,这个时候系统的效率就非常重要了,这是就可以采用操作系统,对于STM32最常见的就是RTOS,强调的实时性,分为硬实时和软实时,硬实时不允许超时,软实时对超时处理没有这么严格。
再试试的操作系统中,我们可以把实现的功能划分为多个任务,每个任务负责实现其中的一个部分,每个任务都是一个很简单的程序,通常是一个死循环。RTOS操作系统的核心内容在于实时内核,实时内核是一个可剥夺形内核,即任务调度器可以剥夺其他任务的CPU使用权。
RTOS运行逻辑为:RTOS系统运行的永远是处于就绪态优先级最高的任务。
第一部分:任务:
任务的特性
- 简单
- 没有使用限制
- 支持抢占
- 支持优先级
- 每个任务都拥有堆栈导致了RAM使用量增大
- 如果使用抢占的话必须仔细考虑重入问题
四种任务状态
- 运行态:正在运行;
- 就绪态:准备好可以运行了,下次做任务调度的时候按照优先级来运行;
- 阻塞态:没有正常运行,可能在等待某个事件发生;
- 挂起态:
任务优先级决定了任务的执行优先级
可调范围为:0~configMAX_PRIORITIES-1
数字越大,优先级越高,这个宏表示最带的优先级,在config里面可以找到,有32级优先级,也即是0~31,同一优先级可以有多个任务
任务的基本实现
即在函数中实现任务的具体工作,一般每个任务会是一个死循环,如果要退出一定要调用删除任务API vTaskDelete
任务堆栈
任务堆栈用来保存现场(CPU寄存器值),创建任务的时候需要指定任务堆栈,任务堆栈的变量类型为StackType_t,此变量类型如下:
- #define portSTACK_TYPE unit32_t
- #define portBASE_TYPE long
- typedef portSTACK_TYPE StackType_t
需要注意的时在配置文件中,任务堆栈大小#define START_STK_SIZE 128 ,这里分配的内存时128*4B而不是128B
注意,如果任务分配的堆栈太小有可能会卡死,这个时候要把堆栈调大。
任务创建和删除(动态方法)
1.实验目的
学习xTaskCreate()和vTaskDelete()这两个函数的使用
2. 设计实验
设计三个任务:start_task、task1_task、task2_task,功能分别如下:
start_task: 用来创建其他的两个任务
task1_task: 当此任务运行5次后就会调用vTaskDelete删除任务
task2_task: 此任务也会控制LED0的闪烁,并且周期性的刷新LCD指定区域的背景颜色
task2_task: 此任务普通的应用任务,此任务也会控制LED1的闪烁,并且周期性的刷新LCD指定区域的背景颜色
搭建一个框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
#include "sys.h" #include "delay.h" #include "usart.h" #include "led.h" #include "FreeRTOS.h" #include "task.h" #define START_STK_SIZE 120 #define STATR_TASK_PRIO 1 //注意,优先级0和31不能使用,因为空闲任务需要使用优先级0 void start_task(void * pvParameters); // 任务函数 TaskHandle_t StartTask_Handler; // 任务句柄 #define TASK1_STK_SIZE 120 #define TASK1_TASK_PRIO 2 void task1_task(void * pvParameters); TaskHandle_t Task1Task_Handler; #define TASK2_STK_SIZE 120 #define TASK2_TASK_PRIO 3 void task2_task(void * pvParameters); TaskHandle_t Task2Task_Handler; int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4 delay_init(); //延时函数初始化 uart_init(115200); //初始化串口 LED_Init(); //初始化LED xTaskCreate((TaskFunction_t ) start_task, // 任务的函数 (char * ) "start_task", // 任务的名字,随便取名 (uint16_t ) START_STK_SIZE, //任务的大小 (void * ) NULL, // 任务的参数 (UBaseType_t ) STATR_TASK_PRIO, // 优先级 (TaskHandle_t * ) &StartTask_Handler); } void start_task(void * pvParameters) { // 创建Task1 xTaskCreate((TaskFunction_t ) task1_task, // 任务的函数 (char * ) "task1_task", // 任务的名字,随便取名 (uint16_t ) TASK1_STK_SIZE, //任务的大小 (void * ) NULL, // 任务的参数 (UBaseType_t ) TASK1_TASK_PRIO, // 优先级 (TaskHandle_t * ) &Task1Task_Handler); // 创建Task2 xTaskCreate((TaskFunction_t ) task2_task, // 任务的函数 (char * ) "task2_task", // 任务的名字,随便取名 (uint16_t ) TASK2_STK_SIZE, //任务的大小 (void * ) NULL, // 任务的参数 (UBaseType_t ) TASK2_TASK_PRIO, // 优先级 (TaskHandle_t * ) &Task2Task_Handler); // 删除任务,要删除那个任务就把任务句柄放进来,如果是NULL就是删除自身 vTaskDelete(StartTask_Handler); } void task1_task(void * pvParameters) { } void task2_task(void * pvParameters) { } |
接下来实现每个任务的具体内容:
首先先简单的测试一下框架是不是正确的,改写一下框架中两个任务的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void task1_task(void * pvParameters) { while(1) { LED0 = ~LED0; vTaskDelay(2000); } } void task2_task(void * pvParameters) { while(1) { LED1 = 0; vTaskDelay(200); LED1 = 1; vTaskDelay(800); } } |
完成最终代码,在TASK执行到第5次的时候删除掉TASK2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
#include "sys.h" #include "delay.h" #include "usart.h" #include "led.h" #include "FreeRTOS.h" #include "task.h" #define START_STK_SIZE 120 #define STATR_TASK_PRIO 1 //注意,优先级0和31不能使用,因为空闲任务需要使用优先级0 void start_task(void * pvParameters); // 任务函数 TaskHandle_t StartTask_Handler; // 任务句柄 #define TASK1_STK_SIZE 120 #define TASK1_TASK_PRIO 2 void task1_task(void * pvParameters); TaskHandle_t Task1Task_Handler; #define TASK2_STK_SIZE 120 #define TASK2_TASK_PRIO 3 void task2_task(void * pvParameters); TaskHandle_t Task2Task_Handler; int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4 delay_init(); //延时函数初始化 uart_init(115200); //初始化串口 LED_Init(); //初始化LED xTaskCreate((TaskFunction_t ) start_task, // 任务的函数 (char * ) "start_task", // 任务的名字,随便取名 (uint16_t ) START_STK_SIZE, //任务的大小 (void * ) NULL, // 任务的参数 (UBaseType_t ) STATR_TASK_PRIO, // 优先级 (TaskHandle_t * ) &StartTask_Handler); vTaskStartScheduler(); // 开启任务调度器 } void start_task(void * pvParameters) { // 创建Task1 xTaskCreate((TaskFunction_t ) task1_task, // 任务的函数 (char * ) "task1_task", // 任务的名字,随便取名 (uint16_t ) TASK1_STK_SIZE, //任务的大小 (void * ) NULL, // 任务的参数 (UBaseType_t ) TASK1_TASK_PRIO, // 优先级 (TaskHandle_t * ) &Task1Task_Handler); // 创建Task2 xTaskCreate((TaskFunction_t ) task2_task, // 任务的函数 (char * ) "task2_task", // 任务的名字,随便取名 (uint16_t ) TASK2_STK_SIZE, //任务的大小 (void * ) NULL, // 任务的参数 (UBaseType_t ) TASK2_TASK_PRIO, // 优先级 (TaskHandle_t * ) &Task2Task_Handler); // 删除任务,要删除那个任务就把任务句柄放进来,如果是NULL就是删除自身 vTaskDelete(StartTask_Handler); } void task1_task(void * pvParameters) { char task1_num=0; while(1) { task1_num++; printf("TASK1 is Running...%d\r\n", task1_num); if(task1_num == 5) { // i=0; printf("Delete TASK2!!!\r\n"); vTaskDelete(Task2Task_Handler); } LED0 = ~LED0; vTaskDelay(1000); } } void task2_task(void * pvParameters) { char task2_num=0; while(1) { task2_num++; printf("TASK2 is Running.. %d\r\n", task2_num); LED1 = ~LED1; vTaskDelay(1000); } } |
任务的创建和删除(静态方法)
如果需要使用静态方法则需要将宏在FreeRTOSConfig里面将configSUPPORT_STATIC_ALLOCATION设置为1,否则由于条件编译时0因此找不到静态创建方法,如果config里面没有就加上去,这个时候如果编译就会报出一个vApplicationGetIdleTaskMemory错误,这是由于FreeRTOS默认会自动创建至少一个任务就是空闲任务,宏打开之后这个空闲任务也会使用静态方法来创建,那么这个空闲任务也是也是需要堆栈内存并且其任务控制块也是需要内存的,这一块需要用户去提供,所以这个报错的函数就需要用户自己去实现了。
另外还会报一个定时器的错误,这个原因同上,也是需要我们自己去实现的。
下面是实现了这两个函数之后没有错误的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
#include "sys.h" #include "delay.h" #include "usart.h" #include "led.h" #include "FreeRTOS.h" #include "task.h" // 空闲任务 static StackType_t IdelTaskStack[configMINIMAL_STACK_SIZE]; static StaticTask_t IdleTaskTCB; // 定时器任务 static StackType_t TimerTaskStack[configMINIMAL_STACK_SIZE]; static StaticTask_t TimerTaskTCB; // 空闲任务所需内存 void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, // 任务控制块内存 tackType_t **ppxIdleTaskStackBuffer, // 堆栈内存 uint32_t *pulIdleTaskStackSize ) // 堆栈大小 { *ppxIdleTaskTCBBuffer=&IdleTaskTCB; *ppxIdleTaskStackBuffer=IdelTaskStack; *pulIdleTaskStackSize=configMINIMAL_STACK_SIZE; } // 定时器任务所需内存 void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimerTaskTCBBuffer, // 任务控制块内存 StackType_t **ppxTimerTaskStackBuffer, // 堆栈内存 uint32_t *pulTimerTaskStackSize ) // 堆栈大小 { *ppxTimerTaskTCBBuffer=&TimerTaskTCB; *ppxTimerTaskStackBuffer=TimerTaskStack; *pulTimerTaskStackSize=configMINIMAL_STACK_SIZE; } int main(void) { delay_init(); //延时函数初始化 uart_init(115200); //初始化串口 LED_Init(); //初始化LED vTaskStartScheduler(); //开启任务调度 } |
综上,我做了:
- 改宏configSUPPORT_STATIC_ALLOCATION,以使能静态创建函数
- 使能时候会出现两个问题,有两个函数需要自己进行实现,分别是给空闲任务分配必要的内存vApplicationGetIdleTaskMemory以及给定时器分配内存vApplicationGetTimerTaskMemory
接下来按照之前动态方法编写的测试代码要求来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
#include "sys.h" #include "delay.h" #include "usart.h" #include "led.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); // TASK1任务 #define TASK1_TASK_PRIO 2 #define TASK1_STK_SIZE 128 StackType_t TASK1TaskStack[START_STK_SIZE]; // 任务堆栈 StaticTask_t TASK1TaskTCB; TaskHandle_t TASK1Task_Handler; void task1_task(void *pvParameters); // TASK2任务 #define TASK2_TASK_PRIO 3 #define TASK2_STK_SIZE 128 StackType_t TASK2TaskStack[START_STK_SIZE]; // 任务堆栈 StaticTask_t TASK2TaskTCB; TaskHandle_t TASK2Task_Handler; void task2_task(void *pvParameters); // 空闲任务 static StackType_t IdelTaskStack[configMINIMAL_STACK_SIZE]; static StaticTask_t IdleTaskTCB; // 定时器任务 static StackType_t TimerTaskStack[configMINIMAL_STACK_SIZE]; static StaticTask_t TimerTaskTCB; // 空闲任务所需内存 void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, // 任务控制块内存 StackType_t **ppxIdleTaskStackBuffer, // 堆栈内存 uint32_t *pulIdleTaskStackSize ) // 堆栈大小 { *ppxIdleTaskTCBBuffer=&IdleTaskTCB; *ppxIdleTaskStackBuffer=IdelTaskStack; *pulIdleTaskStackSize=configMINIMAL_STACK_SIZE; } // 定时器任务所需内存 void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimerTaskTCBBuffer, // 任务控制块内存 StackType_t **ppxTimerTaskStackBuffer, // 堆栈内存 uint32_t *pulTimerTaskStackSize ) // 堆栈大小 { *ppxTimerTaskTCBBuffer=&TimerTaskTCB; *ppxTimerTaskStackBuffer=TimerTaskStack; *pulTimerTaskStackSize=configMINIMAL_STACK_SIZE; } int main(void) { delay_init(); //延时函数初始化 uart_init(115200); //初始化串口 LED_Init(); //初始化LED // 创建开始任务 StartTask_Handler=xTaskCreateStatic((TaskFunction_t) start_task, (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) { // 创建Task1 TASK1Task_Handler=xTaskCreateStatic((TaskFunction_t) task1_task, (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, (char * ) "task1_task", (uint32_t ) TASK2_STK_SIZE, (void * ) NULL, (UBaseType_t ) TASK2_TASK_PRIO, (StackType_t * ) TASK2TaskStack, (StaticTask_t *) &TASK2TaskTCB ); // 创建完任务之后要把开始任务删除掉 vTaskDelete(StartTask_Handler); } void task1_task(void * pvParameters) { char task1_num=0; while(1) { task1_num++; printf("TASK1 is Running...%d\r\n", task1_num); LED0 = ~LED0; // 运行到第5次的时候进行删除 if(task1_num == 8) { // i=0; vTaskDelete(TASK2Task_Handler); printf("Delete TASK2!!!\r\n"); } vTaskDelay(1000); } } void task2_task(void * pvParameters) { char task2_num=0; while(1) { task2_num++; printf("TASK2 is Running.. %d\r\n", task2_num); LED1 = ~LED1; vTaskDelay(1000); } } |
任务挂起和恢复
任务挂起相当于是任务暂停,和阻塞态一样,任务挂态以后也不能被调度器调用进入运行状态,但是挂起态的任务没有超时时间。需要注意的是挂起态在恢复之后不是直接进入运行态,而是先进入就绪态,具体什么时候运行取决于任务调度器。
任务挂起和恢复API函数如下:
- vTaskSuspend(): 挂起一个任务
- vTaskResume():恢复一个任务的运行
- xTaskResumeFromISR():在中断服务函数中恢复一个任务运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
#include "sys.h" #include "delay.h" #include "usart.h" #include "led.h" #include "key.h" #include "FreeRTOS.h" #include "task.h" // start_task #define START_STK_SIZE 120 #define STATR_TASK_PRIO 1 //注意,优先级0和31不能使用,因为空闲任务需要使用优先级0 void start_task(void * pvParameters); // 任务函数 TaskHandle_t StartTask_Handler; // 任务句柄 // task1 #define TASK1_STK_SIZE 120 #define TASK1_TASK_PRIO 2 void task1_task(void * pvParameters); TaskHandle_t Task1Task_Handler; // task2 #define TASK2_STK_SIZE 120 #define TASK2_TASK_PRIO 3 void task2_task(void * pvParameters); TaskHandle_t Task2Task_Handler; // task3 #define KEY_STK_SIZE 120 #define KEY_TASK_PRIO 4 void KEY_task(void * pvParameters); TaskHandle_t KEYTask_Handler; int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4 delay_init(); //延时函数初始化 uart_init(115200); //初始化串口 LED_Init(); //初始化LED KEY_Init(); xTaskCreate((TaskFunction_t ) start_task, // 任务的函数 (char * ) "start_task", // 任务的名字,随便取名 (uint16_t ) START_STK_SIZE, //任务的大小 (void * ) NULL, // 任务的参数 (UBaseType_t ) STATR_TASK_PRIO, // 优先级 (TaskHandle_t * ) &StartTask_Handler); vTaskStartScheduler(); // 开启任务调度器 } void start_task(void * pvParameters) { // 创建Task1 xTaskCreate((TaskFunction_t ) task1_task, // 任务的函数 (char * ) "task1_task", // 任务的名字,随便取名 (uint16_t ) TASK1_STK_SIZE, //任务的大小 (void * ) NULL, // 任务的参数 (UBaseType_t ) TASK1_TASK_PRIO, // 优先级 (TaskHandle_t * ) &Task1Task_Handler); // 创建Task2 xTaskCreate((TaskFunction_t ) task2_task, // 任务的函数 (char * ) "task2_task", // 任务的名字,随便取名 (uint16_t ) TASK2_STK_SIZE, //任务的大小 (void * ) NULL, // 任务的参数 (UBaseType_t ) TASK2_TASK_PRIO, // 优先级 (TaskHandle_t * ) &Task2Task_Handler); // 创建Task3 xTaskCreate((TaskFunction_t ) KEY_task, // 任务的函数 (char * ) "KEY_task", // 任务的名字,随便取名 (uint16_t ) KEY_STK_SIZE, //任务的大小 (void * ) NULL, // 任务的参数 (UBaseType_t ) KEY_TASK_PRIO, // 优先级 (TaskHandle_t * ) &KEYTask_Handler); // 删除任务,要删除那个任务就把任务句柄放进来,如果是NULL就是删除自身 vTaskDelete(StartTask_Handler); } void task1_task(void * pvParameters) { char task1_num=0; while(1) { task1_num++; printf("TASK1 is Running...%d\r\n", task1_num); LED0 = ~LED0; // 运行到第100次的时候进行删除 if(task1_num == 100) { // i=0; vTaskDelete(Task2Task_Handler); printf("Delete TASK2!!!\r\n"); } vTaskDelay(1000); } } void task2_task(void * pvParameters) { char task2_num=0; while(1) { task2_num++; printf("TASK2 is Running.. %d\r\n", task2_num); LED1 = ~LED1; vTaskDelay(1000); } } void KEY_task(void * pvParameters) { u8 key; while(1) { key=KEY_Scan(0); switch(key) { case WKUP_PRES: vTaskSuspend(Task1Task_Handler); printf("Task1 suspend!!\r\n"); break; case 1: vTaskResume(Task1Task_Handler); printf("Task1 Resumed!!\r\n"); break; } vTaskDelay(10); } } |