FreeRTOS 学习四:队列

1. 简介:


  1. 在多任务程序中,总要考虑任务间的通信和同步机制,在FreeRTOS中都是基于队列实现的。
  2. 虽然叫做 FIFO,但是限制不是很严格,可以在FIFO的队首写入。
  3. 队列是具有自己独立权限的内核对象,并不属于或赋予任何任务。
  4. 任务读队列的时候,可以设定阻塞等待超时时间,等待期间,一旦队列中数据有效,任务将从阻塞状态转到就绪态,等待的时间超时后将直接转到就绪态,如果多个任务都在等数据的有效,则优先级最高的任务转到就绪态,如果都是相同优先级的任务,则等待时间最长的任务转到阻塞态。
  5. 任务写队列的时候,可以设定阻塞等待时间,当队列已满时任务依然往队列中写,任务进入阻塞态,如果设定了时间,如果等待时间过完,依然没有空间,写任务将退出阻塞态,多任务情况同4的情况。
  6. 在中断中使用队列的时候,需要From_ISR后缀的函数。ISR是中断服务例程的简写。

2. 队列函数:


2.1 创建队列


typedef void * QueueHandle_t;
typedef unsigned long UBaseType_t;

QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);

QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength, UBaseType_t uxItemSize, uint8_t *pucQueueStorageBuffer, StaticQueue_t *pxQueueBuffer);
参数:
uxQueueLength,队列深度
uxItemSize,队列中元素的长度,以字节为单位
返回值:
NULL,表示没有足够的堆空间分配给队列而导致创建失败。
非NULL,表示队列创建成功。此返回值应当保存下来,以作为操作此队列的句柄。
其他说明: xQueueCreateStatic函数和xQueueCreate函数的区别:
xQueueCreate的使用需要配置 configSUPPORT_DYNAMIC_ALLOCATION = 1,默认值就是1,其中的队列要求的空间(包括队列的状态和队列元素)是从ram中的堆(heap)中自动分配
xQueueCreateStatic的使用是在编译的时候就静态的分配了ram

2.2 写入数据


typedef long BaseType_t;
typedef void * QueueHandle_t;

BaseType_t xQueueSendToBack(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait); /* 队尾插入 */
BaseType_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait); /* 功能同xQueueSendToBack相同,之所以有两个是为了和老版本的兼容 */

BaseType_t xQueueSendToToFront(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait); /* 队头插入 */

BaseType_t xQueueSendFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken); /* 中断函数中使用 */
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken);
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken);
参数:
xQueue,目标队列的句柄
pvItemToQueue,要存的数据指针
xTicksToWait,阻塞超时时间,阻塞等待的最长时间,如果此值是0,如果队列已满,立即返回,此值依然是以tick为单位,常量 portTICK_RATE_MS 用来把心跳时间单位转换为毫秒时间单位,如果此值是 portMAX_DELAY ,并且有 INCLUDE_vTaskSuspend = 1,则一直阻塞等待
pxHigherPriorityTaskWoken,状态指针,当发送到队列一起了一个任务接触阻塞状态,并且,接触阻塞状态的任务的优先级高于目前运行的优先级,此指针指向的变量 = pdTRUE,在终端退出前需要有个上下文切换,下边有个程序例子,在FreeRTOS7.3.0版本之后,这个参数是个优选项,可以被设置成NULL。
返回值:
pdPASS,数据发送成功
errQUEUE_FULL,队列已满,无法将数据写入

中断中使用队列的例子:

void vBufferISR( void )
{
char cIn;
BaseType_t xHigherPriorityTaskWoken;

    /* We have not woken a task at the start of the ISR. */
    xHigherPriorityTaskWoken = pdFALSE;

    /* Loop until the buffer is empty. */
    do
    {
        /* Obtain a byte from the buffer. */
        cIn = portINPUT_BYTE( RX_REGISTER_ADDRESS );

        /* Post the byte. */
        xQueueSendFromISR( xRxQueue, &cIn, &xHigherPriorityTaskWoken );

    } while( portINPUT_BYTE( BUFFER_COUNT ) );

    /* Now the buffer is empty we can switch context if necessary. */
    if( xHigherPriorityTaskWoken )
    {
        /* Actual macro used here is port specific. */
        taskYIELD_FROM_ISR ();
    }
}

2.3 读出数据


BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait); /* 从队列头中拷贝出数据,并把队列头的数据删除 */

BaseType_t xQueuePeek(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait); /* 单独的从队列头拷贝数据,不删除源数据 */

BaseType_t xQueueReceiveFromISR(QueueHandle_t   xQueue, void    *pvBuffer, BaseType_t * const pxHigherPriorityTaskWoken); /* 中断中使用 */
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue, void * const pvBuffer);
参数:
xQueue,目标队列的句柄
pvBuffer,接收缓存指针,指向一段内存区域,区域的大小应当足够保存一个数据单元
xTicksToWait,阻塞超时时间,以系统心跳为单位,可以 portTICK_RATE_MS 将心跳时间单位转换成毫秒单位
pxHigherPriorityTaskWoken,一个任务可能正在等待队列中有效数据的到来,如果一个此函数导致等待的队列退出等待状态,这个指针指向的变量变为pdTRUE,此处也有个例子
返回值:
pdPASS,读数据成功
errQUEUE_FULL,读数据不成功

例子:

QueueHandle_t xQueue;

