首页 > 其他分享 >FreeRTOS - 队列

FreeRTOS - 队列

时间:2024-10-17 22:19:05浏览次数:3  
标签:FreeRTOS 队列 void else 任务 data pxQueue

在学习FreeRTOS过程中,结合韦东山-FreeRTOS手册和视频、野火-FreeRTOS内核实现与应用开发、及网上查找的其他资源,整理了该篇文章。如有内容理解不正确之处,欢迎大家指出,共同进步。

1. 队列

1.1 队列基本概念

队列(queue)可以用于"任务到任务"、“任务到中断”、"中断到任务"直接传输信息。

  • 队列可以包含若干个数据:队列中有若干项,这被称为"长度"(length)
  • 每个数据大小固定
  • 创建队列时就要指定长度、数据大小
  • 数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾部,读数据时从头部读
  • 也可以强制写队列头部:覆盖头部数据

1.2 传输数据的两种方法

使用队列传输数据时有两种方法:

  • 拷贝:把数据、把变量的值复制进队列里
  • 引用:把数据、把变量的地址复制进队列里

FreeRTOS使用拷贝值的方法,这更简单:

  • 局部变量的值可以发送到队列中,后续即使函数退出、局部变量被回收,也不会影响队列中的数据
  • 无需分配buffer来保存数据,队列中有buffer
  • 局部变量可以马上再次使用
  • 发送任务、接收任务解耦:接收任务不需要知道这数据是谁的、也不需要发送任务来释放数据
  • 如果数据实在太大,你还是可以使用队列传输它的地址
  • 队列的空间有FreeRTOS内核分配,无需任务操心
  • 对于有内存保护功能的系统,如果队列使用引用方法,也就是使用地址,必须确保双方任务对这个地址都有访问权限。使用拷贝方法时,则无此限制:内核有足够的权限,把数据复制进队列、再把数据复制出队列。
1.3 队列的核心
  • 队列的核心:关中断、环形缓冲区、链表。
    • 关中断:实现互斥
    • 环形缓冲区:保存数据
    • 链表:实现休眠和唤醒

  • 使用队列的好处:
    • 读时队列中无数据、写时队列满,则任务进入休眠(阻塞状态),不抢占CPU资源,不参与运行,可以把时间留出来给其他任务运行。
    • 使用队列可以保证数据在传输过程中不被破坏。
    • 支持休眠和唤醒,使程序运行的效率更高。
1.3.1 队列怎样实现互斥访问----关中断
  • 互斥的核心:关中断、开中断
    • 关中断:
      • 使时钟中断无法产生,任务就无法切换;
      • 关中断可以认为是关全部中断,也可以是部分中断,包括Tick中断。关了中断后,就相当于裸机程序

1.3.2 环形缓冲区

1.3.3 怎样休眠/唤醒–链表
  • API函数允许指定阻塞时间。
  • 如果多个任务阻塞在一个队列上,那么最高优先级别的任务会第一个解除阻塞。
  • 注:中断程序中必须使用“FromISR”结尾的API函数!

有两个链表:

  • 写队列不成功而挂起
    • 每当任务企图向一个满的队列写数据时,任务会进入阻塞状态,直到队列中出现有效空间或者阻塞时间到期。
  • 读队列不成功而挂起
    • 每当任务企图从一个空的队列读取数据时,任务会进入阻塞状态(这样任务不会消耗任何CPU时间并且另一个任务可以运行)直到队列中出现有效数据或者阻塞时间到期。

写队列:

  • 写队列不成功而阻塞 :
    • 将任务放入List_t xTasksWaitingToSend;从该链表中可以找到写此队列不成功的任务
    • 将任务从ReadList ——> DelayList;
  • 被唤醒 :别的任务读队列,有空间了可以写;超时时间到
    • List_t xTasksWaitingToSend;中移除
    • DelayList ——> ReadList ;

读队列:

  • 读队列不成功而阻塞:
    • 放入List_t xTasksWaitingToReceive;从该链表中可以找到读此队列不成功的任务
    • 将任务从ReadList ——> DelayList;
  • 被唤醒 :别的任务写数据了;超时时间到,
    • xTasksWaitingToReceive;中移除
    • DelayList ——> ReadList ;
1.4 队列的超时唤醒

1.5 读队列流程

  1. 当有多个任务来读该队列时,需要做一些保护:关中断
  2. 判断队列中有无数据
    • 无 Data
      • (1)不阻塞,直接返回,返回ERR
      • (2)休眠
        • 将任务放入:xTasksWaitingToReceive;
        • 将该任务从 ReadList ——> DelayList中
        • 如果有任务优先级 > 当前任务优先级
          • 任务切换
    • 有 Data
  3. 有Data:本来 队列 中就有数据,或 从休眠中被唤醒(写数据了;超时到了)
    • 任务从队列中读数据;
      • 拷贝方式;
      • 将队列中数据数减一;
    • 读数据后,队列中有空间了,如果有 任务 A / B 在等待写数据的话,就将其唤醒。唤醒:
      • xTasksWaitingToSend中的第 1 个任务移除
      • 将其从 DelayList——> ReadList 中
      • 如果恢复的任务优先级 > 当前任务
        • 任务切换
