FreeRTOS实时内核指南——队列管理 概览 基于 FreeRTOS 的应用程序由一组独立的任务构成——每个任务都是具有独立权限的小程序。这些独立的任务之间很可能会通过相互通信以提供有用的系统功能。FreeRTOS 中所有的通信与同步机制都是基于队列实现的。
队列特性 数据存储 队列可以保存有限个具有确定长度的数据单元。队列可以保存的最大单元数目 被称为队列的“深度”。在队列创建时需要设定其深度和每个单元的大小。 通常情况下,队列被作为 FIFO(先进先出)使用,即数据由队列尾写入,从队列首读出。当然,由队列首写入也是可能的。 往队列写入数据是通过字节拷贝把数据复制存储到队列中;从队列读出数据使得把队列中的数据拷贝删除。
可被多任务存取 队列是具有自己独立权限的内核对象,并不属于或赋予任何任务。所有任务都可以向同一队列写入和读出。一个队列由多方写入是经常的事,但由多方读出倒是很少遇到。
读队列时阻塞 当某个任务试图读一个队列时,其可以指定一个阻塞超时时间 。在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务例程往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。 由于队列可以被多个任务读取,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列数据有效。这种情况下,一旦队列数据有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高 的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久 的任务。
写队列时阻塞 同读队列一样,任务也可以在写队列时指定一个阻塞超时时间。这个时间是当被写队列已满时,任务进入阻塞态以等待队列空间有效的最长时间。 由于队列可以被多个任务写入,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列空间有效。这种情况下,一旦队列空间有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的 任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久 的任务。
队列使用 创建队列 1 2 3 4 5 #define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) ) QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType ) PRIVILEGED_FUNCTION;
参数名
描述
uxQueueLength
队列能够存储的最大单元数目,即队列深度。
uxItemSize
队列中数据单元的长度,以字节为单位。
返回值
NULL
表示没有足够的堆空间分配给队列而导致创建失败。非NULL
值表示队列创建成功。此返回值应当保存下来,以作为操作此队列的句柄。
数据发送 1 2 3 4 5 6 7 #define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) \ xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK ) BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition ) PRIVILEGED_FUNCTION;
参数名
描述
xQueue
目标队列的句柄。这个句柄即是调用xQueueCreate()
创建该队列时的返回值
pvItemToQueue
发送数据的指针。其指向将要复制到目标队列中的数据单元。由于在创建队列时设置了队列中数据单元的长度,所以会从该指针指向的空间复制对应长度的数据到队列的存储区域。
xTicksToWait
阻塞超时时间。如果在发送时队列已满,这个时间即是任务处于阻塞态等待队列空间有效的最长等待时间。阻塞时间是以系统心跳周期为单位的,所以绝对时间取决于系统心跳频率。常量portTICK_RATE_MS
可以用来把心跳时间单位转换为毫秒时间单位。
返回值
1.pdPASS—数据被成功发送到队列中。2.errQUEUE_FULL—队列已满而无法将数据写入。
数据接收 1 2 3 BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
参数名
描述
xQueue
目标队列的句柄。这个句柄即是调用xQueueCreate()
创建该队列时的返回值
pvBuffer
接收缓存指针。其指向一段内存区域,用于接收从队列中拷贝来的数据。数据单元的长度在创建队列时就已经被设定,所以该指针指向的内存区域大小应当足够保存一个数据单元。
xTicksToWait
阻塞超时时间。如果在发送时队列已满,这个时间即是任务处于阻塞态等待队列空间有效的最长等待时间。阻塞时间是以系统心跳周期为单位的,所以绝对时间取决于系统心跳频率。常量portTICK_RATE_MS
可以用来把心跳时间单位转换为毫秒时间单位。
返回值
1.pdPASS
—成功地从队列中读到数据。2.errQUEUE_FULL
—队列已空而没有读到任何数据。
查询数据个数 1 UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue ) PRIVILEGED_FUNCTION;
参数名
描述
xQueue
目标队列的句柄。这个句柄即是调用xQueueCreate()
创建该队列时的返回值
返回值
当前队列中保存的数据单元个数。返回 0 表明队列为空。
使用案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "../build/config/sdkconfig.h" #include "freertos/queue.h" void sendTask(void *pvParam) { QueueHandle_t QueueHandle; QueueHandle = (QueueHandle_t) pvParam; BaseType_t state; int temp = 0; while (1) { state = xQueueSend(QueueHandle, &temp, 0); if(state != pdPASS) { printf("send fail\n"); } else{ printf("send success\n"); } temp++; vTaskDelay(1000 / portTICK_PERIOD_MS); } } void receiveTask(void *pvParam) { QueueHandle_t QueueHandle; QueueHandle = (QueueHandle_t) pvParam; BaseType_t state; int buffer = 0; while (1) { if(uxQueueMessagesWaiting(QueueHandle) != 0) { state = xQueueReceive(QueueHandle, &buffer, 0); if(state != pdPASS) { printf("receive fail\n"); } else{ printf("receive %d\n", buffer); } } else{ printf("no data"); } vTaskDelay(1000 / portTICK_PERIOD_MS); } } void app_main(void) { TaskHandle_t MyHandle1 = NULL;//创造一个TaskHandle_t类型的变量; TaskHandle_t MyHandle2 = NULL;//创造一个TaskHandle_t类型的变量; QueueHandle_t QueueHandle; QueueHandle = xQueueCreate(10, sizeof(int)); if(QueueHandle != NULL) { printf("Create queue success\n"); xTaskCreate(sendTask, "sendTask", 2048, (void *)QueueHandle, 1, MyHandle1);//创建任务函数 xTaskCreate(receiveTask, "receiveTask", 2048, (void *)QueueHandle, 1, MyHandle2);//创建任务函数 } else { printf("Create queue fail\n"); } vTaskDelete(NULL); }
传递结构体 一个任务从单个队列中接收来自多个发送源的数据是经常的事。通常接收方收到数据后,需要知道数据的来源,并根据数据的来源决定下一步如何处理。一个简单的方式就是利用队列传递结构体,结构体成员中就包含了数据信息和来源信息。 修改上述代码为传递结构体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 #include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "../build/config/sdkconfig.h" #include "freertos/queue.h" typedef struct { uint8_t id; int data; }xData; void sendTask(void *pvParam) { QueueHandle_t QueueHandle; QueueHandle = (QueueHandle_t) pvParam; BaseType_t state; xData sendData = {0 , 100}; while (1) { state = xQueueSend(QueueHandle, &sendData, 0); if(state != pdPASS) { printf("send fail\n"); } else{ printf("send success\n"); } sendData.id = sendData.id + 1; sendData.data = sendData.data + 50; vTaskDelay(1000 / portTICK_PERIOD_MS); } } void receiveTask(void *pvParam) { QueueHandle_t QueueHandle; QueueHandle = (QueueHandle_t) pvParam; BaseType_t state; xData buffer = {0, 0}; while (1) { if(uxQueueMessagesWaiting(QueueHandle) != 0) { state = xQueueReceive(QueueHandle, &buffer, 0); if(state != pdPASS) { printf("receive fail\n"); } else{ printf("id = %d\n", buffer.id); printf("data = %d\n", buffer.data); } } else{ printf("no data"); } vTaskDelay(1000 / portTICK_PERIOD_MS); } } void app_main(void) { TaskHandle_t MyHandle1 = NULL;//创造一个TaskHandle_t类型的变量; TaskHandle_t MyHandle2 = NULL;//创造一个TaskHandle_t类型的变量; QueueHandle_t QueueHandle; QueueHandle = xQueueCreate(10, sizeof(xData)); if(QueueHandle != NULL) { printf("Create queue success\n"); xTaskCreate(sendTask, "sendTask", 2048, (void *)QueueHandle, 1, MyHandle1);//创建任务函数 xTaskCreate(receiveTask, "receiveTask", 2048, (void *)QueueHandle, 1, MyHandle2);//创建任务函数 } else { printf("Create queue fail\n"); } vTaskDelete(NULL); }
工作于大型数据单元 如果队列存储的数据单元尺寸较大,那最好是利用队列来传递数据的指针 而不是对数据本身在队列上一字节一字节地拷贝进或拷贝出。传递指针无论是在处理速度上还是内存空间利用上都更有效。但是,当你利用队列传递指针时,一定要十分小心地做到以下两点:
指针指向的内存空间的所有权必须明确 当任务间通过指针共享内存时,应该从根本上保证所不会有任意两个任务同时修改共享内存中的数据,或是以其它行为方式使得共享内存数据无效或产生一致性问题。原则上,共享内存在其指针发送到队列之前,其内容只允许被发送任务访问;共享内存指针从队列中被读出之后,其内容亦只允许被接收任务访问。
指针指向的内存空间必须有效 如果指针指向的内存空间是动态分配的,只应该有一个任务负责对其进行内存释放。当这段内存空间被释放之后,就不应该有任何一个任务再访问这段空间。
切忌用指针访问任务栈上分配的空间。因为当栈帧发生改变后,栈上的数据将不再有效。
多进单出 多个任务把数据写入一个队列,一个任务进行读。设置写入的任务级别为同级别,读任务的优先级别要比写任务高一级别。
不推荐这种方式:容易造成系统工作混乱。最好的工作方式是一个队列只有一个写操作,可以有多个读操作,但是写操作只能有一个。建议使用队列集合 。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 void sendTask1(void *pvParam) { QueueHandle_t QueueHandle; QueueHandle = (QueueHandle_t) pvParam; BaseType_t state; xData sendData = {0 , 111}; while (1) { state = xQueueSend(QueueHandle, &sendData, 0); vTaskDelay(1000 / portTICK_PERIOD_MS); } } void sendTask2(void *pvParam) { QueueHandle_t QueueHandle; QueueHandle = (QueueHandle_t) pvParam; BaseType_t state; xData sendData = {1 , 999}; while (1) { state = xQueueSend(QueueHandle, &sendData, 0); vTaskDelay(1000 / portTICK_PERIOD_MS); } } void sendTask3(void *pvParam) { QueueHandle_t QueueHandle; QueueHandle = (QueueHandle_t) pvParam; BaseType_t state; xData sendData = {2 , 555}; while (1) { state = xQueueSend(QueueHandle, &sendData, 0); vTaskDelay(1000 / portTICK_PERIOD_MS); } } void receiveTask(void *pvParam) { QueueHandle_t QueueHandle; QueueHandle = (QueueHandle_t) pvParam; BaseType_t state; xData buffer = {0, 0}; while (1) { state = xQueueReceive(QueueHandle, &buffer, portMAX_DELAY); if(state != pdPASS) { printf("receive fail\n"); } else { printf("id = %d", buffer.id); printf(" data = %d\n", buffer.data); } } } void app_main(void) { TaskHandle_t MyHandle1 = NULL;//创造一个TaskHandle_t类型的变量; TaskHandle_t MyHandle2 = NULL;//创造一个TaskHandle_t类型的变量; QueueHandle_t QueueHandle; QueueHandle = xQueueCreate(10, sizeof(xData)); if(QueueHandle != NULL) { printf("Create queue success\n"); xTaskCreate(sendTask1, "sendTask", 2048, (void *)QueueHandle, 1, MyHandle1);//创建任务函数 xTaskCreate(sendTask2, "sendTask", 2048, (void *)QueueHandle, 1, MyHandle1);//创建任务函数 xTaskCreate(sendTask3, "sendTask", 2048, (void *)QueueHandle, 1, MyHandle1);//创建任务函数 xTaskCreate(receiveTask, "receiveTask", 2048, (void *)QueueHandle, 2, MyHandle2);//创建任务函数 } else { printf("Create queue fail\n"); } vTaskDelete(NULL); }
队列集合 任务通信过程中,如果消息类型不同,使用一个队列实现比较麻烦。通过队列集合,可以对多个队列以及信号量进行监听,只要有一个消息到来,就可以让任务退出阻塞状态。
多个队列,但是每个队列只有一个写操作,一个读操作(读取所有队列)
创建集合 1 QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength ) PRIVILEGED_FUNCTION;
参数名
描述
uxEventQueueLength
Queue sets store events that occur on the queues and semaphores contained in the set. uxEventQueueLength specifies the maximum number of events that can be queued at once. To be absolutely certain that events are not lost uxEventQueueLength should be set to the total sum of the length of the queues added to the set, where binary semaphores and mutexes have a length of 1, and counting semaphores have a length set by their maximum count value.
return
If the queue set is created successfully then a handle to the created queue set is returned. Otherwise NULL is returned.
添加队列到集合 1 2 BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet ) PRIVILEGED_FUNCTION;
参数名
描述
xQueueOrSemaphore
The handle of the queue or semaphore being added to the queue set (cast to an QueueSetMemberHandle_t type)
xQueueSet
The handle of the queue set to which the queue or semaphore is being added.
return
If the queue or semaphore was successfully added to the queue set then pdPASS is returned. If the queue could not be successfully added to the queue set because it is already a member of a different queue set then pdFAIL is returned.
从集合中选择数据 1 2 QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, const TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
参数名
描述
xQueueSet
The queue set on which the task will (potentially) block.
xTicksToWait
The maximum time, in ticks, that the calling task will remain in the Blocked state (with other tasks executing) to wait for a member of the queue set to be ready for a successful queue read or semaphore take operation
return
return the handle of a queue (cast to a QueueSetMemberHandle_t type) contained in the queue set that contains data,or the handle of a semaphore (cast to a QueueSetMemberHandle_t type) contained in the queue set that is available, or NULL if no such queue or semaphore exists before before the specified block time expires.
使用案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 #include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "../build/config/sdkconfig.h" #include "freertos/queue.h" typedef struct { uint8_t id; int data; }xData; void sendTask1(void *pvParam) { QueueHandle_t QueueHandle; QueueHandle = (QueueHandle_t) pvParam; BaseType_t state; xData sendData = {1 , 111}; while (1) { state = xQueueSend(QueueHandle, &sendData, 0); vTaskDelay(1000 / portTICK_PERIOD_MS); } } void sendTask2(void *pvParam) { QueueHandle_t QueueHandle; QueueHandle = (QueueHandle_t) pvParam; BaseType_t state; xData sendData = {2 , 222}; while (1) { state = xQueueSend(QueueHandle, &sendData, 0); vTaskDelay(1000 / portTICK_PERIOD_MS); } } void sendTask3(void *pvParam) { QueueHandle_t QueueHandle; QueueHandle = (QueueHandle_t) pvParam; BaseType_t state; xData sendData = {3 , 333}; while (1) { state = xQueueSend(QueueHandle, &sendData, 0); vTaskDelay(1000 / portTICK_PERIOD_MS); } } void receiveTask(void *pvParam) { QueueSetHandle_t QueueHandle; QueueHandle = (QueueSetHandle_t) pvParam; QueueSetMemberHandle_t QueueSetData; BaseType_t state; xData buffer = {0, 0}; while (1) { QueueSetData = xQueueSelectFromSet(QueueHandle, portMAX_DELAY); // 没有数据就一直等待 state = xQueueReceive(QueueSetData, &buffer, portMAX_DELAY); if(state != pdPASS) { printf("receive fail\n"); } else { printf("id = %d", buffer.id); printf(" data = %d\n", buffer.data); } } } void app_main(void) { TaskHandle_t MyHandle1 = NULL;//创造一个TaskHandle_t类型的变量; TaskHandle_t MyHandle2 = NULL;//创造一个TaskHandle_t类型的变量; QueueHandle_t QueueHandle1; QueueHandle1 = xQueueCreate(10, sizeof(xData)); QueueHandle_t QueueHandle2; QueueHandle2 = xQueueCreate(10, sizeof(xData)); QueueHandle_t QueueHandle3; QueueHandle3 = xQueueCreate(10, sizeof(xData)); QueueSetHandle_t QueueSet; QueueSet = xQueueCreateSet(10+10+10); xQueueAddToSet(QueueHandle1, QueueSet); xQueueAddToSet(QueueHandle2, QueueSet); xQueueAddToSet(QueueHandle3, QueueSet); if((QueueHandle1 != NULL) && (QueueHandle2 != NULL) && (QueueHandle3 != NULL)) { printf("Create queue success\n"); xTaskCreate(sendTask1, "sendTask", 2048, (void *)QueueHandle1, 1, MyHandle1);//创建任务函数 xTaskCreate(sendTask2, "sendTask", 2048, (void *)QueueHandle2, 1, MyHandle1);//创建任务函数 xTaskCreate(sendTask3, "sendTask", 2048, (void *)QueueHandle3, 1, MyHandle1);//创建任务函数 xTaskCreate(receiveTask, "receiveTask", 2048, (void *)QueueSet, 2, MyHandle2);//创建任务函数 } else { printf("Create queue fail\n"); } vTaskDelete(NULL); }
队列邮箱 邮箱是长度为1的队列,接收数据的任务或者中断ISR从这个邮箱中读取数据的时候,并不会将这个数据从邮箱中删除,它会一直保留,直到新的数据被写入到邮箱覆盖之前的数据。任何任务或者ISR都可以从这个邮箱中读取数据。
写入数据 1 2 3 4 5 6 7 #define xQueueOverwrite( xQueue, pvItemToQueue ) \ xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE ) BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition ) PRIVILEGED_FUNCTION;
参数名
描述
xQueue
目标队列的句柄。
pvItemToQueue
发送数据的指针。其指向将要复制到目标队列中的数据单元。由于在创建队列时设置了队列中数据单元的长度,所以会从该指针指向的空间复制对应长度的数据到队列的存储区域。
返回值
pdTRUE if the item was successfully posted, otherwise errQUEUE_FULL.
读取数据 1 2 3 BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
参数名
描述
xQueue
目标队列的句柄。
pvBuffer
接收缓存指针。其指向一段内存区域,用于接收从队列中拷贝来的数据。数据单元的长度在创建队列时就已经被设定,所以该指针指向的内存区域大小应当足够保存一个数据单元。
xTicksToWait
阻塞超时时间。如果在发送时队列已满,这个时间即是任务处于阻塞态等待队列空间有效的最长等待时间。阻塞时间是以系统心跳周期为单位的,所以绝对时间取决于系统心跳频率。常量portTICK_RATE_MS
可以用来把心跳时间单位转换为毫秒时间单位。
返回值
pdTRUE if an item was successfully received from the queue, otherwise pdFALSE.
xQueuePeek
不会删除句柄里面的数据,所以第一次 xQueuePeek
开始就一直有数据,导致最后portMAX_DELAY参数没起作用。
使用案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 #include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "../build/config/sdkconfig.h" #include "freertos/queue.h" void writeTask(void *pvParam) { QueueHandle_t MailBox; MailBox = (QueueHandle_t) pvParam; BaseType_t state; int temp = 0; while (1) { state = xQueueOverwrite(MailBox, &temp); if(state != pdPASS) { printf("send fail\n"); } else{ printf("send success\n"); } temp++; vTaskDelay(5000 / portTICK_PERIOD_MS); } } void readTask1(void *pvParam) { QueueHandle_t MailBox; MailBox = (QueueHandle_t) pvParam; BaseType_t state; int buffer = 0; while (1) { state = xQueuePeek(MailBox, &buffer, 0); if(state != pdPASS) { printf("read fail\n"); } else{ printf("read %d\n", buffer); } vTaskDelay(1000 / portTICK_PERIOD_MS); } } void readTask2(void *pvParam) { QueueHandle_t MailBox; MailBox = (QueueHandle_t) pvParam; BaseType_t state; int buffer = 0; while (1) { state = xQueuePeek(MailBox, &buffer, 0); if(state != pdPASS) { printf("read fail\n"); } else{ printf("read %d\n", buffer); } vTaskDelay(1000 / portTICK_PERIOD_MS); } } void readTask3(void *pvParam) { QueueHandle_t MailBox; MailBox = (QueueHandle_t) pvParam; BaseType_t state; int buffer = 0; while (1) { state = xQueuePeek(MailBox, &buffer, 0); if(state != pdPASS) { printf("read fail\n"); } else{ printf("read %d\n", buffer); } vTaskDelay(1000 / portTICK_PERIOD_MS); } } void app_main(void) { TaskHandle_t MyHandle1 = NULL;//创造一个TaskHandle_t类型的变量; TaskHandle_t MyHandle2 = NULL;//创造一个TaskHandle_t类型的变量; QueueHandle_t MailBox; MailBox = xQueueCreate(1, sizeof(int)); if(MailBox != NULL) { printf("Create MailBox success\n"); xTaskCreate(writeTask, "writeTask", 2048, (void *)MailBox, 1, MyHandle1);//创建任务函数 xTaskCreate(readTask1, "readTask1", 2048, (void *)MailBox, 2, MyHandle2);//创建任务函数 xTaskCreate(readTask2, "readTask2", 2048, (void *)MailBox, 2, MyHandle2);//创建任务函数 xTaskCreate(readTask3, "readTask3", 2048, (void *)MailBox, 2, MyHandle2);//创建任务函数 } else { printf("Create MailBox fail\n"); } vTaskDelete(NULL); }