跟着野火学FreeRTOS:第二段(队列管理)

     队列(

Q

u

e

u

e

s

Queues

Queues)是

F

r

e

e

R

T

O

S

FreeRTOS

FreeRTOS中的一种数据结构,这种数据结构提供了一种任务和任务之间,任务和中断之间的通信机制。队列可以存储一定有限数量的固定大小(

u

x

I

t

e

m

S

i

z

e

uxItemSize

uxItemSize)的数据项,这个 一定有限数量的值就是队列的长度

u

x

Q

u

e

u

e

L

e

n

g

t

h

uxQueueLength

uxQueueLength,

u

x

Q

u

e

u

e

L

e

n

g

t

h

uxQueueLength

uxQueueLength和

u

x

I

t

e

m

S

i

z

e

uxItemSize

uxItemSize的值都在是创建队列的时候指定好的,队列除了存储数据的空间以外还有一部分空间用来记录队列的相关信息,比如前面提到的队列的长度以及数据项的大小,该部分信息用一个结构体来定义如图2所示。队列的大致的结构,如图1所示,图1中队列的长度位

n

+

1

n+1

n+1。记录队列的相关信息的空间位于分配给队列的空间的最前面,后面接着就是存储队列中实际数据的空间。

 


图1.

 


图2.

     图3到图6用图示的形式简单的介绍了一下利用队列进行通信的流程,这里

T

a

s

k

B

Taskquad B

TaskB接收

T

a

s

k

A

Taskquad A

TaskA发送的数据。图3是队列刚刚建立好的时候,队列里面没有通信的数据,图4和图5分别是

T

a

s

k

A

Taskquad A

TaskA向队列里面发送了数据10接着发送了数据20之后的情况,图6是

T

a

s

k

B

Taskquad B

TaskB接收了

T

a

s

k

A

Taskquad A

TaskA第一次发送的数据10之后队列的情况。一般情况下队列是一种

F

I

F

O

(

F

i

r

s

t

I

n

F

i

r

s

t

O

u

t

)

FIFO(Firstquad Inquad Firstquad Out)

FIFO(FirstInFirstOut)类型的数据结构,往队列里面发送数据的时候会将数据发送到队列的尾部(

T

a

s

k

A

Taskquad A

TaskA第二次写入的数据20位于第一次写入的数据10的后面),从队列里面接收数据的时候会从队列的头部读取数据(在图5中此时队列里面已经有两个数据,

T

a

s

k

B

Taskquad B

TaskB此时去读取队列里面数据的时候读取到的是数据10),但是

F

r

e

e

R

T

O

S

FreeRTOS

FreeRTOS也支持往队列里面发送数据的时候将数据发送到队列的头部,这样的话写入头部的数据就可以优先被接收其它接收数据的任务先接收到,这种操作可以用于一些需要紧急处理的数据。

 


图3.

 


图4.

 


图5.

 


图6.

     还有就是发送到队列里面的数据是直接将要发送的数据 复制一份到图1队列里面的

x

I

t

e

m

x

xItemquad x

xItemx里面,而不是将要发送的数据的指针存储到队列里面。
     同一个队列可以被多个任务或中断服务程序写入数据或读出数据,比较常见的情况是多个任务或中断服务程序对同一个队列进行数据写入,但是多个任务或中断服务程序对同一个队列进行读出数据的情况比较少见。
     

F

r

e

e

R

T

O

S

FreeRTOS

FreeRTOS里面的读队列操作支持一个可选的 阻塞时间参数,当这个参数不为0的时候,如果任务在尝试读队列的时候,队列为空,这时任务会进入阻塞状态等待其它任务或中断服务程序往这个队列里面写入数据,如果在参数配置的时间之内有其它任务或中断服务程序往这个队列里面写入了数据,那么这个处于阻塞状态的任务会立即转换为就绪态,假设在参数配置的时间之内没有其它任务或中断服务程序往这个队列里面写入了数据,这个处于阻塞状态的任务也会自动转换为就绪态。假如有多个任务因为尝试读取同一队列(这里多个任务的阻塞时间参数都不为0),但是队列为空,而都进入到了阻塞状态,假如在它们的阻塞时间参数超时之前有其它任务或中断服务程序往这个队列里面写入了数据,此时这些任务里面优先级最高的任务将转换为就绪态来准备读取队列里面的数据,如果这里多个任务的优先级相同,那么等待时间最久的任务将优先转换为就绪态来准备读取队列里面的数据。
     

F

r

e

e

R

T

O

S

FreeRTOS

FreeRTOS里面的写队列操作也支持一个可选的 阻塞时间参数,具体说明和读操作类似,这里就不在累述了。这里还需要注意的是

F

r

e

e

R

T

O

S

FreeRTOS

FreeRTOS中的队列操作,比如读,写,当然还有其它操作,的接口都有基本版本以及中断版本,中断版本的接口都带有

I

S

R

ISR

ISR后缀,中断版本没有阻塞时间参数,中断版本的接口只能在中断服务程序中调用
     其实队列还是比较简单的,有了以上基础再看看图7的文档中第4章

F

r

e

e

R

T

O

S

FreeRTOS

FreeRTOS官方对队列管理的介绍以及图8中第3章队列管理的相应的接口介绍就可以开始实际写代码操作了。

 


图7.

 


图8.

     其实队列还是比较简单的,有了以上基础再看看图7的文档中第4章

F

r

e

e

R

T

O

S

FreeRTOS