1.6 队列写流程

任务和中断都可以向队列发送消息。

如果队列未满或者允许覆盖入队,FreeRTOS会将消息拷贝到队列队尾,否则根据用户指定的阻塞超时时间进行阻塞。

当其他任务从其等待的队列中读入了数据(队列未满),该任务将自动由阻塞态转为就绪态。

当任务等待的时间,超过了指定的阻塞时间,计时队列中还不允许入队,任务也会自动从阻塞态转为就绪态,此时发送消息的任务或中断程序会收到一个错误码 errQUEUE_FULL。
在这里插入图片描述

  1. 关中断
  2. 判断队列中有无空间
    • 无 空间
      • (1)不阻塞,直接退出,返回ERR
      • (2)休眠(阻塞)
        • 将任务放入:xTasksWaitingToSend
        • 将该任务从 ReadList ——> DelayList中
    • 有 空间
  3. 有 空间:本来 队列 中就有空间,或 从休眠中被唤醒(读数据了有空间了;超时到了)
    • 任务往队列中写数据(拷贝方式);
    • 有任务在等待读队列,唤醒它。唤醒:
      • xTasksWaitingToReceive;中的第 1 个任务移除
      • 将其从 DelayList——> ReadList 中
      • 如果恢复任务的优先级 > 当前运行的任务优先级
        • 任务切换
    • 没有任务在等待读队列
      • 数据拷贝成功 ----> 任务切换

一个任务写队列时,如果队列已经满了,它会被挂起,何时被唤醒?

  • 超时:
    • 任务写队列不成功时,它会被挂起:从ready list移到delayed list中
    • 在delayed list中,按照"超时时间"排序
    • 系统Tick中断不断发生,在Tick中断里判断delayed list中的任务时间到没?时间到后就唤醒它

2. 队列函数

2.1 创建队列

  • 定义一个句柄变量,用于保存创建的队列:QueueHandle_t xQueue1;
  • 使用xQueueCreate()创建一个队列;
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, // 队列长度,最多能存放多少个数据
                           UBaseType_t uxItemSize // 每个数据的大小:以字节为单位);

2.1.1 队列结构体

2.1.2 xQueueGenericCreate函数

xQueueGenericCreate内部会分配队列空间、初始化队列。

2.2 向队列发送消息函数(写队列)
  • 举例
// 举例
// 创建队列
g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data));

// 写队列:将 input 中的数据写入到 g_xQueuePlatform
xQueueSend(g_xQueuePlatform,&input,0);
// 读队列:从 g_xQueuePlatform 中读数据,放入到 idata中
xQueueReceive(g_xQueuePlatform,&idata,portMAX_DELAY);
// 删除队列
vQueueDelete(g_xQueuePlatform);
2.2.1 写队列函数
  1. xQueueSend()函数

  • xQueueSend() 等 同 于 xQueueSendToBack()。
  • 用于向队列尾部发送一个队列消息。消息以拷贝的形式入队,而不是以 引用的形式。
  • 该函数绝对不能在中断服务程序里面被调用,中断中必须使用带有中断保护 功能的 xQueueSendFromISR()来代替。

  1. xQueueSendFromISR()

xQueueSendFromISR(): 用于在中断服务程序中向队列尾部发送一个队列消息。

xQueueSendToBackFromISR 等同于 xQueueSendFromISR ()。
在这里插入图片描述

