FreeRTOS-队列
0+ 在FreeRTOS中,队列是为了任务与任务或任务与中断之间通信而专门准备的,它是任务与任务、任务与中断间传递消息的重要手段,所以我们也称之为消息队列。并且队列也是后面章节中信号量实现的基础,所以有必要深入了解队列及其源码。
- 我们在前面讲述过FreeRTOS中的列表和列表项,我们发现FreeRTOS中的列表更像是我们所说的数据结构中的链表,FreeRTOS中所使用的的链表为双向循环链表。同样地,FreeRTOS中的队列的队列与平时数据结构中所说的队列大同小异,只不过在FreeRTOS中将队列做了一个扩展,就是队列既可以先进先出(FIFO)也可以先进后出(FILO),即它兼具了数据结构中的队列和栈。
队列结构体及相关API
- 与列表和列表项一样,队列也有其相关的结构体和API,下面逐一来分析一下。
队列结构体
- 队列结构体定义如下。
typedef struct QueueDefinition
{
int8_t *pcHead; /*指向队列存储区首地址*/
int8_t *pcTail; /*指向队列存储区最后一个字节地址*/
int8_t *pcWriteTo; /*指向存储区下一个空闲区域 */
union /* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */
{
int8_t *pcReadFrom; /*指向最后一个出队队列项的首地址*/
UBaseType_t uxRecursiveCallCount;/*当使用互斥信号量时,用以保存互斥量被调用次数*/
} u;
List_t xTasksWaitingToSend; /*等待队列列表,当队列满了而且不允许复写时,会导致入队失败而阻塞,这些任务会被添加到该列表中并按照任务优先级排序 */
List_t xTasksWaitingToReceive; /*当队列空的时候,有任务想要读取队列的消息而进入阻塞状态,会被添加到该列表中,并按照任务优先级排序*/
volatile UBaseType_t uxMessagesWaiting;/*当前队列中队列项的数量*/
UBaseType_t uxLength; /*队列的长度,即队列中由多少个队列项组成 */
UBaseType_t uxItemSize; /*每个队列项的大小,即该队列项是多少字节的 */
volatile int8_t cRxLock; /*队列上锁以后任务从队列中接收队列项的数量*/
volatile int8_t cTxLock; /*队列上锁以后任务发送给队列的队列项的数量*/
/* 下面的条件编译部分可以先不看 */
#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated;
#endif
#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition *pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
- 后面我们会说这些成员是如何来使用的。下面来看一下关于队列的一些API。
xQueueCreate()
- 该函数用于创建队列,函数定义如下:
#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
- 输入形参有两个,一个是队列长度(包含多少个队列项),另一个是队列项大小,也就是每个队列项占用多少个字节。并且该函数是一个宏,实际调用了函数xQueueGenericCreate(),函数定义如下。
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
{
Queue_t *pxNewQueue;
size_t xQueueSizeInBytes;
uint8_t *pucQueueStorage;
configASSERT( uxQueueLength > ( UBaseType_t ) 0 );
if( uxItemSize == ( UBaseType_t ) 0 )----1
{
xQueueSizeInBytes = ( size_t ) 0;
}
else
{
xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); ----2
}
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );----3
if( pxNewQueue != NULL )
{
pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );----4
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );----5
}
return pxNewQueue; ----6
}
- 队列项大小有效性判断,如果队列项大小为0,那么不需要为队列分配空间
- 队列项大小有效,则计算所有队列项所占用的内存大小
- 为新创建的队列项分配内存,注意:除了队列项需要占用内存外,队列结构体也需要一定的内存,所以整个队列申请的内存空间为二者之和
- 如果队列内存申请成功,则找到队列中首项的地址,即队列首地址+队列结构体内存空间偏移
- 初始化队列结构体,初始化操作将在下面分析
- 如果创建成功了,则返回队列的句柄,用户可以通过句柄来控制队列
- 上面的过程整理下来就是,先进行有效性判断,有效则为队列申请内存,并找到队列首项的地址,再对队列进行初始化,然后返回其句柄。
prvInitialiseNewQueue()
- 上面我们已经知道了,在创建队列的时候主要就分配内存和队列初始化两个操作,内存分配已经讲述过了,接下来分析一下队列初始化函数,函数定义如下。
static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, const uint8_t ucQueueType, Queue_t *pxNewQueue )
{
( void ) ucQueueType;
if( uxItemSize == ( UBaseType_t ) 0 )----1
{
pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
}
else
{
pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
}
pxNewQueue->uxLength = uxQueueLength;----2
pxNewQueue->uxItemSize = uxItemSize;----3
( void ) xQueueGenericReset( pxNewQueue, pdTRUE );----4
/*下面是条件编译的一部分可以不看*/
#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 );
}
- 判断队列项大小的有效性,如果有效则将队列结构体成员head指向队列中的第一项,这里pucQueueStorage我们在上面已经分析了,它是队列首项的首地址。
- 将指定的队列长度赋值给结构体成员uxLength
- 将指定的队列项大小赋值给结构体成员uxItemSize
- 调用队列复位函数,该函数会在下面分析
- 所以到这里,我们就已经能够看出来,队列初始化其实就是对创建队列的结构体成员进行初始化,在prvInitialiseNewQueue()中已经初始化了pcHead、uxLength 、uxItemSize 这三个成员,那么接下来看一下在xQueueGenericReset()函数中又会初始化哪些成员。
xQueueGenericReset()
- 该函数是用于重置队列,那么既然重置队列了,必然会将队列中的队列项都清空,将队列中的一些指针也都恢复成最初状态,函数定义如下。
BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )
{
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
configASSERT( pxQueue );
taskENTER_CRITICAL();
{
pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize );----1
pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;----2
pxQueue->pcWriteTo = pxQueue->pcHead;----3
pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - ( UBaseType_t ) 1U ) * pxQueue->uxItemSize );----4
pxQueue->cRxLock = queueUNLOCKED;----5
pxQueue->cTxLock = queueUNLOCKED;----6
if( xNewQueue == pdFALSE )---7
{
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )---8
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )---9
{
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* Ensure the event queues start in the correct state. */
vListInitialise( &( pxQueue->xTasksWaitingToSend ) );---10
vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );---11
}
}
taskEXIT_CRITICAL();
/* A value is returned for calling semantic consistency with previous
versions. */
return pdPASS;
}
- 将队尾指针指向队列的最后一项的末尾地址,因为pxQueue->pcHead已经指向队列的首地址了,那么在该基础上再偏移所有队列项内存占用内存空间和,所以就指向了该队列所申请空间的最后一个字节
- 初始化uxMessagesWaiting 成员为0,即队列中入队数量=0
- 初始化写入指针地址,当入队时,就将内容写到这里,初始时必然是要往第一项写入
- pxQueue->u.pcReadFrom 指向队列中最后一项的首地址
- 接收消息(出队)不锁定
- 发送消息(入队)不锁定
- 该队列是否是新创建的队列
- 该队列不是新创建的队列,判断因为队列已满而不能入队导致的任务阻塞列表是否为空
- 任务阻塞列表不为空,则将任务从事件列表中移除,并判断是否需要重新进行任务调度
- 该队列为新创建的队列,则初始化任务发送等待列表
- 初始化任务接收等待列表
- 下面是一个队列长度为4,每个队列项 为32字节初始化后的队列
xQueueGenericCreate()
- FreeRTOS提供了四个任务级入队函数,我们先来看一看这四个任务级入队函数的定义
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define xQueueOverwrite( xQueue, pvItemToQueue ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )
- 从上面四个函数的定义可见,这四个函数都是一个宏,最终都调用了函数xQueueGenericSend(),这四个函数区别仅在最后一个输入参数不同。
- queueSEND_TO_BACK 从后面入队
- queueSEND_TO_FRONT 从前面入队
- queueOVERWRITE 支持复写,当队列满的时候就把最后一个队列项给覆盖掉
- 所以,接下来只需要着重分析xQueueGenericSend()这个函数即可。函数定义如下
BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition )
{
BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
configASSERT( pxQueue );
configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
}
#endif
for( ;; )----1
{
taskENTER_CRITICAL();
{
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )----2
{
traceQUEUE_SEND( pxQueue );
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );----3
//队列集这部分忽略
...
...
taskEXIT_CRITICAL();
return pdPASS;----4
}
else
{
if( xTicksToWait == ( TickType_t ) 0 )----5
{
taskEXIT_CRITICAL();
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL;----6
}
else if( xEntryTimeSet == pdFALSE )----7
{
vTaskSetTimeOutState( &xTimeOut );----8
xEntryTimeSet = pdTRUE;
}
else----9
{
/* Entry time was already set. */
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
vTaskSuspendAll();----10
prvLockQueue( pxQueue );----11
/* Update the timeout state to see if it has expired yet. */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )----12
{
if( prvIsQueueFull( pxQueue ) != pdFALSE )----13
{
traceBLOCKING_ON_QUEUE_SEND( pxQueue );
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );----14
prvUnlockQueue( pxQueue );----15
if( xTaskResumeAll() == pdFALSE )----16
{
portYIELD_WITHIN_API();
}
}
else----17
{
/* Try again. */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else----18
{
/* The timeout has expired. */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL;
}
}
}
- 进入一个大循环,入队的所有操作都将在这个大循环中进行
- 如果队列没有满或者是复写模式
- 将数据按照相应位置拷贝到队列中,该函数会在后面分析,这里只需要知道是进行了入队操作就行
- 入队成功,跳出函数
- 如果队列满了并且不支持复写模式,则判断是否有等待阻塞时间
- 未设置等待阻塞时间,则直接跳出函数,并返回入队失败
- 设置了等待阻塞时间,则判断是否设置了进入时间,即通过该判断可以设置什么时候超过了这个阻塞时间
- 设置超过阻塞时间点
- 已经设置了解除阻塞时间点
- 任务由于向队列发送消息或者从队列读取消息而发生阻塞时,在把任务添加到阻塞列表之前,不允许中断或者其他任务操作这个队列,所以挂起任务调度器,以防止其他任务操作队列
- 除了挂起任务调度器以外,还需要将队列上锁,相应地,不允许中断服务程序操作队列的事件表
- 如果还在阻塞时间范围内
- 如果队列是满的
- 将队列添加到等待列表中,并设置等待时间
- 解锁队列事件列表
- 任务调度器解除挂起,如果还没有进行任务调度,则开始一次任务调度,防止任务调度器挂起期间有高优先级任务请求而未响应
- 队列没有满,那么可以尝试重复入队
- 如果阻塞等待时间已经过去了,也就是入队失败,那么解除事件锁定,并解除调度器挂起
- 上面过程相对来说比较繁琐,过程也很复杂,需要慢慢理解。下面来分析一下这里面调用的一些重要的函数。
prvCopyDataToQueue()
- 该函数是将真正入队时候队列的相关操作。函数定义如下。
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue, const void *pvItemToQueue, const BaseType_t xPosition )
{
BaseType_t xReturn = pdFALSE;
UBaseType_t uxMessagesWaiting;
uxMessagesWaiting = pxQueue->uxMessagesWaiting;
if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )----1
{
#if ( configUSE_MUTEXES == 1 )----2
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* The mutex is no longer being held. */
xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );
pxQueue->pxMutexHolder = NULL;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
}
else if( xPosition == queueSEND_TO_BACK )----3
{
( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); ----4
pxQueue->pcWriteTo += pxQueue->uxItemSize;----5
if( pxQueue->pcWriteTo >= pxQueue->pcTail ) ----6
{
pxQueue->pcWriteTo = pxQueue->pcHead;----7
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else----8
{
( void ) memcpy( ( void * ) pxQueue->u.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); ----9
pxQueue->u.pcReadFrom -= pxQueue->uxItemSize;----10
if( pxQueue->u.pcReadFrom < pxQueue->pcHead ) ----11
{
pxQueue->u.pcReadFrom = ( pxQueue->pcTail - pxQueue->uxItemSize );----12
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xPosition == queueOVERWRITE )----13
{
if( uxMessagesWaiting > ( UBaseType_t ) 0 )----14
{
--uxMessagesWaiting;----15
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;----16
return xReturn;
}
- 判断队列项大小的有效性
- 这部分为互斥信号量相关的,这里不进行分析
- 队列项大小有效,如果从后方入队
- 将要插入的队列项入队,注意如果是从后方入队,则是第一项入队,也就是头指针指向的那一个,由于读取队列项的时候是从最后一项读取,所以这种方式入队也就相当于栈,遵从先进后出(FILO)的原则
- 写入指针移向下一个队列项首地址
- 判断写入指针是否指向队列最后一个字节或队列以外的空间
- 如果指向了队列最后一个字节的地址,或队列以外的空间,则重新将其指向队列头部
- 如果是从前方入队,这种情况下队列遵从先进先出的机制
- 将入队的项拷贝到队列最后一项,这样读取的时候也是从最后一项开始
- 将队列读取指针移向前一个队列项首地址
- 判断读取指针的地址是否在队列合理范围内
- 如果不在合理范围内,则重新指向队列最后一项
- 如果采用的是复写模式
- 判断入队数量是否大于0
- 因为是复写模式,所以为了保证数据的有效性,则每入队一项,自动减1,后面会+1,这样相当于不变化,
- 入队成功,队列项数量+1
prvLockQueue()
- 该函数是将队列时间列表上锁。函数定义如下
#define prvLockQueue( pxQueue ) \
taskENTER_CRITICAL(); \
{ \
if( ( pxQueue )->cRxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \
} \
if( ( pxQueue )->cTxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \
} \
} \
taskEXIT_CRITICAL()
- 从上面可以看出,该函数完成的任务简单,就是将cRxLock、cTxLock两个赋值为queueLOCKED_UNMODIFIED,即上锁状态
prvUnlockQueue()
- 该函数是将队列事件列表解锁,函数定义如下。
static void prvUnlockQueue( Queue_t * const pxQueue )
{
taskENTER_CRITICAL();
{
int8_t cTxLock = pxQueue->cTxLock;
while( cTxLock > queueLOCKED_UNMODIFIED )----1
{
...
...
//队列集部分代码忽略
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )----2
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )----3
{
/* The task waiting has a higher priority so record that
a context switch is required. */
vTaskMissedYield();----4
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--cTxLock;----5
}
pxQueue->cTxLock = queueUNLOCKED;----6
}
taskEXIT_CRITICAL();
/* Do the same for the Rx lock. */
taskENTER_CRITICAL();
{
int8_t cRxLock = pxQueue->cRxLock;
while( cRxLock > queueLOCKED_UNMODIFIED )----7
{
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )----8
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )----9
{
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--cRxLock;----10
}
else
{
break;
}
}
pxQueue->cRxLock = queueUNLOCKED;----11
}
taskEXIT_CRITICAL();
}
- 循环,直到将发送列表锁定期间所有要发送的列表项全部移除
- 判断接收列表是否为空
- 接收列表不为空,则将列表项从中移除,并判断移除的列表项优先级是否大于当前正在运行任务的优先级
- 移除的列表项的任务优先级大,则进行任务切换
- cTxLock自减1
- 解锁发送列表
- 循环,直到将接收列表锁定期间所有要接收的列表项全部移除
- 判断接收列表是否为空
- 移除的列表项的任务优先级大,则进行任务切换
- cRxLock自减1
- 解除接收列表
- 入队函数与出队函数类似,就不再进行分析了,下面给出队列的两个用法示例。
用法示例
- 队列操作包含任务级和中断级,任务级API只能在创建的任务中调用,中断级API只能在系统所管理的中断中调用,所以在中断级中调用队列相关的API时,不但要保证所调用的是中断级API,还要保证该中断是FreeRTOS所能管理的中断。
任务级用法
实验目标:在两个创建的两个任务之间用队列实现通信,通过按键发送队列消息,另一端接收队列消息
- 定义两个任务,任务名分别为key_task和led1_task,宏定义如下。
#define KEY_TASK_PRIO 5
#define KEY_STACK_SIZE 25
TaskHandle_t KeyTask_Handler;
void key_task(void *pvParameters);
#define LED1_TASK_PRIO 3
#define LED1_STACK_SIZE 50
TaskHandle_t Led1Task_Handler;
void led1_task(void *pvParameters);
- 定义队列句柄,长度及队列项大小,这里因为功能简单,所以只需要一个队列项,队列项大小为1字节,定义如下
QueueHandle_t Queue_Handler;
#define QUEUE_LENGTH 1
#define QUEUE_SIZE sizeof(u8)
- 在start_task中创建上面的任务及队列,函数定义如下
void start_task(void *pvParameters)
{
Queue_Handler = xQueueCreate( QUEUE_LENGTH, QUEUE_SIZE );
if (Queue_Handler == NULL)
{
printf("Creat queue failed!!!\r\n");
}
xTaskCreate((TaskFunction_t )led1_task, //任务函数
(const char* )"led1_task", //任务名称
(uint16_t )LED1_STACK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )LED1_TASK_PRIO, //任务优先级
(TaskHandle_t* )&Led1Task_Handler); //任务句柄
xTaskCreate((TaskFunction_t )key_task, //任务函数
(const char* )"key_task", //任务名称
(uint16_t )KEY_STACK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )KEY_TASK_PRIO, //任务优先级
(TaskHandle_t* )&KeyTask_Handler); //任务句柄
vTaskDelete(StartTask_Handler);
}
- key_task和led1_task函数定义如下
u8 keyFlag=0;
void key_task(void* pvParameters)
{
BaseType_t err;
while(1)
{
if (Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON)
{
if ( (keyFlag&0x01) == 0x01)
keyFlag &= ~(0x01<<0);
else
keyFlag |= (0x01<<0);
if (Queue_Handler != NULL)
{
//err = xQueueOverwrite(Queue_Handler,&keyFlag);//复写
err = xQueueSend(Queue_Handler,&keyFlag,10);
}
if(err !=pdTRUE)
{
printf("Queue Send Failed!!!\r\n");
}
}
vTaskDelay(10);
}
}
void led1_task(void* pvParameters)
{
BaseType_t err;
while(1)
{
if (Queue_Handler!=NULL)
{
err = xQueueReceive(Queue_Handler,&keyFlag,portMAX_DELAY);
if (err != pdTRUE)
{
printf("receive Failed!!!\r\n");
}
else
{
printf("keyFlag=%#x\r\n",keyFlag);
}
LED1 = (keyFlag&0x01);
}
vTaskDelay(10);
}
}
中断级队列操作与这类似在此不展开叙述了。