第一节、任务的创建方式:
1. 创建第一个多任务程序:点亮彩色RGB灯:
访问亚马逊官方网站:FreeRTOS - Market leading RTOS (Real Time Operating System) for embedded systems with Internet of Things extensions 下载FreeRTOS参考手册。用于查询所使用的API.
或使用freeRTOS官方在线中文手册:RTOS - Free professionally developed and robust real time operating system for small embedded systems development (freertos.org)
1. 看电路图:
2.写驱动:
color_led_driver.c:
#include "color_led_driver.h"
#include "main.h"
//全采led灯的驱动:
void color_led_set(uint8_t red, uint8_t green, uint8_t blue)
{
//红灯:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, (GPIO_PinState)!red);
//绿灯:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, (GPIO_PinState)!green);
//蓝灯:
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, (GPIO_PinState)!blue);
}
3.应用测试:
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
for (;;)
{
// led_on();
color_led_set(1, 0, 0);
HAL_Delay(1000);
// led_off();
color_led_set(0, 1, 0);
HAL_Delay(1000);
color_led_set(0, 0, 1);
HAL_Delay(1000);
}
/* USER CODE END StartDefaultTask */
}
2.创建新任务:
1.动态创建新任务:
task. h
BaseType_t xTaskCreate(TaskFunction_t pvTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE uxStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask);
功能:动态创建一个新任务并将其添加到准备运行的任务列表中。
pvTaskCode 指向任务入口函数的指针,指向任务入口函数的指针(即 实现任务的函数名称,请参阅如下示例)。
任务通常 以 无限循环的形式实现;实现任务的函数决不能试图返回 或退出。 但是,任务可以 自我删除。
pcName 任务的描述性名称
uxStackDepth 要分配用于 任务堆栈的 堆栈。
pvParameters 作为参数传递给创建的任务的一个值
uxPriority 创建任务执行的优先级。
pxCreatedTask 用于将句柄传递至由xTaskCreate()函数创建的任务 。pxCreatedTask是可选的,可设置为 NULL。
返回值:
如果任务创建成功,则返回 pdPASS。 否则 返回 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY。
第一个多任务并发执行的程序:
/* USER CODE END Includes */
TaskHandle_t color_led_task;
//新的color_led_task任务执行的回调函数:
void color_led_function(void *arg)
{
while (true)
{
/* color_led的应用 */
//红灯亮:
color_led_set(1, 0, 0);
HAL_Delay(1000);
//绿灯亮:
color_led_set(0, 1, 0);
HAL_Delay(1000);
//蓝灯亮:
color_led_set(0, 0, 1);
HAL_Delay(1000);
//黄灯亮:
color_led_set(1, 1, 0);
HAL_Delay(1000);
//紫灯亮:
color_led_set(1, 0, 1);
HAL_Delay(1000);
//粉灯亮
color_led_set(0, 1, 1);
HAL_Delay(1000);
}
}
/* USER CODE END Variables */
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t)osPriorityNormal,
};
void StartDefaultTask(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void)
{
/* USER CODE BEGIN Init */
xTaskCreate(color_led_function, "color_led_app", 256, NULL,osPriorityNormal,&color_led_task);
/* USER CODE END Init */
/* Create the thread(s) */
/* creation of defaultTask */
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
}
/* USER CODE BEGIN Header_StartDefaultTask */
/**
* @brief Function implementing the defaultTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
for (;;)
{
led_on();
HAL_Delay(1000);
led_off();
HAL_Delay(1000);
}
/* USER CODE END StartDefaultTask */
}
2.静态创建新任务:xTaskCreateStatic:
task. h
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer );
功能:静态创建一个新任务并将其添加到准备运行的任务列表中。xTaskCreateStatic() 创建任务,
则 RAM 由应用程序编写者提供,这会产生更多的参数,但允许在编译时静态分配 RAM。
参数:
pxTaskCode 指向任务入口函数的指针(即实现任务的函数名称,请参阅如下示例)。
任务通常以无限循环的形式实现;实现任务的函数决不能尝试返回或退出。
但是,任务可以自行删除。
pcName 任务的描述性名称。此参数主要用于方便调试,但也可用于获取任务句柄。
任务名称的最大长度由 FreeRTOSConfig.h 中的 configMAX_TASK_NAME_LEN 定义。
ulStackDepth puxStackBuffer 参数用于将 StackType_t 变量数组传递给 xTaskCreateStatic()。必须将 ulStackDepth 设置为数组中的索引数。
请参阅常见问题:堆栈应有多大?
pvParameters 传递给已创建任务的参数值。
如果将 pvParameters 设置为变量的地址,则在创建的任务执行时变量必须仍然存在,因此传递堆栈变量的地址无效。
uxPriority 所创建任务执行的优先级。
包含 MPU支持的系统可选择通过在 uxPriority 中设置位 portPRIVILEGE_BIT,以特权(系统)模式创建任务。
例如,要创建优先级为 2 的特权任务,请将 uxPriority 设置为 (2 | portPRIVILEGE_BIT)。
断言优先级低于 configMAX_priority。
如果未定义 configASSERT,则优先级会被静默限制为 (configMAX_PRIORITIES - 1)。
puxStackBuffer 必须指向至少具有 ulStackDepth 索引的 StackType_t 数组(请参阅上面的 ulStackDepth 参数),该数组用作任务的堆栈,因此必须是永久性的(而不是在函数的堆栈上声明)。
pxTaskBuffer 必须指向 StaticTask_t 类型的变量。该变量用于保存新任务的数据结构体 (TCB) ,因此必须是持久的(而不是在函数的堆栈中声明)。
返回值:
如果 puxStackBuffer 和 pxTaskBuffer 均不为 NULL,则创建任务,并返回任务的句柄。如果 puxStackBuffer 或 pxTaskBuffer 为 NULL,则不会创建任务,并返回 NULL。
2.1构建蜂鸣器与光敏模块的驱动:
1. beep模块电路图看电路图写驱动:
2.写驱动:
#include "beep.h"
#include "main.h"
//开启beep
void beep_start(void)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);
}
//ͣ停止beep
void beep_stop(void)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);
}
3.电敏模块电路图
驱动代码:
#include "ldr_driver.h"
//获取环境光是否是亮还是暗
GPIO_PinState isDark()
{
return HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11);
}
2.2静态创建任务代码及应用程序分离实例演示:
freeRTOS.c:
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* USER CODE BEGIN Includes */
#include "ldr_driver.h"
#include "beep.h"
#include "beep_and_ldr_app.h"
#include "color_led_app.h"
#include <stdbool.h>
#include "led_driver.h"
/* USER CODE BEGIN Variables */
extern struct Beep_LDR_Task my_beep;
extern struct Color_LED_Task color_task;
/* USER CODE END Variables */
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
//默认任务回调的函数声明:定义在下面。
void StartDefaultTask(void *argument);
//freeRTOS初始化函数声明:定义在下面
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
void MX_FREERTOS_Init(void)
{
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* 动态创建蜂鸣器测试任务, ... */
xTaskCreate(beep_and_ldr_test, "beep_test_app",256,NULL,osPriorityNormal,&my_beep.beep_ldr_task);
/* 静态创建小彩灯闪烁任务*/
color_task.task = xTaskCreateStatic(color_led_test,"color_led_app",
SIZE,NULL,osPriorityNormal,color_task.stackBuf,&color_task.TCB);
}
//默认任务的回调函数:
void StartDefaultTask(void *argument)
{
while (true)
{
osDelay(10000);
}
}
beep_and_ldr_app.c:应用程序:
#include "beep_and_ldr_app.h"
struct Beep_LDR_Task my_beep = {0};
void beep_and_ldr_test(void* arg)
{
//这就是测试应用程序:
for (;;)
{
if (isDark())
{
beep_start();
}
else
{
beep_stop();
}
}
}
color_led_app.c:应用程序:
#include "color_led_app.h"
#include <stdbool.h>
struct Color_LED_Task color_task;
void color_led_test(void* arg)
{
while (true)
{
/* color_led的应用 */
// 红灯亮:
color_led_set(1, 0, 0);
HAL_Delay(1000);
// 绿灯亮:
color_led_set(0, 1, 0);
HAL_Delay(1000);
// 蓝灯亮:
color_led_set(0, 0, 1);
HAL_Delay(1000);
// 黄灯亮:
color_led_set(1, 1, 0);
HAL_Delay(1000);
// 紫灯亮:
color_led_set(1, 0, 1);
HAL_Delay(1000);
// 粉灯亮
color_led_set(0, 1, 1);
HAL_Delay(1000);
}
}
工程结构如图所示
:
第二节、任务控制:
任务的状态如图所示:
阻塞的意义:(在任务空闲时或等待资源时应放弃CPU资源),提高CPU利用率。
1.任务在空闲时应放弃CPU资源进入阻塞,当时间到达时主动的唤醒的方式:vTaskDelay与vTaskDelayUntil:
1. void vTaskDelay( const TickType_t xTicksToDelay );
按给定的 tick 数延迟任务,任务进入阻塞状态,即在延迟时间内不参与任务的调度。
任务保持阻塞的实际时间取决于 tick 频率。
参数:
xTicksToDelay 调用任务应阻塞的 tick 周期数。
2. void vTaskDelayUntil( TickType_t *pxPreviousWakeTime,
const TickType_t xTimeIncrement );
参数:
pxPreviousWakeTime:指向一个变量的指针,该变量 用于保存任务最后一次解除阻塞的时间。
该变量在第一次使用前 必须用当前时间进行初始化。
在这之后,该变量 会在 vTaskDelayUntil() 中自动更新。
xTimeIncrement:周期时间段。
两者的区别:后者比前者更精确,使用的绝对时间片段,不非前者相对时间片段。
注意:任务的阻塞操作不可以在中断上文中执行。
vTaskDelay代码示例:
#include "color_led_app.h"
#include <stdbool.h>
struct Color_LED_Task color_task;
void color_led_test(void *arg)
{
while (true)
{
/* color_led的应用 */
// 红灯亮:
color_led_set(1, 0, 0);
vTaskDelay(1000);
// 绿灯亮:
color_led_set(0, 1, 0);
vTaskDelay(1000);
// 蓝灯亮:
color_led_set(0, 0, 1);
vTaskDelay(1000);
// 黄灯亮:
color_led_set(1, 1, 0);
vTaskDelay(1000);
// 紫灯亮:
color_led_set(1, 0, 1);
vTaskDelay(1000);
// 粉灯亮
color_led_set(0, 1, 1);
vvTaskDelay(1000);
}
}
vTaskDelayUntil代码示例:
#include "color_led_app.h"
#include <stdbool.h>
struct Color_LED_Task color_task;
void color_led_test(void *arg)
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 1000;
// Initialise the xLastWakeTime variable with the current time.
xLastWakeTime = xTaskGetTickCount();
while (true)
{
/* color_led的应用 */
// 红灯亮:
color_led_set(1, 0, 0);
vTaskDelayUntil(&xLastWakeTime, xFrequency);
// 绿灯亮:
color_led_set(0, 1, 0);
vTaskDelayUntil(&xLastWakeTime, xFrequency);
// 蓝灯亮:
color_led_set(0, 0, 1);
vTaskDelayUntil(&xLastWakeTime, xFrequency);
// 黄灯亮:
color_led_set(1, 1, 0);
vTaskDelayUntil(&xLastWakeTime, xFrequency);
// 紫灯亮:
color_led_set(1, 0, 1);
vTaskDelayUntil(&xLastWakeTime, xFrequency);
// 粉灯亮
color_led_set(0, 1, 1);
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
2.任务放弃CPU资源,等待其它任务被动的唤醒的方式:
2.1任务暂停,并进入阻塞状态:vTaskSuspend:
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
功能:暂停任意任务。无论任务优先级如何,任务被暂停后将永远无法获取任何微控制器处理时间。
参数:
xTaskToSuspend 指定被挂起的任务的句柄。传递空句柄将导致调用任务被暂停。
2.2任务被动唤醒:(被其它任务唤醒)vTaskResume:
void vTaskResume( TaskHandle_t xTaskToResume );
功能:恢复已挂起的任务。
参数:
xTaskToResume 要恢复的任务句柄。
注意:任务的阻塞操作不可以在中断上文中执行。
代码示例:
beep_and_ldr_app.c:
#include "beep_and_ldr_app.h"
#include "color_led_app.h"
struct Beep_LDR_Task my_beep = {0};
extern struct Color_LED_Task color_task;
void beep_and_ldr_test(void* arg)
{
//这就是测试应用程序:
for (;;)
{
if (isDark())
{
//天暗时,蜂鸣器响,彩灯不闪泺
beep_start();
//挂起color_task任务
vTaskSuspend(color_task.task);
}
else
{
//天亮时,蜂鸣器不响,彩灯闪泺
beep_stop();
//唤醒color_task任务:
vTaskResume(color_task.task);
vTaskDelay(2000);
}
}
}
3.任务结束:vTaskDelete(任务句柄)
void vTaskDelete( TaskHandle_t xTask );
功能:此函数的作用为从 RTOS 内核管理中移除任务。
被删除的任务将从所有的就绪、阻塞、挂起和事件的列表中移除。
参数:
xTask 待删除的任务的句柄。传递 NULL 将导致当前调用任务被删除。
注意:空闲任务负责从已删除任务中释放 RTOS 内核分配的内存。因此,重要的是,如果您的应用程序调用了 vTaskDelete (),空闲任务不会失去微控制器处理时间。任务代码分配的内存不会自动释放,并且应在删除任务之前释放。
代码示例:
#include "cmsis_os.h"
#include "beep_and_ldr_app.h"
#include "color_led_app.h"
struct Beep_LDR_Task my_beep = {0};
extern struct Color_LED_Task color_task;
extern osThreadId_t defaultTaskHandle;
void beep_and_ldr_test(void* arg)
{
//这就是测试应用程序:
for (;;)
{
if (isDark())
{
//天暗时,蜂鸣器响,彩灯不闪泺
beep_start();
vTaskSuspend(color_task.task);
//结束F103开发板上的光源灯闪烁的任务:
//结束任务的方式:
if(defaultTaskHandle != NULL)
{
vTaskDelete(defaultTaskHandle);
defaultTaskHandle = NULL;
}
}
else
{
//天亮时,蜂鸣器不响,彩灯闪泺
beep_stop();
vTaskResume(color_task.task);
vTaskDelay(2000);
}
}
}
4.理解RTOS中的任务调度:
就绪列表中的同等优先级的任务以链表连接,为了更好理解,图解以二维数组表示。
OS任务调用策略:
优先级高的先执行,在没有更高优先组任务时,低优先级任务再执行。
同等优先级的任务会轮转(轮流、时间片轮转是一个意思)执行。
为保障退出的任务资源可以让空闲任务回收资源,所以必须保障0优先级的任务可以得到CPU的时间片。
所以在编写程序时,如任务空闲或等待资源时应让任务进入阻塞状态,以便idle任务可以获取时间片,进行资源的清理。
第三节、任务间的通信方式xQueue(队列或消息队列):
1. 为何使用队列进行任务间的通信,而非全局变量呢?
与Linux进程间通信方式类似,FreeRTOS中的通知也采用的队列方式,其本质与Linux一样都是使用的一种环形队列。不过这个数据结构不需要我们去构建,已经封装好了。我们只需要操作上层的接口实现任务间的数据传递即可。(当然任务间在FreeRTOS中也可以使用全局变量传递,但全局变量在多任务中可能会产生竞态的问题)。FreeRTOS中的队列是用于任务间通信和同步的重要机制。它们提供了线程安全的数据传输方式,并支持阻塞和非阻塞的发送和接收操作。通过使用队列,任务和中断服务程序可以高效、安全地共享数据。
2.xQueue队列的基本功能介绍:
- FIFO(先进先出):队列遵循先进先出的原则,即最早进入队列的项目最早被移除。
- 多任务通信:队列可以在多个任务之间共享数据,一个任务可以向队列发送数据,另一个任务可以从队列接收数据。
- 线程安全:FreeRTOS提供的队列机制是线程安全的,确保多任务访问队列时不会出现竞争条件或数据损坏。
- 队列默认使用拷贝赋值方式传递数据,而不是引用。
3.xQueue的创建:xQueueCreate:
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
UBaseType_t uxItemSize );
功能:动态创建一个队列,并返回队列的句柄。
参数:uxQueueLength 队列可以容纳的最大项目数。
参数:uxItemSize 存储队列中的每个数据项所需的大小(以字节为单位)
返回值:
如果队列创建成功,则返回所创建队列的句柄。
如果创建队列所需的内存无法 分配 ,则返回 NULL。
与创建任务类似也提供了相应的静态创建队列的方法:
xQueueCreateStatic() 创建队列,相应的方法介绍请查看FreeRTOS在线手册。
4.向队列中发数据:xQueueSend:
BaseType_t xQueueSend(QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait);
功能:在队列中发布项目。该项目按副本排队,而不是按引用排队。
不得从中断服务程序调用此函数。请参阅 xQueueSendFromISR() 以获取 可用于 ISR 的替代方案。
参数:xQueue 队列的句柄,数据项将发布到此队列。
参数:pvItemToQueue 指向待入队数据项的指针
参数:xTicksToWait 如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。
等待的最大时间可以使用宏:protMAX_DELAY,也可以使用-1或者~0
如果队列已满,并且 xTicksToWait 设置为0 ,调用将立即返回。
返回:如果成功发布项目,则返回 pdTRUE,否则返回 errQUEUE_FULL。
5.从队列中接收数据后删除:xQueueReceive, 只读取不删除数据:xQueuePeek():
BaseType_t xQueueReceive(QueueHandle_t xQueue,
void *pvBuffer,
TickType_t xTicksToWait);
功能:从队列中接收项目。该项目通过复制接收,因此必须提供足够大小的缓冲区。
中断服务程序中不得使用此函数。请参阅 xQueueReceiveFromISR() 了解可以选择的替代方案
参数:xQueue 要从中接收项目的队列的句柄。pvBuffer 指向缓冲区的指针,接收到的项目将被复制到这个缓冲区。
参数:xTicksToWait 如果在调用时队列为空,则任务应阻塞等待项目接收的最长时间。
返回:
如果从队列成功接收到项目,返回 pdTRUE,否则返回 pdFALSE。
代码实例:
freeRTOS.c:在任务前创建队列:
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ldr_driver.h"
#include "beep.h"
#include "beep_and_ldr_app.h"
#include "color_led_app.h"
#include <stdbool.h>
#include "led_driver.h"
#include "queue.h"
/* USER CODE BEGIN Variables */
extern struct Beep_LDR_Task my_beep;
extern struct Color_LED_Task color_task;
/* USER CODE END Variables */
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
QueueHandle_t glink_queue = NULL;
void StartDefaultTask(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues,动态创建消息队列 ... */
glink_queue = xQueueCreate(1,sizeof(int));
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* creation of defaultTask */
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* 动态创建蜂鸣器测试任务, ... */
xTaskCreate(beep_and_ldr_test, "beep_test_app",256,NULL,osPriorityNormal,&my_beep.beep_ldr_task);
/* 静态创建小彩灯闪烁任务*/
color_task.task = xTaskCreateStatic(color_led_test,"color_led_app",
SIZE,NULL,osPriorityNormal,color_task.stackBuf,&color_task.TCB);
}
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
while (true)
{
led_on();
osDelay(1000);
led_off();
osDelay(1000);
}
/* USER CODE END StartDefaultTask */
}
任务A:生产者:
#include "cmsis_os.h"
#include "beep_and_ldr_app.h"
#include "color_led_app.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
struct Beep_LDR_Task my_beep = {0};
extern struct Color_LED_Task color_task;
extern osThreadId_t defaultTaskHandle;
extern QueueHandle_t glink_queue;
void beep_and_ldr_test(void* arg)
{
int data = 0;
//这就是测试应用程序:
for (;;)
{
if (isDark())
{
data = 100;
//如果天黑,我们就通过消息队列发送数,放入数据100,让小彩灯任务闪烁红灯:
//向队列中发出数据
xQueueSend(glink_queue,&data,1000);
}
else
{
data = 200;
//天亮时,放入消息队列200,让小彩灯的蓝灯闪烁。
//向队列中发出数据:
xQueueSend(glink_queue,&data,1000);
}
}
}
任务B:消费者:
#include "color_led_app.h"
#include <stdbool.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
extern QueueHandle_t glink_queue;
struct Color_LED_Task color_task;
void color_led_test(void *arg)
{
int data = 0;
while (true)
{
/* color_led的应用 */
// 红灯闪烁:
//接收数据,并做出判断:
xQueueReceive(glink_queue,&data,1000);
if(data == 100)
{
color_led_set(1,0,0);
vTaskDelay(1000);
color_led_set(0,0,0);
vTaskDelay(1000);
}
if(data == 200)
//蓝灯闪烁:
{
color_led_set(0,0,1);
vTaskDelay(1000);
color_led_set(0,0,0);
vTaskDelay(1000);
}
}
}
第四节、任务同步与互斥之信号量Semaphore:
信号量,顾名思义就是 传递信号的量。信号量也是一种队列,只不过有时候我们只需要传递一个状态,并不需要传递具体信息,所以使用信号量更简洁,方便。
信号量和队列一样也是一种实现任务间通信的机制,可以实现任务之间的同步或临界资源的互斥访问。
所谓的同步,我在队列中已经介绍过,类似于生产与消费的关系,只有生产者生产数据之后,消费者才能操作数据。但在FreeRTOS中,数据的采集和处理往往会分成多个程序块,所以就需要我们协调他们的执行顺序。协调执行顺序就是同步。目的是让多个程序块严格按照一定的顺序执行,保证程序执行顺序正确。
说完同步,我们再说一下互斥:互斥就是同一个资源,防止被多个程序同时访问。比如一个LED小灯,同一个时刻只能让一个任务去访问,如果多个任务访问,可能会出现状态混乱。同步与互斥是类似的概念,只是侧重点有点小小的区别:同步侧重与协调任务的顺序。而互斥侧重与共享资源的保护。
比如说,任务A在没有获取数据前,B任务想要使用数据,那么就是阻塞等待。这就是同步。
比如说,任务A访问共享资源时,B任务也想访问,但是访问时会被拒绝,只能阻塞等待或退出,这就是互斥。
所以说同步与互斥是类似的概念,大家理解即可,不用太死扣字眼。
FreeRTOS信号量有以下几种类型:
1. 计数信号量
2.二值信号量
3.互斥信号量
4.递归互斥信号量
1. 计数信号量与二值信号量:
计数信号量就是:计数值没有限制的信号量
二值信号量:就是只有0与1为计数值的信号量。
1.1代码演示:
freeRTOS.c:创建二值信号量:
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ldr_driver.h"
#include "beep.h"
#include "beep_and_ldr_app.h"
#include "color_led_app.h"
#include <stdbool.h>
#include "led_driver.h"
#include "queue.h"
#include "semphr.h"
/* USER CODE END Includes */
extern struct Beep_LDR_Task my_beep;
extern struct Color_LED_Task color_task;
/* USER CODE END Variables */
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
void StartDefaultTask(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
void MX_FREERTOS_Init(void) {
//添加信号量:
led_semaphor = xSemaphoreCreateBinary();
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* 动态创建蜂鸣器测试任务, ... */
xTaskCreate(beep_and_ldr_test, "beep_test_app",256,NULL,osPriorityNormal,&my_beep.beep_ldr_task);
/* 静态创建小彩灯闪烁任务*/
color_task.task = xTaskCreateStatic(color_led_test,"color_led_app",
SIZE,NULL,osPriorityNormal,color_task.stackBuf,&color_task.TCB);
}
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
while (true)
{
led_on();
osDelay(1000);
led_off();
osDelay(1000);
}
beep_and_ldr_app.c:
#include "cmsis_os.h"
#include "beep_and_ldr_app.h"
#include "color_led_app.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
struct Beep_LDR_Task my_beep = {0};
extern struct Color_LED_Task color_task;
extern osThreadId_t defaultTaskHandle;
extern QueueHandle_t glink_queue;
extern SemaphoreHandle_t led_semaphor;
int data = 0;
void beep_and_ldr_test(void* arg)
{
//这就是测试应用程序:
for (;;)
{
data = isDark();
//释放信号量:
xSemaphoreGive(led_semaphor);
}
}
color_led_app.c:
#include "color_led_app.h"
#include <stdbool.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
extern QueueHandle_t glink_queue;
struct Color_LED_Task color_task;
extern SemaphoreHandle_t led_semaphor;
extern int data;
void color_led_test(void *arg)
{
while (true)
{
//获取信号量:
xSemaphoreTake(led_semaphor, portMAX_DELAY);
/* color_led的应用 */
// 红灯闪烁:
if(data == 1)
{
color_led_set(1,0,0);
vTaskDelay(1000);
color_led_set(0,0,0);
vTaskDelay(1000);
}
if(data == 0)
//蓝灯闪烁:
{
color_led_set(0,0,1);
vTaskDelay(1000);
color_led_set(0,0,0);
vTaskDelay(1000);
}
}
}
2.互斥信号量:
这两个信号量主要用于互斥而产生的,但有一个机制就是继承优选级,防止优先级翻转的问题。图示:
继承优先机制:即当高优先级任务在等待低优先级任务资源时,会临时提高低先级任务的优先级与高优先级任务相同,主要用来防止优先级反转的问题的。
你也可以理解为: 互斥信号量 = 二值信号量 + 继承优选级机制。
互斥信号量会产生任务的阻塞,不可以在中断ISR中使用。
所以在同一个任务中,使用take与give保护任务的执行,达到互斥的效果。所以说互斥量也称为互斥锁。
注意:在使用互斥锁时,give之后一定要留足够的任务切换的时间。
3.递归互斥量:
递归互斥信号量很好的解决了互斥信量的两个缺陷:
互斥量的两个缺陷:
1. 假设任务1获取了互斥信号量,那么本应该任务1释放信号量,但是实际上其它任务也能释放信号量,如果其中某个任务释放并获取了信号量,那么就会产生错误。
2.死锁:在同一任务中获取两资互斥锁,第一次可以获取,第二次因为未释放而进入阻塞,因为无法释放所以死锁。
那么使用递归互斥量就可以很好的解决以上的问题:
因为递归互斥量只能:
1.谁获取谁释放。解决第一个缺陷。
2.获到互斥量的任务可以多次获取,但是也需要相同次数的释放。解决第二个缺陷。
递归互斥量也是基于互斥量实现的,所以也一样具有优先级继承机制,解决优先级反转而产生的高优级任务最后执行的问题。
代码演示:
freeRTOS.c中添加互斥量:
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ldr_driver.h"
#include "beep.h"
#include "beep_and_ldr_app.h"
#include "color_led_app.h"
#include <stdbool.h>
#include "led_driver.h"
#include "queue.h"
#include "semphr.h"
/
extern struct Beep_LDR_Task my_beep;
extern struct Color_LED_Task color_task;
QueueHandle_t glink_queue;
SemaphoreHandle_t mutex_lock;
/* USER CODE END Variables */
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */
void StartDefaultTask(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
void MX_FREERTOS_Init(void) {
glink_queue = xQueueCreate(1,sizeof(int));
mutex_lock = xSemaphoreCreateMutex();
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* 动�?�创建蜂鸣器测试任务, ... */
xTaskCreate(beep_and_ldr_test, "beep_test_app",256,NULL,osPriorityNormal,&my_beep.beep_ldr_task);
/* 静�?�创建小彩灯闪烁任务*/
color_task.task = xTaskCreateStatic(color_led_test,"color_led_app",
SIZE,NULL,osPriorityNormal,color_task.stackBuf,&color_task.TCB);
}
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
while (true)
{
//take:
xSemaphoreTake(mutex_lock, portMAX_DELAY);
led_on();
osDelay(1000);
led_off();
osDelay(1000);
//give:
xSemaphoreGive(mutex_lock);
vTaskDelay(1);
}
}
默认任务与小彩灯任务抢占互斥量,实现互斥,没有抢占的任务,阻塞等待。
#include "color_led_app.h"
#include <stdbool.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
extern QueueHandle_t glink_queue;
extern SemaphoreHandle_t mutex_lock;
struct Color_LED_Task color_task;
void color_led_test(void *arg)
{
int data = 0;
while (true)
{
xSemaphoreTake(mutex_lock,portMAX_DELAY);
/* color_led的应用 */
// 红灯闪烁:
xQueueReceive(glink_queue,&data,1000);
if(data == 100)
{
color_led_set(1,0,0);
vTaskDelay(1000);
color_led_set(0,0,0);
vTaskDelay(1000);
}
if(data == 200)
//蓝灯闪烁:
{
color_led_set(0,0,1);
vTaskDelay(1000);
color_led_set(0,0,0);
vTaskDelay(1000);
}
xSemaphoreGive(mutex_lock);
//留点时间给任务切换:
vTaskDelay(1);
}
}
第五节、多任务通信之队列集:xQueue_Set
队列xQueue已经很好用了,为何要用队列集?队列集又中哪个方面比使用队列更有优势呢?
1.为何要使用队列集呢?与单独使用队列的优势是?
一句话就是:集中管理,统一操作!
应用场景图示:
2. 创建队列集xQueueCreateSet():
QueueSetHandle_t xQueueCreateSet(const UBaseType_t uxEventQueueLength);
显式创建队列集, 然后才能使用它。
创建后,可以将标准 FreeRTOS 队列和信号量添加到集合中
(通过调用 xQueueAddToSet())。然后,使用 xQueueSelectFromSet() 确定集合中包含的队列或信号量中的哪些
(如果有) 处于队列读取或信号量获取操作将成功的状态。
参数:uxEventQueueLength 队列集存储集合中包含的队列和 信号量上发生的事件。
uxEventQueueLength 指定一次可以排队的最大事件数 。
返回值:如果成功创建队列集,则返回所创建队列集的句柄 。 否则返回 NULL。
使用此函数时请关注手册中的注意事项:
1.队列和信号量在添加到队列集时必须为空 。
2.阻塞包含互斥锁的队列集不会导致 互斥锁持有者继承已阻塞任务的优先级。
3.不得对队列集的成员执行接收(若为队列)或获取(若为 信号量)操作,
除非 调用 xQueueSelectFromSet() 先返回了队列集成员的句柄。
注意:队列集中不推荐添加互斥锁,因为xQueueSelectFromSet()是阻塞的,是同步的,就没有互斥的特性了,再有就是互斥锁在队列集使用时将失去优先级继承机制,所以不推荐在队列集中使用互斥锁。
3.添加队列或信号量到队列集:xQueueAddToSet
BaseType_t xQueueAddToSet (QueueSetMemberHandle_t xQueueOrSemaphore,
QueueSetHandle_t xQueueSet);
功能:将 RTOS 队列或信号量添加至先前由 xQueueCreateSet() 调用创建的队列集。
参数:
xQueueOrSemaphore 正在添加到队列集的队列或信号量的句柄 (转换为 QueueSetMemberHandle_t 类型)。
xQueueSet 正在添加队列或信号量的队列集句柄 。
返回值:
如果队列或信号量成功添加到队列集 那么返回 pdPASS。
如果队列无法成功添加到 队列集,因为它已经是其他队列集的成员,那么返回 pdFAIL 。
4.从队列集中选择(包含数据的)队列(或可供获取的)信号量的句柄xQueueSelectFromSet:
QueueSetMemberHandle_t xQueueSelectFromSet (QueueSetHandle_t xQueueSet,
const TickType_t xTicksToWait);
功能:
xQueueSelectFromSet() 从队列集成员中选择队列或信号量, 它们要么包含数据(若选择队列),要么可供获取 (若选择信号量)。
xQueueSelectFromSet() 能有效 允许任务同时读取一个队列集中的所有 队列和信号量后阻塞(挂起)。
参数:
xQueueSet 任务(可能)阻塞的队列集。
xTicksToWait 调用任务保持阻塞状态(其他任务正在执行),
等待队列集成员做好准备 以便成功读取队列或获取信号量所需的最长时间, 以滴答为单位。
返回值:
xQueueSelectFromSet() 将返回 队列集中包含数据的队列的句柄(转换为 QueueSetMemberHandle_t 类型)
或队列集中可用信号量的句柄(转换为 QueueSetMemberHandle_t 类型),
如果在指定的阻塞时间到期之前不存在这样的队列或信号量, 则返回 NULL。
同理此函数也有在ISR中的操作函数,加上后缀FromISR()即可。提供一种非阻塞的操作用于中断处理函数中。
5.从队列集中删除 RTOS 队列或信号量。xQueueRemoveFromSet:
BaseType_t xQueueRemoveFromSet( QueueSetMemberHandle_t xQueueOrSemaphore,
QueueSetHandle_t xQueueSet);
功能:从队列信中删除队列或信号:仅当队列或信号量为空时,才能从队列集中删除 RTOS 队列或信号量 。
参数:
xQueueOrSemaphore 从队列集中删除的队列或信号量的句柄 (转换为 QueueSetMemberHandle_t 类型)。
xQueueSet 包含队列或信号量的队列集的句柄 。
返回:
如果队列或信号量已成功从队列集中删除, 则返回 pdPASS。
如果队列不在队列集中,或者 队列(或信号量)不为空,则返回 pdFAIL。
6.代码应用实例演示:
FreeRTOS.c中的逻辑:
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ldr_driver.h"
#include "beep.h"
#include "beep_and_ldr_app.h"
#include "color_led_app.h"
#include <stdbool.h>
#include "led_driver.h"
#include "queue.h"
#include "semphr.h"
extern struct Beep_LDR_Task my_beep;
extern struct Color_LED_Task color_task;
QueueHandle_t glink_queue;
SemaphoreHandle_t sempahor_binary;
QueueSetHandle_t xQueueSet;
/* USER CODE END Variables */
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
void StartDefaultTask(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
void MX_FREERTOS_Init(void) {
glink_queue = xQueueCreate(1,sizeof(int));
sempahor_binary = xSemaphoreCreateBinary();
xQueueSet = xQueueCreateSet(1+1);
//添加队列或信号量(二值信号量,不推荐使用互斥量)
xQueueAddToSet(glink_queue, xQueueSet);
xQueueAddToSet(sempahor_binary,xQueueSet);
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* 动�?�创建蜂鸣器测试任务, ... */
xTaskCreate(beep_and_ldr_test, "beep_test_app",256,NULL,osPriorityNormal,&my_beep.beep_ldr_task);
/* 静�?�创建小彩灯闪烁任务*/
color_task.task = xTaskCreateStatic(color_led_test,"color_led_app",
SIZE,NULL,osPriorityNormal,color_task.stackBuf,&color_task.TCB);
}
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
while (true)
{
//先运行任务逻辑:
led_on();
osDelay(1000);
led_off();
osDelay(1000);
//give:
xSemaphoreGive(sempahor_binary);
}
/* USER CODE END StartDefaultTask */
}
color_led_app.c:
#include "color_led_app.h"
#include <stdbool.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
extern QueueHandle_t glink_queue;
extern SemaphoreHandle_t sempahor_binary;
struct Color_LED_Task color_task;
extern QueueSetHandle_t xQueueSet;
QueueSetMemberHandle_t xActiveHandleMember;
void color_led_test(void *arg)
{
int data = 0;
while (true)
{
xActiveHandleMember = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);
// 如果是队列:recive
if (xActiveHandleMember == glink_queue)
{
// 从队列中接收数据:
xQueueReceive(xActiveHandleMember, &data, portMAX_DELAY);
// 如果天亮就发绿光,天黑发蓝光:
if (data == 100)
{
color_led_set(0, 0, 1);
vTaskDelay(1000);
color_led_set(0, 0, 0);
vTaskDelay(1000);
}
if (data == 200)
{
color_led_set(0, 1, 0);
vTaskDelay(1000);
color_led_set(0, 0, 0);
vTaskDelay(1000);
}
}
// 如果是二值信号量:实现同步:
if (xActiveHandleMember == sempahor_binary)
{
vTaskDelay(1000);
xSemaphoreTake(xActiveHandleMember, portMAX_DELAY);
color_led_set(1, 0, 0);
vTaskDelay(1000);
color_led_set(0, 0, 0);
vTaskDelay(1000);
}
}
}
第六节、事件组xEventGroup
1.一句话概括是什么?
事件组是更加精简的一种类似于队列集的方式。但是它是一种无队列的同步机制,可以实现多个任务等待同一个或多个事件的同步机制。
2.什么结构?
使用一个4字节基本整形来表示一个事件组,高8位留,可处理24个事件。每个bit是事件位表示一个关心的事件是否产生,0为没有产生,1为产生了事件。
3.适用场景:
4.事件组常用API:
1. 创建事件组:xEventGroupCreate
EventGroupHandle_t xEventGroupCreate( void );
//创建一个事件组
返回值:成功返回事件组句柄,失败返回NULL;
2.设置事件组中的相应的事件位:xEventGroupSetBits
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
参数解释
xEventGroup: 事件组的句柄,标识哪个事件组将被设置。
uxBitsToSet: 这是一个掩码,表示调用任务需要设置的位。
多个位可以使用按位或(|)操作符组合在一起。
返回值
返回事件组设置位之前的值。
这是一个 EventBits_t 类型的值,其中包含所有事件组位在设置前的状态。
3.阻塞等待事件的产生:
EventBits_t xEventGroupWaitBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait
);
参数解释
xEventGroup: 事件组的句柄,标识哪个事件组将被等待。
uxBitsToWaitFor: 这是一个掩码,表示调用任务需要等待的位。
多个位可以使用按位或(|)操作符组合在一起。
xClearOnExit: 如果为 pdTRUE,则在返回前清除事件组中满足条件的位。
xWaitForAllBits:
如果为 pdTRUE,则仅在所有位(由 uxBitsToWaitFor 参数指定)都被设置时才返回。
如果为 pdFALSE,则在任意一个位被设置时返回。
xTicksToWait: 调用任务在事件位变为所需状态之前要等待的最大时间(以系统节拍计时)。
如果设置为 portMAX_DELAY,则表示无限期等待。
返回值
返回事件组当前的值。这是一个 EventBits_t 类型的值,其中包含所有事件组位的当前状态。
任务可以根据返回值判断哪些位被设置。
其它API自行手册查阅
5.事件组代码实例:
实现需求:F103开发板光源灯闪烁5次,及外部光线暗时,两个条件都满足时,小彩灯闪烁一次红灯。
生产者freeRTOS.c中的默认任务:
/* USER CODE BEGIN Header */
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ldr_driver.h"
#include "beep.h"
#include "beep_and_ldr_app.h"
#include "color_led_app.h"
#include <stdbool.h>
#include "led_driver.h"
#include "queue.h"
#include "semphr.h"
#include "event_groups.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
#define BIT_0 1 << 0
#define BIT_1 1 << 1
extern struct Beep_LDR_Task my_beep;
extern struct Color_LED_Task color_task;
EventGroupHandle_t xEventGroup;
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t)osPriorityNormal,
};
void StartDefaultTask(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
void MX_FREERTOS_Init(void)
{
/* USER CODE BEGIN Init */
xEventGroup = xEventGroupCreate();
/* USER CODE END Init */
/* Create the thread(s) */
/* creation of defaultTask */
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* 动�?�创建蜂鸣器测试任务, ... */
xTaskCreate(beep_and_ldr_test, "beep_test_app", 256, NULL, osPriorityNormal, &my_beep.beep_ldr_task);
/* 静�?�创建小彩灯闪烁任务*/
color_task.task = xTaskCreateStatic(color_led_test, "color_led_app",
SIZE, NULL, osPriorityNormal, color_task.stackBuf, &color_task.TCB);
}
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
while (true)
{
// 先运行任务逻辑:
for (int i = 0; i < 5; i++)
{
led_on();
osDelay(1000);
led_off();
osDelay(1000);
}
// 设置事件标记:
xEventGroupSetBits(xEventGroup, BIT_0);
}
/* USER CODE END StartDefaultTask */
}
生产者:环境光模块beep_and_ldr.c:
#include "cmsis_os.h"
#include "beep_and_ldr_app.h"
#include "color_led_app.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "event_groups.h"
#define BIT_0 1 << 0
#define BIT_1 1 << 1
struct Beep_LDR_Task my_beep = {0};
extern struct Color_LED_Task color_task;
extern osThreadId_t defaultTaskHandle;
extern QueueHandle_t glink_queue;
extern EventGroupHandle_t xEventGroup;
void beep_and_ldr_test(void* arg)
{
//这就是测试应用程序:
for (;;)
{
if (isDark())
{
//设置事件标记:
xEventGroupSetBits(xEventGroup,BIT_1);
}
}
}
消费者:小彩灯模块color_led_app.c:
#include "color_led_app.h"
#include <stdbool.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "event_groups.h"
#define BIT_0 1 << 0
#define BIT_1 1 << 1
extern EventGroupHandle_t xEventGroup;
// extern QueueHandle_t glink_queue;
// extern SemaphoreHandle_t sempahor_binary;
struct Color_LED_Task color_task;
// extern QueueSetHandle_t xQueueSet;
// QueueSetMemberHandle_t xActiveHandleMember;
void color_led_test(void *arg)
{
EventBits_t group_bits;
while (true)
{
group_bits = xEventGroupWaitBits(xEventGroup,BIT_0 | BIT_1, pdTRUE, pdTRUE, portMAX_DELAY);
if(group_bits & (BIT_0 | BIT_1) == (BIT_0 | BIT_1) )
{
//业务逻辑:
color_led_set(1,0,0);
vTaskDelay(1000);
color_led_set(0,0,0);
vTaskDelay(1000);
}
}
}
第七节、任务通知:
1. 什么是任务通知?
FreeRTOS提供了一种超轻量级的任务间同步机制。它们非常高效,并且不需要使用复杂的数据结构或内存分配,即无需队列,也无需要事件组,因此非常适合在嵌入式系统中使用, 它就是任务通知。
它实现任务一对一的同步通信机制,没有广播功能。只能任务或ISR向某个任务发出通知。
2.与其它同步方式对比:
freeRTOS官方这样说道:任务通知具有高度灵活性,使用时无需 创建单独队列、 二进制信号量、 计数信号量 或事件组。 通过直接通知解除 RTOS 任务阻塞状态的速度和使用中间对象(如二进制信号量)相比快了 45% *, 使用的 RAM 也更少 。
3.常用的任务通知的API:
1. 简化版任务通知:当作轻量级信号量的方式:xTaskNotifyGive、ulTaskNotifyTake
当任务通知 用作轻量级且更快的二进制或计数信号量 替代方案时,可以使用宏 xTaskNotifyGive ()归还。通另一个函数xTaskNotifyTake()获取。
1. BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify);
功能:向指定任务发送通知,增加其通知值。这种形式的通知常用于同步操作。
参数:xTaskToNotify: 要通知的任务的句柄。
返回值:成功时返回 pdPASS,失败时返回 errQUEUE_FULL。
2. uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
功能:收任务通知的函数,用于等待任务通知并减少通知值。
参数:
xClearCountOnExit:一个布尔值,指示在接收到通知后是否清除通知值。
pdTRUE:函数返回时清除通知值。
pdFALSE:函数返回时不清除通知值。
xTicksToWait: 等待通知的最大时间(以 tick 为单位)。
返回值:
返回接收到的通知计数值。
2.专业版任务通知:xTaskNotify通知、xTaskNotifyWait等待接收通知
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction );
功能: 用于在任务间发送通知。
它允许你对任务的通知值进行灵活的操作,可以设置、清除、递增通知值,或进行简单的消息传递。
参数
xTaskToNotify: 要通知的任务句柄。
ulValue: 要设置的通知值。具体操作取决于 eAction 的值。
eAction: 通知操作的类型。这个枚举类型决定了如何处理 ulValue。可以是以下值之一:
eNoAction:不对通知值进行任何操作,仅触发通知。
eSetBits:将通知值中的指定位设置为 1。可实现轻量级事件组
eIncrement:将通知值递增 1。可实现轻量级计数信号量
eSetValueWithOverwrite:将通知值设置为 ulValue,如果任务已有通知值,则覆盖旧值。
eSetValueWithoutOverwrite:将通知值设置为 ulValue,如果已有值则不修改。
返回值
pdPASS: 如果操作成功。pdFAIL: 如果操作失败(例如,目标任务句柄无效)。
2.BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait);
功能:用于在任务中等待通知,并在接收到通知后获取通知值。
这个函数允许任务在等待通知的过程中,可以选择是否清除通知值的特定位。
参数
ulBitsToClearOnEntry:在任务进入等待状态时要清除的通知值的位掩码。
如果设置为 pdFalse,则不清除任何位。pdTrue,则清除。
ulBitsToClearOnExit:在任务退出等待状态时要清除的通知值的位掩码。
如果设置为 pdFalse,则不清除任何位。pdTrue,则清除。
pulNotificationValue:指向一个 uint32_t 类型变量的指针,用于接收通知值。
如果不需要接收通知值,可以将其设置为 NULL。
xTicksToWait:等待通知的最长时间(以 tick 为单位)。
如果设置为 portMAX_DELAY,任务将无限期等待直到接收到通知。
返回值
如果接收到通知,则返回 pdPASS,并将通知值存储在 pulNotificationValue 指向的变量中(如果 pulNotificationValue 不为 NULL)。
如果在指定时间内未接收到通知,则返回 pdFAIL。
4.代码实例演示:
生产者:
#include "cmsis_os.h"
#include "beep_and_ldr_app.h"
#include "color_led_app.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "event_groups.h"
struct Beep_LDR_Task my_beep = {0};
extern struct Color_LED_Task color_task;
extern osThreadId_t defaultTaskHandle;
void beep_and_ldr_test(void* arg)
{
//这就是测试应用程序:
for (;;)
{
int data = 0;
if (isDark())
{
//发出任务的通知:
data = 100;
xTaskNotify(color_task.task,data,eSetValueWithOverwrite);
}
else{
data = 200;
xTaskNotify(color_task.task,data,eSetValueWithOverwrite);
}
}
}
消费者:
#include "color_led_app.h"
#include <stdbool.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "event_groups.h"
extern struct Beep_LDR_Task my_beep;
struct Color_LED_Task color_task;
void color_led_test(void *arg)
{
uint32_t data = 0;
while (true)
{
//接收任务的通知:
xTaskNotifyWait(pdTRUE,pdTRUE,&data,portMAX_DELAY);
if(data == 100)
{
//天黑了:
color_led_set(1,0,0);
vTaskDelay(1000);
color_led_set(0,0,0);
vTaskDelay(1000);
}
if(data == 200)
{
//天亮了:
color_led_set(0,1,0);
vTaskDelay(1000);
color_led_set(0,0,0);
vTaskDelay(1000);
}
}
}
第八节、总结: