【FreeRTOS初探】队列管理

队列管理

FreeRTOS中,所有的通讯与同步机制都是基于队列实现的。

测试代码地址

github.com/CherryXiuHuaWoo/WIN32-MSVC-FreeRTOS-

队列的特性

数据存储

队列可以保存有限个具有确定长度数据单元

在队列创建时,需要设定:

  • 队列深度——队列可以保存的最大单元数目
  • 每个单元的大小

特性:
- FIFO(先进先出)——数据由队列尾部(Tail)写入,由队列头部(Head)读出
- 往队列写入数据——通过字节拷贝把数据复制存储到队列中。
- 从队列读出数据——把队列中的数据拷贝删除
- 队列是具有自己独立权限内核对象,并不属于或赋予任何任务所有任务都可以向同一个队列写入和读出
-

读队列时阻塞

  • 某个任务试图读一个队列时,可以指定一个阻塞超时时间
  • 在阻塞超时时间内,若队列为空,该任务保持阻塞态等待数据有效。
  • 当其他任务或中断往队列中写入数据,该任务自动由阻塞态转移为就绪态
  • 对单个队列而言,可能有多个任务处于阻塞态,以等待队列数据有效。这种情况下,一旦队列数据有效,只会有一个任务会被解除阻塞——这个任务是所有等待任务中优先级最高的任务。如果所有等待任务的优先级相同,则被接触阻塞的任务将是等待最久的任务。

写队列时阻塞

  • 任务可在写队列时,指定一个阻塞超时时间
  • 阻塞超时时间是当被写队列已满时,任务进入阻塞态以等待队列空间有效的最长时间
    这里写图片描述

2、如何创建一个队列?

创建队列 xQueueCreate()

  1. 函数功能:创建一个队列,并返回一个xQueueHandle句柄,以便对其创建的队列进行引用。
  2. 使用方法:当创建队列,FreeRTOS从堆空间中分配内存空间,用于存储队列数据结构本身以及队列中包含的数据单元。若内存堆没有足够空间来创建队列,xQueueCreate将返回NULL。
  3. 函数原型
xQueueHandle xQueueCreate( 
    unsigned portBASE_TYPE uxQueueLength,
    unsigned portBASE_TYPE uxItemSize );

参数:
uxQueueLength——队列能够存储的最大单元数目,即队列深度
uxItemSize——队列数据单元的长度,以字节为单位

3、队列如何管理器数据?

4、如何向队列发送数据?

xQueueSendToBack

作用:用于将数据发送到队列尾部
函数原型:

portBASE_TYPE xQueueSendToBack( xQueueHandle xQueue,
                                 const void pvItemToQueue,
                                 portTickType xTicksToWait);

参数:

  • xQueue——目标队列的句柄,即为调用xQueueCreate()创建改队列时的返回值。
  • pvItemToQueue——发送数据的指针。指向将要复制到目标队列中的数据单元。
  • xTicksToWait——阻塞超时时间。若在发送时队列已满,改时间是任务处于阻塞等待队列空间有效的最长等待时间。若把xTicksToWait设置为portMAX_DELAY,并且在FreeRTOSConfig.h中设定INCLUDE_vTaskSuspend为1,那阻塞等待将没有超时限制。
  • 返回值:pdPASS(成功)errQUEUE_FULL(队列已满无法将数据写入)。

xQueueSendToFront

  • 作用:用于将数据发送到队列队首
  • 函数原型:
portBASE_TYPE xQueueSendToFront( xQueueHandle xQueue,
                                 const void pvItemToQueue,
                                 portTickType xTicksToWait);

5、如何从队列接收数据?

xQueueReceive()

  1. 作用:从队列中接收数据单元,接收到的单元同时会从队列中删除
  2. 函数原型:
portBASE_TYPE xQueueReceive( xQueueHandle xQueue,
                             const void *pvBuffer,
                             portTickType xTicksToWait);

参数:
xQueue——被读队列的句柄
pvBuffer——接收缓存指针
xTicksToWait——阻塞超时时间
返回值:pdPASS(成功),errQUEUE_FULL(队列已空读不到任何数据)

xQueuePeek()

  • 作用:从队列中接收数据单元,不会从队列中删除接收到的单元
  • 函数原型:
portBASE_TYPE xQueuePeek( xQueueHandle xQueue,
                             const void *pvBuffer,
                             portTickType xTicksToWait);

6、队列阻塞是什么?

uxQueueMessagesWaiting

作用:查询队列当前有效数据单元个数
函数原型:

unsigned portBASE_TYPE uxQueueMessageWaiting( xQueueHandle xQueue);

参数:
xQueue——被查询队列的句柄。
返回值:当前队列中保存的数据单元个数,返回0表示队列为空。

7、往队列发送和从队列接收时,任务优先级有什么影响?

8、队列读写实例