FreeRTOS官方对队列管理的介绍以及图8中第3章队列管理的相应的接口介绍就可以开始实际写代码操作了。下面我们来看一个简单的例子,这个例子中我们定义了三个任务一个队列,队列里面有3个数据项,其中两个任务向队列里面发送数据,一个任务从队列里面读取数据,两个发送数据的任务的优先级相同且大于接收数据的任务的优先级。因为两个发送数据的任务(阻塞时间设置为100

m

s

ms

ms)的优先级大于数据接收任务的优先级,因此轮到接收数据的任务接收数据的时候队列应该已经满了,所以接收任务的阻塞时间设置为0。对于数据发送任务只要数据接收任务读取了队列之中的一个数据项,阻塞的数据发送任务马上就会抢占数据接收任务来进行数据的发送直到队列再次满而进入阻塞状态,这时数据接收任务就开始了数据的接收。
     该例子的主要代码都在下面了,这里队列里面存储的数据项是一个包含两个元素的结构体一个用来区别发送的数据到底是来自于两个发送任务中的哪一个,另一个用来存储实际的数据。这里一个任务发送数据100,另一个任务发送数据200。

static QueueHandle_t xQueue=NULL;	

/* Define an enumerated type used to identify the source of the data. */
typedef enum
{
    Task1Sender,
    Task2Sender
} DataSource_t;
/* Define the structure type that will be passed on the queue. */
typedef struct
{
    uint8_t ucValue;
    DataSource_t eDataSource;
} Data_t;


/* Declare two variables of type Data_t that will be passed on the queue. */
static const Data_t xStructsToSend[ 2 ] =
{
    { 100, Task1Sender }, /* Used by Task 1. */
    { 200, Task2Sender }  /* Used by Task 2. */
};



 

static void vSenderTask( void *pvParameters )
{
    BaseType_t xStatus;
    const TickType_t xTicksToWait = pdMS_TO_TICKS( 100 );
    /* As per most tasks, this task is implemented within an infinite loop. */
    while(1)
    {
        /* Send to the queue.
			
           The second parameter is the address of the structure being sent. The
           address is passed in as the task parameter so pvParameters is used
           directly.
           The third parameter is the Block time - the time the task should be kept
           in the Blocked state to wait for space to become available on the queue
           if the queue is already full. A block time is specified because the
           sending tasks have a higher priority than the receiving task so the queue
           is expected to become full. The receiving task will remove items from
           the queue when both sending tasks are in the Blocked state. 
			  */
        xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );
        if( xStatus != pdPASS )
        {
            /* The send operation could not complete, even after waiting for 100ms.
               This must be an error as the receiving task should make space in the
               queue as soon as both sending tasks are in the Blocked state. */
            printf( "Could not send to the queue.
" );
        }
    }
}




static void vReceiverTask( void *pvParameters )
{
    /* Declare the structure that will hold the values received from the queue. */
    Data_t xReceivedStructure;
    BaseType_t xStatus;
    /* As per most tasks, this task is implemented within an infinite loop. */
    while(1)
    {
        /* Because it has the lowest priority this task will only run when the
        sending tasks are in the Blocked state. The sending tasks will only enter
        the Blocked state when the queue is full so this task always expects the
        number of items in the queue to be equal to the queue length, which is 3 in
        this case. */
        if( uxQueueMessagesWaiting( xQueue ) != 3 )
        {
            printf( "Queue should have been full!
" );
        }
        /* Receive from the queue.
				
           The second parameter is the buffer into which the received data will be
           placed. In this case the buffer is simply the address of a variable that
           has the required size to hold the received structure.
           The last parameter is the block time - the maximum amount of time that the
           task will remain in the Blocked state to wait for data to be available
           if the queue is already empty. In this case a block time is not necessary
           because this task will only run when the queue is full. 
				*/
        xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );
        if( xStatus == pdPASS )
        {
            /* Data was successfully received from the queue, print out the received
            value and the source of the value. */
            if( xReceivedStructure.eDataSource == Task1Sender )
            {
                printf( "From Sender 1 = %d
", xReceivedStructure.ucValue );
            }
            else
            {
                printf( "From Sender 2 = %d
", xReceivedStructure.ucValue );
            }
        }
        else
        {
            /* Nothing was received from the queue. This must be an error as this
            task should only run when the queue is full. */
            printf( "Could not receive from the queue.
" );
        }
    }
}

 
 

int main(void)
{	 
    NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4);	
    uart_init(115200);	
	
    /* The queue is created to hold a maximum of 3 structures of type Data_t. */
    xQueue = xQueueCreate( 3, sizeof( Data_t ) );
    if( xQueue != NULL )
    {
        /* Create two instances of the task that will write to the queue. The
        parameter is used to pass the structure that the task will write to the
        queue, so one task will continuously send xStructsToSend[ 0 ] to the queue
        while the other task will continuously send xStructsToSend[ 1 ]. Both
        tasks are created at priority 2, which is above the priority of the receiver. */
        xTaskCreate( vSenderTask, "Sender1", 1000, (void *) &( xStructsToSend[ 0 ] ), 2, NULL );
        xTaskCreate( vSenderTask, "Sender2", 1000, (void *) &( xStructsToSend[ 1 ] ), 2, NULL );
        /* Create the task that will read from the queue. The task is created with
        priority 1, so below the priority of the sender tasks. */
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
        /* Start the scheduler so the created tasks start executing. */
        vTaskStartScheduler();
    }
    else
    {
        /* The queue could not be created. */
    }
    /* If all is well then main() will never reach here as the scheduler will
    now be running the tasks. If main() does reach here then it is likely that
    there was insufficient heap memory available for the idle task to be created.
    Chapter 2 provides more information on heap memory management. */
    while(1);   	
}

该例子的工程代码在这里。

 


图1.