FreeRTOS学习总结
一、移植
上图是从FreeRTOS官网下载的源文件目录,移植所需要的文件都在Source文件夹下
如上图,在工程文件夹下创建FreeRTOS文件夹,子文件夹和相应文件,均从Source文件夹下直接copy即可。
- FreeRTOS的内核文件,新手直接无脑copy。(对于熟手如果你的工程不需要用到队列,或者事件组等,可以不需要相关文件)
- 硬件相关,在Source\portable[IDE][Target Device]文件夹下先找到你用的IDE工具文件夹,在里面再找到目标硬件的文件夹,直接COPY。图中5(ARM_CM3下)port.c和portmacro.h是硬件直接相关的两个文件,大多数主流MCU在FreeRTOS官方已经支持。如果没有支持,那么你就要自己重写这两个文件。图中4(MemMang下)的heap_1.c~heap_5.c分别是FreeRTOS支持的5种堆内存分配方式,选其一使用即可,具体参见官方手册
- 在Source\include文件夹直接无脑copy即可。注意:其中FreeRTOSConfig.h是需要你从其他Sample工程中COPY过来,根据自己需要修改的。在Source\include中没有
- 在你的工程中添加所有.c文件和.h文件,并在main.c中加入代码 #include “FreeRTOS.h”,
- 编译无错误,移植完成
二、内存管理
FreeRTOS系统启动后,所有系统内使用的内存成为堆,堆得空间大小由FreeRTOSConfig.h中的configTOTAL_HEAP_SIZE选项来确定。堆空间是使用了ZI区的空间,具体如下图:
FreeRTOS不使用标准c语言的Malloc()和free()来申请释放内存。而是使用pvPortMalloc()和pvPortFree()来进行堆内存的申请和释放FreeRTOS Heap中的空间。调用这两个函数,有5中可选的内存分配方式可选。
heap_1.c
heap_1的工作原理是将configTOTAL_HEAP_SIZE指定的堆空间划分成一个数组,且将这个数组再细分成更小的块。
实现了一个非常基本的pvPortMalloc()版本,而没有实现vPortFree()。内存在内核启动任何应用程序任务或功能前动态分配,且一旦分配,将一直保留到应用程序的整个生命周期。因此不需要考虑任何确定性和碎片等复杂问题,只需要考虑代码的大小和简单性。
只有那些从不删除任务或其他内核对象的应用程序有可能使用heap_1方式。一些原本会禁止使用动态内存分配的商业关键和安全关键系统也有可能使用heap_1。关键系统通常禁止在应用中动态内存分配,因为存在内存碎片和失败的分配相关的不确定性——但Heap_1总是确定性的,不能将内存碎片分割。
heap_2.c
新项目中请使用heap_4.c替代heap_2.c,任然保留该文件是为了向后兼容。
heap_4和heap_2一样,并不会把相邻的自由块合并成一个大的自由块,因此会存在碎片化的问题。但对于那些申请和释放的空间大小总是相同的应用来说,碎片化并不会成为问题。
==heap_2适合那些反复创建,删除任务,且每次分配相同任务栈空间的应用。==如下图
heap_2将最接近且满足申请空间大小的内存分配给应用程序,如:
- 列表中依次有5BYTE,100BYTE,25BYTE的三个自由空间块可供使用。
- 申请20BYTE空间。
- 将第三个块,即25BYTE的块划分为一个20BYTE和一个5BYTE空间。
- 20BYTE供申请者使用,5BYTE放回列表供其他申请使用。
heap_3.c
Heap_3.c使用标准库malloc()和free()函数,因此堆的大小由链接器配置确定,而configTOTAL_HEAP_SIZE设置没有影响。Heap_3通过暂时暂停FreeRTOS调度程序,使malloc()和free()线程安全。线程安全,和调度程序暂停,都是在第7章,资源管理中讨论的主题。
heap_4.c
和heap_1和heap_2一样,heap_4的工作原理是将configTOTAL_HEAP_SIZE指定的堆空间细分成若干份的一个数组。因为这个数组是静态声明的,所以会表现出在应用程序启动之前,已经消耗了大量芯片内存。
和heap_2不同,heap_4会合并那些相邻的自由块,以形成一个更大的块,来最小化碎片化的问题。
heap_4将分配算法遇到的第一个满足申请大小的空间分配给应用程序,如:
- 列表中依次有5BYTE,200BYTE,100BYTE的三个自由空间块可供使用。
- 申请20BYTE空间。
- 将第二个块200BYTE的块分配给申请者,从中划分成一个20BYTE和一个180BYTE空间。
- 其中20BYTE的空间供申请者使用,180BYTE放回列表供其他申请者使用。
==heap_4适合那些返回创建和删除任务,且每次创建时申请栈空间不同的应用程序。==如下图:
在使用heap_4时,可以制定堆空间的开始地址
可以在应用中自行制定FreeRTOS的对空间具体放在哪里,起始地址在哪里。比如可以选择将FreeRTOS的堆空间放在快速的片上内存中,或者速度较慢的外部内存中。此时,需要:
- 在FreeRTOSConfig.h中将configAPPLICATION_ALLOCATED_HEAP定义为1。
- 在应用程序中声明一个uint8_t ucHeap[configTOTAL_HEAP_SIZE]的数组。
具体语法根据你所使用的的编译器决定,如:
//Using GCC syntax to declare the array that will be used by heap_4, and place the array in a memory section named .my_heap
uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] __attribute__ ( ( section( ".my_heap" ) ) );
//Listing 3. Using IAR syntax to declare the array that will be used by heap_4, and place the array at the absolute address 0x20000000
uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] @ 0x20000000;
heap_5
heap_5的分配和释放算法逻辑和heap_4一样,但不同的是,heap_5所分配的空间并仅限于一个单一的提前静态申请的数组(FreeRTOS堆空间)。而是可以使用多种不同的分开的内存空间。 heap_5适合在那些系统所提供的内存没有连续空间的系统(FreeRTOS所运行的系统)中使用。
使用heap_5,必须在使用任何FreeRTOS对象之前,显示的调用vPortDefineHeapRegions()函数来初始化可用的空间。
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );
每个可用的内存空间被描述成一个 HeapRegion_t类型的结构体
typedef struct HeapRegion
{
/* The start address of a block of memory that will be part of the heap.*/
uint8_t *pucStartAddress;
/* The size of the block of memory in bytes. */
size_t xSizeInBytes;
} HeapRegion_t;
所有的可用空间组成一个HeapRegion_t类型的数组,传递给vPortDefineHeapRegions()函数。例如:
/* Define the start address and size of the two RAM regions not used by the
linker. */
#define RAM2_START_ADDRESS ( ( uint8_t * ) 0x00020000 )
#define RAM2_SIZE ( 32 * 1024 )
#define RAM3_START_ADDRESS ( ( uint8_t * ) 0x00030000 )
#define RAM3_SIZE ( 32 * 1024 )
/* Declare an array that will be part of the heap used by heap_5. The array will be placed in RAM1 by the linker. */
#define RAM1_HEAP_SIZE ( 30 * 1024 )
static uint8_t ucHeap[ RAM1_HEAP_SIZE ];
/* Create an array of HeapRegion_t definitions. Whereas in Listing 6 the first entry described all of RAM1, so heap_5 will have used all of RAM1, this time the first entry only describes the ucHeap array, so heap_5 will only use the part of RAM1 that contains the ucHeap array. The HeapRegion_t structures must still appear in start address order, with the structure that contains the lowest start address appearing first. */
const HeapRegion_t xHeapRegions[] =
{
{ ucHeap, RAM1_HEAP_SIZE },
{ RAM2_START_ADDRESS, RAM2_SIZE },
{ RAM3_START_ADDRESS, RAM3_SIZE },
{ NULL, 0 } /* Marks the end of the array. */
};
int main( void )
{
/* Initialize heap_5. */
vPortDefineHeapRegions( xHeapRegions );
/* Add application code here. */
}
堆空间优化相关函数
//返回自该函数调用时刻,可用的堆空间
size_t xPortGetFreeHeapSize( void );
//返回自应用启动以来,可用的最小堆空间
size_t xPortGetMinimumEverFreeHeapSize( void );
三、任务管理
任务函数模型如下
//Listing 12. The structure of a typical task function
void ATaskFunction( void *pvParameters )
{
/* Variables can be declared just as per a normal function. Each instance of a task created using this example function will have its own copy of the lVariableExample variable. This would not be true if the variable was declared static – in which case only one copy of the variable would exist, and this copy would be shared by each created instance of the task. (The prefixes added to variable names are described in section 1.5, Data Types and Coding Style Guide.) */
int32_t lVariableExample = 0;
/* A task will normally be implemented as an infinite loop. */
for( ;; )
{
/* The code to implement the task functionality will go here. */
}
/* Should the task implementation ever break out of the above loop, then the task must be deleted before reaching the end of its implementing function. The NULL parameter passed to the vTaskDelete() API function indicates that the task to be deleted is the calling (this) task. The convention used to name API functions is described in section 0, Projects that use a FreeRTOS version older than V9.0.0 must build one of the heap_n.c files. From FreeRTOS V9.0.0 a heap_n.c file is only required if configSUPPORT_DYNAMIC_ALLOCATION is set to 1 in FreeRTOSConfig.h or if configSUPPORT_DYNAMIC_ALLOCATION is left undefined. Refer to Chapter 2, Heap Memory Management, for more information.Data Types and Coding Style Guide. */
vTaskDelete( NULL );
}
任务状态
任务状态分为Runing,Ready,Suspended,Blocked四种状态。他们之间的关系如图:
1、抢占调度模式下,FreeRTOS的任务调度器将始终保证就绪(ready)状态,且优先级最高的任务进入运行(running)状态。
2、当有多个就绪状态,且优先级相同时,则等待最久的进入运行(running)状态,即优先级相同时,轮流执行。
3、挂起(suspended)状态下,任务将不参与调度,不会被运行。vTaskSuspend()进入挂起状态,vTaskResume()或vTaskResumeFromISR()退出挂起状态。
4、阻塞(blocked)状态由事件(event)驱动,只有当相关事件(event)发生后,任务才能离开阻塞(blocked)状态,进入就绪(ready)状态。
5、调用vTaskDelay()进入阻塞状态,Delay到期事件触发,突出阻塞状态进入就绪状态。
6、当有更高优先级的任务进入就绪状态时,会直接将正在运行的较低优先级任务打断,直接进入运行状态。
空闲任务及其回调函数
1、必须始终最少有一个任务可以进入运行状态,当调用vTaskStartScheduler()时,调度程序会自动创建一个空闲任务(IDLE TASK),该任务具有最低(0优先级)优先级。
2、可以创建和IDLE TASK任务相同优先级的任务。如果在FreeRTOSConfig.h中将configIDLE_SHOULD_YIELD设置为1,则,当调度器只有IDLE任务和其他0优先级的任务时,则IDLE将自动退出运行状态,将CPU时间让给其他任务。否则IDLE任务将运行完整个CPU TICK时间后,在轮到其他同为0优先级的任务。
3、IDLE任务可以添加回调函数,每个IDLE函数的时间片,会调用此回调函数。
4、IDLE回调函数通常用于处理:低优先级的,后台处理或连续处理的功能。
5、IDLE任务可用于测量系统的剩余处理能力。
6、IDLE回调永远不可以进入阻塞或者挂起状态,否则将导致调度器进入没有可调度的任务状态。且当vTaskDelete()被调用,其相关的内核资源需要IDLE任务来清理回收。如果IDLE任务被其回调函数保持在阻塞状态,则无法完成回收。
7、使用IDLE回调,必须在FreeRTOSConfig.h中将configUSE_IDLE_HOOK配置为1。
任务调度算法
1、抢占调度模式下,FreeRTOS的任务调度器将始终保证就绪(ready)状态,且优先级最高的任务进入运行(running)状态。
2、可通过配置FreeRTOSConfig.h中的三个配置项来改变调度算法。
- configUSE_PREEMPTION 启用或关闭抢占调度
- configUSE_TIME_SLICING 启用或关闭时间片
- configUSE_TICKLESS_IDLE 启用或关闭IDLE任务下低功耗模式
- configUSE_IDLE_HOOK 设定IDLE任务是否让出时间给同优先级的其他任务
队列
1、队列是一种FIFO数据结构。
2、队列的实现有两种,一是直接将数据复制到队列中(复制队列),二是队列仅保存指向数据的指针(引用队列)。
3、FreeRTOS使用直接保存数据的方法。这样做的好处有:
- 栈变量可以直接发送到队列,即使在声明它的函数退出后变量将不存在。
- 数据可以发送到队列,而不需要首先分配缓冲区来保存数据,然后将数据复制到分配的缓冲区中。
- 发送任务可以立即重用已发送到队列的变量或缓冲区。
- 发送任务和接收任务是完全解耦合的——应用程序设计者不需要关心哪个任务“拥有”数据,或者哪个任务负责释放数据。
- 复制队列并不阻止队列也被用于引用队列。也可以将指向数据的指针复制到队列中。
RTOS完全负责分配用于存储数据的内存。 - 在内存保护系统中,任务可以访问的RAM将受到限制。在这种情况下,只有当发送和接收任务都可以访问存储数据的RAM时,才能使用引用队列。复制队列不施加这种限制;内核始终以完全的特权运行,允许使用队列跨内存保护边界传递数据。
4、QueueHandle_t为队列句柄数据类型,使用队列前不许显示的创建它。
5、发送数据到队列可以SendToBack,也可以SendToFront。
6、从队列中接收(读取)一个项目。接收到的项目将从队列中删除。因此,队列可以用于多个数据源向队列发送数据,而通常不用于多个数据源从队列中取数据。也就是说,通常可以用于,一个或多个任务同时阻塞在从一个队列发送数据。而通常只用于一个任务阻塞在从一个队列取数据。
使用队列发送大型数据或可变大小数据
如果需要发送的数据量很大,则直接将数据从发送任务复制到队列中,然后在读取任务中将数据从队列中复制处理,效率会非常低,此时可以使用指针型队列。让任务间只传递数据所在地址的指针。此时需保证:
1、发送和接收时,都要确保用于存储数据的内存独享,否则会出现数据被其他任务或程序篡改。
2、在发送完成到接收完成之间,保存数据的内存有效,不被释放。
/* 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 * ) );
/* 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 );
}
}
/* A task that receives the address of a buffer from the queue created in Listing 52, and written to in Listing 53. The buffer contains a string, which is printed out. */
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 );
}
}
使用队列发送不同类型和长度的数据
//Listing 55. The structure used to send events to the TCP/IP stack task in FreeRTOS+TCP
/* 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;
//示例TCP/IP事件及其关联数据包括:
/*eNetworkRxEvent:已从网络接收数据包。从网络接收到的数据将使用IPStackEvent_t类型的结构发送到TCP/IP任务。结构的eEventType成员设置为eNetworkRxEvent,结构的pvData成员用于指向包含接收数据的缓冲区。*/
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 );
}
//eTCPAcceptEvent:Socket正在接受或者等待,一个来自客户端的连接。
/*接受事件将被使用IPStackEvent_t类型的结构从名为FReeRTOS_crenty()的任务发送到TCP/IP任务。结构的eEventType成员被设置为eTCP接受事件,并且结构的pvData成员被设置为正在接受连接的套接字的句柄。*/
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 );
}
//eNetworkDownEvent:该网络需要连接,或重新连接。
/*网络关闭事件使用IPStackEvent_t类型的结构从网络接口发送到TCP/IP任务。结构的“事件类型”成员设置为“eNetworkDownEvent”。网络停机事件不与任何数据关联,因此不使用该结构的pvData成员*/
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 );
}
/*在TCP/IP任务中接收和处理这些事件的代码如下所示。可以看出,从队列中接收到的IPStackEvent_t结构中的eEventType成员被用来确定如何解释pvData成员。*/
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. */
}
队列集
队列集是多个不同队列的集合。其中的每个队列可以存储不同类型的数据。用于单个任务任务从多个数据源接收不同大小,不同意义,不同类型的数据。
使用:
1、创建队列集
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength );
2、添加队列,每个队列保存的数据可以使不同大小,不同含义,不同类型,信号量也可以被添加到队列集。
BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet );
3、向队列集中的某个队列发送数据
4、从队列集中获取有数据的队列
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, const TickType_t xTicksToWait );
5、从获取到的队列中读取数据
例如:
/* 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;
}
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 );
}
}
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 );
}
}
使用队列创建邮箱
在FreeRTOS中,邮箱一次表示长度为一的队列。它与队列的不同为:
- 队列用于将数据从一个任务发送到另一个任务,或者从中断服务例程发送到一个任务。发送方将一个项目放在队列中,而接收方将从队列中删除该项目。数据通过队列从发送方传递到接收方。
- 邮箱用于保存可由任何任务或任何中断服务例程读取的数据。数据不会通过邮箱,而是保留在邮箱中,直到它被覆盖。发件人将覆盖邮箱中的值。接收方会从邮箱中读取该值,但不会从邮箱中删除该值。
创建邮箱:
typedef struct xExampleStructure
{
TickType_t xTimeStamp;
uint32_t ulValue;
} Example_t;
xMailbox = xQueueCreate( 1, sizeof( Example_t ) );
发送数据到邮箱:
与xQueueSendToBack()不同,如果队列已经满,那么xQueueOverwrite()将覆盖已经在队列中的数据。xQueueOverwrite()应该只用于长度为1的队列。
BaseType_t xQueueOverwrite( QueueHandle_t xQueue, const void * pvItemToQueue );
从邮箱中读取数据:
xQueuePeek()用于从队列中接收(读取)一个项目,而不需要从队列中删除该项目。xQueuePeek()从队列的头接收数据,而不修改存储在队列中的数据或数据存储在队列中的顺序。
BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait );