FreeRTOS从环境搭建到进阶
FreeRTOS从环境搭建到进阶
Free RTOS
简单易用的实时操作系统
管理硬件和驱动 为应用程序提供服务
单片机 单核 一次只能执行一个任务
操作系统让单核 同一时刻处理多项任务
原理
多个任务快速切换
优点
提高资源利用率(硬件资源)
硬件速度太快,人的速度太慢
CPU可以在一个任务等待延时或者某些外部事件时,切换到另一个任务,从而避免CPU做无意义的等待。
不会卡住 不会丢数据(UART串口通信 如果是前一个任务执行完再执行这个任务 可能过了取数据时间 导致丢失数据)
一个任务执行完,再去执行其他任务,其他任务的数据可能丢失
缺点
浪费性能(切换任务的时间)
如何切换任务:
调度器
调度策略
时间片轮询:相同优先级,每个任务执行相同的时间,轮流执行
优先级调度:抢占优先级,数字越大优先级越高
ABC 停A才能执行B 停B才能执行C
调度原理:
优先级 当前状态
数据结构
任务状态:
就绪态才能转换为运行态 运行态转换为就绪态(被优先级高的打断 或者 任务的时间片用完)
运行中 就绪(在排队)
阻塞(时间 n个阶段一个任务 走完一个阶段) free RTOS 专用的delay 只对当前任务有效
挂起 (事件 排到了 不满足条件 满足条件后 继续排队 (就绪态)) 事件:vTaskSuspend xTaskResume
状态转换逻辑:

任务队列(怎么写代码)
时间片轮询:队列性质(先进先出)
优先级:就绪队列
就绪队列:按照优先级分成n个队列
每个队列优先级不同,数字越大,优先级越高
队列里面的任务优先级相同按时间片轮询(先进先出)
就绪队列数据结构: (二维数组)

阻塞队列(有期徒刑)
时间到了,到就绪队列
时间短的排前面(按照优先级号,排到就绪队列)
挂起队列(无期徒刑)
外部调用,才能到就绪队列
如何实现任务切换:
Systick中断
上下文切换
依赖3个中断
SysTick(优先级最高)
一般设置1ms
决定时间片的时间
维护系统时钟,并判断是否需要执行上下文切换,如果需要,则触发PendSV中断
PendSV
执行实际的上下文切换操作
SVC
用于将整个系统的第一个任务的上下文加载到CPU
FreeRTOS移植
STM32F103ZET6
不使用pfrintf调试 打印的时候 RTOS可能在执行上下文切换
用LED灯调试 PA0 PA1 PA8
SysTick(优先级最高)


demo

定制功能

编译器适配:
portable:接口
与编译器对接(Keil)

堆内存管理

heap_5多个flash不连号(一般不使用)
移植开始====================
新建文件夹FreeRTOS

FreeRTOS->include

FreeRTOS->portable


定制文件放到FreeRTOS

==========================
实现上下文切换功能(3个中断)
不用实现SVC_Handler和PendSV_Handler(源码是汇编):定制里面 用宏定义实现

只实现SysTick_Handler

/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
#include "task.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PFP */
extern void xPortSysTickHandler(void);
/* USER CODE END PFP */
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
xPortSysTickHandler();
}
/* USER CODE END SysTick_IRQn 0 */
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
定制:
在FreeRTOSConfig.h中增加如下三个宏
#define xPortPendSVHandler PendSV_Handler //xPortPendSVHandler是由FreeRTOS提供的PendSV中断服务程序的实现
#define vPortSVCHandler SVC_Handler //vPortSVCHandler是由FreeRTOS提供的SVC中断服务程序的实现
#define INCLUDE_xTaskGetSchedulerState 1 //使能xTaskGetSchedulerState函数


错误:每次HAL都重新生成PendSV SVC_Handler
方案1:
每次用HAL生成代码都需要注释掉

方案2

FreeRTOS命令惯例
空闲任务
软件架构
可以单独写个启动任务,启动任务优先级需要最高

需要删除自身

创建任务
案例1 动态分配内存
xTaskCreate
使用动态内存分配 令LED1闪烁
相关参数
configMAX_PRIORITIES 任务最高优先级
configMINIMAL_STACK_SIZE 任务最小堆栈大小,注意单位
使用===========================
RTOS
RTOS运行的任务
常规代码 while代码永远执行不了
新建App文件夹
新建文件:

实现方法:
启动FreeRTOS
1 创建n个任务
2 启动任务调度器 启动FreeRTOS操作系统
2下面的代码不会执行,2执行完找上面1创建的n个任务
系统的空闲任务 占用0优先级
静态数组空间 不是堆栈空间
声明结构体不要声明指针, 声明指针不会自动创建结构体


延迟函数必须使用freeRTOS的延迟函数
vTaskDelay(100) 任务会阻塞100次中断 1次中断1ms(SysTick) 总共阻塞100ms
测试1
优先级一样
优先级不一样 vTaskDelay不要
优先级不一样 vTaskDelay开启
优先级0 系统空闲任务

如果高优先级任务不阻塞自身 调度器永远调度高优先级任务 别的任务永远执行不了
代码
#include "FreeRTOS_Task.h"
void taskCode1(void *args);
#define task1_stack_size 128
#define tsk1_priority 1
TaskHandle_t task1_handle;
void taskCode2(void *args);
#define task2_stack_size 128
#define tsk2_priority 2
TaskHandle_t task2_handle;
void taskCode3(void *args);
#define task3_stack_size 128
#define tsk3_priority 3
TaskHandle_t task3_handle;
void App_FreeRTOS_Start(void)
{
xTaskCreate(
taskCode1, /* Function that implements the task. */
"vTaskCode1", /* Text name for the task. */
task1_stack_size, /* Stack size in words, not bytes. */
NULL, /* Parameter passed into the task. */
tsk1_priority, /* Priority at which the task is created. */
&task1_handle);
xTaskCreate(
taskCode2, /* Function that implements the task. */
"vTaskCode2", /* Text name for the task. */
task2_stack_size, /* Stack size in words, not bytes. */
NULL, /* Parameter passed into the task. */
tsk2_priority, /* Priority at which the task is created. */
&task2_handle);
xTaskCreate(
taskCode3, /* Function that implements the task. */
"vTaskCode3", /* Text name for the task. */
task3_stack_size, /* Stack size in words, not bytes. */
NULL, /* Parameter passed into the task. */
tsk3_priority, /* Priority at which the task is created. */
&task3_handle);
vTaskStartScheduler();
}
void taskCode1(void *args)
{
while (1)
{
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
vTaskDelay(500);
}
}
void taskCode2(void *args)
{
while (1)
{
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
vTaskDelay(500);
}
}
void taskCode3(void *args)
{
while (1)
{
HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
vTaskDelay(500);
}
}
优先级上限


==============================================
案例2
裸机开发与FreeRTOS开发区别
需求说明
令LED1和LED2以不同的频率闪烁
要求
分别用FreeRTOS和裸机开发的方式实现
体会
在FreeRTOS中,每个Task都可以将自己看作是唯一的Task
裸机开发Task之间会相互影响
Idle作用 防止有任务有延迟
vTaskDelay阻塞当前任务
阻塞时间完 任务进入就绪队列
其他任务可以执行(主动释放资源)
案例3

一直运行的延迟
其他任务无法执行 (一直占用CPU)
现象
只有task3控制的灯一直亮

