在FreeRTOS中,队列是实现任务之间同步、互斥和通信的一种重要方法(其他的实现方法有:任务通知、事件组、信号量、互斥量)。
任何任务都可以向队列里存放任何数据,任何任务也可以从队列里读取数据,实现不同任务之间的通信。
1
队列特性
队列的数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾部,读数据时从头部读,逻辑顺序如下图所示。
使用队列传输数据时有两种方法:
拷贝:把数据、把变量的值复制进队列里
引用:把数据、把变量的地址复制进队列里
FreeRTOS中的队列一般都使用拷贝的方式传输数据,局部变量的值可以发送到队列中,后续即使函数退出、局部变量被回收,也不会影响队列中的数据,发送任务、接收任务解耦时,接收任务不需要知道这数据是谁的、也不需要发送任务来释放数据。
如果数据实在太大,还是可以使用队列传输它的地址。
2
队列函数
1.创建
队列的创建有两种方法:动态分配内存、静态分配内存。
一般都用动态分配内存的方法,使用函数:xQueueCreate()
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
参数解释:
uxQueueLength :队列长度
uxItemSize:每个数据的大小,以字节为单位
返回值:非0:成功,返回句柄,以后使用句柄来操作队列;NULL:失败,因为内存不足
2.删除
删除队列的函数为 vQueueDelete() ,只能删除使用动态方法创建的队列,它会释放内存。
void vQueueDelete( QueueHandle_t xQueue );
参数解释:
xQueue:队列句柄
3.写队列
可以把数据写到队列头部,也可以写到尾部,这些函数有两个版本:在任务中使用、在 ISR 中使用。
在任务中使用:
BaseType_t xQueueSend( QueueHandle_t xQueue,const void *pvItemToQueue,TickType_t xTicksToWait );
在ISR中使用:
BaseType_t xQueueSendToBackFromISR( QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken );
参数解释:
xQueue :队列句柄,要写哪个队列
pvItemToQueue : 数据指针,这个数据的值会被复制进队列
xTicksToWait :如果队列满则无法写入新数据,可以让任务进入阻塞状态,xTicksToWait表示阻塞的最大时间(Tick Count)。如果被设为0,无法写入数据时函数会立刻返回;如果被设为portMAX_DELAY,则会一直阻塞直到有空间可写
返回值:pdPASS:数据成功写入了队列;errQUEUE_FULL:写入失败,因为队列满了。
4.读队列
使用 xQueueReceive() 函数读队列,读到一个数据后,队列中该数据会被移除。这个函数有两个版 本:在任务中使用、在ISR 中使用。
BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait );
BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue, void *pvBuffer, BaseType_t *pxTaskWoken );
参数解释:
xQueue :队列句柄,要写哪个队列
pvBuffffer: bufer 指针,队列的数据会被复制到这个 buffer
xTicksToWait :如果队列空则无法读出数据,可以让任务进入阻塞状态,xTicksToWait表示阻塞的最大时间(Tick Count)。如果被设为0,无法读出数据时函数会立刻返回;如果被设为portMAX_DELAY,则会一直阻塞直到有数据可写
返回值:pdPASS:从队列读出数据入;errQUEUE_EMPTY:读取失败,因为队列空了。
5.其他
复位:队列刚被创建时,里面没有数据;使用过程中可以调用 xQueueReset() 把队列恢复为初始状态。
/*
pxQueue : 复位哪个队列;
* 返回值: pdPASS(必定成功)
*/
BaseType_t xQueueReset( QueueHandle_t pxQueue);
查询:可以查询队列中有多少个数据、有多少空余空间。
/** 返回队列中可用数据的个数 */
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
/** 返回队列中可用空间的个数 */
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );
覆盖:当队列长度为 1 时,可以使用 xQueueOverwrite() 或 xQueueOverwriteFromISR() 来覆盖数据。注意,队列长度必须为1。当队列满时,这些函数会覆盖里面的数据,这也以为着这些函数不会被阻塞。
/* 覆盖队列
* xQueue: 写哪个队列
* pvItemToQueue: 数据地址
* 返回值: pdTRUE表示成功, pdFALSE表示失败
*/
BaseType_t xQueueOverwrite(QueueHandle_t xQueue, const void * pvItemToQueue );
BaseType_t xQueueOverwriteFromISR( QueueHandle_t xQueue, const void * pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken );
偷看:如果想让队列中的数据供多方读取,也就是说读取时不要移除数据,要留给后来人。那么可以使用' 窥 视' ,也就是 xQueuePeek() 或 xQueuePeekFromISR() 。这些函数会从队列中复制出数据,但是不移除数据。这也意味着,如果队列中没有数据,那么' 偷看 ' 时会导致阻塞;一旦队列中有数据,以后每次 ' 偷看' 都会成功。
/* 偷看队列
* xQueue: 偷看哪个队列
* pvItemToQueue: 数据地址, 用来保存复制出来的数据
* xTicksToWait: 没有数据的话阻塞一会
* 返回值: pdTRUE表示成功, pdFALSE表示失败
*/
BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait );
BaseType_t xQueuePeekFromISR( QueueHandle_t xQueue, void *pvBuffer, );
3
队列实验
代码:
/* vSenderTask被用来创建2个任务,用于写队列
* vReceiverTask被用来创建1个任务,用于读队列
*/
static void vSenderTask( void *pvParameters );
static void vReceiverTask( void *pvParameters );
/*-----------------------------------------------------------*/
/* 队列句柄, 创建队列时会设置这个变量 */
QueueHandle_t xQueue;
int main( void )
{
prvSetupHardware();
/* 创建队列: 长度为5,数据大小为4字节(存放一个整数) */
xQueue = xQueueCreate( 5, sizeof( int32_t ) );
if( xQueue != NULL )
{
/* 创建2个任务用于写队列, 传入的参数分别是100、200
* 任务函数会连续执行,向队列发送数值100、200
* 优先级为1
*/
xTaskCreate( vSenderTask, 'Sender1', 1000, ( void * ) 100, 1, NULL );
xTaskCreate( vSenderTask, 'Sender2', 1000, ( void * ) 200, 1, NULL );
/* 创建1个任务用于读队列
* 优先级为2, 高于上面的两个任务
* 这意味着队列一有数据就会被读走
*/
xTaskCreate( vReceiverTask, 'Receiver', 1000, NULL, 2, NULL );
/* 启动调度器 */
vTaskStartScheduler();
}
else
{
/* 无法创建队列 */
}
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
/*-----------------------------------------------------------*/
/*-----------------------------------------------------------*/
static void vSenderTask( void *pvParameters )
{
int32_t lValueToSend;
BaseType_t xStatus;
/* 我们会使用这个函数创建2个任务
* 这些任务的pvParameters不一样
*/
lValueToSend = ( int32_t ) pvParameters;
/* 无限循环 */
for( ;; )
{
/* 写队列
* xQueue: 写哪个队列
* &lValueToSend: 写什么数据? 传入数据的地址, 会从这个地址把数据复制进队列
* 0: 不阻塞, 如果队列满的话, 写入失败, 立刻返回
*/
xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );
if( xStatus != pdPASS )
{
printf( 'Could not send to the queue.rn' );
}
}
}
/*-----------------------------------------------------------*/
static void vReceiverTask( void *pvParameters )
{
/* 读取队列时, 用这个变量来存放数据 */
int32_t lReceivedValue;
BaseType_t xStatus;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
/* 无限循环 */
for( ;; )
{
/* 读队列
* xQueue: 读哪个队列
* &lReceivedValue: 读到的数据复制到这个地址
* xTicksToWait: 如果队列为空, 阻塞一会
*/
xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );
if( xStatus == pdPASS )
{
/* 读到了数据 */
printf( 'Received = %drn', lReceivedValue );
}
else
{
/* 没读到数据 */
printf( 'Could not receive from the queue.rn' );
}
}
}
在这个程序中,有一个接收队列数据的任务,两个发送队列数据的任务,接收队列数据的任务优先级高,先执行,但是这时队列为空,触发该任务阻塞,这时低优先级的任务交替执行,向队列中发送数据,接收任务发现队列不为空后(解除触发的事件),立刻被唤醒从队列中读取数据并打印出来,实验结果和逻辑图如下:
上一篇:STM32H5 DA证书链实战经验
下一篇:STM32基础知识:串口通信-DMA方式
推荐阅读最新更新时间:2024-11-02 10:54
设计资源 培训 开发板 精华推荐
- DER-713 - 使用 InnoSwitch3-EP PowiGaN 和 MinE-CAP 的 65W 高功率密度适配器
- MC78M24CTG 24V 电流升压稳压器的典型应用
- MIC4826 EL 驱动器,用于使用 2 节碱性电池的遥控灯
- 用于照明的 MOSFET 功率驱动器
- 5V DC 至 DC 单路输出电源
- ADA4627-1BRZ-R7带保护同相放大器典型应用电路
- 带Usb收发器的Fan3988 Usb/充电器和过压检测分界器的典型应用系统
- AT32国产飞控 两层单面便于手搓版
- EVAL-ADM2482EEB5Z,ADM2482E 隔离式 RS-485 收发器评估板
- 用于便携式的 3V DC 到 DC 单输出电源
- 了解并观看是德科技汽车电子、物联网(IOT)精彩专题,下载技术文章送好礼!
- 悦读 TI DEYISUPPORT 中国工程师精彩博文,答题赢好礼喽!
- 听技术大咖侃谈Type-C 测量那些事儿—— 即刻获取能量,轻松闯关赢礼品!
- 晒出TI C2000的使用经验或优秀设计作品!
- TI 高精度实验室信号链精品课大作战——你学习我送礼!
- Intel有奖下载之七,礼品多多等你拿!
- PI LYTSwitch™-6系列IC 让你了解不知道的秘密看专题赢好礼!
- TI|痛点解锁机:你的电源设计痛点,我们懂!解锁、评论赢好礼!
- 有奖测评 | 英飞凌新品情报站:最新 5V XENSIVTM PAS CO2 传感器 套件测评