FreeRTOS英文名为 real_timeOS,TSOS为分时操作系统(windos系统)
详细见:FreeRTOS官方网站地址: www.freertos.org/
如果我们要在开发版编写以下程序,让LED1每次点亮100ms、熄灭100ms,同时让LED3每次点亮50ms、熄灭150ms。
我们注意到这两个LED的亮灭周期都为200ms,程序相对来说比较好设计
文件:main.c int main(void) { ....... while (1) { // 1. 同时点亮LED1和LED3,并延迟50ms HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_SET); HAL_Delay(50); // 2. 熄灭LED3,并再次延迟50ms HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_RESET); HAL_Delay(50); // 3. 熄灭LED1,并延迟100ms HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); HAL_Delay(100); } }那如果变复杂一点,比如说周期不一样,那么就需要用到状态机的思想。
LED1仍然点亮100ms、熄灭100ms,但LED3的情况有所变化,这次它亮灯30ms、灭灯150ms。
首先需要画出状态转换图:接着写代码的思路应该为:需要获取当前时间,设置好状态变量。代码见下
文件:main.c uint8_t led1State = 0; // LED1的状态:0 - 熄灭,1 - 点亮 uint8_t led3State = 0; // LED3的状态:0 - 熄灭,1 - 点亮 uint32_t led1LastToggleTime = 0; // LED1上次翻转的时间 uint32_t led3LastToggleTime = 0; // LED3上次翻转的时间 // @作用:LED1的状态机 void LED1_Proc(void) { uint32_t now = HAL_GetTick(); // 获取当前时间 switch(led1State) { case 0: // 当前处于熄灭状态 if(HAL_GetTick() - led1LastToggleTime > 100) // 超过100ms后点亮 { HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); led1State = 1; // 状态机切换到点亮状态 led1LastToggleTime = now; } break; case 1: // 当前处于点亮状态 if(HAL_GetTick() - led1LastToggleTime > 100) // 超过100ms后熄灭 { HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); led1State = 0; // 状态机切换到熄灭状态 led1LastToggleTime = now; } break; default:break; } } // @作用:LED3的状态机 void LED3_Proc(void) { uint32_t now = HAL_GetTick(); // 获取当前时间 switch(led3State) { case 0: // 当前处于熄灭状态 if(HAL_GetTick() - led3LastToggleTime > 150) // 超过150ms后点亮 { HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_SET); led3State = 1; // 状态机切换到点亮状态 led3LastToggleTime = now; } break; case 1: // 当前处于点亮状态 if(HAL_GetTick() - led3LastToggleTime > 30) // 超过30ms后熄灭 { HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_RESET); led3State = 0; // 状态机切换到熄灭状态 led3LastToggleTime = now; } break; default:break; } } int main(void) { ......... while (1) { LED1_Proc(); // 循环运行LED1的状态机 LED3_Proc(); // 循环运行LED3的状态机 } }我们发现这么简单的任务,需要的代码比较复杂,需要编写状态机,所以为了简化流程,我们需要学习FreeRTOS。
怎么用freertos创建任务呢,下面是简单任务的创建代码,
// 任务函数的一般写法 // 任务函数使用void类型的返回值,但是有一个void*类型的参数 void vTask1(void *pvParameters) { // 编程方式类似于main()方法,一般写一个无限循环 for(;;) { // 使用vTaskDelay()而不使用HAL_Delay()进行延迟 } // 坚决不能写return! vTaskDelete(NULL); //一旦退出需删除自身 }主函数的写法:
int main(void) { //一些底层的初始化代码放这里 ......... //先创建任务 xTaskCreate(vTask1, "Task1", .....); //任务1 xTaskCreate(vTask2, "Task2", .....); //任务2 //再启动调度器 vTaskStartScheduler(); //无限循环 while (1) //代码不会执行到这里,除非因创建空闲任务或者定时器任务失败而退出 { } }接下来我们来了解freertos的总图
由于框图我们可以看出,核心是调度器,不一样的调度算法决定了任务之间如何切换以及什么时间点切换,接着看到任务这一栏,空闲任务和定时器任务是两个特殊的任务(这两个任务必须被创建创建失败则启动rtos失败),定时器任务我们后面在看,先来了解空闲任务,看到以下这段代码,
文件:main.c // @作用LED1的任务函数 void vLED1Task(void *pvParameters) { while(1) { HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); // 点亮 vTaskDelay(pdMS_TO_TICKS(100)); // 延迟100ms HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); // 熄灭 vTaskDelay(pdMS_TO_TICKS(100)); // 延迟100ms } } int main(void) { xTaskCreate(vLED1Task, "LED1", 128, NULL, 1, NULL); // 创建LED1任务 while (1) { } }我们用的是vTaskDelay()这个函数,任务调用该函数后,系统内核将该任务从“就绪列表”移动到“阻塞列表”,并立即触发任务调度器,这个时候cpu执行空闲任务。而我们熟知的HAL_Delay(),它让 CPU 依然处于运行态。它虽然在“空转”,但并没有停止工作。
接下来看三个中断:在调度器的上方也有3个中断:Systick中断、PendSV中断和SVC中断。这3个中断大家可能没见过,它们比较特殊,都是跟内核相关的,所以大家见得不多。这里先跟大家说一下它们的地位:这3种中断是FreeRTOS调度器赖以生存的底层硬件,驱动调度器运行的原动力。其中Systick中断用于产生系统节拍(计时用的),并驱动调度器的“时间片机制”(刚刚讲过,跟调度算法有关);SVC中断用来启动第一个任务;PendSV中断用来执行后续的任务切换
2.2内核的文件结构
文件的分类
在kernal这一栏,task和list必须添加,其他的根据需要,我们需要知道每个文件的中文名,portable为移植层,移植层为FreeRTOS内核和某款单片机之间的媒介
config文件里是各种宏定义,介绍几个常见的配置项
配置项1:configCPU_CLOCK_HZ - 内核的运行频率(告诉FreeRTOS你的单片机的内核频率)
配置项2:configTICK_TYPE_WIDTH_IN_BITS - 设置TickType_t类型的位宽
配置项3:configKERNEL_INTERRUPT_PRIORITY - PendSV和SYSTICK的中断优先级(它们分别是SVC中断、PendSV中断和SYSTICK中断。SVC中断默认设置最高优先级,剩下的两个中断的优先级由该配置项设置。)
下面需要进行中断服务程序的配置
3.3回顾c语言基础
熟悉类型的变量,以及变量的作用域和生命周期,还有加上static后对全局变量和局部变量的影响
SRAM被划分为不同的区域:(SRAM的起始地址是0x2000 0000,长度为48k字节)
堆:
使用malloc()函数时,数据会被存储到堆中,而需要的空间为:数据的大小+信息头(8个字节).当我们调用xTaskCreate()创建一个任务的时候,在xTaskCreate()内部会自动调用pvPortMalloc()为新创建的任务在堆里分配一段内存,用来保存这个新任务的关键数据。其中,pvPortMalloc()是FreeRTOS里用来管理堆内存的编程接口,它的功能和标准C语言里的malloc()相似。
栈:每次调用时需要保存上下文,以便返回时恢复。这里所说的“上下文”包括参数、局部变量和返回地址。
在freertos中,每个任务都有自己栈,因为在freertos中,任务是"并行的",需要在各个任务之间来回切换。
编写代码时的规范:
特别注意:xTaskGetTickCount(void);返回值为系统节拍数,如果需要的是时间,需要在使用pdTICKS_TO_MS()宏将获取到的系统节拍转化为毫秒级的时间。
变量的命名规则:类型前缀”和“实际名称”两部分组成,举个例子
函数的命名规则:当函数前边有static关键字时(静态方法),前缀应该替换为prv(prv = private = 私有的,表示这个方法是私有的,作用域在文件内部)。