#include "FreeRTOS_demo.h"
void delay_ms(uint32_t ms)
{
uint32_t count = ms * 72000 / 9;
while (count--)
{
__NOP();
}
}
// TASK1任务参数
// 特定类型的函数指针
void task1(void *pvParam);
// 设置任务栈大小
#define TASK1_STACK_SIZE 128
// 设置优先级 => 值越大优先级越高 越先调度
#define TASK1_PRIORITY 1
TaskHandle_t task1_handle;
// TASK2任务参数
void task2(void *pvParam);
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 2
TaskHandle_t task2_handle;
// TASK3任务参数
void task3(void *pvParam);
#define TASK3_STACK_SIZE 128
#define TASK3_PRIORITY 3
TaskHandle_t task3_handle;
void FreeRTOS_Start(void)
{
// 创建任务
xTaskCreate(task1, "TASK1", TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle);
xTaskCreate(task2, "TASK2", TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle);
xTaskCreate(task3, "TASK3", TASK3_STACK_SIZE, NULL, TASK3_PRIORITY, &task3_handle);
// 最后一行代码一定是启动调度器
// 调度器会启动一个空闲任务 没事干就去空闲任务
vTaskStartScheduler();
}
// task1函数实现
void task1(void *pvParam)
{
while (1)
{
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
// 阻塞当前任务
vTaskDelay(400);
}
}
void task2(void *pvParam)
{
while (1)
{
Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
// 阻塞当前任务500ms => 之后进入就绪态排队
vTaskDelay(500);
}
}
void task3(void *pvParam)
{
while (1)
{
Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
// 阻塞当前任务
// vTaskDelay(600);
// 一直运行的延迟 => 一直占用CPU
delay_ms(600);
// vTaskDelay(100);
}
}
task3 延迟delay_ms下面加上vTaskDelay(100)
现象:
task1亮完 task2 task3灯同时亮
void task3(void *pvParam)
{
while (1)
{
Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
// 阻塞当前任务
// vTaskDelay(600);
// 一直运行的延迟 => 一直占用CPU
delay_ms(600);
vTaskDelay(100);
}
}
案例4

Systick中断频率
configtick_rate_hz
案例5 静态分配内存
xTaskCreateStatic
configSUPPORT_STATIC_ALLOCATION必须在FreeRTOSConfig.h中设置为 1
需求说明
令LED1闪烁
要求
静态分配内存
掌握xTaskCreateStatic用法 (不常用 了解)
静态
每个任务有单独的RAM保存状态

动态创建栈 空间大小 操作系统创建指定空间大小的数组
静态创建栈 空间大小 自己创建指定空间大小的数组
创建任务
栈信息
标识信息 -
报错

自定义:
#include "FreeRTOS_demo.h"
void delay_ms(uint32_t ms)
{
uint32_t count = ms * 72000 / 9;
while (count--)
{
__NOP();
}
}
// 任务函数
void task1(void *args);
// 栈空间大小
#define TASK1_STK_SIZE 128
#define TASK1_PRIO 1
StackType_t task1_stk[TASK1_STK_SIZE] = {0};
StaticTask_t task1_tcb;
TaskHandle_t task1_handle;
// 栈空间大小
#define IDLE_TASK_STK_SIZE 128
StackType_t IDLE_task_stk[IDLE_TASK_STK_SIZE] = {0};
StaticTask_t IDLE_task_tcb;
// 静态创建一旦打开 需要手动给IDLE task分配空间
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
//注意这里的IDLE_task_tcb IDLE_task_stk 必须重新定义不能与xTaskCreateStatic的参数一致
*ppxIdleTaskTCBBuffer = &IDLE_task_tcb;
*ppxIdleTaskStackBuffer = IDLE_task_stk;
*pulIdleTaskStackSize = IDLE_TASK_STK_SIZE;
}
void FreeRTOS_Start(void)
{
// 创建静态任务
task1_handle = xTaskCreateStatic(task1, "task1", TASK1_STK_SIZE, NULL, TASK1_PRIO, task1_stk, &task1_tcb);
// 最后一行代码一定是启动调度器
// 调度器会启动一个空闲任务 没事干就去空闲任务
vTaskStartScheduler();
}
void task1(void *args)
{
while (1)
{
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
vTaskDelay(1000);
}
}
===================================
删除任务

vTaskDelete
1 宏定义INCLUDE_vTaskDelete必须定义为 1
2 从 RTOS 内核管理中移除任务。要删除的任务将从所有就绪、 阻塞、挂起和事件列表中移除
注意:
1 空闲任务负责释放由 RTOS 内核分配给已删除任务的 内存。因此,如果应用程序调用了 vTaskDelete(),请务必确保空闲任务获得足够的微控制器处理时间。
2 任务代码分配的内存不会自动释放, 应在任务删除之前手动释放。
deepseek:
在FreeRTOS中,使用vTaskDelete删除任务时,任务控制块(TCB)和栈的内存会自动释放(若为动态分配),
但任务代码中手动分配的内存(如通过pvPortMalloc)需开发者自行释放。
手动释放任务内存的步骤
明确内存分配来源:
TCB和栈:若通过xTaskCreate动态创建任务,TCB和栈由FreeRTOS自动释放,无需干预。
用户分配的内存:若任务中调用了pvPortMalloc、malloc或类似函数,需手动释放。
在任务结束前释放内存:
1 自删除任务:若任务调用vTaskDelete(NULL)自行删除,需在调用前释放内存。
void vMyTask(void *pvParams) {
char *buffer = pvPortMalloc(100); // 动态分配内存
// 使用buffer...
// 删除任务前释放内存
vPortFree(buffer);
vTaskDelete(NULL); // 删除自身
}
2 被外部删除:若其他任务删除该任务,需通过同步机制(如队列、信号量或任务通知)通知目标任务自行清理。
// 任务A通知任务B删除自身
void vTaskA(void *pvParams) {
xTaskNotify(taskB_handle, CLEANUP_REQUEST, eSetBits);
// 等待任务B确认清理完成
vTaskDelete(taskB_handle);
}
// 任务B响应清理请求
void vTaskB(void *pvParams) {
char *buffer = pvPortMalloc(100);
while (1) {
uint32_t notification;
if (xTaskNotifyWait(0, CLEANUP_REQUEST, ¬ification, 0) == pdTRUE) {
vPortFree(buffer); // 收到通知后释放内存
break; // 退出循环,任务结束
}
// 正常任务逻辑...
}
}
删除其他内核对象:
若任务中创建了队列、信号量等,需调用vQueueDelete()、vSemaphoreDelete()等函数释放资源。
资源不会立即回收
空闲任务负责回收内存
留点时间给空闲任务,空闲任务优先级最低
高优先级任务删除低优先级任务
高优先级主动释放CPU 阻塞自身 否则低优先级任务无法执行 vTaskDelay(100)
案例1

案例2

