一、什么是队列
队列是任务到任务、任务到中断、中断到任务数据交流的一种机制。
在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度。
在创建队列时要指定队列长度以及队列项目的大小。
二、队列特点
1、数据入队出队方式:队列通常采用”先进先出“(FIFO)先进入消息队列的消息先传给任务,
也可以配置为后进后出(LIFO)的方式。
2、数据传递方式:FreeRTOS消息队列传递的是实际数据即将数据拷贝到队列中进行传递,并不是数据地址,RTX,uCOS-II 和 uCOS-III 是传递的地址。FreeRTOS中可以通过传递指针地址方式来传递指针。
3、多任务访问:队列不属于某个任务,任何任务和中断都可以向队列发送或读取消息。
4、出队/入队阻塞:当任务向一个队列发送消息时可以指定一个阻塞时间。
- 若阻塞时间为0 :直接返回不会等待。
- 若为0~port_MAX_DELAY:等待设定的阻塞时间,超时后直接返回。
- 若为port_MAX_DELAY:一直等到可以入队。
当一个任务试图从一个空队列中读取时,该队列将 进入阻塞状态(因此它不会消耗任何 CPU 时间,且其他任务可以运行) 直到队列中的数据变得可用,或者阻塞时间过期。
当一个任务试图写入到一个满队列时,该队列将 进入阻塞状态(因此它不会消耗任何 CPU 时间,且其他任务可以运行) 直到队列中出现可用空间,或者阻塞时间过期。
如果同一个队列上有多个处于阻塞状态的任务, 那么具有最高优先级的任务将最先解除阻塞。
如果优先级相同,等待时间最长的任务最先解除阻塞。
三、队列的函数
1、xQueueCreate
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
创建一个新队列并返回 可引用此队列的句柄。
configSUPPORT_DYNAMIC_ALLOCATION 必须在 FreeRTOSConfig.h 中被设置为 1,或保留未定义状态(此时,它默认 为 1) ,才能使用此 RTOS API 函数。
每个队列需要 RAM 用于保存队列状态和 包含在队列(队列存储区域)中的项目。 如果使用 xQueueCreate() 创建队列,则所需的 RAM 将自动 从 FreeRTOS 堆中分配。 如果使用 xQueueCreateStatic() 创建队列, 则 RAM 由应用程序编写者提供,这会产生更多的参数, 但这样能够在编译时静态分配 RAM 。
参数:
uxQueueLength | 队列可同时容纳的最大项目数 。 |
uxItemSize | 存储队列中的每个数据项所需的大小(以字节为单位)。 数据项按副本排队,而不是按引用排队, 因此该值为每个排队项目将被复制的字节数。队列中每个数据项 必须大小相同。 |
返回:
如果队列创建成功,则返回所创建队列的句柄 。 如果创建队列所需的内存无法 分配 ,则返回 NULL。
2、xQueueSend
这是一个调用 xQueueGenericSend() 的宏。
等同于 xQueueSendToBack()。
在队列中发布项目。不得从中断服务程序调用此函数。请参阅 xQueueSendFromISR() 以获取 可用于 ISR 的替代方案。
参数:
xQueue | 队列的句柄,数据项将发布到此队列。 |
pvItemToQueue | 指向待入队数据项的指针。创建队列时定义了队列将保留的项的大小,因此固定数量的字节将从 pvItemToQueue 复制到队列存储区域。 |
xTicksToWait | 如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。如果队列已满,并且 xTicksToWait 设置为0 ,调用将立即返回。时间在 tick 周期中定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。 如果 INCLUDE_vTaskSuspend 设置为 “1” ,则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。 |
返回:如果成功发布项目,则返回 pdTRUE,否则返回 errQUEUE_FULL。
3、xQueueSendToBack
这是一个调用 xQueueGenericSend() 的宏。
等同于 xQueueSend()。
从队列尾部入队一个数据项。 数据项通过复制 而非引用入队。 不得从中断服务程序调用此函数。 请参阅 xQueueSendToBackFromISR (),获取可在 ISR 中使用的 替代方案。
参数:
xQueue | 队列的句柄,数据项将发布到此队列。 |
pvItemToQueue | 指向待入队数据项的指针。创建队列时定义了队列将保留的项的大小,因此固定数量的字节将从 pvItemToQueue 复制到队列存储区域。 |
xTicksToWait | 如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。如果设置为 0,调用将立即返回。时间以滴答周期为单位定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。 如果 INCLUDE_vTaskSuspend 设置为 “1” ,则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。 |
返回:
如果成功发布项目,返回 pdTRUE,否则返回 errQUEUE_FULL。
4、xQueueSendToFront
此宏用于调用 xQueueGenericSend()。
从队列头部入队一个数据项。 数据项通过复制 而非引用入队。 不得从中断服务程序 调用此函数。 请参阅 xQueueSendToFrontFromISR() 了解 可在 ISR 中使用的替代方法。
参数:
xQueue | 队列的句柄,数据项将发布到此队列。 |
pvItemToQueue | 指向待入队数据项的指针。创建队列时定义了队列将保留的项的大小,因此固定数量的字节将从 pvItemToQueue 复制到队列存储区域。 |
xTicksToWait | 如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。如果设置为 0,调用将立即返回。时间以滴答周期为单位定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。 如果 INCLUDE_vTaskSuspend 设置为 “1” ,则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。 |
返回:
如果成功发布项目,返回 pdTRUE,否则返回 errQUEUE_FULL。
5、xQueueReceive
这是一个调用 xQueueGenericReceive() 函数的宏。
从队列中接收项目。该项目通过复制接收,因此必须提供足够大小的缓冲区。创建队列时定义了复制到缓冲区中的字节数。
成功接收后会将数据删除。
中断服务程序中不得使用此函数。请参阅 xQueueReceiveFromISR 了解可以选择的替代方案。
参数:
xQueue | 要从中接收项目的队列的句柄。 |
pvBuffer | 指向缓冲区的指针,接收到的项目将被复制到这个缓冲区。 |
xTicksToWait | 如果在调用时队列为空,则任务应阻塞等待项目接收的最长时间。 如果队列为空,将 xTicksToWait 设置为 0 将导致函数立即返回。时间在滴答周期中定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。 如果 INCLUDE_vTaskSuspend 设置为 “1” ,则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。 |
返回:
如果从队列成功接收到项目,返回 pdTRUE,否则返回 pdFALSE。
6、xQueuePeek
这是一个调用 xQueueGenericReceive() 函数的宏。
从队列中接收项目,而无须从队列中删除该项目。 项目由复制接收,因此必须提供适当大小的缓冲区 。 队列创建时,复制到缓冲区中的字节数已定义 。
成功接收的数据不会删除其仍在队列中。
中断服务例程中不得使用此宏。
参数:
xQueue | 要从中接收项目的队列的句柄。 |
pvBuffer | 指针,指向将复制收到的项目的缓冲区。 它必须至少足够大,才能容纳创建队列时定义的队列项的大小。 |
xTicksToWait | 如果在调用时队列为空,则任务应阻塞等待项目接收的最长时间。 时间已在滴答周期中定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 来将其转换为实时。 如果 INCLUDE_vTaskSuspend 设置为 “1” ,则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。 |
返回:
如果从队列中成功接收(窥视)项目,则返回 pdTRUE,否则返回 pdFALSE。
四、实验程序
1、句柄声明和数组声明
QueueHandle_t Key_Hander = NULL;
QueueHandle_t BigData_Hander = NULL;
char BigData[50] = {"123zxcv123vbnm"};
2、 创建队列和开始任务
void freertos_demo(void)
{
Key_Hander = xQueueCreate(2,sizeof(uint8_t));
if(Key_Hander != NULL)
{
printf("小数据队列创建成功\r\n");
}
BigData_Hander = xQueueCreate(1,sizeof(char *));
if(BigData_Hander != NULL)
{
printf("大数据队列创建成功\r\n");
}
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_handler );
vTaskStartScheduler();
}
3、开始任务
void start_task( void * pvParameters )
{
taskENTER_CRITICAL();/*进入临界区*/
xTaskCreate( (TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handler );
xTaskCreate( (TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handler );
xTaskCreate( (TaskFunction_t ) task3,
(char * ) "task3",
(configSTACK_DEPTH_TYPE) TASK3_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK3_PRIO,
(TaskHandle_t * ) &task3_handler );
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /*退出临界区*/
}
4、任务1、2、3
/* 入队 */
void task1( void * pvParameters )
{
uint8_t key = 0;
BaseType_t backValue = 0;
char *buf;
buf = &BigData[0];
while(1)
{
key = key_scan(0);
if(key == KEY0_PRES)
{
backValue = xQueueSendToBack(Key_Hander,&key,portMAX_DELAY);
if(backValue == pdTRUE)
{
printf("小数据入队成功\r\n");
}
else printf("小数据入队失败\r\n");
}
else if(key == KEY1_PRES)
{
backValue = xQueueSendToBack(BigData_Hander,&buf,portMAX_DELAY);
if(backValue == pdTRUE)
{
printf("大数据入队成功\r\n");
}
else printf("大数据入队失败\r\n");
}
}
}
/* 出队 */
void task2( void * pvParameters )
{
uint8_t key = 0;
BaseType_t backValue = 0;
while(1)
{
backValue = xQueueReceive(Key_Hander,&key,portMAX_DELAY);
if(backValue == pdTRUE)
{
printf("小数据出队成功\r\n");
}
else printf("小数据出队失败\r\n");
}
}
void task3( void * pvParameters )
{
char *bigData;
BaseType_t backValue = 0;
while(1)
{
backValue = xQueueReceive(BigData_Hander,&bigData,portMAX_DELAY);
if(backValue == pdTRUE)
{
printf("大数据出队成功\r\n%s\r\n",bigData);
}
else printf("大数据出队失败\r\n");
}
}
五、实验现象
1、按下KEY0按键,小数据队列入队出队
数据入队后由于任务2优先级大于任务1,所以任务2会抢占任务1
2、按下KEY1,大数据队列入队出队
六、发送大数据指针分析
memcpy函数原型
void *memcpy(void *str1, const void *str2, size_t n)
参数:
- str1 -- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
- str2 -- 指向要复制的数据源,类型强制转换为 void* 指针。
- n -- 要被复制的字节数。
发送函数xQueueGenericSend会调用prvCopyDataToQueue将数据复制到队列
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue,
const void * pvItemToQueue,
const BaseType_t xPosition )
{
BaseType_t xReturn = pdFALSE;
UBaseType_t uxMessagesWaiting;
/********省略******************/
if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
{
/********省略******************/
}
else if( xPosition == queueSEND_TO_BACK )
{
( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );
}
else
{
( void ) memcpy( ( void * ) pxQueue->u.xQueue.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );
/********省略******************/
}
/********省略******************/
}
/*-----------------------------------------------------------*/
接收函数xQueueReceive 调用prvCopyDataFromQueue将数据从队列中复制
static void prvCopyDataFromQueue( Queue_t * const pxQueue,
void * const pvBuffer )
{
if( pxQueue->uxItemSize != ( UBaseType_t ) 0 )
{
/********省略******************/
( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.xQueue.pcReadFrom, ( size_t ) pxQueue->uxItemSize );
}
}
由于函数使用memcpy函数,需要传入数据的地址来进行复制,任务1中
char *buf; buf = &BigData[0];
xQueueSendToBack(BigData_Hander,&buf,portMAX_DELAY);
buf存放的是数组BigData的地址,&buf传递的是buf的地址相当于传递了数组地址的地址,接收队列复制后的数据就是数组的地址。任务3 bigData就等于&BigData[0];
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » 基于正点原子的FreeRTOS笔记——队列
发表评论 取消回复