从0到1学习FreeRTOS:FreeRTOS 内核应用开发:(九)消息队列 NO.2 消息队列常用函数讲解
目录
一、消息队列创建函数 xQueueCreate():
二、消息队列静态创建函数 xQueueCreateStatic():
三、消息队列删除函数 vQueueDelete():
四、向消息队列发送消息函数:
五、从消息队列读取消息函数:
使用队列模块的典型流程如下:
创建消息队列。
写队列操作。
读队列操作。
删除队列。
一、消息队列创建函数 xQueueCreate():
xQueueCreate()用于创建一个新的队列并返回可用于访问这个队列的队列句柄。队列句柄其实就是一个指向队列数据结构类型的指针。
每创建一个新的队列都需要为其分配内存空间,一部分用于存储队列的基本信息,剩下的作为队列消息的存储区域。
函数 | QueueHandle_txQueueCreate( UBaseType_tuxQueueLength,
UBaseType_tuxItemSize ); | |
功能 | 用于创建一个新的队列。 | |
参数 | uxQueueLength | 队列能够存储的最大消息单元数目,即队列长度。 |
uxItemSize | 队列中消息单元的大小,以字节为单位。 | |
返回值 | 如果创建成功则返回一个队列句柄,用于访问创建的队列。如果创建不成功则返回
NULL,可能原因是创建队列需要的RAM无法分配成功。 |
初始化消息队列:
初始化息队列的成员变量,pcTail指向存储消息内存空间的结束地址。
pcWriteTo指向队列消息存储区下一个可用消息空间,也就是pcHead指向的空间。
pcReadFrom指向消息队列最后一个消息空间。
初始化入队/出队阻塞列表。
QueueHandle_t Test_Queue =NULL;
#define QUEUE_LEN 4 /* 队列的长度,最大可包含多少个消息 */
#define QUEUE_SIZE 4 /* 队列中每个消息大小(字节) */
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建 Test_Queue */
Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */
(UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */
if (NULL != Test_Queue)
printf("创建 Test_Queue 消息队列成功!\r\n");
taskEXIT_CRITICAL(); //退出临界区
在创建消息队列的时候,是需要用户自己定义消息队列的句柄指针的。
创建完成会返回消息队列的句柄,用户通过句柄就可使用消息队列进行发送与读取消息队列的操作,如果返回的是NULL则表示创建失败。
二、消息队列静态创建函数 xQueueCreateStatic():
不常用,略。
三、消息队列删除函数 vQueueDelete():
队列删除函数是根据消息队列句柄直接删除的,删除之后这个消息队列的所有信息都会被系统回收清空,而且不能再次使用这个消息队列了。
xQueue是vQueueDelete()函数的形参,是消息队列句柄,表示的是要删除哪个想队列。
注意: 在消息队列有消息的时候、有任务阻塞在消息队列上等待消息的时候,尽量不要删除它,虽然源码并没有禁止删除的操作,但是为了代码健壮性,还是需要注意的 。
#define QUEUE_LENGTH 5
#define QUEUE_ITEM_SIZE 4
int main( void )
{
QueueHandle_t xQueue;
/* 创建消息队列 */
xQueue = xQueueCreate( QUEUE_LENGTH, QUEUE_ITEM_SIZE );
if ( xQueue == NULL ) {
/* 消息队列创建失败 */
} else {
/* 删除已创建的消息队列 */
vQueueDelete( xQueue );
}
}
四、向消息队列发送消息函数:
1、xQueueSend()与 xQueueSendToBack()
从消息队列的入队操作我们可以看出: 如果阻塞时间不为 0,则任务会因为等待入队而进入阻塞, 在将任务设置为阻塞的过程中, 系统不希望有其它任务和中断操作这个队列的 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表,因为可能引起其它任务解除阻塞,这可能会发生优先级翻转。
因此FreeRTOS 使用挂起调度器禁止其它任务操作队列,因为挂起调度器意味着任务不能切换并且不准调用可能引起任务切换的 API 函数。但挂起调度器并不会禁止中断,中断服务函数仍然可以操作队列事件列表,可能会解除任务阻塞、可能会进行上下文切换,这也是不允许的。于是,解决办法是不但挂起调度器,还要给队列上锁,禁止任何中断来操作队列。
函数 | BaseType_txQueueSend( QueueHandle_txQueue,
const void * pvItemToQueue,
TickType_txTicksToWait); | |
功能 | 用于向队列尾部发送一个队列消息。 | |
参数 | xQueue | 队列句柄。 |
pvItemToQueue | 指针,指向要发送到队列尾部的消息。 | |
xTicksToWait | 队列满时,等待队列空闲的最大超时时间。如果队列满并且xTicksToWait被设置成0,
函数立刻返回。超时时间的单位为系统节拍周期,常量portTICK_PERIOD_MS用于辅助
计算真实的时间,单位为ms。如果INCLUDE_vTaskSuspend设置成1,并且指定延时为
portMAX_DELAY将导致任务挂起(没有超时)。 | |
返回值 | 消息发送成功成功返回pdTRUE,否则返回errQUEUE_FULL。 |
static void Send_Task(void* parameter)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
uint32_t send_data1 = 1;
uint32_t send_data2 = 2;
while (1) {
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) {
/* K1 被按下 */
printf("发送消息 send_data1! \n");
xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
&send_data1,/* 发送的消息内容 */
0 ); /* 等待时间 0 */
if (pdPASS == xReturn)
printf("消息 send_data1 发送成功!\n\n");
}
if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) {
/* K2 被按下 */
printf("发送消息 send_data2! \n");
xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
&send_data2,/* 发送的消息内容 */
0 ); /* 等待时间 0 */
if (pdPASS == xReturn)
printf("消息 send_data2 发送成功!\n\n");
}
vTaskDelay(20);/* 延时 20 个 tick */
}
}
2、 xQueueSendFromISR()与 xQueueSendToBackFromISR():
函数 | BaseType_txQueueSendFromISR( QueueHandle_txQueue,
constvoid *pvItemToQueue,
BaseType_t*pxHigherPriorityTaskWoken); | |
功能 | 在中断服务程序中用于向队列尾部发送一个消息。 | |
参数 | xQueue | 队列句柄。 |
pvItemToQueue | 指针,指向要发送到队列尾部的消息。 | |
pxHigherPriorityTaskWoken | 如果入队导致一个任务解锁,并且解锁的任务优先级高于当前被中断的
任务,则将*pxHigherPriorityTaskWoken设置成pdTRUE,然后在中断退
出前需要进行一次上下文切换,去执行被唤醒的优先级更高的任务。从
FreeRTOS V7.3.0起,pxHigherPriorityTaskWoken作为一个可选参数,
可以设置为NULL。 | |
返回值 | 消息发送成功返回pdTRUE,否则返回errQUEUE_FULL。 |
void vBufferISR( void )
{
char cIn;
BaseType_t xHigherPriorityTaskWoken;
/* 在 ISR 开始的时候,我们并没有唤醒任务 */
xHigherPriorityTaskWoken = pdFALSE;
/* 直到缓冲区为空 */
do {
/* 从缓冲区获取一个字节的数据 */
cIn = portINPUT_BYTE( RX_REGISTER_ADDRESS );
/* 发送这个数据 */
xQueueSendFromISR( xRxQueue, &cIn, &xHigherPriorityTaskWoken );
} while ( portINPUT_BYTE( BUFFER_COUNT ) );
/* 这时候 buffer 已经为空,如果需要则进行上下文切换 */
if ( xHigherPriorityTaskWoken ) {
/* 上下文切换,这是一个宏,不同的处理器,具体的方法不一样 */
taskYIELD_FROM_ISR ();
}
}
3、xQueueSendToFront()与xQueueGenericSend():
函数 | BaseType_txQueueSendToFront( QueueHandle_txQueue,
const void * pvItemToQueue,
TickType_txTicksToWait ); | |
功能 | 于向队列队首发送一个消息。 | |
参数 | xQueue | 队列句柄。 |
pvItemToQueue | 指针,指向要发送到队首的消息。 | |
xTicksToWait | 队列满时,等待队列空闲的最大超时时间。如果队列满并且xTicksToWait被设置成0,函数
立刻返回。超时时间的单位为系统节拍周期,常量portTICK_PERIOD_MS用于辅助计算真
实的时间,单位为ms。如果INCLUDE_vTaskSuspend设置成1,并且指定延时为
portMAX_DELAY将导致任务无限阻塞(没有超时)。 | |
返回值 | 发送消息成功返回pdTRUE,否则返回errQUEUE_FULL。 |
4、xQueueSendToFrontFromISR()与xQueueGenericSendFromISR():
函数 | BaseType_t xQueueSendToFrontFromISR ( QueueHandle_txQueue,
constvoid *pvItemToQueue,
BaseType_t*pxHigherPriorityTaskWoken); | |
功能 | 在中断服务程序中用于向消息队列队首发送一个消息。 | |
参数 | xQueue | 队列句柄。 |
pvItemToQueue | 指针,指向要发送到队首的消息。 | |
pxHigherPriorityTaskWoken | 如果入队导致一个任务解锁,并且解锁的任务优先级高于当前被中断的
任务,则将*pxHigherPriorityTaskWoken设置成pdTRUE,然后在中断退
出前需要进行一次上下文切换,去执行被唤醒的优先级更高的任务。从
FreeRTOS V7.3.0起,pxHigherPriorityTaskWoken作为一个可选参数,
可以设置为NULL。 | |
返回值 | 消息发送成功返回pdTRUE,否则返回errQUEUE_FULL。 |
五、从消息队列读取消息函数:
1、xQueueReceive()与 xQueueGenericReceive():
函数原型 | BaseType_t xQueueReceive( QueueHandle_txQueue,
void *pvBuffer,
TickType_txTicksToWait); | |
功能 | 用于从一个队列中接收消息,并把接收的消息从队列中删除。 | |
参数 | xQueue | 队列句柄。 |
pvBuffer | 指针,指向接收到要保存的数据。 | |
xTicksToWait | 队列空时,阻塞超时的最大时间。如果该参数设置为0,函数立刻返回。超时时间的单位为系
统节拍周期,常量portTICK_PERIOD_MS用于辅助计算真实的时间,单位为ms。如果
INCLUDE_vTaskSuspend设置成1,并且指定延时为portMAX_DELAY将导致任务无限阻塞。 | |
返回值 | 队列项接收成功返回pdTRUE,否则返回pdFALSE。 |
static void Receive_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 pdPASS */
uint32_t r_queue; /* 定义一个接收消息的变量 */
while (1) {
xReturn = xQueueReceive( Test_Queue, /* 消息队列的句柄 */
&r_queue, /* 发送的消息内容 */
portMAX_DELAY); /* 等待时间 一直等 */
if (pdTRUE== xReturn)
printf("本次接收到的数据是: %d\n\n",r_queue);
else
printf("数据接收出错,错误代码: 0x%lx\n",xReturn);
}
}
2、xQueuePeek():
其实这个函数与xQueueReceive()函数的实现方式一样,连使用方法都一样,只不过xQueuePeek()函数接收消息完毕不会删除消息队列中的消息而已。
#define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) \
xQueueGenericReceive( ( xQueue ), ( pvBuffer ), \
( xTicksToWait ), pdFALSE )
#define xQueuePeek( xQueue, pvBuffer, xTicksToWait ) \
xQueueGenericReceive( ( xQueue ), ( pvBuffer ), \
( xTicksToWait ), pdTRUE )
3、xQueueReceiveFromISR()与 xQueuePeekFromISR():
QueueHandle_t xQueue;
/* 创建一个队列,并往队列里面发送一些数据 */
void vAFunction( void *pvParameters )
{
char cValueToPost;
const TickType_t xTicksToWait = ( TickType_t )0xff;
/* 创建一个可以容纳 10 个字符的队列 */
xQueue = xQueueCreate( 10, sizeof( char ) );
if ( xQueue == 0 ) {
/* 队列创建失败 */
}
/* ... 任务其他代码 */
/* 往队列里面发送两个字符
如果队列满了则等待 xTicksToWait 个系统节拍周期*/
cValueToPost = 'a';
xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );
cValueToPost = 'b';
xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );
/* 继续往队列里面发送字符
当队列满的时候该任务将被阻塞*/
cValueToPost = 'c';
xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );
}
/* 中断服务程序:输出所有从队列中接收到的字符 */
void vISR_Routine( void )
{
BaseType_t xTaskWokenByReceive = pdFALSE;
char cRxedChar;
while ( xQueueReceiveFromISR( xQueue,
( void * ) &cRxedChar,
&xTaskWokenByReceive) ) {
/* 接收到一个字符,然后输出这个字符 */
vOutputCharacter( cRxedChar );
/* 如果从队列移除一个字符串后唤醒了向此队列投递字符的任务,
那么参数 xTaskWokenByReceive 将会设置成 pdTRUE,这个循环无论重复多少次
仅会有一个任务被唤醒 */
}
if ( xTaskWokenByReceive != pdFALSE ) {
/* 我们应该进行一次上下文切换,当 ISR 返回的时候则执行另外一个任务 */
/* 这是一个上下文切换的宏,不同的处理器,具体处理的方式不一样 */
taskYIELD ();
}
}