#include "FreeRTOS_demo.h"
void delay_ms(uint32_t ms)
{
uint32_t count = ms * 72000 / 9;
while (count--)
{
__NOP();
}
}
// TASK1任务参数
// 特定类型的函数指针
void task1(void *pvParam);
// 设置任务栈大小
#define TASK1_STACK_SIZE 128
// 设置优先级 => 值越大优先级越高 越先调度
#define TASK1_PRIORITY 1
TaskHandle_t task1_handle;
// TASK2任务参数
void task2(void *pvParam);
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 1
TaskHandle_t task2_handle;
// TASK3任务参数
void task3(void *pvParam);
#define TASK3_STACK_SIZE 128
#define TASK3_PRIORITY 1
TaskHandle_t task3_handle;
// KEY_TASK任务参数
void key_task(void *pvParam);
#define KEY_TASK_STACK_SIZE 128
#define KEY_TASK_PRIORITY 3
TaskHandle_t key_task_handle;
// START_TASK任务参数
void start_task(void *pvParam);
#define START_TASK_STACK_SIZE 128
#define START_TASK_PRIORITY 4
TaskHandle_t start_task_handle;
void FreeRTOS_Start(void)
{
// 创建任务
xTaskCreate(start_task, "START_TASK", START_TASK_STACK_SIZE, NULL, START_TASK_PRIORITY, &start_task_handle);
// 调度器会启动一个空闲任务 没事干就去空闲任务
vTaskStartScheduler();
}
void start_task(void *pvParam)
{
xTaskCreate(task1, "TASK1", TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle);
xTaskCreate(task2, "TASK2", TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle);
xTaskCreate(task3, "TASK3", TASK3_STACK_SIZE, NULL, TASK3_PRIORITY, &task3_handle);
xTaskCreate(key_task, "KEY_TASK", KEY_TASK_STACK_SIZE, NULL, KEY_TASK_PRIORITY, &key_task_handle);
// vTaskDelete(start_task_handle);
vTaskDelete(NULL); // 删除自身任务
}
// task1函数实现
void task1(void *pvParam)
{
while (1)
{
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
// 阻塞当前任务
vTaskDelay(400);
// delay_ms(600);
}
}
void task2(void *pvParam)
{
while (1)
{
Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
// 阻塞当前任务500ms => 之后进入就绪态排队
vTaskDelay(500);
// delay_ms(600);
}
}
void task3(void *pvParam)
{
while (1)
{
Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
// 阻塞当前任务
// vTaskDelay(600);
// 一直运行的延迟 => 一直占用CPU
vTaskDelay(100);
}
}
void key_task(void *pvParam)
{
while (1)
{
Key_Type key_task = Int_Key_Scan();
if (key_task == KEY1)
{
Int_LED_Off(LED1_GPIO_Port, LED1_Pin);
vTaskDelete(task1_handle);
}
else if (key_task == KEY2)
{
Int_LED_Off(LED2_GPIO_Port, LED2_Pin);
vTaskDelete(task2_handle);
}
else if (key_task == KEY3)
{
Int_LED_Off(LED3_GPIO_Port, LED3_Pin);
vTaskDelete(task3_handle);
}
vTaskDelay(100);
}
}
=================================
任务挂起和恢复

vTaskSuspend
vTaskResume
只能写在task里面
专用的函数才能写在中断函数里面(fromISR)
按键1 挂起task1 按键2 恢复task1
需要阻塞自己
后面一般使用阻塞 不使用挂起
#include "FreeRTOS_demo.h"
void delay_ms(uint32_t ms)
{
uint32_t count = ms * 72000 / 9;
while (count--)
{
__NOP();
}
}
// TASK1任务参数
// 特定类型的函数指针
void task1(void *pvParam);
// 设置任务栈大小
#define TASK1_STACK_SIZE 128
// 设置优先级 => 值越大优先级越高 越先调度
#define TASK1_PRIORITY 1
TaskHandle_t task1_handle;
// TASK2任务参数
void task2(void *pvParam);
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 1
TaskHandle_t task2_handle;
// KEY_TASK任务参数
void key_task(void *pvParam);
#define KEY_TASK_STACK_SIZE 128
#define KEY_TASK_PRIORITY 3
TaskHandle_t key_task_handle;
// START_TASK任务参数
void start_task(void *pvParam);
#define START_TASK_STACK_SIZE 128
#define START_TASK_PRIORITY 4
TaskHandle_t start_task_handle;
void FreeRTOS_Start(void)
{
// 创建任务
xTaskCreate(start_task, "START_TASK", START_TASK_STACK_SIZE, NULL, START_TASK_PRIORITY, &start_task_handle);
// 调度器会启动一个空闲任务 没事干就去空闲任务
vTaskStartScheduler();
}
void start_task(void *pvParam)
{
xTaskCreate(task1, "TASK1", TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle);
xTaskCreate(task2, "TASK2", TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle);
xTaskCreate(key_task, "KEY_TASK", KEY_TASK_STACK_SIZE, NULL, KEY_TASK_PRIORITY, &key_task_handle);
// vTaskDelete(start_task_handle);
vTaskDelete(NULL); // 删除自身任务
}
// task1函数实现
void task1(void *pvParam)
{
while (1)
{
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
// 阻塞当前任务
vTaskDelay(400);
// delay_ms(600);
}
}
void task2(void *pvParam)
{
while (1)
{
Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
// 阻塞当前任务500ms => 之后进入就绪态排队
vTaskDelay(500);
// delay_ms(600);
}
}
void key_task(void *pvParam)
{
while (1)
{
Key_Type key_task = Int_Key_Scan();
if (key_task == KEY1)
{
Int_LED_Off(LED1_GPIO_Port, LED1_Pin);
vTaskSuspend(task1_handle);
}
else if (key_task == KEY2)
{
vTaskResume(task1_handle);
}
else if (key_task == KEY3)
{
Int_LED_Off(LED2_GPIO_Port, LED2_Pin);
vTaskSuspend(task2_handle);
}
else if (key_task == KEY4)
{
vTaskResume(task2_handle);
}
vTaskDelay(100);
}
}
===========================
进/出临界区



taskENTER_CRITICAL
taskEXIT_CRITICAL
1 configMAX_SYSCALL_INTERRUPT_PRIORITY 设置的优先级的中断, 并启用所有高于此优先级的中断,(启用>11的优先级中断)。
2 抢占式上下文切换仅在中断内发生, 在中断被禁用时不会发生。 因此,调用 taskENTER_CRITICAL() 的任务一定会保持在运行状态, 直到退出临界区,除非任务明确试图阻塞或让出 (任务不应在临界区内部进行该操作)。
3 taskENTER_CRITICAL() 和 taskEXIT_CRITICAL() 的调用采用嵌套结构。因此,只有在每次调用 taskENTER_CRITICAL() 后执行相应的 taskEXIT_CRITICAL() 调用时, 才会退出临界区。
4 临界区必须尽量简短,否则会对中断响应时间产生不利影响。 每次调用 taskENTER_CRITICAL() 时,都必须有对应的 taskEXIT_CRITICAL() 调用。
5 不得从临界区调用 FreeRTOS API 函数。
6 不得从中断服务程序 (ISR) 调用 taskENTER_CRITICAL() 和 taskEXIT_CRITICAL()
使用sysTick中断时 不能使用HAL_delay会冲突,使用自定义的delay
程序---操作系统---硬件
操作系统控制硬件
程序直接控制硬件 IIC SPI IO
使用场景:
原子性
时序不能被打断
中间执行的操作 尽量少
临界区:操作系统内核
优先级小的可以进入临界区
优先级大的不能打断
多次进入临界区 所有任务退出临界区 FreeRTOS才能继续运行
案例

线程安全问题
count++的操作不是原子操作,需要执行4步汇编

解决
进入临界区可以确保是原子操作
方案1

方案2(推荐)
进入临界区代码应该尽量少
count才是公用变量,task1 task2都修改count

临界区实现原理

进入临界区
禁用中断
进入临界区次数
第一次进入临界区:

退出临界区

FreeRTOS能够管理的中断



191左移4位11(中断优先级)
中断代码
操作系统代码
操作系统之外的代码
代码优先级

挂起/恢复调度器

vTaskSuspendAll
xTaskResumeAll
挂起调度器(一般使用临界区)
阻止上下文切换 (切换任务)
中断没有关闭,可以执行中断,还是没法保证原子性
临界区禁用了中断
===============
任务相关的实用API

获取任务优先级
uxTaskPriorityGet()
设置任务优先级
vTaskPrioritySet()
获取系统中任务的数量
uxTaskGetNumberOfTasks()
获取当前任务的任务句柄
xTaskGetCurrentTaskHandle()
根据任务名获取该任务的任务句柄
xTaskGetHandle()
获取任务状态
eTaskGetState()

