起源
先说一下FreeRTOS的起源,FreeRTOS是由Richard Barry在2003年由设计的,由于其设计的小巧简单,整个核心代码只有3到4个C文件。在设计之初就异军突起,累计开发者数百万,是目前市场占有率最高的RTOS,现在FreeRTOS已经支持三十多种芯片,基本包含市场上所有的微控制器。FreeRTOS在2018年被亚马逊收购,继续遵循GPLV2许可协议完全免费。 Richard Barry为了让代码容易阅读、移植和维护,大部分的代码都是以C语言编写,只有一些内核调度函数采用汇编编写。
正题
获取源码
freeRTOS官网,进入官网点击download FreeRTOS,按照提示下载源码。
最新的版本是10.2.1,下载完成后是一个exe文件,点击解压。
2. 命名规则
FreeRTOS核心源码文件的编写遵循MISRA(The Motor Industry Software Reliability Association 汽车工业软件可靠性联会)代码规则,同时支持各种编译器
变量
uint32_t定义的变量都加上前缀ul,u代表unsigned 无符号,l代表long长整型。
uint16_t定义的变量都加上前缀us。u代表unsigned无符号,s代表short短整型。
uint8_t定义的变量都加上前缀uc。u代表unsigned无符号,c代表char字符型。
size_t 定义的变量也要加上前缀ux。枚举变量会加上前缀e。 指针变量会加上前缀p。
函数
加上了static声明的函数,定义时要加上前缀prv(这个是单词private的缩写)。
带有返回值的函数,根据返回值的数据类型,加上相应的前缀,如果没有返回值,即void类型 ,函数的前缀加上字母v。
根据文件名,文件中相应的函数定义时也将文件名加到函数命名中,比如tasks.c文件中函数vTaskDelete,函数中的task就是文件名中的task。
宏定义
据宏定义所在的文件,文件中的宏定义声明时也将文件名加到宏定义中,比如宏定义configUSE_PREEMPTION 是定义在文件 FreeRTOSConfig.h里面。宏定义中的config就是文件名中的config。另外注意,前缀要小写。
除了前缀,其余部分全部大写,同时用下划线分开。
自定义数据类型
主要有4中类型 :
TickType_t:如果使能宏定义configUSE_16_BIT_TICKS,定义为16位无符号类型,如果没有使能这个宏,则表示32位无符号类型。
BaseType_t:对于32位的处理器,定位32位有符号数,对于16位处理器,则表示16位有符号数。
UBaseType_t:BaseType_t的无符号类型。
STackType_t:栈变量的数据类型,16位处理器就对应16位变量,32位处理器对应32位变量.
3.FreeRTOS 初探
在一般的教程中最开始都是系统移植,但是我觉得在对RTOS一无所知的时候就去移植反而会使自己一头雾水,出现一点错误都不知道从那里下手解决问题。
先介绍一下FreeRTOS对一般用户能够用到的特点:
理论上支持无限多个优先级
理论上支持无线多个任务
支持抢占式调度(执行优先级最高的任务),时间片调度(定时切换任务)
支持小时队列,二值信号量,计数信号量,递归信号量,互斥信号量,消息传递和消息同步
静态可裁剪(根据config文件)
RTOS工作流程
RTOS工作的一般流程
编辑
添加图片注释,不超过 140 字(可选)
单片机工作的一般流程
编辑
添加图片注释,不超过 140 字(可选)
任务TASK我们暂时可以理解为一个while(1)的死循环。不死不休的执行着里面的程序。至于什么时间切换到下一个任务,就需要我们在while(1)中告诉调度器我们在什么时候想切换任务了。
调度器:就是负责根据当前条件切换到相应的任务中。
我们可以认为FreeRTOS跟单片机程序一样,它的调度器就是在中断函数中切换任务。这样一想FreeRTOS是不是一下就简单了。
如何开始写FreeRTOS的应用程序
我的目的是让读者能够快速上手FreeRTOS,其中有所遗漏则学需要读者自己补齐。
创建任务: 使用函数xTaskCreate ,这个函数是freeRTOS提供的创建任务的函数,FreeRTOS官方已经提供了详细的注释,如果有阅读英文的能力最好是看官方提供的注释。
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, // 函数指针
const char * const pcName, // 函数名称
configSTACK_DEPTH_TYPE usStackDepth,//堆栈大小
void *pvParameters,//函数参数
UBaseType_t uxPriority,//优先级
TaskHandle_t *pvCreatedTask//任务句柄,保存任务的结构体
);
//任务函数实例
void vTaskCode( void * pvParameters )
{
for( ;; )
{
// Task code goes here.
}
}
2. 参数解释:
函数指针就是我们执行功能的函数指针,他的格式为 void vTaskCode( void * pvParameters )
函数名称则是在系统中保存的一个字符串,这个一般是给开发者看的,开发者可以直观的看到函数的名字,计算机其实是不需要的。
堆栈大小,堆栈大小的意思就是这个任务所能运行的内存的大小,也就是说整个任务只能运行在这段内存中,所以在定义这个参数时应该根据函数的功能预估出函数所堆栈大小。
函数参数,这个参数一般是用于向给任务传入参数时使用,因为函数指针的格式一定规定好了,所以只能以指针的形式将参数传递到函数内部了,但是一般情况下这个参数是不需要的传入NULL即可。
优先级:定义的任务在整个系统中所执行的时机,FreeTROS的优先级是从0~configMAX_PRIORITIES,0优先级最小,系统已经默认分配了,开发者从优先级1开始分配。
任务句柄,这个参数本质上是一个结构体,这个结构体中保存了当前任务的一些信息,如果想在其他任务中使用这个函数,只需要使用这个句柄就可以找到这个任务了。
3. 任务运行
在调用完创建任务函数之后,还需要执行vTaskStartScheduler() 开始运行调度器,
vTaskStartScheduler();
执行完vTaskStartScheduler后,系统就会按照当前任务中谁的任务优先级高,运行谁的规则执行(抢占式优先级)。
4. 例程展示
#include "stdio.h"
#include "FreeRTOSconfig.h"
/*tskIDLE_PRIORITY为系统最低优先级 0*/
#define TASK1_PRIORITY ( tskIDLE_PRIORITY + 1 )
#define TASK2_PRIORITY ( tskIDLE_PRIORITY + 2 )
#define TASK3_PRIORITY ( tskIDLE_PRIORITY + 3 )
#define TASK_STACK_SIZE 512
void vTaskFunction1( void * pvParameters )
{
for( ;; )
{
printf("vTaskFunction1 \n");
vTaskDelay(100);
}
}
void vTaskFunction2( void * pvParameters )
{
for( ;; )
{
printf("vTaskFunction2 \n");
vTaskDelay(100);
}
}
void vTaskFunction3( void * pvParameters )
{
for( ;; )
{
printf("vTaskFunction3 \n");
vTaskDelay(100);
}
}
int main(void)
{
...
xTaskCreate( vTaskFunction1, "TASK1", TASK_STACK_SIZE, NULL, TASK1_PRIORITY, NULL );
xTaskCreate( vTaskFunction2, "TASK2", TASK_STACK_SIZE, NULL, TASK2_PRIORITY, NULL );
xTaskCreate( vTaskFunctio3n, "TASK3", TASK_STACK_SIZE, NULL, TASK3_PRIORITY, NULL );
vTaskStartScheduler();
while(1){
//永远不会执行到这里!
}
return 0;
}
以上是一个最简单了示例,我在每一个函数中都调用了vTaskDelay这个函数,vTaskDelay这个函数是延时函数(相对延时,延时时间并不准确),调用这个函数的原因在于跳出当前任务,因为RTOS是抢占是调度器,就是移植执行最好优先级的任务,所以如果不让最高优先级停下来的话 ,其他任务是不可能有执行机会的。
vTaskDelay函数就是让当前任务进入到阻塞状态,让任务进行切换,等延时时间到了调度器查看当前任务是不是最好优先级的任务,如果是就执行这个任务。在实际应用当中不一定只能使用vTaskDelay函数进行任务切换,在之后我会介绍使用其他的方式切换任务。
5. 任务状态
刚刚我写到阻塞这个词,挂起状态是表述FreeRTOS任务的当前所处的状态,根据当前的状态调度器判断当前任务是否应该被执行。 FreeRTOS有四个任务状态 Running 运行态 : 当任务正在运行时这个任务就工作在运行态 Ready 就绪态 : 指能够运行,但是当前的CPU被更高优先级的任务所占用 Blcokd 阻塞态 : 任务一直处在等待信号量,消息队列,事件标志的状态称为阻塞态。(可以理解成被阻塞不能往下运行了) Suspended 挂起态 : * 类似于阻塞态,但是这个状态是通过调用vTaskSuspend()函数引起的,挂起后任务不能执行,只有调用xTaskResume()函数才能将任务从挂起状态中恢复出来。