一、消息队列基本概念
队列又称消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传递信息,实现了任务接收来自任务或者中断的不固定长度的消息。通过消息队列服务,任务或中断服务可以将一个或多个消息放入消息队列中。
二、消息队列运作机制
创建消息队列时,系统会先给消息队列分配一块内存,其大小等于任务控制块大小加上(单个消息空间大小与消息队列长度的乘积),接着初始化消息队列,此时消息队列为空。FreeROS的消息队列控制块有多个元素构成,当消息队列被创建时,系统会为控制块分配对应的空间,用于保存消息队列的一些信息如存储位置,头指针pcHead,尾指针pcTail,消息大小uxItemSize以及队列长度uxLength
同时每个消息队列都与消息空间在一段连续的空间里中,在创建成功时,这些内存就被占用了,只有删除了消息队列的时候。这段内存才会被释放掉,创建成功的时候就已经分配好每个消息空间和消息队列的容量了,无法更改。
任务或中断服务程序都可以向消息队列发送消息,当发送消息时,如果队列未满或者允许覆盖入队,系统会将消息拷贝到消息队列队尾,否则,会根据用户的阻塞时间进行阻塞,在这段时间,如果一直不允许入队,该任务将保持阻塞以等待队列允许入队。当其他任务从其等待的队列读取了数据(队列未满),该任务由阻塞状态变为就绪状态。当等待的时间超过了指定的阻塞时间,即使队列还不允许入队,该任务也会从阻塞状态变为就绪态,此时发送消息的任务或中断就会收到一个错误码errQUEUE_FULL.
发送紧急消息的过程发送消息的过程一模一样,唯一不同的是,当发送紧急消息时,发送的位置是从队列头而非队列尾,这样,接收者就能优先接收紧急消息,从而即使处理消息
消息队列运作过程
三、队列创建操作图示
四、队列结构体
typedef struct QueueDefinition /* 旧的命名约定用于防止破坏支持内核的调试器 */
{
int8_t * pcHead; /*< 指向队列存储区开始地址 */
int8_t * pcWriteTo; /*< 指向存储区中下一个空闲区域 */
union
{
QueuePointers_t xQueue; /*< 当此结构用作队列时,仅需要的数据 */
SemaphoreData_t xSemaphore; /*< 当此结构用作信号量时,只需要的数据。 */
} u;
List_t xTasksWaitingToSend; /*< 等待发送任务列表,那些因为队列满导致入队失败而进入阻塞态的任务就会挂到此列表上 */
List_t xTasksWaitingToReceive; /*< 等待接收任务列表,那些因为队列空导致出队失败而进入阻塞态的任务就会挂到此列表上 */
volatile UBaseType_t uxMessagesWaiting; /*< 队列中当前队列项数量,也就是消息数 */
UBaseType_t uxLength; /*< 创建队列时指定的队列长度,也就是队列中最大允许的队列项(消息)数量 */
UBaseType_t uxItemSize; /*< 创建队列时指定的每个队列项(消息)最大长度,单位字节 */
volatile int8_t cRxLock; /*< 当队列上锁以后用来统计从队列中接收到的队列项数
量,也就是出队的队列项数量,当队列没有上锁的话此字
段为 queueUNLOCKED*/
volatile int8_t cTxLock; /*< 当队列上锁以后用来统计发送到队列中的队列项数量,
也就是入队的队列项数量,当队列没有上锁的话此字
段为 queueUNLOCKED */
#if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated; /*<如果使用静态存储的话此字段设置为 pdTURE。 */
#endif
#if ( configUSE_QUEUE_SETS == 1 ) /*队列集相关宏*/
struct QueueDefinition * pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 ) /*跟踪调试相关宏*/
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
/* 旧的xQUEUE名称在上面保留,然后在下面将其类型化为新的队列名称Queue_t,
以支持使用旧的内核感知调试器。 */
typedef xQUEUE Queue_t;
五、创建队列
1.消息队列动态创建 xQueueCreate
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_t ucQueueType )
{
Queue_t * pxNewQueue = NULL;
size_t xQueueSizeInBytes;
uint8_t * pucQueueStorage;
if( ( uxQueueLength > ( UBaseType_t ) 0 ) &&
/* 乘法检查溢出. */
( ( SIZE_MAX / uxQueueLength ) >= uxItemSize ) &&
/* 加法检查溢出. */
( ( SIZE_MAX - sizeof( Queue_t ) ) >= ( uxQueueLength * uxItemSize ) ) )
{
/* 分配足够消息存储空间,空间的大小为队列长度*单个消息大小 */
xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
/* 向系统申请内存,内存大小为消息队列控制块大小+消息存储空间大小 */
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); /*lint !e9087 !e9079 see comment above. */
if( pxNewQueue != NULL )
{
/* 计算出消息存储空间的起始地址因为申请的内存是包含了消息队列控制块的内存空间,
但是我们存储消息的内存空间在消息队列控制块后面。 */
pucQueueStorage = ( uint8_t * ) pxNewQueue;
pucQueueStorage += sizeof( Queue_t ); /*lint !e9016 Pointer arithmetic allowed on char types, especially when it assists conveying intent. */
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
{
/* Queues can be created either statically or dynamically, so
* note this task was created dynamically in case it is later
* deleted. */
pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
/*调用prvInitialiseNewQueue()函数将消息队列进行初始化,xQueueGenericCreate()主要是用于分配消息队列内存的*/
prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
}
else
{
traceQUEUE_CREATE_FAILED( ucQueueType );
mtCOVERAGE_TEST_MARKER();
}
}
else
{
configASSERT( pxNewQueue );
mtCOVERAGE_TEST_MARKER();
}
return pxNewQueue;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
根据入口参数判断,自定义的队列长度和单个消息的大小,是否过大,因为队列的大小也是有上限的。内存的大小为队列大小*单个大小。如果满足条件则系统调用pcPortMalloc函数向系统申请内存空间,内存大小为消息控制快的大小加消息存储空间大小,因为这份内存空间是需要保证连续的。接着计算出消息存储内存空间的起始地址,因为调用pvPortMalloc函数申请的内存是包含了消息队列控制块的内存空间,就是这个Queue_t的空间。但是我们存储消息的内存空间在消息队列控制块后面,最后调用prvInitialiseQueue函数将消息队列进行初始化。因为xQueueGenericCreate函数主要用于分配消息队列内存的,prvInitialiseNewQueue函数才会将消息队列进行初始化。
static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, /*消息队列长度*/
const UBaseType_t uxItemSize, /*单个消息大小*/
uint8_t * pucQueueStorage, /*存储消息起始地址*/
const uint8_t ucQueueType, /*消息队列类型*/
Queue_t * pxNewQueue ) /*消息队列控制块*/
{
/* Remove compiler warnings about unused parameters should
* configUSE_TRACE_FACILITY not be set to 1. */
( void ) ucQueueType;
if( uxItemSize == ( UBaseType_t ) 0 )
{
/* 如果没有为消息队列分配存储消息的内存空间,而且 pcHead 指针
不能设置为 NULL,因为队列用作互斥量时,pcHead 要设置成 NULL,这里只能将 pcHead
指向一个已知的区域,指向消息队列控制块 pxNewQueue。 */
pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
}
else
{
/* 如果分配了存储消息的内存空间,则设置 pcHead 指向存储消息的起始地址 pucQueueStorage */
pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
}
/* 初始化消息队列控制块的其他成员,消息队列的长度与消息的大小 */
pxNewQueue->uxLength = uxQueueLength;
pxNewQueue->uxItemSize = uxItemSize;
/* 重置消息队列,,在消息队列初始化的时候,需要重置一下相关参数*/
( void ) xQueueGenericReset( pxNewQueue, pdTRUE );
#if ( configUSE_TRACE_FACILITY == 1 )
{
pxNewQueue->ucQueueType = ucQueueType;
}
#endif /* configUSE_TRACE_FACILITY */
#if ( configUSE_QUEUE_SETS == 1 )
{
pxNewQueue->pxQueueSetContainer = NULL;
}
#endif /* configUSE_QUEUE_SETS */
traceQUEUE_CREATE( pxNewQueue );
}
创建队列的时候,是需要用户自定义消息队列的句柄的,但是注意了,定义了句柄并不等于创建了队列,创建队列必须是调用了消息队列创建函数进行创建(动态、静态),否则,以后根据队列句柄使用消息队列的其它函数时就会发生错误
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "lcd.h"
#include "SEGGER_RTT.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#define START_TASK_PRIO 1 //任务优先级
#define TASK1_TASK_PRIO 2 //任务优先级
#define TASK2_TASK_PRIO 3 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小 单位是words 不是byte
#define TASK1_STK_SIZE 128 //任务堆栈大小
#define TASK2_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄 任务控制块的首地址
TaskHandle_t Task1Task_Handler; //任务句柄
TaskHandle_t Task2Task_Handler; //任务句柄
QueueHandle_t KeyQueue_Handler; //按键值消息队列句柄
//按键消息队列的数量
#define KEYMSG_Q_NUM 1 //队列的长度,最大可包含多少个消息
void start_task(void *pvParameters); //任务函数声明
void task1_task(void *pvParameters); //任务函数声明
void task2_task(void *pvParameters); //任务函数声明
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);
int main(void)
{
HAL_Init(); //初始化HAL库
Stm32_Clock_Init(8, 336, 2, 7); //设置时钟,168Mhz
delay_init(168); //初始化延时函数
KEY_Init();
LED_Init(); //初始化LED端口
//动态创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
//开启任务调度
/* 启动RTOS,其实就是启动“任务管理器”,启动之后任务管理器就开始调度线程,
* 此时pc(程序计数器)就会指向某线程的指令,开始多线程并发运行。
* 如果没有创建多线程的话,那就只有一个线程。*/
vTaskStartScheduler();
/* 由于调用了vTaskStartScheduler之后,PC就指向了线程中的指令,因此vTaskStartScheduler后面代码
* 并不会被CPU执行,所以vTaskStartScheduler后的代码没有意义。 */
while(1)
{
//这里的代码不会被执行,写了也没用
}
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建TASK1任务
xTaskCreate((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
//创建TASK2任务
xTaskCreate((TaskFunction_t )task2_task,
(const char* )"task2_task",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
//创建按键消息队列
KeyQueue_Handler=xQueueCreate(KEYMSG_Q_NUM,sizeof(u8)); //创建消息 KeyQueue_Handler
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/**********************************************************************
* @ 函数名 : task1_task
* @ 功能说明: LED_Task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
void task1_task(void *pvParameters)
{
u8 keyValue=0;
BaseType_t err;
for(;;)
{
keyValue=KEY_Scan(0); //扫描按键
if((KeyQueue_Handler!=NULL)&&(keyValue)) //消息队列Key_Queue创建成功,并且按键被按下
{
/* 发送消息,这里发送的是指针,一定要进行强制转换&keyValue
* 0表示不管发送是否成功都立即返回,不休眠阻塞
* 10表示休眠阻塞时间是10ms,目的是为了防止队列消息满了,发送失败。所以休眠阻塞直至可以发送入队消息
*/
err=xQueueSend(KeyQueue_Handler,&keyValue,10);
if(err == errQUEUE_FULL) //发送按键值
{
SEGGER_RTT_printf(0,"Queue key_ Queue is full, data sending failed!\r\n");
}
}
vTaskDelay(200); /* 延时200ms,也就是200个时钟tick */
}
}
/**********************************************************************
* @ 函数名 : task2_task
* @ 功能说明: LED_Task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
void task2_task(void *pvParameters)
{
static uint8_t revKeyValue=0;
for(;;)
{
if(KeyQueue_Handler!=NULL) //消息队列Key_Queue创建成功
{
if(xQueueReceive(KeyQueue_Handler,&revKeyValue,portMAX_DELAY))//请求消息Key_Queue
{
switch(revKeyValue)
{
case KEY0_PRES: //KEY0控制LED0
LED0=!LED0;
break;
case KEY1_PRES: //KEY1控制LED1
LED1=!LED1;
break;
}
}
}
vTaskDelay(200); /* 延时200ms,也就是200个时钟tick */
}
}
2.消息队列静态创建(xQueueCreateStatic)
3.消息队列删除(xQueueDelete)
队列删除函数是根据句柄之间删除的,删除之后消息队列所有的信息都会被系统回收清空,而且不能再使用这个消息队列。
//消息队列删除函数 vQueueDelete()源码(已省略暂时无用部分)
void vQueueDelete( QueueHandle_t xQueue )
{
Queue_t * const pxQueue = xQueue;
configASSERT( pxQueue );/*对传入的消息队列句柄进行检查,如果消息队列是有效的才允许进行删除操作*/
traceQUEUE_DELETE( pxQueue );
#if ( configQUEUE_REGISTRY_SIZE > 0 )
{
/* 将消息队列从注册表中删除,我们目前没有添加到注册表中,暂时不用理会 */
vQueueUnregisterQueue( pxQueue );
}
#endif
#if ( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) )
{
/* 因为用的消息队列是动态分配内存的,所以需要调用vPortFree来释放消息队列的内存 */
vPortFree( pxQueue );
}
}
如果删除消息队列时,有任务正在等待,则不应该进行删除操作(官方说的是不允许进行删除操作,但是源码没有进行禁止删除的操作,使用的时候需要注意)
#define QUEUE_LENGTH 5
#define QUEUE_ITEM_SIZE 4
int main( void )
{
QueueHandle_t xQueue;
/* 创建消息队列 */
xQueue = xQueueCreate(QUEUE_LENGTH, QUEUE_ITEM_SIZE );
if ( xQueue == NULL )
{
/* 消息队列创建失败 */
} else
{
/* 删除已创建的消息队列*/
vQueueDelete(xQueue);
}
}
4.向队列发送消息(xQueueSend)
·
其实消息队列发送函数有好几个,有些用于任务中调用,有些用于中断中调用
xQueueSend()用于向队列尾发送一个队列消息。消息以拷贝的形式入队,而不是引用的形式。该函数绝对不能在中断中使用,中断必须使用带有保护功能的xQueueSendFromISR来替代
调用实例:
void task1_task(void *pvParameters)
{
u8 keyValue=0;
BaseType_t err;
for(;;)
{
keyValue=KEY_Scan(0); /*扫描按键*/
if((KeyQueue_Handler!=NULL)&&(keyValue)) /*消息队列Key_Queue创建成功,并且按键被按下*/
{
/* 发送消息,这里发送的是指针,一定要进行强制转换&keyValue
* 0表示不管发送是否成功都立即返回,不休眠阻塞
* 10表示休眠阻塞时间是10ms,目的是为了防止队列消息满了,发送失败。所以休眠阻塞直至可以发送入队消息
*/
err=xQueueSend( KeyQueue_Handler, /* 消息队列的句柄 */
&keyValue, /* 发送消息的内容 */
10 ); /* 等待时间 */
if(err == errQUEUE_FULL)
{
SEGGER_RTT_printf(0,"Queue key_ Queue is full, data sending failed!\r\n");
}
}
vTaskDelay(200); /* 延时200ms,也就是200个时钟tick */
}
}
5.从队列读取消息(xQueueReceive)
调用实例:
void task2_task(void *pvParameters)
{
BaseType_t xReturn = pdTRUE; /* 定义一个创建信息返回值,默认为 pdPASS */
static uint8_t revKeyValue=0; /* 定义一个接收消息的变量 */
for(;;)
{
if(KeyQueue_Handler!=NULL) /*消息队列Key_Queue创建成功*/
{
xReturn = xQueueReceive(KeyQueue_Handler, /* 消息队列的句柄 */
&revKeyValue, /* 接受消息的内容 */
portMAX_DELAY); /* 等待时间 一直等 */
if(xReturn!=0)
{
switch(revKeyValue)
{
case KEY0_PRES: //收到的数据是1 KEY0控制LED0
LED0=!LED0;
break;
case KEY1_PRES: //收到的数据是2 KEY1控制LED1
LED1=!LED1;
break;
}
}
}
vTaskDelay(200); /* 延时200ms,也就是200个时钟tick */
}
}
如果想接收了消息后并不想从队列中删除消息时用函数xQueuePeek代替
#define xQueuePeek( xQueue, pvBuffer, xTicksToWait ) \
xQueueGenericReceive( ( xQueue ), ( pvBuffer ), \
( xTicksToWait ), pdTRUE )
五、消息队列实验
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "lcd.h"
#include "SEGGER_RTT.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#define START_TASK_PRIO 1 //任务优先级
#define TASK1_TASK_PRIO 2 //任务优先级
#define TASK2_TASK_PRIO 3 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小 单位是words 不是byte
#define TASK1_STK_SIZE 128 //任务堆栈大小
#define TASK2_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄 任务控制块的首地址
TaskHandle_t Task1Task_Handler; //任务句柄
TaskHandle_t Task2Task_Handler; //任务句柄
QueueHandle_t KeyQueue_Handler; //按键值消息队列句柄
//按键消息队列的数量
#define KEYMSG_Q_NUM 1 //队列的长度,最大可包含多少个消息
void start_task(void *pvParameters); //任务函数声明
void task1_task(void *pvParameters); //任务函数声明
void task2_task(void *pvParameters); //任务函数声明
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);
int main(void)
{
HAL_Init(); //初始化HAL库
Stm32_Clock_Init(8, 336, 2, 7); //设置时钟,168Mhz
delay_init(168); //初始化延时函数
KEY_Init();
LED_Init(); //初始化LED端口
//动态创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
//开启任务调度
/* 启动RTOS,其实就是启动“任务管理器”,启动之后任务管理器就开始调度线程,
* 此时pc(程序计数器)就会指向某线程的指令,开始多线程并发运行。
* 如果没有创建多线程的话,那就只有一个线程。*/
vTaskStartScheduler();
/* 由于调用了vTaskStartScheduler之后,PC就指向了线程中的指令,因此vTaskStartScheduler后面代码
* 并不会被CPU执行,所以vTaskStartScheduler后的代码没有意义。 */
while(1)
{
//这里的代码不会被执行,写了也没用
}
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建TASK1任务
xTaskCreate((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
//创建TASK2任务
xTaskCreate((TaskFunction_t )task2_task,
(const char* )"task2_task",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
//创建按键消息队列
KeyQueue_Handler=xQueueCreate(KEYMSG_Q_NUM,sizeof(u8)); //创建消息 KeyQueue_Handler
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/**********************************************************************
* @ 函数名 : task1_task
* @ 功能说明: task1_task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
void task1_task(void *pvParameters)
{
u8 keyValue=0;
BaseType_t err;
for(;;)
{
keyValue=KEY_Scan(0); /*扫描按键*/
if((KeyQueue_Handler!=NULL)&&(keyValue)) /*消息队列Key_Queue创建成功,并且按键被按下*/
{
/* 发送消息,这里发送的是指针,一定要进行强制转换&keyValue
* 0表示不管发送是否成功都立即返回,不休眠阻塞
* 10表示休眠阻塞时间是10ms,目的是为了防止队列消息满了,发送失败。所以休眠阻塞直至可以发送入队消息
*/
err=xQueueSend( KeyQueue_Handler, /* 消息队列的句柄 */
&keyValue, /* 发送消息的内容 */
10 ); /* 等待时间 */
if(err == errQUEUE_FULL)
{
SEGGER_RTT_printf(0,"Queue key_ Queue is full, data sending failed!\r\n");
}
}
vTaskDelay(200); /* 延时200ms,也就是200个时钟tick */
}
}
/**********************************************************************
* @ 函数名 : task2_task
* @ 功能说明: LED_Task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
void task2_task(void *pvParameters)
{
BaseType_t xReturn = pdTRUE; /* 定义一个创建信息返回值,默认为 pdPASS */
static uint8_t revKeyValue=0; /* 定义一个接收消息的变量 */
for(;;)
{
if(KeyQueue_Handler!=NULL) /*消息队列Key_Queue创建成功*/
{
xReturn = xQueueReceive(KeyQueue_Handler, /* 消息队列的句柄 */
&revKeyValue, /* 接受消息的内容 */
portMAX_DELAY); /* 等待时间 一直等 */
if(xReturn!=0)
{
switch(revKeyValue)
{
case KEY0_PRES: //收到的数据是1 KEY0控制LED0
LED0=!LED0;
break;
case KEY1_PRES: //收到的数据是2 KEY1控制LED1
LED1=!LED1;
break;
}
}
}
vTaskDelay(200); /* 延时200ms,也就是200个时钟tick */
}
}