需要在FreeRTOSConfig.h中打开宏定义
eDeleted
删除了任务 没有被回收资源
获取任务栈历史最小剩余空间
uxTaskGetStackHighWaterMark()
获取指定单个的任务信息
vTaskGetInfo()
获取所有任务状态信息
uxTaskGetSystemState()
任务管理器
获取任务的运行时间
vTaskGetRunTimeStats()
配置
1 configGENERATE_RUN_TIME_STATS configUSE_STATS_FORMATTING_FUNCTIONS 和 configSUPPORT_DYNAMIC_ALLOCATION 必须定义为 1
2 配置外设 定时器/计数器
应用程序必须提供 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS():
3 返回定时器的当前计数值
portGET_RUN_TIME_COUNTER_VALUE():
4 计数器的频率应该至少是 滴答计数的 10 倍
滴答定时器周期为1ms 这里将TIM6定时器频率设置为滴答计数器的100倍 即周期为10us
5 计数值使用自己定义的tick_count,在中断函数里面实现计数,不使用定时器的count(最大65535)
一轮计数完成的中断

计数


显示内容
1 任务名称
2 Abs 时间(绝对时间)
指实际执行任务所耗费的总时间,即任务处于 “正在运行”状态的总时间。由用户为其应用程序选择合适的时间基数。
3 % 时间(时间百分比)
实质上,这里显示的是相同的信息,只不过是以占总处理时间的百分比形式显示, 而不是绝对时间。


以“表格”形式获取所有任务的信息
vTaskList()
显示内容
1 任务名称
2 任务状态
在 ASCII 表中,以下字母用于表示任务的状态:
'B' - 已阻塞
'R' - 准备就绪
'D' - 已删除(等待清理)
'S' - 已挂起或已阻塞,没有超时
3 优先级
( unsigned int ) pxTaskStatusArray[ x ].uxCurrentPriority,
4 任务栈历史最小剩余空间
( unsigned int ) pxTaskStatusArray[ x ].usStackHighWaterMark,
5 任务号
( unsigned int ) pxTaskStatusArray[ x ].xTaskNumber );

延时函数

基准时间
vTaskDelay:当前时间作为基准时间
可以保证所有优先级任务都能执行
缺点:最大的优先级 能执行多长时间 会越来越慢
xTaskDelayUntil:自定义基准时间
计算时,不能丢失精度
vTaskDelay
指定延迟时间

xTaskDelayUntil
也可以使用vTaskDelayUntil
将任务延迟到指定时间。此函数可以由周期性任务使用, 来确保恒定的执行频率。
稳定的时间周期,
基准时间+指定延迟时间
每次调用完成都会自动更新基准时间

自定义时间作为基准 延迟500ms
中间只延时了500-50=450
进阶篇
任务之间的交互
1 消息队列
传输数据(无格式要求)

特点


支持阻塞操作 不占用CPU
需要引入queue.h
xQueueCreate()
xQueueSend()
xQueueReceive()
只有一个任务写,多个任务读 ,写优先级 大于 读优先级
发送数据 接收数据 源码分析
#include "FreeRTOS_demo.h"
void delay_ms(uint32_t ms)
{
uint32_t count = ms * 72000 / 9;
while (count--)
{
__NOP();
}
}
// 写队列任务
void send_task(void *args);
#define SEND_TASK_STACK_SIZE 128
#define SEND_TASK_PRIO 3
TaskHandle_t send_task_handle;
// 读队列任务
void recv_task(void *args);
#define RECV_TASK_STACK_SIZE 128
#define RECV_TASK_PRIO 3
TaskHandle_t recv_task_handle;
// 打印占用时间任务
void print_task(void *args);
#define PRINT_TASK_STACK_SIZE 128
#define PRINT_TASK_PRIO 3
TaskHandle_t print_task_handle;
// 声明队列
uint8_t queue_buffer[1] = {0};
uint8_t flag = 0;
QueueHandle_t queue;
QueueHandle_t key_queue;
void FreeRTOS_Start(void)
{
// 创建队列
queue = xQueueCreate(1, sizeof(uint8_t));
key_queue = xQueueCreate(1, sizeof(Key_Type));
xTaskCreate(send_task, "send_task", SEND_TASK_STACK_SIZE, NULL, SEND_TASK_PRIO, &send_task_handle);
xTaskCreate(recv_task, "recv_task", RECV_TASK_STACK_SIZE, NULL, RECV_TASK_PRIO, &recv_task_handle);
xTaskCreate(print_task, "print_task", PRINT_TASK_STACK_SIZE, NULL, PRINT_TASK_PRIO, &print_task_handle);
// 最后一行代码一定是启动调度器
// 调度器会启动一个空闲任务 没事干就去空闲任务
vTaskStartScheduler();
}
void send_task(void *args)
{
while (1)
{
Key_Type key = Int_Key_Scan();
if (key == KEY1)
{
// 手动实现消息队列的线程安全 => 只需要进入临界区即可
// taskENTER_CRITICAL();
// // 将数据写入到队列中
// queue_buffer[0] = 1;
// flag = 1;
// taskEXIT_CRITICAL();
// 通过队列发送数据
uint8_t item = 1;
xQueueSend(queue, &item, portMAX_DELAY);
}
if (key != KEY_NONE)
{
// 有按键按下
// 通过队列发送数据
xQueueSend(key_queue, &key, portMAX_DELAY);
}
vTaskDelay(10);
}
}
void recv_task(void *args)
{
while (1)
{
// if (flag == 1)
// {
// // 有数据需要读
// // 手动实现消息队列的线程安全 => 只需要进入临界区即可
// taskENTER_CRITICAL();
// // 从队列中读取数据
// printf("key1 is pressed\r\n");
// flag = 0;
// taskEXIT_CRITICAL();
// }
// 减少CPU占用 减少扫描的次数 => 但是不及时
// vTaskDelay(10);
// 使用队列的接收方法
Key_Type data;
xQueueReceive(key_queue, &data, portMAX_DELAY);
if (data == KEY1)
{
printf("key1 is pressed\r\n");
}
else if (data == KEY2)
{
printf("key2 is pressed\r\n");
}
else if (data == KEY3)
{
printf("key3 is pressed\r\n");
}
else if (data == KEY4)
{
printf("key4 is pressed\r\n");
}
}
}
uint8_t time_buff[128];
void print_task(void *args)
{
while (1)
{
vTaskGetRunTimeStats((char *)time_buff);
printf("\n%s\r\n", time_buff);
vTaskDelay(1000);
}
}
2 信号量
give释放不会阻塞 0->1
take获取会阻塞 1->0
信号量在队列基础上实现
2.1 二值信号量
需要引入semphr.h
xSemaphoreCreateBinary()
xSemaphoreTake()
相当于调用suspend(null);
xSemaphoreGive()
相当于调用resume(receive_task);
xSemaphoreTakeFromISR()
xSemaphoreGiveFromISR()
案例1

HAL配置
开启串口中断 中断优先级大于11(12)
注意:唤醒接收任务的阻塞 => 不能直接写在中断处理函数中 => 任意中断都会触发
不用信号量接收数据

REC_TASK CPU占用率高

使用信号量实现

带空闲的中断函数

REC_TASK CPU占用率低

