参照正点原子以及以下gitee笔记整理本博客,并将实验结果附在文末。
https://gitee.com/xrbin/FreeRTOS_learning/tree/master
1、队列集介绍
答:
-
一个队列只允许任务间传递的消息为同一种数据类型,如果需要再任务间传递不同数据类型的消息时,那么就可以使用队列集!!!
-
作用:用于对多个队列或信号量进行“监听”,其中不管哪一个消息到来,都可让任务退出阻塞状态。(不需要通过轮询来检查每个对象)
(监听的作用,对于队列,等待接收,对于信号量,获取信号量)
假设:有一个任务,使用到队列接收和信号量的获取,如下:
不使用队列集:
使用队列集:
二、队列集相关API函数
1、队列集相关API函数
答:
2、队列集创建函数
答:
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength );
函数参数:
函数返回值:
3、队列集添加函数
答:
BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore,
QueueSetHandle_t xQueueSet);
此函数用于往队列集中添加队列,要注意的时,队列在被添加到队列集之前队列中不能有有效的消息。
函数参数:
函数返回值:
4、队列集移除函数
答:
BaseType_t xQueueRemoveFromSet( QueueSetMemberHandle_t xQueueOrSemaphore,
QueueSetHandle_t xQueueSet );
此函数用于从队列集中移除队列,要注意的是,队列在从队列集中移除之前,必须没有有效的消息。
函数参数:
函数返回值:
5、队列集获取函数
答:
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,
TickType_t const xTicksToWait)
此函数用于在任务中获取队列集中有有效消息的队列。
函数参数:
函数返回值:
三、队列集操作实验
实验简介
实验现象
为什么是先执行获取到队列数据为:1再执行往队列queue_handle写入数据成功!!?
这是由于FreeRTOS任务优先级和调度机制导致的。让我分析一下关键点:
任务优先级设置
#define TASK1_PRIO 2 // task1 优先级为 2
#define TASK2_PRIO 3 // task2 优先级为 3
在FreeRTOS中,数字越大表示优先级越高,所以task2的优先级比task1高。
执行流程分析
1.初始状态:task2在xQueueSelectFromSet(queueset_handle,portMAX_DELAY)处阻塞等待队列集消息
2.按下KEY0:task1执行到xQueueSend(queue_handle, &key, portMAX_DELAY)
3.关键时刻:当xQueueSend成功将数据写入队列时,这个操作会立即唤醒正在等待的task2
4.任务切换:由于task2优先级更高(3 > 2),FreeRTOS调度器会立即从task1切换到task2
5.执行顺序:
-
task2先执行:printf(“获取到的队列数据为:%d\r\n”,key);
-
task2执行完成后,才回到task1继续执行:printf(“往队列queue_handle写入数据成功!!\r\n”);
总结
这种现象完全符合FreeRTOS的抢占式调度机制:
-
高优先级任务一旦就绪,会立即抢占低优先级任务的CPU时间
-
xQueueSend操作唤醒了等待的高优先级task2
-
所以看起来是"接收"先于"发送成功"的打印信息
-
实验代码
/**
****************************************************************************************************
* @file freertos.c
* @author 正点原子团队(ALIENTEK)
* @version V1.4
* @date 2022-01-04
* @brief FreeRTOS 移植实验
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:正点原子 探索者F407开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
****************************************************************************************************
*/
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handle;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handle;
void task1(void * pvParameters);
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handle;
void task2(void * pvParameters);
/*队列集*/
QueueSetHandle_t queueset_handle;
QueueHandle_t queue_handle;
QueueHandle_t Semaphore_handle;
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate( (TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(TaskHandle_t * ) &start_task_handle);
//开启任务调度器
vTaskStartScheduler();
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /*进入临界区,任务切换不会进行*/
queueset_handle = xQueueCreateSet(2); /*该队列集包含一个队列 一个二值信号量*/
if(queueset_handle != NULL)
{
printf("队列集创建成功\r\n");
}
queue_handle= xQueueCreate(1,sizeof(uint8_t)); /*创建队列*/
if(queue_handle != NULL)
{
printf("队列创建成功\r\n");
}
Semaphore_handle = xSemaphoreCreateBinary(); /*创建二值信号量*/
if(Semaphore_handle != NULL)
{
printf("二值信号量创建成功\r\n");
}
/*向队列集中添加队列和二值信号量*/
xQueueAddToSet(queue_handle ,queueset_handle);
xQueueAddToSet(Semaphore_handle ,queueset_handle);
xTaskCreate( (TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handle);
xTaskCreate( (TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handle);
vTaskDelete( NULL );
taskEXIT_CRITICAL(); /*退出临界区,才会开始任务切换*/
/*
简单而言,临界区保护,就是保护那些不想被打断的从程序段
*/
}
/* 任务一,实现队列发送和信号量释放 */
void task1(void * pvParameters)
{
uint8_t key = 0;
BaseType_t err = 0;
while(1)
{
/*当Key0按下,往队列中写入数据,当Key1按下,释放二值信号量*/
key = key_scan(0);
if(key == KEY0_PRES)
{
err = xQueueSend(queue_handle, &key, portMAX_DELAY);
if(err ==pdPASS)
{
printf("往队列queue_handle写入数据成功!!\r\n");
}
}
else if(key == KEY1_PRES)
{
err = xSemaphoreGive( Semaphore_handle);
if(err == pdPASS)
{
printf("释放信号量成功\r\n");
}
}
vTaskDelay(10);
}
}
/* 任务二,获取队列集消息 */
void task2(void * pvParameters)
{
QueueSetMemberHandle_t member_handle;
uint8_t key;
while(1)
{
/*读取队列集中的消息,并打印*/
member_handle = xQueueSelectFromSet(queueset_handle,portMAX_DELAY);
if(member_handle == queue_handle)
{
xQueueReceive(member_handle, &key,portMAX_DELAY);
printf("获取到的队列数据为:%d\r\n",key);
}else if(member_handle == Semaphore_handle)
{
xSemaphoreTake(member_handle, portMAX_DELAY );
printf("获取信号量成功\r\n");
}
}
}