2.2.2 通用消息队列发送函数 xQueueGenericSend()(任务)
BaseType_t xQueueGenericSend( QueueHandle_t xQueue, 
                             const void * const pvItemToQueue, // 指针,指向要发送的消息
                             TickType_t xTicksToWait, 
                             const BaseType_t xCopyPosition )
  • xQueue:消息队列句柄;
  • pvItemToQueue: 指针,指向要发送的消息
  • xTicksToWait:指定阻塞超时时间
  • xCopyPosition:发送数据到消息队列的位置:
    • queueSEND_TO_BACK:发送到队尾;
    • queueSEND_TO_FRONT:发送到队头;
    • queueOVERWRITE:以覆盖的方式发送

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;

	for( ;; )
	{
		taskENTER_CRITICAL();
		{
			/* 队列未满,或,以覆盖的方式发送,无队列满或未满,都可以发送*/
			if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) 
                   || ( xCopyPosition == queueOVERWRITE ) )
			{
				traceQUEUE_SEND( pxQueue );
                // 队列未满,将消息拷贝到队列中
				xYieldRequired = 
                    prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );

                /* 有任务在等待读数据 */
                if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
                {
                    if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                    {
                        /* 将任务从事件链表xTasksWaitingToReceive中删除,
                        事件链表按由新阿基顺序排序,链表中的第一个优先级最高。
                        放入到就绪链表。
                        如果恢复的任务优先级比当前运行任务的优先级高,
                        那么需要进行一次任务切换*/
                        queueYIELD_IF_USING_PREEMPTION();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else if( xYieldRequired != pdFALSE )
                {
                    /* 没有等待的任务,拷贝成功也需要任务切换 */
                    queueYIELD_IF_USING_PREEMPTION();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }

				taskEXIT_CRITICAL();
				return pdPASS;
			}
			else  /* 队列已满 */
			{
				if( xTicksToWait == ( TickType_t ) 0 )
				{
					/* 不指定阻塞时间,直接退出 */
					taskEXIT_CRITICAL();
					traceQUEUE_SEND_FAILED( pxQueue );
					return errQUEUE_FULL;
				}
				else if( xEntryTimeSet == pdFALSE )
				{
					/* 指定了超时时间,系统会初始化阻塞超时结构体变量,
                    初始化进入阻塞的时间 xTickCount和溢出次数 xNumOfOverflows,
                    为后面的阻塞任务做准备*/
					vTaskInternalSetTimeOutState( &xTimeOut );
					xEntryTimeSet = pdTRUE;
				}
				else
				{
					/* Entry time was already set. */
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		taskEXIT_CRITICAL();

		/* 挂起调度器 ,不会进行任务切换,
        但不能禁止中断的发生,所以还需要给队列上锁*/
		vTaskSuspendAll();
		prvLockQueue( pxQueue );

		/* 检查指定的超时时间是否已经过去了 */
		if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
		{  // 没过
            /* 队列是满的 */
			if( prvIsQueueFull( pxQueue ) != pdFALSE )
			{	/* 根据用户指定的超时时间来阻塞一下任务*/
				traceBLOCKING_ON_QUEUE_SEND( pxQueue );
                /* 将其放入到写队列不成功链表中,xTasksWaitingToSend*/
				vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );

				/* 队列解锁 */
				prvUnlockQueue( pxQueue );

				/* 恢复调度器,如果调度器挂起期间有任务解除阻塞,
                并且解除阻塞的任务优先级比当前任务高,就需要进行一次任务切换*/
				if( xTaskResumeAll() == pdFALSE )
				{
					portYIELD_WITHIN_API();
				}
			}
			else // 队列没满,有空间
			{
				/* 重新发送消息 */
				prvUnlockQueue( pxQueue );
				( void ) xTaskResumeAll();
			}
		}
		else // 超时时间已过,返回一个errQUEUE_FULL错误代码,退出
		{
			/* The timeout has expired. */
			prvUnlockQueue( pxQueue );
			( void ) xTaskResumeAll();

			traceQUEUE_SEND_FAILED( pxQueue );
			return errQUEUE_FULL;
		}
	}
}
2.2.2.1 prvCopyDataToQueue

  1. 尾部入队

  1. 头部入队

2.2.2.2 xTaskRemoveFromEventList
BaseType_t xTaskRemoveFromEventList( const List_t * const pxEventList )
{
TCB_t *pxUnblockedTCB;
BaseType_t xReturn;

	/* 事件链表按优先级顺序排序,链表中第一个的优先级最高*/
	pxUnblockedTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxEventList );
	configASSERT( pxUnblockedTCB );
	( void ) uxListRemove( &( pxUnblockedTCB->xEventListItem ) );
	/*调度器未挂起 */
	if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
	{	/* 从阻塞链表移除 */
		( void ) uxListRemove( &( pxUnblockedTCB->xStateListItem ) );
		/* 添加到就绪链表 */
		prvAddTaskToReadyList( pxUnblockedTCB );
	}else{
		/* 调度器挂起,将 任务放入xPendingReadyList链表(任务已就绪,但调度器挂起)*/
		vListInsertEnd( &( xPendingReadyList ), &( pxUnblockedTCB->xEventListItem ) );
	}

	if( pxUnblockedTCB->uxPriority > pxCurrentTCB->uxPriority )
	{
		xReturn = pdTRUE;
		xYieldPending = pdTRUE;
	}else{
		xReturn = pdFALSE;
	}

	#if( configUSE_TICKLESS_IDLE != 0 )
	{
		prvResetNextTaskUnblockTime();
	}
	#endif

	return xReturn;
}
2.2.2.3 vTaskPlaceOnEventList
void vTaskPlaceOnEventList( List_t * const pxEventList, const TickType_t xTicksToWait )
{
	configASSERT( pxEventList );

	/* 按优先级大小插入到事件链表中,高优先级插入到链表头部,
       确保 最高优先级的任务先唤醒 */
	vListInsert( pxEventList, &( pxCurrentTCB->xEventListItem ) );

	prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
}
2.3 从队列中读取消息函数
2.3.1 读队列函数
  1. xQueueReceive()

xQueueReceive()用于从一个队列中接收消息并把消息从队列中删除。接收的消息是以拷贝 的形式进行的,所以我们必须提供一个足够大空间的缓冲区。 具体能够拷贝多少数据到缓 冲区,这个在队列创建的时候已经设定。