代码
注意
xSemaphoreGiveFromISR
#include "FreeRTOS_demo.h"
void delay_ms(uint32_t ms)
{
uint32_t count = ms * 72000 / 9;
while (count--)
{
__NOP();
}
}
// 最小CPU占用来实现串口接收数据
void receive_task(void *args);
#define RECEIVE_TASK_STACK_SIZE 128
#define RECEIVE_TASK_PRIORITY 2
TaskHandle_t receive_task_handle;
// 打印任务
void print_task(void *args);
#define PRINT_TASK_STACK_SIZE 128
#define PRINT_TASK_PRIORITY 3
TaskHandle_t print_task_handle;
xSemaphoreHandle sem;
void FreeRTOS_Start(void)
{
// 创建二值信号量
sem = xSemaphoreCreateBinary();
xTaskCreate(receive_task, "receive_task", RECEIVE_TASK_STACK_SIZE, NULL, RECEIVE_TASK_PRIORITY, &receive_task_handle);
xTaskCreate(print_task, "print_task", PRINT_TASK_STACK_SIZE, NULL, PRINT_TASK_PRIORITY, &print_task_handle);
// 最后一行代码一定是启动调度器
// 调度器会启动一个空闲任务 没事干就去空闲任务
vTaskStartScheduler();
}
uint8_t receive_buff[128];
uint16_t receive_len;
// void receive_task(void *args)
// {
// while (1)
// {
// HAL_UARTEx_ReceiveToIdle(&huart1, receive_buff, 128, &receive_len, 1000);
// if (receive_len > 0)
// {
// // 判断接收到数据
// // 打印接收到的数据
// printf("receive data:%s\r\n", receive_buff);
// receive_len = 0;
// memset(receive_buff, 0, 128);
// }
// // 轮询的方式添加阻塞 会降低CPU占用 但是会造成延迟
// vTaskDelay(10);
// }
// }
void receive_task(void *args)
{
while (1)
{
// 开启串口的中断
HAL_UARTEx_ReceiveToIdle_IT(&huart1, receive_buff, 128);
// 什么时候能获取到信号量 表示什么时候接收到了数据
xSemaphoreTake(sem, portMAX_DELAY); // 相当于调用suspend(null);
// 接收到数据
printf("%s", receive_buff);
// 清空缓存
memset(receive_buff, 0, 128);
}
}
uint8_t time_buff[128];
void print_task(void *args)
{
while (1)
{
vTaskGetRunTimeStats((char *)time_buff);
printf("\n%s\r\n", time_buff);
vTaskDelay(1000);
}
}
// 中断回调函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if (huart->Instance == USART1)
{
receive_len = Size;
// 释放信号量 => resume(receive_task);
xSemaphoreGiveFromISR(sem, NULL);
}
}
案例2

由于HAL进行了优化
USART在接收数据过程中,不会被打断,测试效果不明显,因此需要使用寄存器方式实现

使用寄存器方式实现
int fputc(int ch, FILE *f)
{
/* 1. 等待发送寄存器为空 */
while ((USART1->SR & USART_SR_TXE) == 0);
/* 2. 数据写出到数据寄存器 */
USART1->DR = ch;
return ch;
}
使用信号量实现

问题
没打印数据
原因:
信号量是在“空”状态下创建的,这意味着必须先用 xSemaphoreGive() 函数给出信号量, 然后才能使用 xSemaphoreTake() 函数来获取(获得)该信号量。
默认值是0 如果使用二值信号量表示资源 => 需要先释放一次

代码
#include "FreeRTOS_demo.h"
void delay_ms(uint32_t ms)
{
uint32_t count = ms * 72000 / 9;
while (count--)
{
__NOP();
}
}
// 打印任务1
void printf1_task(void *pvParameters);
#define PRINTF1_TASK_STACK_SIZE 128
#define PRINTF1_TASK_PRIORITY 2
TaskHandle_t printf1_task_handle;
// 打印任务2
void printf2_task(void *pvParameters);
#define PRINTF2_TASK_STACK_SIZE 128
#define PRINTF2_TASK_PRIORITY 2
TaskHandle_t printf2_task_handle;
SemaphoreHandle_t binary_sem;
void FreeRTOS_Start(void)
{
// 使用二值信号量实现互斥效果 避免资源争抢
binary_sem = xSemaphoreCreateBinary();
// 默认值是0 如果使用二值信号量表示资源 => 需要先释放一次
xSemaphoreGive(binary_sem);
// 创建任务
xTaskCreate(printf1_task, "printf1_task", PRINTF1_TASK_STACK_SIZE, NULL, PRINTF1_TASK_PRIORITY, &printf1_task_handle);
xTaskCreate(printf2_task, "printf2_task", PRINTF2_TASK_STACK_SIZE, NULL, PRINTF2_TASK_PRIORITY, &printf2_task_handle);
// 最后一行代码一定是启动调度器
// 调度器会启动一个空闲任务 没事干就去空闲任务
vTaskStartScheduler();
}
void printf1_task(void *pvParameters)
{
while (1)
{
// 需要使用资源的时候 先调用获取信号量
xSemaphoreTake(binary_sem, portMAX_DELAY);
printf("printf1_task===============\r\n");
// 使用完信号量 释放信号量
xSemaphoreGive(binary_sem);
}
}
void printf2_task(void *pvParameters)
{
while (1)
{
// 需要使用资源的时候 先调用获取信号量
xSemaphoreTake(binary_sem, portMAX_DELAY);
printf("printf2_task---------------\r\n");
// 使用完信号量 释放信号量
xSemaphoreGive(binary_sem);
}
}
2.2 互斥信号量
解决优先级翻转



优先级继承机制
优先级翻转
厂长 厂长儿子 主管
优先级 高 低 中
厂长 厂长儿子访问同一资源:如串口
主管访问另一个资源:如LED
厂长儿子被主管打断,由于厂长在等待厂长儿子释放资源, 相当于打断了厂长
优先级继承机制:厂长儿子继承了厂长的优先级,主管就不能打断厂长儿子的任务执行
xSemaphoreCreateMutex()
xSemaphoreTake()
xSemaphoreGive()
代码
使用二值信号量
优先级翻转
LED任务2会打断打印任务1
间接打断了打印任务3
执行顺序
打印任务1->被LED任务2打断->执行LED任务2->打印任务1->打印任务3
#include "FreeRTOS_demo.h"
void delay_ms(uint32_t ms)
{
uint32_t count = ms * 72000 / 9;
while (count--)
{
__NOP();
}
}
// 打印任务1
void printf_task1(void *pvParameters);
#define PRINTF_TASK1_STK_SIZE 128
#define PRINTF_TASK1_PRIO 1
TaskHandle_t printf_task1_handle;
// LED任务2
void led_task2(void *pvParameters);
#define LED_TASK2_STK_SIZE 128
#define LED_TASK2_PRIO 2
TaskHandle_t led_task2_handle;
// 打印任务3
void printf_task3(void *pvParameters);
#define PRINTF_TASK3_STK_SIZE 128
#define PRINTF_TASK3_PRIO 3
TaskHandle_t printf_task3_handle;
SemaphoreHandle_t binary_sem;
// SemaphoreHandle_t mux_sem;
void FreeRTOS_Start(void)
{
// 使用二值信号量实现互斥效果 避免资源争抢
binary_sem = xSemaphoreCreateBinary();
// 解决优先级翻转的问题 => 互斥信号量 => 本质还是二值信号量 具有优先级继承的功能
// mux_sem = xSemaphoreCreateMutex();
// 默认值是0 如果使用二值信号量表示资源 => 需要先释放一次
xSemaphoreGive(binary_sem);
// xSemaphoreGive(mux_sem);
// 创建任务
xTaskCreate(printf_task1, "printf_task1", PRINTF_TASK1_STK_SIZE, NULL, PRINTF_TASK1_PRIO, &printf_task1_handle);
xTaskCreate(led_task2, "led_task2", LED_TASK2_STK_SIZE, NULL, LED_TASK2_PRIO, &led_task2_handle);
xTaskCreate(printf_task3, "printf_task3", PRINTF_TASK3_STK_SIZE, NULL, PRINTF_TASK3_PRIO, &printf_task3_handle);
// 最后一行代码一定是启动调度器
// 调度器会启动一个空闲任务 没事干就去空闲任务
vTaskStartScheduler();
}
void printf_task1(void *pvParameters)
{
vTaskDelay(10);
// 获取信号量
xSemaphoreTake(binary_sem, portMAX_DELAY);
// xSemaphoreTake(mux_sem, portMAX_DELAY);
// 使用串口打印任务
for (uint8_t i = 0; i < 10; i++)
{
printf("printf_task1=========================\n");
}
// 释放信号量
xSemaphoreGive(binary_sem);
// xSemaphoreGive(mux_sem);
// 删除任务
vTaskDelete(NULL);
}
void led_task2(void *pvParameters)
{
vTaskDelay(20);
for (uint8_t i = 0; i < 10; i++)
{
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
delay_ms(500);
}
// 删除任务
vTaskDelete(NULL);
}
void printf_task3(void *pvParameters)
{
vTaskDelay(50);
// 获取信号量
xSemaphoreTake(binary_sem, portMAX_DELAY);
// xSemaphoreTake(mux_sem, portMAX_DELAY);
// 使用串口打印任务
for (uint8_t i = 0; i < 10; i++)
{
printf("printf_task3+++++++++++++++++++++++\n");
}
// 释放信号量
xSemaphoreGive(binary_sem);
// xSemaphoreGive(mux_sem);
// 删除任务
vTaskDelete(NULL);
}
使用互斥信号量
binary_sem = xSemaphoreCreateBinary();
改为
binary_sem = xSemaphoreCreateMutex();
优先级继承机制
打印任务1继承了打印任务3的优先级
LED任务2无法打断打印任务1
执行顺序
打印任务1->打印任务3->LED任务2
2.3 计数型信号量
值可以释放多次

