FreeRTOS是一个现在非常流行的实时操作系统(Real Time Operating System)。本文将介绍FreeRTOS入门使用相关内容,这篇是第一篇,主要介绍基础背景方面的内容。
FreeRTOS官网:https://www.freertos.org/ (网站现在已更新中文)
Real-time operating system for microcontrollers
Developed in partnership with the world’s leading chip companies over an 18-year period, and now downloaded every 170 seconds, FreeRTOS is a market-leading real-time operating system (RTOS) for microcontrollers and small microprocessors. Distributed freely under the MIT open source license, FreeRTOS includes a kernel and a growing set of IoT libraries suitable for use across all industry sectors. FreeRTOS is built with an emphasis on reliability and ease of use.
微控制器实时操作系统
FreeRTOS 是市场领先的面向微控制器和小型微处理器的实时操作系统 (RTOS),与世界领先的芯片公司合作开发,现在每 170 秒下载一次。FreeRTOS 通过 MIT 开源许可免费分发,包括一个内核和一组不断丰富的 IoT 库,适用于所有行业领域。FreeRTOS 的构建强调可靠性和易用性。
OS最核心的功能是调度器,用于调度系统CPU和内存等资源。在大型项目或者多人开发的项目中这通常是必不可少的,没有调度器对资源合理的管理和分配,开发过程中就需要人为花大量时间精力来协调分配资源。
调度器表现到用户而言就是在编写程序时可以编写为一个个独立的任务(task),你可以专心编写每个任务而不太用担心该任务会不会影响其它任务,这是主要是调度器来操心的。
OS其它的功能主要是围绕 task 展开的,用于处理不同 task 间通讯、协同操作等需求。另外OS通常还会提供一些趁手的附加功能。
RTOS在一般的OS基础上针对控制领域特点会更加注重实时响应性能。
目前大多数热门的单片机厂家都有给出移植好FreeRTOS的项目示例,很多时候并不需要自己从头进行移植,本文也直接使用现成的模板展开。但是理解FreeRTOS源码结构和移植相关内容对于FreeRTOS后续的使用上会有一定的帮助,所以这里也稍微对相关内容进行介绍。
在FreeRTOS官网下载源码,下图中上面那个包中有非常多热门单片机的移植使用工程示例,如果要自己移植的话可以参考这些示例:
下面是下载解压后其中的部分目录,最核心的就是内核源码部分了:
当然如果要手动移植的话需要自行处理一些芯片特定代码,可以参考上图中的portable目录下内容。系统移植时通常涉及系统定时器、中断、内存这一些内容,其中有部分还需要使用汇编代码来处理,比如任务切换调度过程就必须用到一些汇编代码才能处理。不过大部分情况下参考portable目录下现有的内容来进行其实也不难。
对于FreeRTOS而言在使用时通常还有个 FreeRTOSConfig.h
文件,该文件用于设置FreeRTOS的一些参数选项。这个文件是即使FreeRTOS已经由厂家移植好了,但根据各自的项目需求还可以在这里调整的地方。
本文演示基于沁恒CH32V307单片机官方FreeRTOS项目模板进行,并不需要自己手动移植系统。
沁恒CH32V307单片机基础使用可以参考《沁恒CH32V307单片机入门(01):基础说明与流程体验》:
https://blog.csdn.net/Naisu_kun/article/details/128734532
使用FreeRTOS项目模板新建项目后可以看到项目中已经包含了FreeRTOS的内核代码以及相关配置文件。目前该项目模板工程中 FreeRTOS 内核版本为 V10.4.6。
使用上面的CH32V307的FreeRTOS项目模板方式创建项目,新项目中只需要替换 main.c
为下面内容:
#include "debug.h"
#include "FreeRTOS.h" // 引入头文件
#include "task.h" // 引入头文件
#include "semphr.h" // 引入头文件
SemaphoreHandle_t xMutex; // 声明互斥量,
// 下面两个任务中使用了同一个串口,所以需要使用互斥量进行保护
/* Task1相关参数与任务处理函数 */
#define TASK1_TASK_PRIO 3
#define TASK1_STK_SIZE 256
TaskHandle_t Task1Task_Handler; // 用于保存任务句柄
void task1_task(void *pvParameters) // 任务就是函数
{
while(1) //每一个任务都是无限循环运行的
{
xSemaphoreTake(xMutex, portMAX_DELAY); // 申请互斥量,如果别人在使用那么这里就会阻塞直到可用为止
printf("USART1_Task1: ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n");
xSemaphoreGive(xMutex); // 释放互斥量
vTaskDelay(250); // FreeRTOS的延时函数,单位是FreeRTOS的任务周期
}
}
/* Task2任务处理函数 */
void task2_task(void *pvParameters)
{
while(1)
{
xSemaphoreTake(xMutex, portMAX_DELAY);
printf("USART1_Task2: 01234567890123456789\r\n");
xSemaphoreGive(xMutex);
vTaskDelay(500);
}
}
/* 主函数 */
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SystemCoreClockUpdate();
Delay_Init();
USART_Printf_Init(115200); // 使用115200波特率,每秒串口约可通讯11520字节,
// 默认configTICK_RATE_HZ为500,即每2ms调度一次,每次调度间可以发送约23个字节
printf("FreeRTOS Kernel Version:%s\r\n",tskKERNEL_VERSION_NUMBER); // 打印FreeRTOS内核版本号
xMutex = xSemaphoreCreateMutex(); // 创建互斥量
// 创建一个任务
// 创建成功会返回pdPASS(1),失败通常返回errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(-1),即内存不足
xTaskCreate((TaskFunction_t )task1_task, // 任务函数
(const char* )"task1", // 任务名称,长度由FreeRTOSConfig.h中configMAX_TASK_NAME_LEN定义
(uint16_t )TASK1_STK_SIZE, // 该任务栈深度(栈大小),对于32位架构一个深度为四字节
(void* )NULL, // 传递给任务的参数
(UBaseType_t )TASK1_TASK_PRIO, // 任务优先级,值越大优先级越高,最大值为FreeRTOSConfig.h中configMAX_PRIORITIES - 1
(TaskHandle_t* )&Task1Task_Handler); // 任务句柄,后续可以用过该句柄来操作任务
xTaskCreate(task2_task, "task2", 128, NULL, 5, NULL); // 创建一个任务
vTaskStartScheduler(); // 任务调度,程序将在这里无序循环
while(1){} // 程序不会运行到这里
}
上面代码中创建了两个任务,交替通过USART1打印一些文本,实际效果如下:
FreeRTOS中供用户使用的最核心的功能是Task,除此之外的大部分内容(队列、互斥锁、信号量……)都是为了Task与Task之间交互而服务了。具体的这些相关内容会在后面的文章中详细介绍。
FreeRTOS对编程风格上有自己的一些规定,但是大多数内容(比如缩进格式、注释格式等)我们并不用太关心,这里只介绍一些FreeRTOS中相对独特的内容。
FreeRTOS的数据类型使用 stdint.h
中定义的一些,但是其中的 char
和 char *
通常包含或者指向 ASCII 字符(也就是默认认为这个必须是有符号类型)。除了这些基本的类型FreeRTOS中还定义了下面四个类型:
TickType_t
系统时钟计数类型,32位架构下为32位数据类型;BaseType_t
平台架构中最高有效数据类型,32位架构下为32位数据类型;UBaseType_t
无符号的 BaseType_t
;StackType_t
平台架构中最高有效数据类型,32位架构下为32位数据类型,并且一般是无符号类型;FreeRTOS中对变量、函数、宏等命名有一定的格式习惯。
变量名:
变量名带有前缀(前缀可以组合)。
变量名前缀 | 说明 | 变量名前缀 | 说明 |
---|---|---|---|
uc | uint8_t | us | uint16_t |
ul | uint32_t | x | size_t |
u | 非stdint类型的无符号类型 | x | 非stdint类型的有符号类型 |
e | 枚举类型 | p | 指针类型 |
c | char | pc | char * |
函数名:
对于私有函数使用 prv
作为前缀,对于其它函数使用返回类型和函数所在文件名作为前缀。
函数名 | 返回值类型 | 所在文件 |
---|---|---|
vTaskDelete | void | task.c |
xQueueReceive | BaseType_t | queue.c |
pvTimerGetTimerID | char * | tmer.c |
宏名:
宏名前缀通常和该宏所在文件有关;除前缀外,所有宏均使用大写字母书写,并使用下划线来分隔单词。
宏名前缀 | 所在文件 |
---|---|
task (如taskENTER_CRITICAL()) | task.h |
port (如portMAX_DELAY) | portable.h 或 portmacro.h |
config (比如configUSE_PREEMPTION) | FreeRTOSConfig.h |
pd (比如pdTRUE) | projdefs.h |
err (比如errQUEUE_FULL) | projdefs.h |
部分通用宏定义:
宏 | 值 | 值 | |
---|---|---|---|
pdFALSE | 0 | pdTRUE | 1 |
pdFAIL | 0 | pdPASS | 1 |
这篇文章对FreeRTOS入门使用相关的一些基础背景内容进行了介绍,还进行了演示使用,更多详细的内容将在接下来的文章中展开。
我刚开始学单片机的时候RTOS比较流行的是μC/OS,FreeRTOS当时还不是很常见,因为其免费特性被很多单片机厂商用于默认的RTOS例程,慢慢的变得常见了。比如现在很流行的ESP32整个开发包默认就是建立在FreeRTOS之上的。
自从FreeRTOS被亚马逊收购后现在发展的也越来越好了。我这篇文章刚开始写的官网还是英文的,现在都更新中文了(虽然感觉是机翻的)。