freeRTOS学习
2. FreeRTOS版本问题
V2的内核版本更高,功能更多,在大多数情况下V1版本的内核完全够用。
3.FreeRTOS各配置选项卡的解释
- Events:事件相关的创建
- Task and Queues:任务与队列的创建
- Timers and Semaphores:定时器和信号量的创建
- Mutexes:互斥量的创建
- FreeRTOS HeapUsage:用于查看堆使用情况
- config parameters:内核参数设置,用户根据自己的实际应用来裁剪定制FreeRTOS内核
- Includeparameters:FreeRTOS部分函数的使能
- UserConstants:相关宏的定义,可以自建一些常量在工程中使用
- Advanced settings:高级设置
4.内核配置、函数使能的一些翻译



freeRTOS内核配置的相关说明,可以参考下面这篇文章。
FreeRTOS系列第6篇---FreeRTOS内核配置说明_vassertcalled-CSDN博客
三、任务的创建与删除
1.什么是任务?
任务可以理解为进程/线程,创建一个任务,就会在内存开辟一个空间。
比如:
玩游戏、陪女朋友,都可以视为任务
Windows 系统中的MarkText、谷歌浏览器、记事本,都是任务。
任务通常都含有While(1)死循环。
2.任务创建与删除相关函数
任务创建与删除相关函数有如下三个:

任务动态创建与静态创建的区别:
动态创建任务的堆栈由系统分配,而静态创建任务的堆栈由用户自己传递。
通常情况下使用动态方式创建任务。
xTaskCreate函数原型

1.pvTaskCode:指向任务函数的指针,任务必须实现为永不返回(即连续循环);
2.pcName:任务的名字,主要是用来调试,默认情况下最大长度是16;
3. pvParameters:指定的任务栈的大小;
4.uxPriority:任务优先级,数值越大,优先级越大;
5.pxCreatedTask:用于返回已创建任务的句柄可以被引用。
官方提供的案例:
/* Task to be created. */
void vTaskCode( void * pvParameters )
{/* The parameter value is expected to be 1 as 1 is passed in thepvParameters value in the call to xTaskCreate() below.configASSERT( ( ( uint32_t ) pvParameters ) == 1 );for( ;; ){/* Task code goes here. */}
}/* Function that creates a task. */
void vOtherFunction( void )
{
BaseType_t xReturned;
TaskHandle_t xHandle = NULL;/* Create the task, storing the handle. */xReturned = xTaskCreate(vTaskCode, /* Function that implements the task. */"NAME", /* Text name for the task. */STACK_SIZE, /* Stack size in words, not bytes. */( void * ) 1, /* Parameter passed into the task. */tskIDLE_PRIORITY,/* Priority at which the task is created. */&xHandle ); /* Used to pass out the created task's handle. */if( xReturned == pdPASS ){/* The task was created. Use the task's handle to delete the task. */vTaskDelete( xHandle );}
}
vTaskDelete函数原型
void vTaskDelete(TaskHandle_t xTaskToDelete);
只需将待删除的任务句柄传入该函数,即可将该任务删除。
当传入的参数为NULL,则代表删除任务自身(当前正在运行的任务)。
3.实操

四、任务的状态
什么是任务调度?
调度器就是使用相关的调度算法来决定当前需要执行的哪个任务。
FreeRTOS中开启任务调度的函数是vTaskStartScheduler(),但在CubeMX中被封装为
osKernelStart()。
FreeRTOs的任务调度规则是怎样的?
FreeRTOS是一个实时操作系统,它所奉行的调度规则:
- 高优先级抢占低优先级任务,系统永远执行最高优先级的任务(即抢占式调度)
- 同等优先级的任务轮转调度(即时间片调度)
还有一种调度规则是协程式调度,但官方已明确表示不更新,主要是用在小容量的芯片上,用得也不多。
抢占式调度运行过程

Task 1:玩游戏
Task 2:老妈喊你吃饭
Task 3:女朋友喊你看电视
总结:
1.高优先级任务,优先执行;
2.高优先级任务不停止,低优先级任务无法执行;
3.被抢占的任务将会进入就绪态
时间片调度运行过程

总结:
1.同等优先级任务,轮流执行,时间片流转
2.一个时间片大小,取决为滴答定时器中断周期;
3.注意没有用完的时间片不会再使用,下次任务Task3得到执行,还是按照一个时间片的
时钟节拍运行
五、任务的状态
FreeRTOS中任务共存在4种状态:
- Running 运行态
当任务处于实际运行状态称之为运行态,即CPU的使用权被这个任务占用(同一时间仅一个
任务处于运行态)。
- Ready 就绪态
处于就绪态的任务是指那些能够运行(没有被阻塞和挂起),但是当前没有运行的任务,因
为同优先级或更高优先级的任务正在运行。
- Blocked 阻塞态
如果一个任务因延时,或等待信号量、消息队列、事件标志组等而处于的状态被称之为阻塞
态。
- Suspended 挂起态
类似暂停,通过调用函数vTaskSuspend()对指定任务进行挂起,挂起后这个任务将不被执
行,只有调用函数xTaskResume)才可以将这个任务从挂起态恢复。