/* Function to create a queue and post some values. */
void vAFunction( void *pvParameters )
{
char cValueToPost;
const TickType_t xTicksToWait = ( TickType_t )0xff;

    /* Create a queue capable of containing 10 characters. */
    xQueue = xQueueCreate( 10, sizeof( char ) );
    if( xQueue == 0 )
    {
        /* Failed to create the queue. */
    }

    /* ... */

    /* Post some characters that will be used within an ISR.  If the queue
    is full then this task will block for xTicksToWait ticks. */
    cValueToPost = 'a';
    xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );
    cValueToPost = 'b';
    xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );

    /* ... keep posting characters ... this task may block when the queue
    becomes full. */

    cValueToPost = 'c';
    xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );
}

/* ISR that outputs all the characters received on the queue. */
void vISR_Routine( void )
{
BaseType_t xTaskWokenByReceive = pdFALSE;
char cRxedChar;

    while( xQueueReceiveFromISR( xQueue,
                                ( void * ) &cRxedChar,
                                &xTaskWokenByReceive) )
    {
        /* A character was received.  Output the character now. */
        vOutputCharacter( cRxedChar );

        /* If removing the character from the queue woke the task that was
        posting onto the queue xTaskWokenByReceive will have been set to
        pdTRUE.  No matter how many times this loop iterates only one
        task will be woken. */
    }

    if( xTaskWokenByReceive != pdFALSE )
    {
        /* We should switch context so the ISR returns to a different task.
        NOTE:  How this is done depends on the port you are using.  Check
        the documentation and examples for your port. */
        taskYIELD ();
    }
}

2.4 删除、复位操作


void vQueueDelete( QueueHandle_t xQueue );

BaseType_t xQueueReset( QueueHandle_t xQueue );
参数:
xQueue,目标队列的句柄
xQueueReset的返回值:
一直返回 pdPASS,读数据成功
说明:
删除操作是释放掉用于储存里边元素的内存空间
复位操作是将队列复位到原始空的状态

2.5 查询操作


UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue ); /* 返回队列中可用Message的数目 */
UBaseType_t uxQueueMessagesWaitingFromISR( QueueHandle_t xQueue ); /* 中断中使用 */

UBaseType_t uxQueueSpacesAvailable( QueueHandle_t xQueue ); /* 返回队列中空余的空间 */

BaseType_t xQueueIsQueueEmptyFromISR( const QueueHandle_t xQueue ); /* 在队列中查询队列空不空 */

BaseType_t xQueueIsQueueFullFromISR( const QueueHandle_t xQueue ); /* 在队列中查询队列满没满 */
参数:
xQueue,目标队列句柄
xQueueIsQueueEmptyFromISR 返回值:
非pdFALSE,队列空
pdFALSE,队列不空
xQueueIsQueueFullFromISR 返回值:
非pdFALSE,队列满
pdFALSE,队列不满

2.6 强制队尾写


BaseType_t xQueueOverwrite(QueueHandle_t xQueue, const void *pvItemToQueue);
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken); /* 中断中使用 */
参数:
xQueue,目标队列
pvItemToQueue,指向要插入的元素的指针
pxHigherPriorityTaskWoken,如果此次操作引起了一个比当前任务优先级高的任务从阻塞态到非阻塞态,这个指针指向的内容变成pdTRUE,相应的应该在退出中断之前有个任务上下文切换的操作

3. 例子:


3.1 一个简单的FIFO的例子:


程序说明

  1. 两个优先级相同的任务,一直向队列尾中插入数据
  2. 一个优先级高的任务一直读队列头

程序:

static void vSenderTask( void *pvParameters )
{
    long lValueToSend;
    portBASE_TYPE xStatus;

    lValueToSend = ( long ) pvParameters;

    for( ;; )
    {
        xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );

        if( xStatus != pdPASS )
        {
            vPrintString( "Could not send to the queue.\r\n" );
        }
        /* 这里需要注意,这里不会等待时间片耗尽,马上通过调度器切换到其他任务 */
        taskYIELD(); 
    }
}

static void vReceiverTask( void *pvParameters )
{
    long lReceivedValue;
    portBASE_TYPE xStatus;
    const portTickType xTicksToWait = 100 / portTICK_RATE_MS;

    for( ;; )
    {
        if( uxQueueMessagesWaiting( xQueue ) != 0 )
        {
            vPrintString( "Queue should have been empty!\r\n" );
        }

        xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );

        if( xStatus == pdPASS )
        {
            vPrintStringAndNumber( "Received = ", lReceivedValue );
        }
        else
        {
            vPrintString( "Could not receive from the queue.\r\n" );
        }
    }
}

xQueueHandle xQueue;

int main( void )
{
    xQueue = xQueueCreate( 5, sizeof( long ) );

    if( xQueue != NULL )
    {
        xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
        xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );

        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );

        vTaskStartScheduler();
    }
    else
    {
        /* The queue could not be created. */ 
    }
    for( ;; );
}

调度的图表表示如下:

这里写图片描述

执行结果如下:

这里写图片描述

3.2 工作于大型数据单元


如果队列存储的数据单元尺寸较大,那最好是利用队列来传递数据的指针而不是对数据本身在队列上一字节一字节地拷贝进或拷贝出。传递指针无论是在处理速度上还是内存空间利用上都更有效。但是,当你利用队列传递指针时,一定要十分小心地做到以下几点:

  1. 指针指向的内存空间不能同时被两个任务修改,原则上,共享内存在其指针发送到队列之前,其内容只允许被发送任务访问;共享内存指针从队列中被读出之后,其内容亦只允许被接收任务访问。
  2. 指针指向的内存空间必须有效,一旦空间被释放,不应该再访问。
  3. 不要用指针访问任务栈上分配的空间,因为当栈发生变化后,栈上的数据将不再有效。
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值