BaseType_t xQueueReceive( QueueHandle_t xQueue,
                          void * const pvBuffer,
                          TickType_t xTicksToWait )

  1. xQueuePeek()

接收到了消息但不删除

BaseType_t xQueuePeek( QueueHandle_t xQueue,
                       void * const pvBuffer,
                       TickType_t xTicksToWait )
  1. xQueueReceiveFromISR()与 xQueuePeekFromISR()

xQueueReceiveFromISR()是 xQueueReceive ()的中断版本,用于在中断服务程序中接收 一个队列消息并把消息从队列中删除;

xQueuePeekFromISR()是 xQueuePeek()的中断版本, 用于在中断中从一个队列中接收消息,但并不会把消息从队列中移除。

2.3.2 从队列读取消息函数 xQueueReceive
BaseType_t xQueueReceive( QueueHandle_t xQueue,
                          void * const pvBuffer,
                          TickType_t xTicksToWait )
{
    BaseType_t xEntryTimeSet = pdFALSE;
    TimeOut_t xTimeOut;
    Queue_t * const pxQueue = xQueue;

    for( ; ; )
    {
        taskENTER_CRITICAL();
        {
            const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;

            /* 看看队列中有没有消息 */
            if( uxMessagesWaiting > ( UBaseType_t ) 0 )
            {
                /* 拷贝消息到用户指定区域 */
                prvCopyDataFromQueue( pxQueue, pvBuffer );
            
                traceQUEUE_RECEIVE( pxQueue );
                /* 读取消息并且消息出队,当前队列中的消息个数减一*/
                pxQueue->uxMessagesWaiting = ( UBaseType_t ) ( uxMessagesWaiting - ( UBaseType_t ) 1 );

                /* 是否有等待发送消息的任务 */
                if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
                {
                    // 有,将其从xTasksWaitingToSend移除,放入就绪队列
                    if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
                    {
                        // 如果恢复任务的优先级比当前任务高,进行任务切换
                        queueYIELD_IF_USING_PREEMPTION();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }

                taskEXIT_CRITICAL();

                traceRETURN_xQueueReceive( pdPASS );

                return pdPASS;
            }
            else
            {   
                if( xTicksToWait == ( TickType_t ) 0 )
                {
                    /* 不阻塞,直接返回 */
                    taskEXIT_CRITICAL();

                    traceQUEUE_RECEIVE_FAILED( pxQueue );
                    traceRETURN_xQueueReceive( errQUEUE_EMPTY );

                    return errQUEUE_EMPTY;
                }
                else if( xEntryTimeSet == pdFALSE )
                {
                    /* 阻塞一定的超时时间 */
                    vTaskInternalSetTimeOutState( &xTimeOut );
                    xEntryTimeSet = pdTRUE;
                }
                else
                {
                    /* Entry time was already set. */
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
        taskEXIT_CRITICAL();

        vTaskSuspendAll();
        prvLockQueue( pxQueue );

        /* 检查超时时间是否已经过去了 */
        if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
        {
            /* 时间未过,队列为空 */
            if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
            {
                traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
                /* 将其放入xTasksWaitingToReceive链表 */
                vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
                prvUnlockQueue( pxQueue );

                if( xTaskResumeAll() == pdFALSE )
                {/* 如果有任务优先级比当前任务高,会进行一次任务切换 */
                    taskYIELD_WITHIN_API();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                /*  如果队列有消息了,就再试一次获取消息 */
                prvUnlockQueue( pxQueue );
                ( void ) xTaskResumeAll();
            }
        }
        else
        {
            /* 超时时间已过,退出 */
            prvUnlockQueue( pxQueue );
            ( void ) xTaskResumeAll();
            /* 如果队列还是空的,返回错误码errQUEUE_EMPTY */

            if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
            {
                traceQUEUE_RECEIVE_FAILED( pxQueue );
                traceRETURN_xQueueReceive( errQUEUE_EMPTY );

                return errQUEUE_EMPTY;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    }
}
2.3.2.1 prvCopyDataFromQueue

2.4 消息队列删除函数 vQueueDelete()
void vQueueDelete( QueueHandle_t xQueue )

队列删除函数是根据消息队列句柄直接删除的,删除之后这个消息队列的所有信息都

会被系统回收清空,而且不能再次使用这个消息队列了。

vPortFree( pxQueue ); // 释放消息队列的内存

3. 队列的应用

3.1 13_queue_game

本节代码为:13_queue_game。以前使用环形缓冲区传输红外遥控器的数据,本程序改为使用队列。

01_game_template使用轮询的方式从环形缓冲区读取红外遥控器的键值,13_queue_game把环形缓冲区改为队列

  • (1)创建队列
  • (2)红外中断IRReceiver_IRQ_Callback:解析出按键后,写队列
  • (3)挡球板任务platform_task:读队列,控制挡球板的运动

3.2 多设备玩游戏

本节代码为:14_queue_game_encoder。实现红外遥控器和旋转编码器控制挡球板的运动。

  1. 创建挡球板队列A:g_xQueuePlatform;
  2. 创建旋转编码器队列B:g_xQueueRotary;
  3. 挡球板任务platform_task:读队列A(g_xQueuePlatform),控制挡球板的运动
  4. 红外中断IRReceiver_IRQ_Callback:解析出按键后,写队列A(g_xQueuePlatform)
  5. 旋转编码器:
    1. 旋转编码器中断RotaryEncoder_IRQ_Callback:解析出编码器状态,写队列B(g_xQueueRotary)
    2. 旋转编码器任务RotaryEncoder_task:读队列B(g_xQueueRotary),处理数据,写挡球板队列A(g_xQueuePlatform)

3.3 队列集
3.3.1 队列集简述

在上一个代码中,

假设有2个输入设备:红外遥控器、旋转编码器,它们的驱动程序应该专注于“产生硬件数据”,不应该跟“业务有任何联系”。比如:红外遥控器驱动程序里,它只应该把键值记录下来、写入某个队列,它不应该把键值转换为游戏的控制键。在红外遥控器的驱动程序里,不应该有游戏相关的代码,这样,切换使用场景时,这个驱动程序还可以继续使用。

把红外遥控器的按键转换为游戏的控制键,应该在游戏的任务里实现。

要支持多个输入设备时,我们需要实现一个“InputTask”,它读取各个设备的队列,得到数据后再分别转换为游戏的控制键。

InputTask如何及时读取到多个队列的数据?要使用队列集

队列集的本质也是队列,只不过里面存放的是“队列句柄”。使用过程如下:

  • 创建队列A,它的长度是n1
  • 创建队列B,它的长度是n2
  • 创建队列集S,它的长度是“n1+n2”
  • 把队列A、B加入队列集S
  • 这样,写队列A的时候,会顺便把队列A的句柄写入队列集S
  • 这样,写队列B的时候,会顺便把队列B的句柄写入队列集S
  • InputTask先读取队列集S,它的返回值是一个队列句柄,这样就可以知道哪个队列有有数据了;然后InputTask再读取这个队列句柄得到数据。

3.3.2 程序框架
  1. 修改“Core\Inc\FreeRTOSConfig.h“,增加:
/* USER CODE BEGIN Includes */
#define configUSE_QUEUE_SETS 1
/* Section where include file can be added */
/* USER CODE END Includes */
  1. 在STM32CubeMX里把堆TOTAL_HEAP_SIZE调大,比如8000:
  2. 红外遥控器代码:
  • 声明一个队列
  • 创建红外遥控器队列
  • 写队列
/*创建红外队列*/
static QueueHandle_t g_xQueueIR;

/* 获取红外的队列句柄 */
QueueHandle_t GetQueueIR(void)
{
	return g_xQueueIR;
}

/* 中断函数中写队列*/
void IRReceiver_IRQ_Callback(void)
{
    /* -----写队列----*/
			data.dev = 0;
			data.val = 0;
			xQueueSendToBackFromISR(g_xQueueIR,&data,NULL);
}
/* 红外接收器的初始化函数 */
void IRReceiver_Init(void)
{
    /* 创建队列 */
    g_xQueueIR = xQueueCreate(IR_QUEUE_LEN, sizeof(struct ir_data));
}
  1. 旋转编码器代码
  • 声明一个队列
  • 创建旋转编码器队列
  • 写队列
static QueueHandle_t g_xQueueRotary; /* 旋转编码器队列 */

/* 旋转编码器创建队列的第三个参数 */
static uint8_t g_ucQueueRotaryBuff[10*sizeof(struct rotary_data)];
/* 旋转编码器创建队列的第四个参数 :用来保存队列的数据结构*/
static StaticQueue_t g_xQueueRotaryStaticstruct;

static int32_t g_count = 0;
static int32_t g_speed = 0; /* 速度(正数表示顺时针旋转,负数表示逆时针旋转,单位:每秒转动次数) */

/* 获取旋转编码器的队列句柄 */
QueueHandle_t GetQueueRotary(void)
{
	return g_xQueueRotary;
}

void RotaryEncoder_IRQ_Callback(void)
{
    /* 写队列 */ 
	rotarydata.cnt = g_count;
	rotarydata.speed = g_speed;
	xQueueSendToBackFromISR(g_xQueueRotary,&rotarydata,NULL);
}

void RotaryEncoder_Init(void)
{
    /* 创建旋转编码器队列,*/
	g_xQueueRotary = xQueueCreateStatic(ROTARY_QUEUE_LEN,sizeof(struct rotary_data),g_ucQueueRotaryBuff,
									   &g_xQueueRotaryStaticstruct);  
}
  1. 游戏
  • 创建队列集,包含:
    • 红外遥控器队列
    • 旋转编码器队列
  • 创建输入任务InputTask:检测多个输入设备并调用对应处理函数
    • 读队列集,得到有数据的队列句柄
    • 读队列句柄得到数据,处理数据
  • 红外遥控器处理数据ProcessIRData
    • 读取红外遥控器键值并转换为游戏控制键,写入挡球板队列
  • 旋转编码器处理数据ProcessRotaryData
    • 读取旋转编码器数据并转换为游戏控制键,写入挡球板队列
  • 挡球板任务
    • 读挡球板队列
    • 移动挡球板
void game1_task(void *params)
{
    /* 创建队列,队列集,创建输入任务InputTask*/
	g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data));
	g_xQueueSetInput = xQueueCreateSet(IR_QUEUE_LEN+ ROTARY_QUEUE_LEN);
	
	g_xQueueIR = GetQueueIR();
	g_xQueueRotary = GetQueueRotary();
	
	xQueueAddToSet(g_xQueueIR, g_xQueueSetInput);
	xQueueAddToSet(g_xQueueRotary, g_xQueueSetInput);
	
	
	xTaskCreate(InputTask, "InputTask", 128, NULL, osPriorityNormal, NULL);
}

/* 输入任务
 * 检测多个输入设备并调用对应处理函数
 */
static void InputTask(void *params)
{
	QueueSetMemberHandle_t xQueueSetHandle;
	while(1)
	{
		
		/* 读队列集,得到有数据队列句柄*/
		xQueueSetHandle = xQueueSelectFromSet(g_xQueueSetInput, portMAX_DELAY);
		
		if (xQueueSetHandle)
		{
			/* 读队列句柄得到数据,处理数据*/
			if (xQueueSetHandle == g_xQueueIR)
			{
				ProcessIRData();
			}
			else if (xQueueSetHandle == g_xQueueRotary)
			{
				ProcessRotaryData();
			}
		}
	}
}

/* 读队列句柄得到数据,处理数据
 * 读取红外遥控器键值并转换为游戏控制键,写入挡球板队列
*/
static void ProcessIRData(void)
{
	struct ir_data irdata;
	static struct input_data input;
	
	/* 读红外队列 */
	xQueueReceive(g_xQueueIR, &irdata, 0);
	
	if (irdata.val == IR_KEY_LEFT)
	{
		input.dev = irdata.dev;
		input.val = UPT_MOVE_LEFT;
	}
	else if (irdata.val == IR_KEY_RIGHT)
	{
		input.dev = irdata.dev;
		input.val = UPT_MOVE_RIGHT;
	}
	else if (irdata.val == IR_KEY_REPEAT)
	{
		/* 保持不变 */
	}
	else 
	{
		input.dev = irdata.dev;
		input.val = UPT_MOVE_NONE;
	}
	
	/* 写挡球板队列 */
	xQueueSend(g_xQueuePlatform,&input,0);
}

/* 读队列句柄得到数据,处理数据
 * 读取旋转编码器数据并转换为游戏控制键,写入挡球板队列
*/
static void ProcessRotaryData(void)
{
	struct rotary_data rdata;
	static struct input_data input;
	int left, cnt;
	
	/* 读旋转编码器队列 */
		
	xQueueReceive(g_xQueueRotary,&rdata,0);
	
	/* 处理数据 */
	/* 判断速度:负数表示向左转动,正数表示向右转动 */
	if (rdata.speed < 0)
	{
		left = 1;
		rdata.speed = 0 - rdata.speed;
	}
	else
	{
		left = 0;
	}
//		cnt = rotarydata.speed / 10;
//		if (!cnt)
//			cnt = 1;
	if (rdata.speed > 100)
		cnt = 4;
	else if (rdata.speed > 50)
		cnt = 2;
	else 
		cnt = 1;
	
	/* 写挡球板队列 */
	input.dev = 1;
	input.val = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT;
	for (int i = 0; i < cnt; i++)
	{
		xQueueSend(g_xQueuePlatform,&input,0);
	}
}

/* 挡球板任务 */
static void platform_task(void *params)
{
    byte platformXtmp = platformX;    
    //uint8_t data;
	//uint8_t last_data;
	static struct input_data idata;
    // Draw platform
    draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
    draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
    
    while (1)
    {
        /* 读取红外遥控器 */
		// 读环形缓冲区
		//if (0 == IRReceiver_Read(&dev, &data))
		
		/* 读队列 */
		xQueueReceive(g_xQueuePlatform,&idata,portMAX_DELAY);
		
		uptMove = idata.val;
            
		// Hide platform
		draw_bitmap(platformXtmp, g_yres - 8, clearImg, 12, 8, NOINVERT, 0);
		draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
		
		// Move platform
		if(uptMove == UPT_MOVE_RIGHT)
			platformXtmp += 3;
		else if(uptMove == UPT_MOVE_LEFT)
			platformXtmp -= 3;
		uptMove = UPT_MOVE_NONE;
		
		// Make sure platform stays on screen
		if(platformXtmp > 250)
			platformXtmp = 0;
		else if(platformXtmp > g_xres - PLATFORM_WIDTH)
			platformXtmp = g_xres - PLATFORM_WIDTH;
		
		// Draw platform
		draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
		draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
		
		platformX = platformXtmp;
    }
}
3.5 队列集-mpu6050

在上个代码的基础上,添加mpu6050控制挡球板运动

  • driver_mpu6050.c代码中:
    • 创建队列
    • 创建mpu6050任务
      • 读数据
      • 解析数据
      • 写队列g_xQueueMPU6050
  • game1.c代码:
    • 将mpu6050队列g_xQueueMPU6050加入队列集;
    • 读取MPU6050数据并转换为游戏控制键,写入挡球板队列

/* --------------在driver_mpu6050.c----------------*/
static QueueHandle_t g_xQueueMPU6050;/*MPU6050队列*/

/* 获取MPU6050的队列句柄 */
QueueHandle_t GetQueueMPU6050(void)
{
	return g_xQueueMPU6050;
}

int MPU6050_Init(void)
{
    g_xQueueMPU6050 = xQueueCreate(MPU6050_QUEUE_LEN, sizeof(struct mpu6050_data));
}


void MPU6050_Task(void *params)
{
    int16_t AccX;
	struct mpu6050_data result;
	int ret;
	extern volatile int bInUsed;
	

    while (1)
    {   
		while (bInUsed);
		bInUsed = 1;
		ret = MPU6050_ReadData(&AccX, NULL, NULL, NULL, NULL, NULL);
		bInUsed = 0;
		/* 读数据 */
        if (0 == ret)
		{
			/* 解析数据 */
			MPU6050_ParseData(AccX, 0, 0, 0, 0, 0, &result);
			
			/* 写队列 */
			xQueueSend(g_xQueueMPU6050,&result,0);
		}
		/* delay */
		vTaskDelay(50);
    }
}

/* --------------game1.c----------------*/
/* 读队列句柄得到数据,处理数据
 * 读取MPU6050数据并转换为游戏控制键,写入挡球板队列
*/
static void ProcessMPU6050Data(void)
{
	struct mpu6050_data mpudata;
	static struct input_data input;

	
	/* 读MPU6050队列 */
	xQueueReceive(g_xQueueMPU6050,&mpudata,0);
	
	/* 处理数据 */
	/* 判断x轴的角度:大于90°则挡球板向左移动,小于90°则挡球板向右移动 */
	if (mpudata.angle_x > 90)
	{
		input.val = UPT_MOVE_LEFT;
	}
	else if (mpudata.angle_x < 90)
	{
		input.val = UPT_MOVE_RIGHT;
	}
	else
	{
		input.val = UPT_MOVE_NONE;
	}

	/* 写挡球板队列 */
	input.dev = 1;
	xQueueSend(g_xQueuePlatform,&input,0);
}

分发数据给多个任务(赛车游戏)


在这里插入图片描述
实现逻辑:

  • 红外遥控器的中断函数解析出按键值后,写入3个队列:3个赛车任务读取其中一个队列得到按键数据。
  • 采用game2.c代码
  1. 红外遥控器代码
static QueueHandle_t g_xQueueIR;/*红外队列*/

static QueueHandle_t g_xQueues[10]; /* 队列数组 */
static int g_Queue_cnt = 0;

/* 获取红外的队列句柄 */
QueueHandle_t GetQueueIR(void)
{
	return g_xQueueIR;
}

/* 使用该函数写三个队列 */
void RegisterQueueHandle(QueueHandle_t g_xQueueHandle)
{
	if (g_Queue_cnt < 10)
	{
		g_xQueues[g_Queue_cnt] = g_xQueueHandle;
		g_Queue_cnt++;
	}
}

static void DispatchKey(struct ir_data *pidata)
{
	int i;
    /* 写三个队列 */
	for (i = 0; i < g_Queue_cnt; i++)
	{
		xQueueSendToBackFromISR(g_xQueues[i],pidata,NULL);
	}
}

void IRReceiver_IRQ_Callback(void)
{
    struct ir_data data;
    /* -----写队列----*/
    data.dev = 0;
    data.val = 0;
    DispatchKey(&data);
}

void IRReceiver_Init(void)
{
    RegisterQueueHandle(g_xQueueIR);  
}

  1. game2代码
/* 1. 创建队列
 * 2. 注册队列
 * 3. 显示汽车
 * 4. 读取队列,控制汽车的移动
*/
static void CarTask(void *params)
{
	struct car *pcar = params;
	struct ir_data irdata;
	/* 创建自己的队列 */
	QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
	
	/* 注册队列 */
	RegisterQueueHandle(xQueueIR);
	/* 显示汽车 */
	ShowCar(pcar);
	
	while(1)
	{
		/* 读取按键值:读队列*/
		xQueueReceive(xQueueIR, &irdata, portMAX_DELAY);
		/* 控制汽车往右移动 */
		if (irdata.val == pcar->control_key)
		{
			if (pcar->x < g_xres - CAR_LENGTH)
			{
				/* 隐藏汽车 */
				HideCar(pcar);
				
				/* 调整位置 */
				pcar->x += 20;
				if (pcar->x > g_xres - CAR_LENGTH)
					pcar->x = g_xres - CAR_LENGTH;
				
				/* 重新显示汽车 */
				ShowCar(pcar);
			}
		}
	}
}

/* 1. 画路标
 * 2. 创建3个汽车任务
 */
void car_game(void)
{
	//int x;
	int i,j;
	g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
    draw_init();
    draw_end();
	
	/* 画出路标 */
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 8; j++)
		{
			draw_bitmap(16 * j, 16 + 17 * i, roadMarking, 8, 1, NOINVERT, 0);
			draw_flushArea(16 * j, 16 + 17 * i, 8, 1);
		}

	}
    
    xTaskCreate(CarTask, "car1", 128, &g_cars[0], osPriorityNormal, NULL);
    xTaskCreate(CarTask, "car2", 128, &g_cars[1], osPriorityNormal, NULL);
    xTaskCreate(CarTask, "car3", 128, &g_cars[2], osPriorityNormal, NULL);	
	
}

标签:FreeRTOS,队列,void,else,任务,data,pxQueue
From: https://blog.csdn.net/luckyme_/article/details/143028234

相关文章

  • 图论day64 :最短路径算法 | SPFA(Bellman_ford的队列优化版)、城市间货物运输 I、Ⅱ、Ⅲ
    图论day64:最短路径算法|SPFA(Bellman_ford的队列优化版)、94.城市间货物运输I(卡码网)【SPFA算法+邻接表优化】、95.城市间货物运输II(判断负权回路)、96.城市间货物运输III【已知有负权回路,该如何计算】、Bellman_ford算法思维导图汇总SPFA(Bellman_ford的队列优化版)94......
  • 雪花算法------用于生成数据库中的主键、消息队列的消息ID等的算法-----算法特点,id结
    雪花算法(SnowflakeAlgorithm)是一种由Twitter公司开发的分布式ID生成算法,用于在分布式系统中生成全局唯一的ID。这种算法非常适合需要高并发、低延迟以及大量唯一ID生成的应用场景,比如数据库中的主键、消息队列的消息ID等。雪花算法的主要特点包括:唯一性:生成的ID在全球范围内......
  • 【Linux】进线程间通信之消息队列
    二、消息队列1.什是消息队列​在Linux中,进程间通信(IPC)的消息队列是一种在进程之间传递数据的机制。它允许不同的进程以异步的方式发送和接收消息。2.消息队列的特点消息队列可以实现多个进程之间的通信,一个进程可以向消息队列发送消息,而另一个进程可以从消息队列中......
  • 栈和队列实际应用对回文数字 各种树的学习
    在今天将PTA上的作业回文数的判断完成了,正好和我昨天进行的课本书写是一样的,具体代码如下:includeincludedefineMAXSIZE100usingnamespacestd;typedefstruct{int*base;int*top;intstacksize;}SqStack;voidinit_stack(SqStack&s){s.base=newint[MAXSIZE];......
  • 05 线性结构——队列(特性、入队与出队、顺序队列和链式队列、顺序队列的“假溢出”问
    目录1队列的基本概念1.1定义1.2队列的组成部分1.3空队列1.4操作流程 1.4.1添加元素(入队)1.4.2删除元素(出队)2队列的存储结构2.1顺序队列2.2链式队列3 顺序队列中的“假溢出”问题及解决方案3.1问题描述3.2解决方案方法1:元素左移法方法2:循环队列4......
  • FreeRTOS:消息队列
    目录一、简介二、特点三、消息队列控制块四、相关API五、使用场景 一、简介        FreeRTOS的消息队列(MessageQueue)是任务之间通信的一种常用机制,允许任务或中断将数据发送到队列中,其他任务从队列中读取数据。        消息队列在嵌入式实时操作......
  • RabbitMQ系列学习笔记(三)--工作队列模式
    文章目录一、工作队列模式原理二、工作队列模式实战1、抽取工具类2、消费者代码3、生产者代码4、查看运行结果本文参考尚硅谷RabbitMQ教程丨快速掌握MQ消息中间件rabbitmqRabbitMQ详解Centos7环境安装Erlang、RabbitMQ详细过程(配图)一、工作队列模式原理与......
  • Linux多进程通信--管道、消息队列、共享内存
    转载至https://www.cnblogs.com/LUO77/p/5816326.html多进程:首先,先来讲一下fork之后,发生了什么事情。由fork创建的新进程被称为子进程(childprocess)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新进程(子进程)的进程id。将子进程id返......
  • 回文(栈和队列两种方法实现)
    includeincludeusingnamespacestd;typedefstruct{char*base;intfront;intrear;}SqQueqe;typedefstruct{char*base;char*top;intstacksize;}SqStack;//初始化栈voidinitStack(SqStack&s){s.base=newchar[101];s.top=s.base;s.stacksize=......
  • 代码随想录算法训练营第三十四天|134. 加油站 135. 分发糖果 860.柠檬水找零 406.根据
    134.加油站在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。你有一辆油箱容量无限的的汽车,从第i个加油站开往第i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。如果你可以绕环路行驶一周,则返回出发时加油站的......