take -- 不能<0 会阻塞
give ++
xSemaphoreCreateCounting()
xSemaphoreTake()
xSemaphoreGive()
uxSemaphoreGetCount()
实验:
按钮释放计数型信号量,当计数型信号量的值是5时,点亮LED1
代码
#include "FreeRTOS_demo.h"
void delay_ms(uint32_t ms)
{
uint32_t count = ms * 72000 / 9;
while (count--)
{
__NOP();
}
}
// 按键任务 => 触发计数信号量释放
void task1(void *pvParameters);
#define TASK1_STK_SIZE 128
#define TASK1_PRIO 1
TaskHandle_t task1_handle;
// 等待计数的值到5 => 触发计数信号量释放
void task2(void *pvParameters);
#define TASK2_STK_SIZE 128
#define TASK2_PRIO 2
TaskHandle_t task2_handle;
SemaphoreHandle_t count_sem;
void FreeRTOS_Start(void)
{
// 创建计数型信号量
count_sem = xSemaphoreCreateCounting(6, 0);
// 启动任务
xTaskCreate(task1, "task1", TASK1_STK_SIZE, NULL, TASK1_PRIO, &task1_handle);
xTaskCreate(task2, "task2", TASK2_STK_SIZE, NULL, TASK2_PRIO, &task2_handle);
// 最后一行代码一定是启动调度器
// 调度器会启动一个空闲任务 没事干就去空闲任务
vTaskStartScheduler();
}
// 实现任务1
void task1(void *pvParameters)
{
while (1)
{
Key_Type key = Int_Key_Scan();
if (key == KEY1)
{
// 信号量释放 count++
xSemaphoreGive(count_sem);
}
}
}
void task2(void *pvParameters)
{
while (1)
{
// 获取计数型信号量的值
uint8_t count = uxSemaphoreGetCount(count_sem);
printf("count = %d\r\n", count);
if (count == 5)
{
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
for (uint8_t i = 0; i < 5; i++)
{
// 获取信号量 count--
xSemaphoreTake(count_sem, portMAX_DELAY);
}
}
vTaskDelay(100);
}
}
3 队列集
同时处理多个队列



xQueueCreateSet()
xQueueAddToSet()
xQueueSelectFromSet()
代码
字节数组和字符串指针