/*以下为main部分*/
    /* Create the queue. */
    xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ) );

    if( xQueue != NULL )
    {
        /* Start the two tasks as described in the comments at the top of this
        file. */
        xTaskCreate(vSenderTask, "SenderTask100", 100, (void *) 100, 1, &xTask1Handle);
        xTaskCreate(vSenderTask, "SenderTask200", 100, (void *)200, 1, &xTask1Handle);
        xTaskCreate(vSenderTask, "SenderTask300", 100, (void *)300, 1, &xTask1Handle);
        xTaskCreate(vReciverTask, "ReciverTask", 100, NULL, 2, &xTask1Handle);

        /* Start the tasks and timer running. */
        vTaskStartScheduler();
    }

/*以下为user文件*/
extern QueueHandle_t xQueue;

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

    lValueToSend = (long)pvParameters;

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

        if (xStatus != pdPASS)
        {
            printf("Could not send to the queue.\r\n");
        }

        /*Allow other tasks send data.taskYIELD API Fuction: inform Scheduler of joining other task.*/
        taskYIELD();
    }
}

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

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

        xStatus = xQueueReceive(xQueue, &lReceivedValue, xTicksToWait);
        if (xStatus == pdPASS)
        {
            printf("Received= %d\r\n", lReceivedValue);
        }
        else
        {
            printf(" Could not receive from the queue.\r\n");
        }
    }
}

这里写图片描述

taskYIELD()

作用通知调度器立即进行任务切换,而不必等待当前任务的时间片耗尽。当任务调用了taskYIELD()等效于自愿放弃运行态
使用方法:写队列在每次循环中都调用taskYIELD()。

9、使用队列传递复合数据类型

一个任务从单个队列中接收来自多个发送源的数据是经常的事情。
接收方接收到数据之后,需要知道数据的来源,并根据数据的来源来决定下一步如何处理。
一个简单的方式就是利用队列传递结构体。结构体成员就包含了数据信息和来源信息
这里写图片描述

extern QueueHandle_t xQueue;
extern TaskHandle_t xTask1Handle;
extern TaskHandle_t xTask2Handle;

xData xStructsToSend[2] = 
{
    { 100, 1},
    { 200, 2}
};
void vSenderTask(void *pvParameters)
{
    xData *lValueToSend;
    portBASE_TYPE xStatus;

    lValueToSend = (xData *)pvParameters;

    while (1)
    {
        xStatus = xQueueSendToBack(xQueue, lValueToSend, 0);

        if (xStatus != pdPASS)
        {
            printf("Could not send to the queue.\r\n");
        }

        /*Allow other tasks send data.taskYIELD API Fuction: inform Scheduler of joining other task.*/
        taskYIELD();
    }
}

void vReciverTask(void *pvParameters)
{
    xData xReceivedStructure;
    portBASE_TYPE xStatus;
    const portTickType xTicksToWait = 100 / portTICK_RATE_MS;

    while (1)
    {
        if (uxQueueMessagesWaiting(xQueue) != 0)
        {
            printf("Queue should have been empty\r\n");

        }

        xStatus = xQueueReceive(xQueue, &xReceivedStructure, xTicksToWait);
        if (xStatus == pdPASS)
        {
            printf("From Sender %d = %d\r\n", xReceivedStructure.ucSource, xReceivedStructure.ucValue);
        }
        else
        {
            printf(" Could not receive from the queue.\r\n");
        }
    }

}

/*main部分*/
extern xData xStructsToSend[2];

/*** SEE THE COMMENTS AT THE TOP OF THIS FILE ***/
void main_blinky( void )
{
const TickType_t xTimerPeriod = mainTIMER_SEND_FREQUENCY_MS;

    /* Create the queue. */
    xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof(xData) );

    if( xQueue != NULL )
    {
        /* Start the two tasks as described in the comments at the top of this
        file. */
        xTaskCreate(vSenderTask, "SenderTask100", 100, &(xStructsToSend[0]), 1, NULL);
        xTaskCreate(vSenderTask, "SenderTask200", 100, &(xStructsToSend[1]), 1, NULL);

        xTaskCreate(vReciverTask, "ReciverTask", 100, NULL, 2, NULL);



        /* Start the tasks and timer running. */
        vTaskStartScheduler();
    }

这里写图片描述

工作于大型数据单元

如果队列存储的数据单元尺寸较大,最好利用队列来传递数据的指针,而不是数据本身在队列上一字节一字节地拷贝进或拷贝出。
注意:

  1. 指针指向的内存空间的所有权必须明确。共享内存在其指针发送到队列之前,其内容只允许被发送任务访问;共享内存指针从队列中被读出之后,其内容亦只允许被接收任务访问。
  2. 指针指向的内存空间必须有效。如果指针指向的内存空间是动态分配,应该有一个任务对其进行内存释放。当这段内存空间被释放后就不应该有任何一个任务再访问这段空间。
  3. 切忌用指针访问任务栈上分配的空间。因为当栈帧发生变化后,栈上的数据将不再有效。

猜你喜欢

转载自blog.csdn.net/xiuhua_wu/article/details/79501020
今日推荐