目录
队列简介
FreeRTOS队列特点
队列操作基本过程
队列结构体介绍
队列结构体整体示意图
队列相关API函数介绍
创建队列相关API函数介绍
往队列写入消息API函数
往队列写入消息函数入口参数解析
从队列读取消息API函数
实验源码
队列简介
队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递)
类似全局变量?假设有一个全局变量a = 0,现有两个任务都在写这个变量a
假设在任务1完成2步骤修改数据的时候,此时任务2优先级高打断任务1,a还是0因为任务a还没有写数据,任务2执行了加1,在回到任务1时,下一步是执行写数据此时r0是任务2执行完时候的1,任务1在吧R0赋值给0,相当于r0等于1在给a,并没有等于2。这样就照成了数据的受损。
全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损。
使用队列的情况如下:
读写队列做好了保护,防止多任务同时访问冲突;FreeRTOS基于队列,实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二值信号量、递归互斥信号量,因此很有必要深入了解 FreeRTOS的队列。
在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目” ,队列能够存储“队列项目”的最大数量称为队列的长度。
队列长度为:5个
队列项目大小为:10字节
队列长度和队列项目不是固定的是创建的时候自己指定的。
FreeRTOS队列特点
1.数据入队出队方式:队列通常采用“先进先出”(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取,FreeRTOS中也可以配置为“后进先出”LIFO方式。
2.数据传递方式:FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递,FreeRTOS采用拷贝数据传递,也可以传递指针,所以在传递较大的数据的时候采用指针传递。
3.多任务访问:队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息。
4.出队、入队阻塞:当任务向一个队列发送消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队。
①若阻塞时间为:0直接返回不会等待;
②若阻塞时间为0~port_MAX_DELAY:等待设定的阻塞时间,若在该时间内还无法入队, 超时后直接返回不再等待;
③若阻塞时间为port_MAX_DELAY:死等,一直等到可以入队为止。出队阻塞与入队阻塞 类似;
入队阻塞:
队列满了,此时写不进去数据;
①将该任务的状态列表项挂载在pxDelayedTaskList;(阻塞列表)
②将该任务的事件列表项挂载在xTasksWaitingToSend;(等待发送列表)
出队阻塞:
队列为空,此时读取不了数据;
①将该任务的状态列表项挂载在pxDelayedTaskList;(阻塞列表)
②将该任务的事件列表项挂载在xTasksWaitingToReceive;(等待接收列表)
当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务在等待同一 个队列的空间。那当队列中有空间时,优先级最高的任务会进入就绪态,如果优先级相同,那等待时间最久的任务回进入就绪态。
队列操作基本过程
1.创建队列
2.往队列写入第一个消息
3.往队列写入第二个消息
4.从队列读取第一个消息
队列结构体介绍
typedef struct QueueDefinition
{
int8_t* pcHead; /* 存储区域的起始地址*/
int8_t*pcWriteTo; /* 下一个写入的位置*/
Union
{
QueuePointers_t xQueue; /*消息队列时使用*/
SemaphoreData_t xSemaphore; /*互斥信号量时使用*/
}u;
List_t xTasksWaitingToSend; /*等待发送列表*/
List_t xTasksWaitingToReceive /* 等待接收列表*/
;volatile UBaseType_t uxMessagesWaiting; /*非空闲队列项目的数量*/
UBaseType_t uxLength; /*队列长度*/
UBaseType_t uxltemSize; /*队列项目的大小*/
volatile int8_t cRxLock; /* 读取上锁计数器*/
volatile int8_t cTxLock; /*写入上锁计数器*/
/*其他的一些条件编译*/
}xQUEUE;
当用于队列使用时:
typedef struct QueuePointers
{
int8_t* pcTail; /*存储区的结束地址*/
int8_t* pcReadFrom; /*最后一个读取队列的地址*/
}QueuePointers_t;
当用于互斥信号量和递归互斥信号量时:
typedef struct SemaphoreData
{
TaskHandle_t xMutexHolder; /*互斥信号量持有者*/
UBaseType_t uxRecursiveCallCount; /*递归互斥信号量的获取计数器*/
}SemaphoreData_t;
队列结构体整体示意图
队列相关API函数介绍
创建队列相关API函数介绍
动态和静态创建队列之间的区别:队列所需的内存空间由FreeRTOS从FreeRTOS管理的堆中分配,而静态创建需要用户自行分配内存。
#define xQueueCreate ( uxQueueLength, uxltemSize)
xQueueGenericCreate( ( uxQueueLength ), ( uxltemSize ), (queueQUEUE_TYPE_BASE ))
此函数用于使用动态方式创建队列,队列所需的内存空间由 FreeRTOS 从 FreeRTOS 管理的堆中分配。
FreeRTOS 基于队列实现了多种功能,每一种功能对应一种队列类型,队列类型的 queue.h 文件中有定义:
#define queueQUEUE_TYPE_BASE ((uint8_t) OU)/* 队列 */
#define queueQUEUE_TYPE_SET ((uint8_t) OU)/* 队列集*/
#define queueQUEUE_TYPE_MUTEX ((uint8_t) 1U)/*互斥信号量*/
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ((uint8_t) 2U)/*计数型信号量*/
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ((uint8_t) 3U)/*二值信号量*/
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ((uint8_t) 4U)/* 递归互斥信号量*/
往队列写入消息API函数
任务级往队列写入消息
/*往队列的尾部写入消息*/
#define xQueueSend( xQueue, pvltemToQueue, xTicksToWait)
xQueueGenericSend( (xQueue ), ( pvltemToQueue ), (xTicksToWait), queueSEND_TO_BACK)
/*往队列的尾部写入消息*/
#define xQueueSendToBack( xQueue, pvltemToQueue, xTicksToWait)
xQueueGenericSend((×Queue ), ( pvltemToQueue ), (xTicksToWait), queueSEND_TO_BACK)
/*往队列的头部写入消息*/
#define xQueueSendToFront( xQueue, pvltemToQueue, xTicksToWait)
xQueueGenericSend((xQueue), (pvltemToQueue), (xTicksToWait), queueSEND_TO_FRONT)
/*覆写队列消息(只用于队列长度为1的情况)*/
#define xQueueOverwrite( xQueue, pvitemToQueue )
xQueueGenericSend( (xQueue ), ( pvitemToQueue ), 0, queueOVERWRITE )
可以看到这几个写入函数调用的是同一个函数xQueueGenericSend(),只是指定了不同的写入位置!
队列一共有 3 种写入位置:
#define queueSEND_TO_BACK ((BaseType_t) 0)/*写入队列尾部*/
#define queueSEND_TO_FRONT ((BaseType_t) 1)/*写入队列头部*/
#define queueOVERWRITE ((BaseType_t) 2)/*覆写队列*/
注意:覆写方式写入队列,只有在队列的队列长度为1时,才能够使用
往队列写入消息函数入口参数解析
BaseType_t xQueueGenericSend(QueueHandle_t xQueue,
QueueHandle_t xQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition );
从队列读取消息API函数
/*从队列头部读取消息,并删除消息*/
BaseType_t xQueueReceive(
QueueHandle_t xQueue,
void* const pvBuffer,
TickType_t xTicksToWait )
此函数用于在任务中,从队列中读取消息,并且消息读取成功后,会将消息从队列中移除。
/*从队列头部读取消息不会删除*/
BaseType_t xQueuePeek(
QueueHandle_t xQueue,
void*const pvBuffer,
TickType_t xTicksToWait)
此函数用于在任务中,从队列中读取消息,但与函数xQueueReceive()不同,此函数在成功读取消息后,并不会移除已读取的消息!
实验源码
start_task 用来创建task1和task2以及task3任务
task1 当按键key0或key1按下,将键值拷贝到队列key_queue(入队)当按键kev_up按 下,将传输大数据,这里拷贝大数据的地址到队列big_date_queue中
task2 读取队列key_queue中的消息(出队)打印出接收到的键值
task3 从队列big_date_queue读取大数据地址,通过地址访问大数据
/**
******************************************************************************
* @file : user_mian.h
* @brief : V1.00
******************************************************************************
* @attention
*
******************************************************************************
*/
/* Include 包含---------------------------------------------------------------*/
#include "stm32f10x.h"
#include <stdbool.h>
#include "user_gpio.h"
#include "user_delay.h"
#include "user_rcc_config.h"
#include "user_uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "user_key.h"
/* Typedef 类型----------------------------------------------------------------*/
/* Define 定义----------------------------------------------------------------*/
/* Macro 宏------------------------------------------------------------------*/
/* Variables 变量--------------------------------------------------------------*/
QueueHandle_t key_queue; /*小数据句柄*/
QueueHandle_t big_date_queue; /*大数据句柄*/
char buff[100] = {" 大数组 12324164985465484684866"};
/* Constants 常量--------------------------------------------------------------*/
/* Function 函数--------------------------------------------------------------*/
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define TASK1_PRIO 2
//任务堆栈大小
#define TASK1_STK_SIZE 100
//任务句柄
TaskHandle_t Task1_Handler;
//任务函数
void task1(void *pvParameters);
//任务优先级
#define TASK2_PRIO 2
//任务堆栈大小
#define TASK2_STK_SIZE 100
//任务句柄
TaskHandle_t Task2_Handler;
//任务函数
void task2(void *pvParameters);
//任务优先级
#define TASK3_PRIO 2
//任务堆栈大小
#define TASK3_STK_SIZE 100
//任务句柄
TaskHandle_t Task3_Handler;
//任务函数
void task3(void *pvParameters);
int main(void)
{
/*配置系统中断分组为4位抢占*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
/*延时函数初始化*/
delay_init();
/*RCC配置*/
Rcc_config();
/*GPIO初始化*/
Gpio_Init();
/*USART1初始化*/
Uart1_Init(9600);
/*创建小数据队列*/
key_queue = xQueueCreate(2,sizeof(uint8_t));
if(key_queue != NULL)
{
printf("key_queue队列创建!!\r\n\r\n");
}else
{
printf("key_queue队列创建失败!!\r\n\r\n");
}
/*创建大数据队列*/
big_date_queue = xQueueCreate(1,sizeof(char *));
if(key_queue != NULL)
{
printf("big_date_queue队列创建!!\r\n\r\n");
}else
{
printf("big_date_queue队列创建失败!!\r\n\r\n");
}
/*创建开始任务*/
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
/*!
\brief 开始任务函数
\param[in] 传递形参,创建任务时用户自己传入
\param[out] none
\retval none
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建任务1
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1_Handler);
//创建任务2
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2_Handler);
//创建任务3
xTaskCreate((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/*!
\brief task1实现入队
\param[in] 传递形参,创建任务时用户自己传入
\param[out] none
\retval none
*/
void task1(void *pvParameters)
{
uint8_t key = 0;
BaseType_t err = 0;
char * buf;
buf = &buff[0];
while(1)
{
/*获取按键值*/
key = Key_Scan(0);
/*写入小数据*/
if(key == KEY0_PRES || key == KEY1_PRES)
{
/*写入按键值,队列满就死等*/
err = xQueueSend(key_queue,&key,portMAX_DELAY);
if(err != pdTRUE)
{
printf("key_queue队列小数据发送失败\r\n\r\n");
}
}else if(key == WKUP_PRES)
{
/*写入大数据,队列满就死等*/
err = xQueueSend(big_date_queue,&buf,portMAX_DELAY);
if(err != pdTRUE)
{
printf("big_date_queue队列大数据发送失败\r\n\r\n");
}
}
vTaskDelay(100);
}
}
/*!
\brief task2实现小数据出队
\param[in] 传递形参,创建任务时用户自己传入
\param[out] none
\retval none
*/
void task2(void *pvParameters)
{
uint8_t key = 0;
uint8_t err = 0;
while(1)
{
/*接受小数据,队列空死等*/
err = xQueueReceive(key_queue,&key,portMAX_DELAY);
if(err != pdTRUE)
{
printf("key_queue队列小数据读取失败\r\n\r\n");
}else
{
printf("读取key_queue队列成功,数据:%d\r\n\r\n",key);
}
}
}
/*!
\brief task3实现大数据出队
\param[in] 传递形参,创建任务时用户自己传入
\param[out] none
\retval none
*/
void task3(void *pvParameters)
{
char * buf;
uint8_t err = 0;
while(1)
{
/*接受大数据,队列空死等*/
err = xQueueReceive(big_date_queue,&buf,portMAX_DELAY);
if(err != pdTRUE)
{
printf("big_date_queue队列大数据读取失败\r\n\r\n");
}else
{
printf("读取big_date_queue队列成功,数据:%s\r\n\r\n",buf);
}
}
}
/************************************************************** END OF FILE ****/