第二章、FreeRTOS任务管理及通信方式

第一节、任务的创建方式:

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);
       }
    }
}

第八节、总结:

  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值