总结:
1.仅就绪态可转变成运行态
2.其他状态的任务想运行,必须先转变成就绪态
任务综合小实验:
实验需求
创建4个任务:taskLED1,taskLED2,taskKEY1,taskKEY2,任务要求如下:
taskLED1:间隔500ms闪烁LED1;
taskLED2:间隔1000ms 闪烁LED2;
taskKEY1:如果taskLED1存在,则按下KEY1后删除 taskLED1,否则创建taskLED1;
taskKEY2:如果taskLED2正常运行,则按下KEY2后挂起taskLED2,否则恢复taskLED2
cubeMX配置:
代码实现:
/* USER CODE END Header_StartTaskKEY01 */ void StartTaskKEY01(void const * argument) {/* USER CODE BEGIN StartTaskKEY01 *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0 == GPIO_PIN_RESET)){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0 == GPIO_PIN_RESET)){printf("KEY1按下!\r\n");if(taskLED01Handle == NULL)//判断按键1有没有按下{printf("任务1不存在,准备创建任务1\r\n");osThreadDef(taskLED01, StartTaskLED01, osPriorityNormal, 0, 128);taskLED01Handle = osThreadCreate(osThread(taskLED01), NULL);if(taskLED01Handle !=NULL )printf("任务1创建完成!\r\n");}else{printf("删除任务1\r\n");osThreadTerminate(taskLED01Handle);taskLED01Handle=NULL;}}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET );}osDelay(10);}/* USER CODE END StartTaskKEY01 */ }核心功能
循环检测按键(KEY01)是否被按下,通过按键状态控制
taskLED01任务的创建与删除:
- 当按键按下且
taskLED01任务不存在时,创建该任务;- 当按键按下且
taskLED01任务已存在时,删除该任务。代码细节分析
无限循环
任务函数通过for(;;)实现无限循环,持续检测按键状态,符合 FreeRTOS 任务 “永不返回” 的特性。按键检测与消抖
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET) //这是一个逻辑判断,结果为0或1 {osDelay(20); // 延时20ms消抖if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0 == GPIO_PIN_RESET)) // 再次检测确认{// 按键按下后的逻辑...while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET ); // 等待按键释放} }
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)(先读引脚状态,再与 “低电平” 比较)。消抖逻辑:第一次检测到按键按下后,延时 20ms(跳过机械抖动阶段),再次检测确认按下,避免误触发。
等待释放:
while循环等待按键松开,避免一次按下被多次识别。
任务的创建与删除
- 当
taskLED01Handle == NULL(任务不存在)时:通过osThreadDef定义任务属性(名称、函数、优先级等),再用osThreadCreate创建任务,成功后更新句柄。- 当
taskLED01Handle != NULL(任务已存在)时:用osThreadTerminate删除任务,并将句柄设为NULL(标记任务已删除)。延时调度循环末尾的
osDelay(10)让任务让出 CPU,给其他任务运行机会,避免独占系统资源(FreeRTOS 的任务调度依赖延时函数触发)/* USER CODE END Header_StartTaskKEY02 */ void StartTaskKEY02(void const * argument) {/* USER CODE BEGIN StartTaskKEY02 */static int flag = 0;//标志位/* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1 == GPIO_PIN_RESET))//当按键2呗按下{osDelay(20);//消抖if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1 == GPIO_PIN_RESET)){printf("KEY2按下!\r\n");if(flag == 0){osThreadSuspend(taskLED02Handle);//挂起printf("任务2已暂停\r\n");flag = 1;}else{osThreadResume(taskLED02Handle);printf("任务2已恢复\r\n");flag = 0;}}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET );}osDelay(10);}/* USER CODE END StartTaskKEY02 */ }FreeRTOS 的按键检测任务函数(
StartTaskKEY02),核心功能是通过检测按键(连接在 GPIOA 的 PIN1 引脚)的状态,来切换另一个 LED 任务(taskLED02)的 “挂起” 与 “恢复” 状态。
核心功能
循环检测按键(KEY02)是否被按下,通过一个标志位(
flag)记录taskLED02的当前状态,每次按键按下时切换状态:
- 若
taskLED02正在运行,则将其挂起(暂停执行);- 若
taskLED02已被挂起,则将其恢复(继续执行)。代码细节分析
静态标志位
flag
static int flag = 0; // 标志位static修饰确保变量只在当前任务函数内可见,且任务运行期间不会被重置(保留上次的值)。- 作用:记录
taskLED02的状态 ——0表示任务正在运行,1表示任务已被挂起。- 无限循环与按键检测
for(;;) // 无限循环,持续检测按键 {if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1 )== GPIO_PIN_RESET) // 检测按键是否按下{osDelay(20); // 延时20ms消抖(跳过机械抖动阶段)if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1 == GPIO_PIN_RESET)) // 再次确认按键按下{// 按键按下后的逻辑...while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET ); // 等待按键释放}}osDelay(10); // 让出CPU,给其他任务运行机会 }
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)(先读取引脚状态,再与 “低电平(按下)” 比较)。- 消抖逻辑:两次检测按键状态(间隔 20ms),避免按键机械抖动导致的误触发。
- 等待释放:
while循环等待按键松开,确保一次按下只触发一次状态切换。任务的挂起与恢复当按键确认按下后,根据
flag的值执行操作:if(flag == 0) // 若标志位为0(任务正在运行) {osThreadSuspend(taskLED02Handle); // 挂起taskLED02(暂停执行)printf("任务2已暂停\r\n");flag = 1; // 更新标志位为“已挂起” } else // 若标志位为1(任务已挂起) {osThreadResume(taskLED02Handle); // 恢复taskLED02(继续执行)printf("任务2已恢复\r\n");flag = 0; // 更新标志位为“运行中” }
osThreadSuspend:FreeRTOS 函数,暂停指定任务(任务进入 “挂起态”,不再参与调度)。osThreadResume:FreeRTOS 函数,恢复被挂起的任务(任务重新进入 “就绪态”,等待调度执行)。任务调度循环末尾的
osDelay(10)是 FreeRTOS 的延时函数,作用是让当前任务(StartTaskKEY02)暂时让出 CPU,允许系统调度其他任务运行,避免当前任务独占资源。
五、队列
什么是队列?
队列又称消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断
和任务间传递信息。
为什么不使用全局变量?
如果使用全局变量,免子(任务1)修改了变量a,等待树獭(任务3)处理,但树獭处理速
度很慢,在处理数据的过程中,狐狸(任务2)有可能又修改了变量a,导致树獭有可能得
到的不是正确的数据。