xQueue2 = xQueueCreate(3, sizeof(uint8_t *));
uint8_t uart_buf[100];
//串口接收数据
HAL_UARTEx_ReceiveToIdle_IT(&huart1, (uint8_t *)uart_buf, 1000);
//队列发送消息
uint8_t *uart_data = uart_buf;
xQueueSend(xQueue2, &uart_data, portMAX_DELAY);
// 获取队列信息
uint8_t *uart_data;
xQueueReceive(xQueue2, &uart_data, portMAX_DELAY);
不使用队列集
#include "FreeRTOS_demo.h"
void delay_ms(uint32_t ms)
{
uint32_t count = ms * 72000 / 9;
while (count--)
{
__NOP();
}
}
// 任务1: 接收按键信息 => 翻转LED灯
void task1(void *pvParameters);
#define TASK1_STACK_SIZE 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
// 任务2: 接收串口的消息 => 根据接收的消息翻转LED灯
void task2(void *pvParameters);
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 2
TaskHandle_t task2_handle;
// 不使用队列集
// 两个单独的接收任务
void key_receive(void *pvParameters);
#define KEY_STACK_SIZE 128
#define KEY_PRIORITY 2
TaskHandle_t key_handle;
void uart_receive(void *pvParameters);
#define UART_STACK_SIZE 128
#define UART_PRIORITY 2
TaskHandle_t uart_handle;
// 队列保存信息
// 写入的按键信息 KeyType
QueueHandle_t xQueue1;
// 读取的串口信息 uint8_t *
QueueHandle_t xQueue2;
// 二值信号量实现串口最小资源占用
SemaphoreHandle_t xSemaphore;
void FreeRTOS_Start(void)
{
// 创建二值信号量
xSemaphore = xSemaphoreCreateBinary();
// 创建两个队列
xQueue1 = xQueueCreate(3, sizeof(Key_Type));
xQueue2 = xQueueCreate(3, sizeof(uint8_t *));
// 创建任务
xTaskCreate(task1, "task1", TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle);
xTaskCreate(task2, "task2", TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle);
xTaskCreate(key_receive, "key", KEY_STACK_SIZE, NULL, KEY_PRIORITY, &key_handle);
xTaskCreate(uart_receive, "uart", UART_STACK_SIZE, NULL, UART_PRIORITY, &uart_handle);
// 最后一行代码一定是启动调度器
// 调度器会启动一个空闲任务 没事干就去空闲任务
vTaskStartScheduler();
}
// 任务1: 接收按键信息 => 翻转LED灯
void task1(void *pvParameters)
{
while (1)
{
Key_Type key;
key = Int_Key_Scan();
if (key != KEY_NONE)
{
xQueueSend(xQueue1, &key, portMAX_DELAY);
}
}
}
uint8_t uart_buf[100];
// 任务2: 接收串口的消息 => 根据接收的消息翻转LED灯
void task2(void *pvParameters)
{
while (1)
{
HAL_UARTEx_ReceiveToIdle_IT(&huart1, (uint8_t *)uart_buf, 1000);
// task任务等待信号量释放表示串口接收到数据
xSemaphoreTake(xSemaphore, portMAX_DELAY);
// 已经获取到数据
// 串口接收到的数据
uint8_t *uart_data = uart_buf;
// 串口接收到的数据长度
xQueueSend(xQueue2, &uart_data, portMAX_DELAY);
}
}
// 任务3: 接收按键信息 => 翻转LED灯
void key_receive(void *pvParameters)
{
while (1)
{
// 获取队列信息
Key_Type key;
xQueueReceive(xQueue1, &key, portMAX_DELAY);
if (key == KEY1)
{
// 翻转LED灯
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
}
else if (key == KEY2)
{
// 翻转LED灯
Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
}
else if (key == KEY3)
{
// 翻转LED灯
Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
}
else if (key == KEY4)
{
// 翻转LED灯
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
}
}
}
// 任务4: 读取串口队列的消息 => 根据接收的消息翻转LED灯
void uart_receive(void *pvParameters)
{
while (1)
{
// 获取队列信息
uint8_t *uart_data;
xQueueReceive(xQueue2, &uart_data, portMAX_DELAY);
// 根据字符串内容 翻转LED灯
if (strstr((char *)uart_data, "LED1") != NULL)
{
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
}
else if (strstr((char *)uart_data, "LED2") != NULL)
{
Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
}
else if (strstr((char *)uart_data, "LED3") != NULL)
{
Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
}
else if (strstr((char *)uart_data, "LEDALL") != NULL)
{
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
}
}
}
// 中断处理函数释放二值信号量
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if (huart->Instance == USART1)
{
// 释放信号量
xSemaphoreGiveFromISR(xSemaphore, NULL);
}
}
使用队列集
相当于queue_set_receive方法合并了key_receive和uart_receive方法
#include "FreeRTOS_demo.h"
void delay_ms(uint32_t ms)
{
uint32_t count = ms * 72000 / 9;
while (count--)
{
__NOP();
}
}
// 任务1: 接收按键信息 => 翻转LED灯
void task1(void *pvParameters);
#define TASK1_STACK_SIZE 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
// 任务2: 接收串口的消息 => 根据接收的消息翻转LED灯
void task2(void *pvParameters);
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 2
TaskHandle_t task2_handle;
// 不使用队列集
// 两个单独的接收任务
void key_receive(void *pvParameters);
#define KEY_STACK_SIZE 128
#define KEY_PRIORITY 2
TaskHandle_t key_handle;
void uart_receive(void *pvParameters);
#define UART_STACK_SIZE 128
#define UART_PRIORITY 2
TaskHandle_t uart_handle;
// 队列集接收任务
void queue_set_receive(void *pvParameters);
#define QUEUE_SET_STACK_SIZE 128
#define QUEUE_SET_PRIORITY 2
TaskHandle_t queue_set_handle;
// 打印任务
void print_task(void *pvParameters);
#define PRINT_STACK_SIZE 128
#define PRINT_PRIORITY 2
TaskHandle_t print_handle;
// 队列保存信息
// 写入的按键信息 KeyType
QueueHandle_t xQueue1;
// 读取的串口信息 uint8_t *
QueueHandle_t xQueue2;
// 队列集句柄
QueueSetHandle_t xQueueSet;
// 二值信号量实现串口最小资源占用
SemaphoreHandle_t xSemaphore;
void FreeRTOS_Start(void)
{
// 创建二值信号量
xSemaphore = xSemaphoreCreateBinary();
// 创建两个队列
xQueue1 = xQueueCreate(3, sizeof(Key_Type));
xQueue2 = xQueueCreate(3, sizeof(uint8_t *));
// 创建队列集
xQueueSet = xQueueCreateSet(2);
// 将队列添加到队列集中
xQueueAddToSet(xQueue1, xQueueSet);
xQueueAddToSet(xQueue2, xQueueSet);
// 创建任务
xTaskCreate(task1, "task1", TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle);
xTaskCreate(task2, "task2", TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle);
// xTaskCreate(key_receive, "key", KEY_STACK_SIZE, NULL, KEY_PRIORITY, &key_handle);
// xTaskCreate(uart_receive, "uart", UART_STACK_SIZE, NULL, UART_PRIORITY, &uart_handle);
// 创建队列集接收任务
xTaskCreate(queue_set_receive, "queue_set", QUEUE_SET_STACK_SIZE, NULL, QUEUE_SET_PRIORITY, &queue_set_handle);
// 创建打印任务
xTaskCreate(print_task, "print", PRINT_STACK_SIZE, NULL, PRINT_PRIORITY, &print_handle);
// 最后一行代码一定是启动调度器
// 调度器会启动一个空闲任务 没事干就去空闲任务
vTaskStartScheduler();
}
// 任务1: 接收按键信息 => 翻转LED灯
void task1(void *pvParameters)
{
while (1)
{
Key_Type key;
key = Int_Key_Scan();
if (key != KEY_NONE)
{
xQueueSend(xQueue1, &key, portMAX_DELAY);
}
}
}
uint8_t uart_buf[100];
// 任务2: 接收串口的消息 => 根据接收的消息翻转LED灯
void task2(void *pvParameters)
{
while (1)
{
HAL_UARTEx_ReceiveToIdle_IT(&huart1, (uint8_t *)uart_buf, 1000);
// task任务等待信号量释放表示串口接收到数据
xSemaphoreTake(xSemaphore, portMAX_DELAY);
// 已经获取到数据
// 串口接收到的数据
uint8_t *uart_data = uart_buf;
// 串口接收到的数据长度
xQueueSend(xQueue2, &uart_data, portMAX_DELAY);
}
}
// 任务3: 接收按键信息 => 翻转LED灯
void key_receive(void *pvParameters)
{
while (1)
{
// 获取队列信息
Key_Type key;
xQueueReceive(xQueue1, &key, portMAX_DELAY);
if (key == KEY1)
{
// 翻转LED灯
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
}
else if (key == KEY2)
{
// 翻转LED灯
Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
}
else if (key == KEY3)
{
// 翻转LED灯
Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
}
else if (key == KEY4)
{
// 翻转LED灯
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
}
}
}
// 任务4: 读取串口队列的消息 => 根据接收的消息翻转LED灯
void uart_receive(void *pvParameters)
{
while (1)
{
// 获取队列信息
uint8_t *uart_data;
xQueueReceive(xQueue2, &uart_data, portMAX_DELAY);
// 根据字符串内容 翻转LED灯
if (strstr((char *)uart_data, "LED1") != NULL)
{
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
}
else if (strstr((char *)uart_data, "LED2") != NULL)
{
Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
}
else if (strstr((char *)uart_data, "LED3") != NULL)
{
Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
}
else if (strstr((char *)uart_data, "LEDALL") != NULL)
{
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
}
}
}
void queue_set_receive(void *pvParameters)
{
while (1)
{
// 获取队列集接收信息
QueueSetMemberHandle_t mem_handle = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);
if (mem_handle == xQueue1)
{
// 按键任务接收到消息
// 获取队列信息
Key_Type key;
xQueueReceive(xQueue1, &key, portMAX_DELAY);
if (key == KEY1)
{
// 翻转LED灯
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
}
else if (key == KEY2)
{
// 翻转LED灯
Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
}
else if (key == KEY3)
{
// 翻转LED灯
Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
}
else if (key == KEY4)
{
// 翻转LED灯
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
}
}
else if (mem_handle == xQueue2)
{
// 获取队列信息
uint8_t *uart_data;
xQueueReceive(xQueue2, &uart_data, portMAX_DELAY);
// 根据字符串内容 翻转LED灯
if (strstr((char *)uart_data, "LED1") != NULL)
{
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
}
else if (strstr((char *)uart_data, "LED2") != NULL)
{
Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
}
else if (strstr((char *)uart_data, "LED3") != NULL)
{
Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
}
else if (strstr((char *)uart_data, "LEDALL") != NULL)
{
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
}
}
}
}
uint8_t runtime_buff[128];
// 打印任务
void print_task(void *pvParameters)
{
while (1)
{
vTaskGetRunTimeStats((char *)runtime_buff);
printf("%s\r\n", runtime_buff);
vTaskDelay(1000);
}
}
// 中断处理函数释放二值信号量
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if (huart->Instance == USART1)
{
// 释放信号量
xSemaphoreGiveFromISR(xSemaphore, NULL);
}
}
4 事件标志组
管理多个条件 最多24个
多个事件的组合(多个单独的信号量)
事件之间没有先后顺序





