FreeRTOS-队列

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
	}

  1. 队列项大小有效性判断,如果队列项大小为0,那么不需要为队列分配空间
  2. 队列项大小有效,则计算所有队列项所占用的内存大小
  3. 为新创建的队列项分配内存,注意:除了队列项需要占用内存外,队列结构体也需要一定的内存,所以整个队列申请的内存空间为二者之和
  4. 如果队列内存申请成功,则找到队列中首项的地址,即队列首地址+队列结构体内存空间偏移
  5. 初始化队列结构体,初始化操作将在下面分析
  6. 如果创建成功了,则返回队列的句柄,用户可以通过句柄来控制队列
  • 上面的过程整理下来就是,先进行有效性判断,有效则为队列申请内存,并找到队列首项的地址,再对队列进行初始化,然后返回其句柄。

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 );
}
  1. 判断队列项大小的有效性,如果有效则将队列结构体成员head指向队列中的第一项,这里pucQueueStorage我们在上面已经分析了,它是队列首项的首地址。
  2. 将指定的队列长度赋值给结构体成员uxLength
  3. 将指定的队列项大小赋值给结构体成员uxItemSize
  4. 调用队列复位函数,该函数会在下面分析
  • 所以到这里,我们就已经能够看出来,队列初始化其实就是对创建队列的结构体成员进行初始化,在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;
}
  1. 将队尾指针指向队列的最后一项的末尾地址,因为pxQueue->pcHead已经指向队列的首地址了,那么在该基础上再偏移所有队列项内存占用内存空间和,所以就指向了该队列所申请空间的最后一个字节
  2. 初始化uxMessagesWaiting 成员为0,即队列中入队数量=0
  3. 初始化写入指针地址,当入队时,就将内容写到这里,初始时必然是要往第一项写入
  4. pxQueue->u.pcReadFrom 指向队列中最后一项的首地址
  5. 接收消息(出队)不锁定
  6. 发送消息(入队)不锁定
  7. 该队列是否是新创建的队列
  8. 该队列不是新创建的队列,判断因为队列已满而不能入队导致的任务阻塞列表是否为空
  9. 任务阻塞列表不为空,则将任务从事件列表中移除,并判断是否需要重新进行任务调度
  10. 该队列为新创建的队列,则初始化任务发送等待列表
  11. 初始化任务接收等待列表
  • 下面是一个队列长度为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;
		}
	}
}
  1. 进入一个大循环,入队的所有操作都将在这个大循环中进行
  2. 如果队列没有满或者是复写模式
  3. 将数据按照相应位置拷贝到队列中,该函数会在后面分析,这里只需要知道是进行了入队操作就行
  4. 入队成功,跳出函数
  5. 如果队列满了并且不支持复写模式,则判断是否有等待阻塞时间
  6. 未设置等待阻塞时间,则直接跳出函数,并返回入队失败
  7. 设置了等待阻塞时间,则判断是否设置了进入时间,即通过该判断可以设置什么时候超过了这个阻塞时间
  8. 设置超过阻塞时间点
  9. 已经设置了解除阻塞时间点
  10. 任务由于向队列发送消息或者从队列读取消息而发生阻塞时,在把任务添加到阻塞列表之前,不允许中断或者其他任务操作这个队列,所以挂起任务调度器,以防止其他任务操作队列
  11. 除了挂起任务调度器以外,还需要将队列上锁,相应地,不允许中断服务程序操作队列的事件表
  12. 如果还在阻塞时间范围内
  13. 如果队列是满的
  14. 将队列添加到等待列表中,并设置等待时间
  15. 解锁队列事件列表
  16. 任务调度器解除挂起,如果还没有进行任务调度,则开始一次任务调度,防止任务调度器挂起期间有高优先级任务请求而未响应
  17. 队列没有满,那么可以尝试重复入队
  18. 如果阻塞等待时间已经过去了,也就是入队失败,那么解除事件锁定,并解除调度器挂起
  • 上面过程相对来说比较繁琐,过程也很复杂,需要慢慢理解。下面来分析一下这里面调用的一些重要的函数。

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;
}
  1. 判断队列项大小的有效性
  2. 这部分为互斥信号量相关的,这里不进行分析
  3. 队列项大小有效,如果从后方入队
  4. 将要插入的队列项入队,注意如果是从后方入队,则是第一项入队,也就是头指针指向的那一个,由于读取队列项的时候是从最后一项读取,所以这种方式入队也就相当于栈,遵从先进后出(FILO)的原则
  5. 写入指针移向下一个队列项首地址
  6. 判断写入指针是否指向队列最后一个字节或队列以外的空间
  7. 如果指向了队列最后一个字节的地址,或队列以外的空间,则重新将其指向队列头部
  8. 如果是从前方入队,这种情况下队列遵从先进先出的机制
  9. 将入队的项拷贝到队列最后一项,这样读取的时候也是从最后一项开始
  10. 将队列读取指针移向前一个队列项首地址
  11. 判断读取指针的地址是否在队列合理范围内
  12. 如果不在合理范围内,则重新指向队列最后一项
  13. 如果采用的是复写模式
  14. 判断入队数量是否大于0
  15. 因为是复写模式,所以为了保证数据的有效性,则每入队一项,自动减1,后面会+1,这样相当于不变化,
  16. 入队成功,队列项数量+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();
}
  1. 循环,直到将发送列表锁定期间所有要发送的列表项全部移除
  2. 判断接收列表是否为空
  3. 接收列表不为空,则将列表项从中移除,并判断移除的列表项优先级是否大于当前正在运行任务的优先级
  4. 移除的列表项的任务优先级大,则进行任务切换
  5. cTxLock自减1
  6. 解锁发送列表
  7. 循环,直到将接收列表锁定期间所有要接收的列表项全部移除
  8. 判断接收列表是否为空
  9. 移除的列表项的任务优先级大,则进行任务切换
  10. cRxLock自减1
  11. 解除接收列表

  • 入队函数与出队函数类似,就不再进行分析了,下面给出队列的两个用法示例。

用法示例

  • 队列操作包含任务级和中断级,任务级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);
	 }
}


中断级队列操作与这类似在此不展开叙述了。

猜你喜欢

转载自blog.csdn.net/dhejsb/article/details/120161995
今日推荐