从零移植 FreeRTOS:用实验入门实时操作系统
1. 写在前面:为什么用这种方式学 FreeRTOS
这篇文章记录我学习 FreeRTOS 的完整过程。和很多教程不同,我不会一开始就铺开讲任务调度、信号量、消息队列这些概念,而是先动手把环境跑起来,让板子上的 LED 真正闪烁起来,再一点点往里加东西。每做完一个实验,再回头补对应的概念讲解,这样印象更深刻。
我使用的硬件和软件环境如下:
- 开发板:STM32F407 VGT6-DISC
- 调试器:USB_TTL接口直接烧录
- IDE:STM32CubeIDE 1.19.0
- FreeRTOS 版本:FreeRTOSv202604.01-LTS(下载地址:FreeRTOS™ - FreeRTOS™)
2. 第一步:用 CubeMX 生成基础工程并配置FreeRTOS
——对应视频2.2
本文章是基于学习视频编写的类似学习记录性质的文章,采用手动移植源码方式。(视频地址:https://www.bilibili.com/video/BV1KSLc6XEEt/?spm_id_from=333.1387.favlist.content.click)
2.1移植操作步骤:
- 打开 STM32CubeMX,选择 STM32F407VGT6 芯片。
- 在 Pinout 选项卡中选择system core选项,选择sys栏,将debug模式改为serial wire 使代码能多次烧录)。
- 保存并生成代码。
- 右键工程,选择new,新建一个source folder,命名为FreeRTOS,在这个文件夹下新建两个普通文件夹,命名为Src,Inc,分别存放RTOS的.c.h文件。
- 将下载的FreeRTOS解压,打开文件的FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel位置,将这个目录下的所有.c文件全部复制到上一步创建的Src文件夹下。
- 打开文件的FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel\include位置,将目录下的所有.h文件全部复制到Inc文件夹下。
- 添加移植层文件,打开文件的C:\Users\祖龙\Desktop\FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel\portable,我是用的是STM32CubeIDE+STM32f407,使用GCC编译和ARM_CM4F内核,则选中文件中的GCC文件下的ARM_CM4F文件,将里面的.h.c文件分别复制到Inc和Src文件中。
- 移植FreeRTOS的堆内存管理部分,打开文件目录的FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel\portable\MemMang,一般情况使用heap4即可将文件复制到Src中。
- 移植配置文件打开文件目录FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel\examples\template_configuration,选择文件中的FreeRTOSConfig.h,将其复制到Core文件中的Inc文件夹下(只有这个文件是复制到Core文件夹下)。
2.2:编辑配置文件:
时钟频率配置

看MX里的时钟配置页面只需要修改参数为AHB上的频率即可,不管时钟如何变化,只要保持配置一致即可。

本工程中我选择开启高速时钟源,修改时钟配置如下图,则配置项中时钟频率就要改成168M

修改位宽:如图,可以修改位宽为16,32,64,设置为多少主要看使用的什么单片机,我使用的STM32,则是32位单片机,这里就设置为32,这个位宽决定了系统节拍计数值范围,越宽计数值范围越大,32比特的计数值范围大约是49天,达到了技术范围则需重置,那程序中不达到重置那最好

设置pendsv和systick的中断优先级为(15 << 4)

设置FreeRTOS调用接口的中断优先级为(5 << 4)

设置是否使用时间片,将值改为1

修改configCHECK_FOR_STACK_OVERFLOW

2.3安装中断服务程序:
中断服务在port.c文件中

安装中断服务其实就是在startup_stm32f407vgtx.s中替换中断服务名称,将以上服务名称分别替换掉startup_stm32f407vgtx.s中的以下部分

这一操作中替换了xPortSysTickHandler,使得HAL库不能继续使用systick作为时钟基准让给了FreeRTOS,而HAL库需要另外启用一个时钟,这里我们按教程中的使用TIM6,修改如图配置,保存并生成代码

现在,FreeRTOS的移植完成了
3. 第一个任务:让两个 LED 按各自的频率闪烁起来
工程生成后,我做的第一件事不是读文档,而是先建一个最简单的任务,让 LED 闪烁。先跑通,再理解。
在 Pinout选项卡的图形化配置界面配置PC13,PC14为output,自动设置为了推挽输出,再给上标签LED1,LED2

保存并生成代码
在main.c中引用FreeRTOS相关头文件
/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
#include "task.h"
/* USER CODE END Includes */
在main.c中写入LED1和LED2的任务代码
/* USER CODE BEGIN 0 */
void vLED1Task(void *Parameters)
{
for(;;)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin,GPIO_PIN_SET);
vTaskDelay(pdMS_TO_TICKS(500));
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin,GPIO_PIN_RESET);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void vLED2Task(void *Parameters)
{
for(;;)
{
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin,GPIO_PIN_SET);
vTaskDelay(pdMS_TO_TICKS(200));
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin,GPIO_PIN_RESET);
vTaskDelay(pdMS_TO_TICKS(200));
}
}
/* USER CODE END 0 */
在main方法中创建任务并启动
/* USER CODE BEGIN 2 */
xTaskCreate(vLED1Task, "LED1", 128, NULL,1,NULL);
xTaskCreate(vLED2Task, "LED2", 128, NULL,1,NULL);
vTaskStartScheduler();
/* USER CODE END 2 */
将FreeRTOS文件添加到目录中,不然工程识别不到,右键工程,选择properties,按下图步骤点击Add将FreeRTOS文件中的Inc和Src添加进去,点击应用并关闭。