引入event_group.h
xEventGroupCreate()
xEventGroupSetBits()
xEventGroupWaitBits()
xEventGroupClearBits()
代码
#include "FreeRTOS_demo.h"
void delay_ms(uint32_t ms)
{
uint32_t count = ms * 72000 / 9;
while (count--)
{
__NOP();
}
}
// 按键任务
void key_task(void *pvParameters);
#define KEY_TASK_STACK_SIZE 128
#define KEY_TASK_PRIORITY 2
TaskHandle_t key_task_handle;
// led闪烁任务
void led_task(void *pvParameters);
#define LED_TASK_STACK_SIZE 128
#define LED_TASK_PRIORITY 3
TaskHandle_t led_task_handle;
EventGroupHandle_t event_group;
void FreeRTOS_Start(void)
{
// 创建事件标志组
event_group = xEventGroupCreate();
// 创建任务
xTaskCreate(key_task, "key_task", KEY_TASK_STACK_SIZE, NULL, KEY_TASK_PRIORITY, &key_task_handle);
xTaskCreate(led_task, "led_task", LED_TASK_STACK_SIZE, NULL, LED_TASK_PRIORITY, &led_task_handle);
// 最后一行代码一定是启动调度器
// 调度器会启动一个空闲任务 没事干就去空闲任务
vTaskStartScheduler();
}
uint8_t key_value = 0;
void key_task(void *pvParameters)
{
// 按下任意一个按键都不会触发LED闪烁 => 必须4个按键全部按一遍才行
while (1)
{
// Key_Type key = Int_Key_Scan();
// if (key == KEY1)
// {
// key_value |= 1;
// }
// else if (key == KEY2)
// {
// key_value |= 2;
// }
// else if (key == KEY3)
// {
// key_value |= 4;
// }
// else if (key == KEY4)
// {
// key_value |= 8;
// }
Key_Type key = Int_Key_Scan();
// 第二个参数表示需要 或的值
xEventGroupSetBits(event_group, 0x01 << ((uint8_t)key));
}
}
void led_task(void *pvParameters)
{
while (1)
{
// if (key_value == 15)
// {
// // 满足最终的条件
// for (uint8_t i = 0; i < 4; i++)
// {
// Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
// delay_ms(500);
// }
// key_value = 0;
// }
// 等待事件标志组完成条件
xEventGroupWaitBits(event_group,15,pdTRUE,pdTRUE,portMAX_DELAY);
for (uint8_t i = 0; i < 4; i++)
{
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
// 延时500ms => 当前任务运行500ms => 两个任务的时间片轮询 => 1s
delay_ms(500);
}
// 如果需要手动清0 => clear_bits
xEventGroupClearBits(event_group,15);
}
}
高级篇
直接任务通知
4种任务同步方式整合
state 二值信号量
value 事件标志组
队列集(带Indexed) 不能完全代替队列集,数组中任务单独判断,不会影响数组中其他任务

一次传4个字节(地址),可以传地址(没法实现线程安全)







xTaskNotifyGive()
ulTaskNotifyTake()
xTaskNotify()
xTaskNotifyWait()
代码
案例1
#include "FreeRTOS_demo.h"
void delay_ms(uint32_t ms)
{
uint32_t count = ms * 72000 / 9;
while (count--)
{
__NOP();
}
}
// 最小CPU占用来实现串口接收数据
void receive_task(void *args);
#define RECEIVE_TASK_STACK_SIZE 128
#define RECEIVE_TASK_PRIORITY 2
TaskHandle_t receive_task_handle;
// 打印任务
void print_task(void *args);
#define PRINT_TASK_STACK_SIZE 128
#define PRINT_TASK_PRIORITY 3
TaskHandle_t print_task_handle;
void FreeRTOS_Start(void)
{
xTaskCreate(receive_task, "receive_task", RECEIVE_TASK_STACK_SIZE, NULL, RECEIVE_TASK_PRIORITY, &receive_task_handle);
xTaskCreate(print_task, "print_task", PRINT_TASK_STACK_SIZE, NULL, PRINT_TASK_PRIORITY, &print_task_handle);
// 最后一行代码一定是启动调度器
// 调度器会启动一个空闲任务 没事干就去空闲任务
vTaskStartScheduler();
}
uint8_t receive_buff[128];
uint16_t receive_len;
void receive_task(void *args)
{
while (1)
{
// 开启串口的中断
HAL_UARTEx_ReceiveToIdle_IT(&huart1, receive_buff, 128);
// 等待任务通知 => 永远是使用阻塞任务的通知组
// 1. true => 表示调用之后清0
// 2. false => 表示调用之后减1
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// 接收到数据
printf("%s", receive_buff);
// 清空缓存
memset(receive_buff, 0, 128);
}
}
uint8_t time_buff[128];
void print_task(void *args)
{
while (1)
{
vTaskGetRunTimeStats((char *)time_buff);
printf("\n%s\r\n", time_buff);
vTaskDelay(1000);
}
}
// 中断回调函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if (huart->Instance == USART1)
{
receive_len = Size;
// 发送任务通知receive_task
vTaskNotifyGiveFromISR(receive_task_handle, NULL);
}
}
案例2
#include "FreeRTOS_demo.h"
void delay_ms(uint32_t ms)
{
uint32_t count = ms * 72000 / 9;
while (count--)
{
__NOP();
}
}
// 按键任务
void key_task(void *pvParameters);
#define KEY_TASK_STACK_SIZE 128
#define KEY_TASK_PRIORITY 2
TaskHandle_t key_task_handle;
// led闪烁任务
void led_task(void *pvParameters);
#define LED_TASK_STACK_SIZE 128
#define LED_TASK_PRIORITY 2
TaskHandle_t led_task_handle;
void FreeRTOS_Start(void)
{
// 创建任务
xTaskCreate(key_task, "key_task", KEY_TASK_STACK_SIZE, NULL, KEY_TASK_PRIORITY, &key_task_handle);
xTaskCreate(led_task, "led_task", LED_TASK_STACK_SIZE, NULL, LED_TASK_PRIORITY, &led_task_handle);
// 最后一行代码一定是启动调度器
// 调度器会启动一个空闲任务 没事干就去空闲任务
vTaskStartScheduler();
}
void key_task(void *pvParameters)
{
// 按下任意一个按键都不会触发LED闪烁 => 必须4个按键全部按一遍才行
while (1)
{
// 根据按下的按键不同 => 发送不同位的按键通知
Key_Type key = Int_Key_Scan();
// 通常使用setBits设置对应位表示事件标志组 或者 eSetValueWithOverwrite可以传递一个32位数据
if (key != KEY_NONE)
{
xTaskNotify(led_task_handle, 1 << ((uint8_t)key), eSetBits);
}
}
}
void led_task(void *pvParameters)
{
uint32_t notify_value;
ulTaskNotifyValueClear(led_task_handle, 0xffff);
while (1)
{
// 每次接收到通知 都会退出阻塞
xTaskNotifyWait(0, 0, ¬ify_value, portMAX_DELAY);
printf("notify_value = %d\r\n", notify_value);
if (notify_value == 15)
{
// 满足条件
for (uint8_t i = 0; i < 4; i++)
{
Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
delay_ms(500);
}
ulTaskNotifyValueClear(led_task_handle, 15);
}
}
}
软件定时器
依赖系统滴答定时器SysTick




xTimerCreate()
xTimerStart()
xTimerStop()
xTimerChangePeriod()
低功耗模式



configUSE_TICKLESS_IDLE=1
低功耗使能
钩子函数:
在系统进入低功耗模式前的函数 configPRE_SLEEP_PROCESSING(x)
系统退出低功耗模式后执行的函数 configPOST_SLEEP_PROCESSING(x)
最多睡眠233ms 2^24/72000
内存管理
xPortGetFreeHeapSize()
当前剩余的heap资源
xPortGetMinimumEverFreeHeapSize()
历史剩余的最小heap资源值


heap_5
外接flash内存

最大堆空间 17*1024=17,408
tcb 120字节

总结
sysTick
pendsv
svc
上下文 保存在TCB块
创建任务
进/出临界区
执行时间尽量短
SysTick中断被禁用
挂起/恢复调度器
SysTick中断正常使用
延时函数
vTaskDelay
xTaskDelayUntil 优先级高
代码执行时间大于 xTaskDelayUntil 时间,会影响xTaskDelayUntil 时间
printf费时
调度 时间
任务之间交互
消息队列
直接任务通知
state信号量
value队列传递数据

软件定时器(硬件定时器不够用)
低功耗模式
空闲
空闲时间>2ms
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)