在这种情况下,就可以使用队列。免子和狐狸产生的数据放在流水线上,树獭可以慢慢一个
个依次处理。
关于队列的几个名词:
队列项目:队列中的每一个数据;
队列长度:队列能够存储队列项目的最大数量;
创建队列时,需要指定队列长度及队列项目大小。
队列特点
1. 数据入队出队方式
通常采用先进先出(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取。
也可以配置为后进先出(LIFO)方式,但用得比较少。
2.数据传递方式
采用实际值传递,即将数据拷贝到队列中进行传递,也可以传递指针,在传递较大的数据的
时候采用指针传递。
3.多任务访问
队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息
4.出队、入队阻塞
当任务向一个队列发送消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队。
阻塞时间如果设置为:
- 0:直接返回不会等待;
- 0-port_MAX_DELAY:等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;
- port_MAX_DELAY:死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;
队列相关API函数
1. 创建队列
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,
UBaseType_t uxItemSize);
参数:
- uxQueueLength:队列可同时容纳的最大项目数。
- uxltemSize:存储队列中的每个数据项所需的大小(以字节为单位)。
返回值:
如果队列创建成功,则返回所创建队列的句柄。如果创建队列所需的内存无法分配,则返回NULL。
2.写队列
写队列总共有以下几个函数:

BaseType_t xQueueSend(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait
);
参数:
- xQueue:队列的句柄,数据项将发送到此队列。
- pvItemToQueue:待写入数据
- xTicksToWait:阻塞超时时间
返回值:
如果成功写入数据,返回pdTRUE,否则返回errQUEUE_FULL。
3.读队列
读队列总共有以下几个函数:

BaseType_t xQueueReceive(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait);
参数:
- xQueue:待读取的队列
- pvItemToQueue:数据读取缓冲区
- xTicksToWait:阻塞超时时间
返回值:
成功返回pdTRUE,否则返回pdFALSE。
实操
实验需求
创建一个队列,按下KEY1向队列发送数据,按下KEY2向队列读取数据。
cube MX配置
六、二值信号量
什么是信号量
信号量(Semaphore),是在多任务环境下使用的一种机制,是可以用来保证两个或多个关
键代码段不被并发调用。
信号量这个名字,我们可以把它拆分来看,信号可以起到通知信号的作用,然后我们的量还
可以用来表示资源的数量,当我们的量只有0和1的时候,它就可以被称作二值信号量,只有
两个状态,当我们的那个量没有限制的时候,它就可以被称作为计数型信号量。
信号量也是队列的一种。
什么是二值信号量?
二值信号量其实就是一个长度为1,大小为零的队列,只有0和1两种状态,通常情况下,我
们用它来进行互斥访问或任务同步。
互斥访问:比如门钥匙,只有获取到钥匙才可以开门
任务同步:比如我录完视频你才可以看视频

二值信号量相关API函数

1.创建二值信号量
SemaphoreHandle_t xSemaphoreCreateBinary(void)
参数:
无
返回值:
成功,返回对应二值信号量的句柄;
失败,返回NULL。
2.释放二值信号量
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore)
参数:
xSemaphore:要释放的信号量句柄
返回值:
成功,返回pdPASS;
失败,返回errQUEUE_FULL。
3.获取二值信号量
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xTicksToWait );
参数:
xSemaphore:要获取的信号量句柄
xTicksToWait:超时时间,0表示不超时,port_MAX_DELAY表示卡死等待;
返回值:
成功,返回 pdPASS;
失败,返回errQUEUE_FULL。