烧录到板子上,LED 以 1 秒为周期闪烁(亮 500ms,灭 500ms),LED2以400ms为周期闪烁(两200ms,灭200ms)。看到灯亮起来的那一刻,FreeRTOS 的第一个任务就跑通了。
这里记住两个关键点:for(;;) 让任务永远不退出;vTaskDelay(pdMS_TO_TICKS(500)) 让任务主动让出 CPU,单位是毫秒。使用这个延时而不是HAL_Delay是因为HAL_Delay是属于HAL库基于systick的,但是systick已经被HAL库转交给了FreeRTOS,HAL库使用的是TIM6,如果再去使用HAL_Delay那一定会出问题,所以这里只能用FreeRTOS的vTaskDelay。
4. 任务管理
4.1创建任务
创建任务的函数一般为下图这个函数

其中的pvParameters字段是传递任务代码的参数,比如有几个逻辑相同的任务,如第三点的两个小灯按各自频率闪烁,其逻辑基本一致,则可使用该字段传递任务代码。
改造代码,首先定义一个结构体用于存储闪灯相关信息
/* USER CODE BEGIN PTD */
typedef struct
{
GPIO_TypeDef *LED_GPIOx;
uint16_t LED_Pin;
uint32_t Period;
}LEDBlinkInfo_TypeDef;
/* USER CODE END PTD */
然后将两个闪灯信息塞进结构体里
static const LEDBlinkInfo_TypeDef led1BlinkInfo = {.LED_GPIOx = LED1_GPIO_Port, .LED_Pin = LED1_Pin, .Period = 1000};
static const LEDBlinkInfo_TypeDef led2BlinkInfo = {.LED_GPIOx = LED2_GPIO_Port, .LED_Pin = LED2_Pin, .Period = 200};
而后编写两个小灯共同的逻辑代码,用info传递信息
static void prvLEDTask(void *pvParameters)
{
LEDBlinkInfo_TypeDef *info = (LEDBlinkInfo_TypeDef *)pvParameters;
for(;;)
{
HAL_GPIO_WritePin(info->LED_GPIOx, info->LED_Pin,GPIO_PIN_SET);
vTaskDelay(pdMS_TO_TICKS(info->Period / 2));
HAL_GPIO_WritePin(info->LED_GPIOx, info->LED_Pin,GPIO_PIN_RESET);
vTaskDelay(pdMS_TO_TICKS(info->Period / 2));
}
}
创建任务并启动
xTaskCreate( prvLEDTask, "LED1", 128, (void *)&led1BlinkInfo, 1, NULL);
xTaskCreate( prvLEDTask, "LED2", 128, (void *)&led2BlinkInfo, 1, NULL);
vTaskStartScheduler();
最后编译并烧录代码,观察开发板,发现小灯按预期结果闪烁
4.2任务优先级:
uxPriority字段,0为最低优先级,configMAX_PRIORITYS-1,而configMAX_PRIORITYS(任务优先级数量)字段可以在FreeRTOS配置文件里修改
4.3任务句柄
pxCreatedTask
例如要创建一个任务来控制灯暂停、恢复和停止闪灯的过程
在我的工程中,使用4.1的代码进行改造,使橙色小灯闪烁的任务暂停,恢复,删除。
在main方法中补全以下代码
/* USER CODE BEGIN PV */
static const LEDBlinkInfo_TypeDef led1BlinkInfo = {.LED_GPIOx = LED1_GPIO_Port, .LED_Pin = LED1_Pin, .Period = 1000};
static const LEDBlinkInfo_TypeDef led2BlinkInfo = {.LED_GPIOx = LED2_GPIO_Port, .LED_Pin = LED2_Pin, .Period = 200};
static TaskHandle_t xLED1TaskHandle;
/* USER CODE END PV */
static void prvLEDTask(void *pvParameters)
{
LEDBlinkInfo_TypeDef *info = (LEDBlinkInfo_TypeDef *)pvParameters;
for(;;)
{
HAL_GPIO_WritePin(info->LED_GPIOx, info->LED_Pin,GPIO_PIN_SET);
vTaskDelay(pdMS_TO_TICKS(info->Period / 2));
HAL_GPIO_WritePin(info->LED_GPIOx, info->LED_Pin,GPIO_PIN_RESET);
vTaskDelay(pdMS_TO_TICKS(info->Period / 2));
}
}
static void prvTask1(void *pvParameters)
{
vTaskDelay(pdMS_TO_TICKS(1000));
vTaskSuspend(xLED1TaskHandle);
vTaskDelay(pdMS_TO_TICKS(1000));
vTaskResume(xLED1TaskHandle);
vTaskDelay(pdMS_TO_TICKS(1000));
vTaskDelete(xLED1TaskHandle);
vTaskDelete(NULL);
}
/* USER CODE END 0 */
xTaskCreate( prvLEDTask, "LED1", 128, (void *)&led1BlinkInfo, 1, &xLED1TaskHandle);
xTaskCreate( prvLEDTask, "LED2", 128, (void *)&led2BlinkInfo, 1, NULL);
xTaskCreate( prvTask1, "Task1", 128, NULL, 1, NULL);
vTaskStartScheduler();
/* USER CODE END 2 */
编译并烧录,看到两小灯按预期结果闪烁
5. 第二个实验:两个任务轮流跑
6. 补课:任务状态与调度器
7. 第三个实验:用信号量同步两个任务
8. 补课:信号量的本质
9. 第四个实验:用互斥量保护共享数据
10. 补课:互斥量与优先级反转
11. 第五个实验:用消息队列传递数据
12. 补课:消息队列的工作方式
13. 阶段性小结:我目前的理解
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)