freeRTOS
源码移植
手动移植:
- 点击Download FreeRTOS
- 点击Download
- 解压zip文件
FreeRTOS是内核文件夹,只需关注这个
FreeRTOS
-
Demo: 示例程序
-
License: 许可,每个源文件开头都要引用一段许可
-
Source: 内核源码
- include: 头文件
- portable: 与具体的硬件平台相关,也叫移植层文件夹
对于MDK-ARM环境,需要Keil、MenMang和RVDS三个文件夹中的文件
Keil文件夹中只有一个
See-also-the-RVDS-directory.txt
文件,意思是同RVDS文件夹RVDS文件夹中包含了ARM各个架构的MCU,stm32f1xx属于Cortex-M3,选
ARM_CM3
文件夹各个文件夹中的文件都相同,包含
port.c
和portmacro.h
两个文件- port.c: 是ARM内核与FreeRTOS的接口文件
- portmacro.h: 定义了所有的硬件特定功能
移植成功与否,需要通过一个基础工程进行验证
步骤:
- 在stm32工程下新建
FreeRTOS
文件夹 - 将源码中的
Source
文件夹下全部文件复制到FreeRTOS
文件夹中 - 打开
portable
文件夹,除了Keil、MemMang、RVDS这三个目录保留以外,其它目录全部删除 - 对
FreeRTOSConfig.h
文件进行配置和裁剪,FreeRTOSConfig.h
文件可以从Demo中获得,放到FreeRTOS\include
文件夹中
CubeMX自动移植:
配置:
Interface: CMSIS_V1
CMSIS是一套接口标准,屏蔽软硬件差异以提高软件的兼容性,RTOSv1使软件可以在不同的操作系统下运行(屏蔽不同RTOS提供的API的差别),RTOSv2拓展了v1,兼容更多的CPU架构和RTOS,使用F1和F4时没必要选v2,更高兼容性的代价时更冗余的代码
配置裁剪
注意:
-
每一个FreeRTOS应用项目都必须包含一个
FreeRTOSConfig.h
头文件,由于该文件不属于内核的一部分,所以可以直接放入Inc
文件夹中 -
并非所有的裁剪相关的宏都必须包含在
FreeRTOSConfig.h
头文件中,未包含的宏会在另一个文件FreeRTOS.h
文件中被声明,并赋予默认值例如
// FreeRTOS.h #ifndef configUSE_CO_ROUTINES #define configUSE_CO_ROUTINES 0 #endif
参数说明:
-
configUSE_PREEMPTION
为1使用抢占式任务调度方式,为0使用协作式调度方式,协作式一般用于硬件资源及其有限的系统中使用,一般设为1
-
configUSE_PORT_OPTIMISED_TASK_SELECTION
- 1:硬件方法查找下一个任务,速度快,但优先级有限制,对stm32,优先级最多32个
- 0:纯软件方法查找下一个任务,速度慢,但是优先级数量无限制
断言
在调试阶段使用,会增加系统开销,调试完成后注释掉即可
// FreeRTOSConfig.h
#define configASSERT( x ) if ((x) == 0) {taskDISABLE_INTERRUPTS(); for( ;; );}
freeRTOS会在关键处调用configASSERT()
,如果判断为0,则执行一些命令,比如说停机、串口发送消息、指示灯闪烁,可以自己定义
命名规则
Task任务
创建任务
- 引用
FreeRTOS.h
和task.h
头文件 - 创建任务句柄
- 创建任务函数
- 调用任务创建函数
xTaskCreate()
创建任务 - 开启调度器
vTaskStartScheduler()
,正常情况下,程序不会执行此语句后面的代码,main函数中的无限循环不会被执行
状态
任务有四种状态:运行态、就绪态、阻塞态、挂起态
- 运行态:正在使用CPU的任务处于运行态,如果是单核CPU,则只有一个任务处于运行态
- 就绪态:准备就绪,可以运行的任务,但是因为有更高优先级的任务正在运行而没有被调度器选中
- 阻塞态:正在等待外部事件的发生,会有一个超时时间
- 挂起态:类似于阻塞态,但是没有超时时间
任务函数
- 任务函数包含一个无限循环,正常情况下不会有return,也不会执行到函数的末尾,如果需要跳出无限循环,则需要在函数末尾加上
vTaskDelete(NULL);
传入参数NULL代表删除当前正在运行的任务 - 一个任务函数可以用于创建多个任务,其中每个任务会有单独的任务栈,所以每个任务的自动变量是独立的,但是静态变量则可以供所有任务访问
任务删除
-
使用
vTaskDelete()
删除任务 -
任务删除后就不复存在,此时不能再对任务进行操作,所以对任务进行任何操作前必须判断任务的状态,使用
if(eTaskGetState(<Task>) != eDeleted){ <对任务的操作> }
任务挂起与恢复
任务挂起:vTaskSuspend()
任务恢复:vTaskResume()
参数均为需要挂起与恢复的任务句柄,可以使用xTaskGetHandle()
获取任务句柄,参数是任务名
**注意:**任务挂起后再恢复,从挂起语句后面开始继续运行,而非从头运行
任务调度
-
抢占式调度:适用于任务有不同优先级的场合,任务会一直运行,直到被更高优先级的任务抢占,或者遇到阻塞式API函数,比如
vTaskDelay()
才让出CPU使用权 -
时间片调度:适用于多个任务有相同优先级的场合,每个相同优先级的任务运行一个时间片后就让出CPU使用权
使用时间片调度方法,需要
configUSE_PREEMPTION
设置为1configUSE_TIME_SLICING
设置为1
时间片长度由
configTICK_RATE_HZ
定义的系统时间节拍决定
优先级
优先级数值越大,代表优先级越高;与stm32的中断优先级相反
调度器确保高优先级的处于运行态或就绪态任务获得CPU使用权
当多个任务优先级相同时,FreeRTOS将使用时间片调度方式
任务切换
taskYIELD()
,实际是启动PendSV
中断
时钟节拍追加
vTaskStepTick()
一般用于低功耗tickless模式,恢复系统时钟节拍后,停止运行的时钟节拍数就可以用该函数补上
Queue消息队列
注意:
- 接收任务的栈大小至少需要512words
- 接受任务的优先级要比发送任务高一级
临界段
https://blog.csdn.net/Qrsleizhipeng/article/details/83377340
代码的临界段也称为临界区,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界段代码
的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即开中断。
taskENTER_CRITICAL(); // 进入临界段
taskENTER_CRITICAL_FROM_ISR(); // 进入临界段,用于中断服务函数中
/* 临界段内的代码要求执行时间越短越好,否则会影响系统的实时性 */
taskEXIT_CRITICAL(); // 退出临界段
taskEXIT_CRITICAL_FROM_ISR(); // 退出临界段,用于中断服务函数中
不能被打断的代码称为临界段,能打断程序执行的往往是中断,因此临界段首先要关闭打开中断
- 临界段代码尽量短,以免破坏实时性
- 临界段函数要成对使用
- 临界段会关闭打开FreeRTOS能管理的中断,高于能管理的中断不会被关闭
挂起和恢复调度器
- 挂起调度器:
vTaskSuspendAll()
- 恢复调度器:
vTaskResumeAll()
在vTaskSuspendAll()
和vTaskResumeAll()
之间的代码不会被更高优先级的任务抢占,调度器被禁止,不用关闭中断
Debug
freeRTOS介绍:https://blog.csdn.net/qq_37634122/article/details/104283673
Error: selected FPU does not support instruction – `vstmdbeq r0!,{s16-s31}’
https://www.jianshu.com/p/54412fefd538
不能单纯修改CMakeLists.txt,要修改CMakeLists_template.txt
C语言宏函数的特殊用法
LED1Hz频率闪烁任务,频率明显低于预设值
原因是任务优先级设置不正确,有优先级更高的任务一直占用着CPU资源
freeRTOS中,优先级数值越高,优先级越高
这点与中断抢占优先级不同
FreeRTOS 中任务的最高优先级是通过 FreeRTOSConfig.h 文件中的 configMAX_PRIORITIES 进行
配置的,用户实际可以使用的优先级范围是 0 到 configMAX_PRIORITIES – 1
使用freeRTOS在任务中使用printf会进入HardFault_Handler
https://www.cnblogs.com/tianxxl/p/11983353.html
https://blog.csdn.net/weixin_45045399/article/details/104001209
原因:任务栈分配太小,如果要使用printf函数,usStackDepth至少要设置512
解决方法:任务的Stack Size从128改为512即可
只有发送任务有反应,接收任务无反应
将接收任务设置为阻塞状态,然后调高优先级
两个相同优先级的任务同时使用printf()函数,串口输出混乱】
原因:printf()
函数任一时刻只允许一个任务访问,多个任务同时使用时,将造成输出的混乱
解决:
-
方法一:通过使用临界段代码保护函数
taskENTER_CRITICAL()
和taskEXIT_CRITICAL()
来避免多个任务同时向串口输出字符void LedTask0(void *pvParameters) { uint16_t counts = 0; while (1) { taskENTER_CRITICAL(); // 进入临界段 printf("任务0运行%d次\n",++counts); taskEXIT_CRITICAL(); // 退出临界段 vTaskDelay(pdMS_TO_TICKS(1000)); } }
-
方法二:通过
vTaskSuspendAll()
和xTaskResumeAll()
挂起调度器void LedTask0(void *pvParameters) { uint16_t counts = 0; while (1) { vTaskSuspendAll(); printf("任务0运行%d次\n",++counts); xTaskResumeAll(); vTaskDelay(pdMS_TO_TICKS(1000)); } }
-
方法三:可能是因为时间片长度太短,CPU的频率太低,在一个时间片的时间长度内,printf()来不及完成,解决方法,将
TICK_RATE_HZ
从1000修改为500
参考文献:
- 《嵌入式实时操作系统FreeRTOS原理及应用——基于STM32微控制器》