目录
2.2、xQueueSendToBack()/xQueueSend And xQueueSendToFront()
2.5、Example:Blocking when receiving from a queue
2.6、Receiving Data From Multiple Sources
2.7、Working with Large or Variable Sized Data
2.7.1、Send Different Types and Lengths of Data
2.8、Receiving From Multiple Queues
在任何的 OS 中,都需要支持任务与任务,中断与任务之间的数据传输机制,在 FreeRTOS 中,这种数据传输的方式被称之为队列(Queue);
队列是一个 FIFO 模型,在创建一个队列用于数据传递的时候,需要指定队列的长度,创建完队列,便可以使用它进行数据传递;一个简单的例子:
有两个任务 A 和 B,任务 A 将数据传递进队列,任务 B 作为接收端,从队列中获取数据:
1、下面是创建了一个长度为 5 的队列:
2、此刻任务 A 写一个数据 10 到 Queue:
3、任务 A 在写一个数据 20 到 Queue;
4、此刻任务 B 从 Queue 中读取,先读取第一个数据 10;
5、此刻 10 已经被读走,20 成为下一个即将被读取的数据:
1、Basic Feature
1.1、Access by Multiple Tasks
同一个队列可以被多个任务(或者在 ISR 中)访问;可以有多个任务对同一个 Queue 写入,也可以多个任务进行读;实际中,多个任务同时写一个 Queue 是比较常见的,多个读者是比较少见的;
1.2、Blocking on Queue Reads
当一个任务企图去读一个 Queue 的时候,可以选择指定阻塞时间;这个是什么意思呢?这个时间指的是,当这个 Queue 为空的时候,这个任务保持阻塞在读这个 Queue 的时间;因为一个任务执行的时候,很多情况是,获得到来自另一个任务(或者中断)的数据后,才得以执行,在这之前,最好是保持在阻塞状态,这样可以免得调度器在调度没用的任务;
一个任务阻塞在读 Queue 上,等待数据,当另外的任务或者 ISR 给这个 Queue 喂了数据后,这个被阻塞的任务将会从阻塞链表中移除,被加入到 Ready 任务链表;或者当指定的阻塞时间到,要读的 Queue 还是为空,那么也会解除阻塞,进入 Ready 链表;
Queue 支持多个读者,所以,如果多个任务都在读一个已经空了的 Queue,那么他们都将进入阻塞状态,
出现这种情况的时候,如果当 Queue 有数据的时候,只有一个最高优先级的任务可以解除阻塞,进入 Ready;如果这几个任务的优先级一样,那么等待数据并处于阻塞的时间最长的那个任务将被解除阻塞;
1.3、Blocking on Queue Writes
对于写来说,和读一样的,当一个任务企图去写一个 Queue 的时候,可以选择指定阻塞时间;这个时间指的是,当这个 Queue 为满的时候,这个任务保持阻塞在写这个 Queue 的时间;
一个任务阻塞在写 Queue 上,等待数据,当另外的任务或者 ISR 读走了 Queue 数据导致这个 Queue 不在是满的状态,这个被阻塞的任务将会从阻塞链表中移除,被加入到 Ready 任务链表;或者当指定的阻塞时间到,要写的 Queue 还是为满,那么也会解除阻塞,进入 Ready 链表;
Queue 支持多个写者,所以,如果多个任务都在写一个已经满了的 Queue,那么他们都将进入阻塞状态,出现这种情况的时候,一旦 Queue 有数据被读走,阻塞在写上的几个任务只有一个最高优先级的任务可以解除阻塞,进入 Ready;如果这几个任务的优先级一样,那么等待数据并处于阻塞的时间最长的那个任务将被解除阻塞;
2、Usage And APIs
2.1、xQueueCreate()
xQueueCreate() 接口用于创建一个 Queue,函数的原型为:
QueueHandle_t xQueueCreate (UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
它有两个入参和一个返回值:
uxQueueLength:Queue 中能够存储的最大的 Items 的个数;
uxItemSize:一个 Item 的大小,单位为 Bytes;
Return:如果返回 NULL,说明创建 Queue 失败;否则将返回这个 Queue 的 Handle 句柄,后面操作这个 Queue 就靠这个句柄;
2.2、xQueueSendToBack()/xQueueSend And xQueueSendToFront()
向队列中插入数据分两组 API,一组是往队列的尾部插入,使用 xQueueSendToBack()/xQueueSend() ;另一组是往队列的头部插入:xQueueSendToFront()
注意:不要在 ISR 中使用 xQueueSendToBack、xQueueSendToFront,应该使用对应的 xQueueSendToBackFromISR 和 xQueueSendToFrontFromISR ;
-
BaseType_t xQueueSendToFront( QueueHandle_t xQueue,
-
const void * pvItemToQueue,
-
TickType_t xTicksToWait );
-
BaseType_t xQueueSendToBack( QueueHandle_t xQueue,
-
const void * pvItemToQueue,
-
TickType_t xTicksToWait );
它有3个入参和一个返回值:
xQueue:队列创建时候的句柄,用于表示向哪个队列写入数据;
pvItemToQueue:放入队列的 Item 的指针;
xTicksToWait:如果队列为满,则阻塞的最大时间;当被设置为 0 的时候,不阻塞,如果队列为满,则直接返回;如果设置为 portMAX_DELAY 的话,意味着如果队列为满,则会挂起这个任务;
Return:当成功放入队列,返回 pdTRUE;否则返回 errQUEUE_FULL;
2.3、xQueueReceive()
xQueueReceive() 用于从队列中读数据;读出数据并将数据从 Queue 中删除
注意:不要在 ISR 中使用 xQueueReceive,应该使用对应的 xQueueReceiveFromISR;
它的函数原型为:
-
BaseType_t xQueueReceive( QueueHandle_t xQueue,
-
void * const pvBuffer,
-
TickType_t xTicksToWait );
它有3个入参和一个返回值:
xQueue:队列创建时候的句柄,用于表示向哪个队列写入数据;
pvBuffer:从队列读出来的数据指针;
xTicksToWait:如果队列为空,则为阻塞的最大时间;当被设置为 0 的时候,不阻塞,如果队列为空,则直接返回;如果设置为 portMAX_DELAY 的话,意味着如果队列为空,则会挂起这个任务;
Return:当成功读出队列的数据,返回 pdTRUE;否则返回 errQUEUE_EMPTY;
2.4、uxQueueMessagesWaiting()
该函数用于查询当前指定的队列中有多少个 Item;
它的函数原型是:
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );
注意:不要在 ISR 中使用 uxQueueMessagesWaiting,应该使用对应的 uxQueueMessagesWaitingFromISR;
它有1个入参和1个返回值:
xQueue:队列创建时候的句柄,用于表示查看哪个队列还有几个 Item;
Return:当前队列中还有几个 Item 元素,如果返回 0,说明队列为空;
2.5、Example:Blocking when receiving from a queue
下面这个例子是:
1、创建一个 Queue;
2、多个任务往 Queue 写数据,一个任务读数据;
-
static void vSenderTask( void *pvParameters )
-
{
-
int32_t lValueToSend;
-
BaseType_t xStatus;
-
/* Two instances of this task are created so the value that is sent to the
-
queue is passed in via the task parameter - this way each instance can use
-
a different value. The queue was created to hold values of type int32_t,
-
so cast the parameter to the required type. */
-
lValueToSend = (
int32_t ) pvParameters;
-
/* As per most tasks, this task is implemented within an infinite loop. */
-
for( ;; )
-
{
-
/* Send the value to the queue.
-
The first parameter is the queue to which data is being sent. The
-
queue was created before the scheduler was started, so before this task
-
started to execute.
-
The second parameter is the address of the data to be sent, in this case
-
the address of lValueToSend.
-
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
-
should the queue already be full. In this case a block time is not
-
specified because the queue should never contain more than one item, and
-
therefore never be full. */
-
xStatus =
xQueueSendToBack( xQueue, &lValueToSend,
0 );
-
if( xStatus != pdPASS )
-
{
-
/* The send operation could not complete because the queue was full -
-
this must be an error as the queue should never contain more than
-
one item! */
-
vPrintString(
"Could not send to the queue.\r\n" );
-
}
-
-
}
-
}
4、创建 1 个接收数据的任务:
-
static void vReceiverTask( void *pvParameters )
-
{
-
/* Declare the variable that will hold the values received from the queue. */
-
int32_t lReceivedValue;
-
BaseType_t xStatus;
-
const TickType_t xTicksToWait =
pdMS_TO_TICKS(
100 );
-
/* This task is also defined within an infinite loop. */
-
for( ;; )
-
{
-
/* This call should always find the queue empty because this task will
-
immediately remove any data that is written to the queue. */
-
if(
uxQueueMessagesWaiting( xQueue ) !=
0 )
-
{
-
vPrintString(
"Queue should have been empty!\r\n" );
-
}
-
/* Receive data from the queue.
-
The first parameter is the queue from which data is to be received. The
-
queue is created before the scheduler is started, and therefore before this
-
task runs for the first time.
-
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 data.
-
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
-
should the queue already be empty. */
-
xStatus =
xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );
-
if( xStatus == pdPASS )
-
{
-
/* Data was successfully received from the queue, print out the received
-
value. */
-
vPrintStringAndNumber(
"Received = ", lReceivedValue );
-
}
-
else
-
{
-
/* Data was not received from the queue even after waiting for 100ms.
-
This must be an error as the sending tasks are free running and will be
-
continuously writing to the queue. */
-
vPrintString(
"Could not receive from the queue.\r\n" );
-
}
-
}
-
}
5、初始化如下:
-
/* Declare a variable of type QueueHandle_t. This is used to store the handle
-
to the queue that is accessed by all three tasks. */
-
QueueHandle_t xQueue;
-
int main( void )
-
{
-
/* The queue is created to hold a maximum of 5 values, each of which is
-
large enough to hold a variable of type int32_t. */
-
xQueue =
xQueueCreate(
5,
sizeof(
int32_t ) );
-
if( xQueue !=
NULL )
-
{
-
/* Create two instances of the task that will send to the queue. The task
-
parameter is used to pass the value that the task will write to the queue,
-
so one task will continuously write 100 to the queue while the other task
-
will continuously write 200 to the queue. Both tasks are created at
-
priority 1. */
-
xTaskCreate( vSenderTask,
"Sender1",
1000, (
void * )
100,
1,
NULL );
-
xTaskCreate( vSenderTask,
"Sender2",
1000, (
void * )
200,
1,
NULL );
-
/* Create the task that will read from the queue. The task is created with
-
priority 2, so above the priority of the sender tasks. */
-
xTaskCreate( vReceiverTask,
"Receiver",
1000,
NULL,
2,
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 FreeRTOS heap memory available for the idle task to be
-
created. Chapter 2 provides more information on heap memory management. */
-
for( ;; );
-
}
首先创建了深度为 5 ,每个 Item 为 int32_t 的数据的 Queue;
创建了 2 个发送线程,优先级都为 1,Sender 1 往 Queue 中发送 100,Sender 2 往 Queue 中发送 200;阻塞时间为 0;
创建了 1 个接收线程,优先级为 2,带阻塞的读 Queue 中的数据;
在时序上看到的情况如下:
由于 Receiver 的优先级最高,并且带阻塞的读,也就是他会抢占低优先级的 Sender1 和 Sender2;也就是说,这个队列只可能有一个 Item,因为读 Queue 的任务优先级高于了写 Queue,一旦有数据,立马会被读走;
2.6、Receiving Data From Multiple Sources
在 FreeRTOS 中一个比较常见的场景是,接收来自不同 Source 的数据;接收端需要知道接收到的数据是从哪端发过来的,这样才知道怎么处理数据,有一种简单的设计方案可以来搞定这种场景,比如,可以定义一个结构体:
-
typedef
struct {
-
uint8_t ucValue;
-
DataSource_t eDataSourceID;
-
} Data_t;
我们通过定义并传递 Data 的 ID 来得知
-
typedef
enum
-
{
-
eCanBusSender,
-
eHMISender
-
} DataSource_t;
如图所示,有很多个数据的来源,都往同一个 Queue 中放置 Data_t 数据,放数据的时候,通过 eDataID 来标记数据的来源;在数据的读取那端,根据读到的 Data_t 中的 eDataID 就能够区分数据的来源;
Example:
两个发送者,一个接受者;
两个发送者的优先级一样,都为 2,接受者优先级为 1;
发送者调用 xQueueSendToBack,并参数带上阻塞时间 100ms;
接受者调用 xQueueReceive ,阻塞时间为 0(因为优先级低,所以当发送者将 Queue 塞满后,进入了阻塞,就轮到接受者执行,一旦接受者读出一个数据,那么此刻高优先级的发送者又将可以运行);
-
/* Define an enumerated type used to identify the source of the data. */
-
typedef
enum
-
{
-
eSender1,
-
eSender2
-
} 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, eSender1 },
/* Used by Sender1. */
-
{
200, eSender2 }
/* Used by Sender2. */
-
};
两个发送者的代码为:
-
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. */
-
for( ;; )
-
{
-
/* 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. */
-
vPrintString(
"Could not send to the queue.\r\n" );
-
}
-
}
-
}
接收端为:
-
static void vReceiverTask( void *pvParameters )
-
{
-
/* Declare the structure that will hold the values received from the queue. */
-
Data_t xReceivedStructure;
-
BaseType_t xStatus;
-
/* This task is also defined within an infinite loop. */
-
for( ;; )
-
{
-
/* 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 )
-
{
-
vPrintString(
"Queue should have been full!\r\n" );
-
}
-
/* 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 == eSender1 )
-
{
-
vPrintStringAndNumber(
"From Sender 1 = ", xReceivedStructure.ucValue );
-
}
-
else
-
{
-
vPrintStringAndNumber(
"From Sender 2 = ", 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. */
-
vPrintString(
"Could not receive from the queue.\r\n" );
-
}
-
}
-
}
初始化代码为:
-
int main( void )
-
{
-
/* 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, &( xStructsToSend[
0 ] ),
2,
NULL );
-
xTaskCreate( vSenderTask,
"Sender2",
1000, &( 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. */
-
for( ;; );
-
}
运行的结果为:
时序上的表现是:
最开始 t1 时刻,Sender 1 往 Queue 中放了 3 个数据(因为它优先级高)后,进入 Blocked;
t2 时刻,Sender 2 希望往 Queue 放数据,结果已经满了,直接进入 Blocked;
t3 时刻,此刻优先级最低的 Receiver 得以执行,读出一个 Item;
t4 时刻,由于 Sender 1 和 Sender 2 都阻塞在写 Queue,此刻 Queue 不为满,所以 Sender 1 得以执行,并往 Queue 中写入一个 Item;
接着就是 Receiver---Sender 2---Receiver---Sender 1---.......
2.7、Working with Large or Variable Sized Data
上面说的使用 Queue 传输,适用于传输小额数据,如果要传输大数据或者变长数据,官方推荐使用另一种方式:传递数据的指针,避免直接传数据进行 Copy;
如果使用这种方式来弄,那么需要注意一下两点:
1、当通过指针在任务之间共享内存时,必须确保,任务不会同时修改内存内容或执行任何其他操作,否则可能导致内存内容无效或不一致。正常情况下,在读共享内存的任务读走有效数据之前,只有发送者才允许取访问该共享内存;
2、如果指针指向的内存是动态分配的,或者从已经准备好的内存池中获取的,那么需要有一个任务去释放他们;被释放后,不允许在访问;
注意:不要使用任务堆栈的指针。堆栈帧更改后,数据将无效。
Example:
创建一个 Queue,可以保存 5 个指针;
发送方:分配内存,并填充,然后发送指针到 Queue
接收放:获取指针,获得指针指向的数据
-
/* Declare a variable of type QueueHandle_t to hold the handle of the queue being created. */
-
QueueHandle_t xPointerQueue;
-
/* Create a queue that can hold a maximum of 5 pointers, in this case character pointers. */
-
xPointerQueue =
xQueueCreate(
5,
sizeof(
char * ) );
Sender 分配内存,发送字符串指针到 Queue;
-
/* A task that obtains a buffer, writes a string to the buffer, then sends the address of the
-
buffer to the queue created in Listing 52. */
-
void vStringSendingTask( void *pvParameters )
-
{
-
char *pcStringToSend;
-
const
size_t xMaxStringLength =
50;
-
BaseType_t xStringNumber =
0;
-
for( ;; )
-
{
-
/* Obtain a buffer that is at least xMaxStringLength characters big. The implementation
-
of prvGetBuffer() is not shown – it might obtain the buffer from a pool of pre-allocated
-
buffers, or just allocate the buffer dynamically. */
-
pcStringToSend = (
char * )
prvGetBuffer( xMaxStringLength );
-
/* Write a string into the buffer. */
-
snprintf( pcStringToSend, xMaxStringLength,
"String number %d\r\n", xStringNumber );
-
/* Increment the counter so the string is different on each iteration of this task. */
-
xStringNumber++;
-
/* Send the address of the buffer to the queue that was created in Listing 52. The
-
address of the buffer is stored in the pcStringToSend variable.*/
-
xQueueSend( xPointerQueue,
/* The handle of the queue. */
-
&pcStringToSend,
/* The address of the pointer that points to the buffer. */
-
portMAX_DELAY );
-
}
-
}
接收端接收拿到数据指针,打印,并释放内存;
-
void vStringReceivingTask( void *pvParameters )
-
{
-
char *pcReceivedString;
-
for( ;; )
-
{
-
/* Receive the address of a buffer. */
-
xQueueReceive( xPointerQueue,
/* The handle of the queue. */
-
&pcReceivedString,
/* Store the buffer’s address in pcReceivedString. */
-
portMAX_DELAY );
-
/* The buffer holds a string, print it out. */
-
vPrintString( pcReceivedString );
-
/* The buffer is not required any more - release it so it can be freed, or re-used. */
-
prvReleaseBuffer( pcReceivedString );
-
}
-
}
2.7.1、Send Different Types and Lengths of Data
前面展示了使用同一个 Queue,不同 Source 发送到 Queue,以及变长的数据发送(通过指针);
如果将这两者结合起来,便可以做到使用一个 Queue 来接收来自不同 Source 的任何数据(变长数据)的目的(FreeRTOS+TCP TCP/IP 协议栈中有用这种方式);
下面看代码:
1、首先定义一些基本的结构,数据的 Source(eIPEvent_t)以及发送给 Queue 的 Item:
-
/* A subset of the enumerated types used in the TCP/IP stack to identify events. */
-
typedef
enum
-
{
-
eNetworkDownEvent =
0,
/* The network interface has been lost, or needs (re)connecting. */
-
eNetworkRxEvent,
/* A packet has been received from the network. */
-
eTCPAcceptEvent,
/* FreeRTOS_accept() called to accept or wait for a new client. */
-
/* Other event types appear here but are not shown in this listing. */
-
} eIPEvent_t;
-
/* The structure that describes events, and is sent on a queue to the TCP/IP task. */
-
typedef
struct
IP_TASK_COMMANDS
-
{
-
/* An enumerated type that identifies the event. See the eIPEvent_t definition above. */
-
eIPEvent_t eEventType;
-
/* A generic pointer that can hold a value, or point to a buffer. */
-
void *pvData;
-
} IPStackEvent_t;
2、网络接收的数据通过 Queue 传递给 TCP/IP 协议栈,eEventType 标记为 eNetworkRxEvent:
-
void vSendRxDataToTheTCPTask( NetworkBufferDescriptor_t *pxRxedData ) {
-
IPStackEvent_t xEventStruct;
-
-
/* Complete the IPStackEvent_t structure. The received data is stored in
-
pxRxedData. */
-
xEventStruct.eEventType = eNetworkRxEvent;
-
xEventStruct.pvData = (
void * ) pxRxedData;
-
/* Send the IPStackEvent_t structure to the TCP/IP task. */
-
xSendEventStructToIPTask( &xEventStruct );
-
}
3、Accept 事件也会给 TCP/IP 任务发送数据:
-
void vSendAcceptRequestToTheTCPTask( Socket_t xSocket ) {
-
IPStackEvent_t xEventStruct;
-
-
/* Complete the IPStackEvent_t structure. */
-
xEventStruct.eEventType = eTCPAcceptEvent;
-
xEventStruct.pvData = (
void * ) xSocket;
-
/* Send the IPStackEvent_t structure to the TCP/IP task. */
-
xSendEventStructToIPTask( &xEventStruct );
-
}
4、网络 Down 掉的事件也会发送给 TCP/IP 任务处理:
-
void vSendNetworkDownEventToTheTCPTask( Socket_t xSocket ) {
-
IPStackEvent_t xEventStruct;
-
-
/* Complete the IPStackEvent_t structure. */
-
xEventStruct.eEventType = eNetworkDownEvent;
-
xEventStruct.pvData =
NULL;
/* Not used, but set to NULL for completeness. */
-
/* Send the IPStackEvent_t structure to the TCP/IP task. */
-
xSendEventStructToIPTask( &xEventStruct );
-
}
5、在 TCP/IP 协议栈受到这些消息后,根据消息的来源,获取数据(均以指针传递),并处理:
-
IPStackEvent_t xReceivedEvent;
-
/* Block on the network event queue until either an event is received, or xNextIPSleep ticks
-
pass without an event being received. eEventType is set to eNoEvent in case the call to
-
xQueueReceive() returns because it timed out, rather than because an event was received. */
-
xReceivedEvent.eEventType = eNoEvent;
-
xQueueReceive( xNetworkEventQueue, &xReceivedEvent, xNextIPSleep );
-
/* Which event was received, if any? */
-
switch( xReceivedEvent.eEventType )
-
{
-
case eNetworkDownEvent :
-
/* Attempt to (re)establish a connection. This event is not associated with any
-
data. */
-
prvProcessNetworkDownEvent();
-
break;
-
case eNetworkRxEvent:
-
/* The network interface has received a new packet. A pointer to the received data
-
is stored in the pvData member of the received IPStackEvent_t structure. Process
-
the received data. */
-
prvHandleEthernetPacket( ( NetworkBufferDescriptor_t * )( xReceivedEvent.pvData ) );
-
break;
-
case eTCPAcceptEvent:
-
/* The FreeRTOS_accept() API function was called. The handle of the socket that is
-
accepting a connection is stored in the pvData member of the received IPStackEvent_t
-
structure. */
-
xSocket = ( FreeRTOS_Socket_t * ) ( xReceivedEvent.pvData );
-
xTCPCheckNewClient( pxSocket );
-
break;
-
/* Other event types are processed in the same way, but are not shown here. */
-
}
2.8、Receiving From Multiple Queues
前面的部分,全部都说的是,多个任务使用一个 Queue,其实 FreeRTOS 对于这种场景,还有一种实现方式,就是使用 Queue Set,就是队列集合;
Queue Set 允许用户定义对个 Queue,并且调用 FreeRTOS 的 xQueueCreateSet 定义一个队列集合,将多个 Queue 通过 xQueueAddToSet 的方式添加到 Queue Set;每个任务只操作自己的 Queue,在接收端使用 xQueueSelectFromSet 配合 xQueueReceive 来接收数据;
Example:
创建两个 Queue,加入到同一个 Queue Set
创建两个发送任务和一个接受任务;
-
/* Declare two variables of type QueueHandle_t. Both queues are added to the same
-
queue set. */
-
static QueueHandle_t xQueue1 =
NULL, xQueue2 =
NULL;
-
/* Declare a variable of type QueueSetHandle_t. This is the queue set to which the
-
two queues are added. */
-
static QueueSetHandle_t xQueueSet =
NULL;
-
int main( void )
-
{
-
/* Create the two queues, both of which send character pointers. The priority
-
of the receiving task is above the priority of the sending tasks, so the queues
-
will never have more than one item in them at any one time*/
-
xQueue1 =
xQueueCreate(
1,
sizeof(
char * ) );
-
xQueue2 =
xQueueCreate(
1,
sizeof(
char * ) );
-
/* Create the queue set. Two queues will be added to the set, each of which can
-
contain 1 item, so the maximum number of queue handles the queue set will ever
-
have to hold at one time is 2 (2 queues multiplied by 1 item per queue). */
-
xQueueSet =
xQueueCreateSet(
1 *
2 );
-
/* Add the two queues to the set. */
-
xQueueAddToSet( xQueue1, xQueueSet );
-
xQueueAddToSet( xQueue2, xQueueSet );
-
/* Create the tasks that send to the queues. */
-
xTaskCreate( vSenderTask1,
"Sender1",
1000,
NULL,
1,
NULL );
-
xTaskCreate( vSenderTask2,
"Sender2",
1000,
NULL,
1,
NULL );
-
/* Create the task that reads from the queue set to determine which of the two
-
queues contain data. */
-
xTaskCreate( vReceiverTask,
"Receiver",
1000,
NULL,
2,
NULL );
-
/* Start the scheduler so the created tasks start executing. */
-
vTaskStartScheduler();
-
/* As normal, vTaskStartScheduler() should not return, so the following lines
-
Will never execute. */
-
for( ;; );
-
return
0;
-
}
两个发送任务的实现,分别往两个不同的 Queue 发送数据,传递数据指针,如下所示:
-
void vSenderTask1( void *pvParameters )
-
{
-
const TickType_t xBlockTime =
pdMS_TO_TICKS(
100 );
-
const
char *
const pcMessage =
"Message from vSenderTask1\r\n";
-
/* As per most tasks, this task is implemented within an infinite loop. */
-
for( ;; )
-
{
-
/* Block for 100ms. */
-
vTaskDelay( xBlockTime );
-
/* Send this task's string to xQueue1. It is not necessary to use a block
-
time, even though the queue can only hold one item. This is because the
-
priority of the task that reads from the queue is higher than the priority of
-
this task; as soon as this task writes to the queue it will be pre-empted by
-
the task that reads from the queue, so the queue will already be empty again
-
by the time the call to xQueueSend() returns. The block time is set to 0. */
-
xQueueSend( xQueue1, &pcMessage,
0 );
-
}
-
}
-
/*-----------------------------------------------------------*/
-
void vSenderTask2( void *pvParameters )
-
{
-
const TickType_t xBlockTime =
pdMS_TO_TICKS(
200 );
-
const
char *
const pcMessage =
"Message from vSenderTask2\r\n";
-
/* As per most tasks, this task is implemented within an infinite loop. */
-
for( ;; )
-
{
-
/* Block for 200ms. */
-
vTaskDelay( xBlockTime );
-
/* Send this task's string to xQueue2. It is not necessary to use a block
-
time, even though the queue can only hold one item. This is because the
-
priority of the task that reads from the queue is higher than the priority of
-
this task; as soon as this task writes to the queue it will be pre-empted by
-
the task that reads from the queue, so the queue will already be empty again
-
by the time the call to xQueueSend() returns. The block time is set to 0. */
-
xQueueSend( xQueue2, &pcMessage,
0 );
-
}
-
}
接收任务通过调用 xQueueSelectFromSet 来获得这个 Queue Set 中,是哪个 Queue Handle 来了数据,并通过 xQueueReceive 来获取数据,如下所示:
-
void vReceiverTask( void *pvParameters )
-
{
-
QueueHandle_t xQueueThatContainsData;
-
char *pcReceivedString;
-
/* As per most tasks, this task is implemented within an infinite loop. */
-
for( ;; )
-
{
-
/* Block on the queue set to wait for one of the queues in the set to contain data.
-
Cast the QueueSetMemberHandle_t value returned from xQueueSelectFromSet() to a
-
QueueHandle_t, as it is known all the members of the set are queues (the queue set
-
does not contain any semaphores). */
-
xQueueThatContainsData = ( QueueHandle_t )
xQueueSelectFromSet( xQueueSet,
-
portMAX_DELAY );
-
/* An indefinite block time was used when reading from the queue set, so
-
xQueueSelectFromSet() will not have returned unless one of the queues in the set
-
contained data, and xQueueThatContainsData cannot be NULL. Read from the queue. It
-
is not necessary to specify a block time because it is known the queue contains
-
data. The block time is set to 0. */
-
xQueueReceive( xQueueThatContainsData, &pcReceivedString,
0 );
-
/* Print the string received from the queue. */
-
vPrintString( pcReceivedString );
-
}
-
}
2.9、Queue to Create a Mailbox
Queue 也可以用作 MailBox 的实现;在 FreeRTOS 中,mailbox 的实现是靠一个长度的 Queue 来做的;
Mailbox 用来为保持数据,以供其他的任务或者 ISR 来读取;在 mailbox 中的数据不会被冲掉,只能被重写;
重写一个 Queue 的数据,使用接口 xQueueOverwrite:
BaseType_t xQueueOverwrite( QueueHandle_t xQueue, const void * pvItemToQueue );
注意:ISR 中不准使用 xQueueOverwrite,要使用 xQueueOverwriteFromISR
两个入参,一个返回值:
xQueue:Queue 的句柄;
pvItemToQueue:要更新的数据指针;
Return:返回 pdPASS;
访问 mailbox 的数据使用接口 xQueuePeek
BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait );
注意:ISR 中不准使用 xQueuePeek,要使用 xQueuePeekFromISR
3 个参数:
xQueue:Queue 的句柄;
pvBuffer:获取到的数据指针;
xTicksToWait:如果 mailbox 没有数据,阻塞的时间;
典型用法:
初始化一个 mailbox,就是 1 个 Example_t 大小的 Queue:
-
typedef
struct
xExampleStructure
-
{
-
TickType_t xTimeStamp;
-
uint32_t ulValue;
-
} Example_t;
-
/* A mailbox is a queue, so its handle is stored in a variable of type
-
QueueHandle_t. */
-
QueueHandle_t xMailbox;
-
-
void vAFunction( void )
-
{
-
/* Create the queue that is going to be used as a mailbox. The queue has a
-
length of 1 to allow it to be used with the xQueueOverwrite() API function, which
-
is described below. */
-
xMailbox =
xQueueCreate(
1,
sizeof( Example_t ) );
-
}
更新 mailbox 内容:
-
void vUpdateMailbox( uint32_t ulNewValue )
-
{
-
/* Example_t was defined in Listing 67. */
-
Example_t xData;
-
/* Write the new data into the Example_t structure.*/
-
xData.ulValue = ulNewValue;
-
/* Use the RTOS tick count as the time stamp stored in the Example_t structure. */
-
xData.xTimeStamp =
xTaskGetTickCount();
-
/* Send the structure to the mailbox - overwriting any data that is already in the
-
mailbox. */
-
xQueueOverwrite( xMailbox, &xData );
-
}
读取 mailbox 内容:
-
BaseType_t vReadMailbox( Example_t *pxData )
-
{
-
TickType_t xPreviousTimeStamp;
-
BaseType_t xDataUpdated;
-
-
/* This function updates an Example_t structure with the latest value received
-
from the mailbox. Record the time stamp already contained in *pxData before it
-
gets overwritten by the new data. */
-
xPreviousTimeStamp = pxData->xTimeStamp;
-
/* Update the Example_t structure pointed to by pxData with the data contained in
-
the mailbox. If xQueueReceive() was used here then the mailbox would be left
-
empty, and the data could not then be read by any other tasks. Using
-
xQueuePeek() instead of xQueueReceive() ensures the data remains in the mailbox.
-
A block time is specified, so the calling task will be placed in the Blocked
-
state to wait for the mailbox to contain data should the mailbox be empty. An
-
infinite block time is used, so it is not necessary to check the value returned
-
from xQueuePeek(), as xQueuePeek() will only return when data is available. */
-
xQueuePeek( xMailbox, pxData, portMAX_DELAY );
-
/* Return pdTRUE if the value read from the mailbox has been updated since this
-
function was last called. Otherwise return pdFALSE. */
-
if( pxData->xTimeStamp > xPreviousTimeStamp )
-
{
-
xDataUpdated = pdTRUE;
-
}
-
else
-
{
-
xDataUpdated = pdFALSE;
-
}
-
-
return xDataUpdated;
-
}