通过队列通信实现红外遥控、旋转编码器和MPU6050数据处理的打砖块游戏开发

 声明:项目源码参考韦东山老师百问网嵌入式专家-韦东山嵌入式专注于嵌入式课程及硬件研发 (100ask.net)

        在本项目中,打砖块游戏的核心逻辑在一个单独的任务中实现,同时系统还需要处理来自红外遥控、旋转编码器和MPU6050传感器的数据输入。为此,使用FreeRTOS的队列机制,将各个硬件模块的输入数据通过队列发送给游戏逻辑任务,以便做出相应的处理。

队列(Queue)

        在FreeRTOS中,队列(Queue) 是一种常用的数据结构,用于在不同任务之间传递数据。队列提供了一种线程安全的方式,可以在线程之间传递消息、事件或数据块。队列可以用来缓冲数据,使得生产者任务和消费者任务之间的解耦。

队列的基本概念

  • 队列长度:队列可以保存的最大数据项数目。例如,一个长度为10的队列可以存储10个数据项。
  • 队列项大小:每个数据项的大小。例如,每个数据项可能是一个整数、结构体或者指针。
  • 发送(写入)数据到队列:通过调用 xQueueSend()xQueueSendToBack() 函数将数据项添加到队列的末尾。还有 xQueueSendToFront() 函数可以将数据添加到队列的头部。
  • 接收(读取)数据从队列:通过调用 xQueueReceive() 函数从队列的头部取出数据项。

队列的本质

        队列中,数据的读写本质就是环形缓中区,在这个基础上增加了互斥措施、阻塞-唤醒机制
        如果这个队列不传输数据,只调整“数据个数”,它就是信号量(信号量)。
        如果信号量中,限定“数据个数”最大值为1,它就是互斥量(mutex)。

应用场景

在嵌入式系统中,队列常用于以下场景:

  • 任务间通信:当多个任务需要协作完成某一功能时,可以使用队列传递状态信息或数据。
  • 事件处理:当一个任务检测到一个事件时,可以将事件信息发送到队列,另一个任务从队列读取事件并处理。
  • 数据缓冲:队列可以用作缓冲区,当数据生产者和消费者速率不匹配时,可以避免数据丢失。

队列集(Queue Set)

        队列集(Queue Set)是FreeRTOS中一个重要的功能,它允许多个队列或信号量组合在一起,方便任务等待其中任何一个队列或信号量变为可用。队列集为复杂的任务间通信和同步提供了一种更加灵活的方式。

队列集的主要特点

  1. 多源触发:队列集可以包含多个队列和信号量,任务可以通过队列集来等待其中任意一个队列或信号量变为可用。例如,任务A可能需要等待来自传感器数据队列、命令队列或信号量中的任何一个可用数据。通过队列集,任务A可以只在一个点等待,而不是在多个队列上循环等待。

  2. 单一等待点:队列集提供了一个统一的等待点,即使多个队列或信号量发生变化,任务也可以通过队列集来捕获这些变化,而不必单独等待每个队列或信号量。这样可以减少系统复杂性和CPU资源的浪费。

  3. 灵活性:队列集可以动态地添加或删除队列和信号量,这使得任务能够灵活地应对不同的通信需求。

使用队列集的步骤

  1. 创建队列集:首先需要创建一个队列集,这个队列集将容纳多个队列或信号量。

    QueueSetHandle_t xQueueSet = xQueueCreateSet( /* Maximum number of items in the set */ );
    
  2. 创建队列或信号量:接下来,创建要添加到队列集中的队列或信号量。

    QueueHandle_t xQueue1 = xQueueCreate( /* Queue length */, /* Item size */ );
    SemaphoreHandle_t xSemaphore = xSemaphoreCreateBinary();
    
  3. 将队列或信号量添加到队列集

    xQueueAddToSet( xQueue1, xQueueSet );
    xQueueAddToSet( xSemaphore, xQueueSet );
    
  4. 等待队列集:任务可以通过 xQueueSelectFromSet() 函数等待队列集中的任意队列或信号量变为可用。

    QueueSetMemberHandle_t xActivatedMember;
    xActivatedMember = xQueueSelectFromSet( xQueueSet, portMAX_DELAY );
    
  5. 处理激活的队列或信号量:一旦队列集中的某个队列或信号量变为可用,xQueueSelectFromSet() 会返回对应的队列或信号量的句柄,任务可以根据这个句柄进行相应处理。

应用场景

队列集在以下场景中特别有用:

  • 多事件源等待:当一个任务需要从多个来源接收消息或事件时,队列集可以将这些队列或信号量组合在一起,使任务能够高效地等待其中任意一个变为可用。

  • 复杂同步机制:在更复杂的实时系统中,任务可能需要同步来自多个不同的任务或中断的信号。队列集允许这些信号的组合和统一管理。

项目框架

通过队列通信实现红外遥控、旋转编码器和MPU6050数据处理的打砖块游戏开发_第1张图片

源码分析

主要用户任务

通过队列通信实现红外遥控、旋转编码器和MPU6050数据处理的打砖块游戏开发_第2张图片

核心用户任务:

       1、小球运动:void game1_task(void *params)

       2、挡球板运动 :static void platform_task(void *params)

在game1_task()任务中死循环不断移动小球,判断是否碰撞、出界。并创建platform_task

在platform_task()中读取挡球板队列,控制挡球板运动,即隐藏挡球板、移动挡球板、显示挡球板。

        挡球板队列由底层控制模块写入

红外接收器

        红外接收器的初始化函数、创建队列


		/* 动态创建队列 */
	g_xQueueIR = xQueueCreate(IR_QUEUE_LEN, sizeof(struct ir_data));
	RegisterQueueHandle(g_xQueueIR);
}

        解析红外接收器数据,并写入红外队列

/* 3. 次数达标后, 解析数据, 放入buffer */
	if (g_IRReceiverIRQ_Cnt == 4)
	{
		/* 是否重复码 */
		if (isRepeatedKey())
		{
			/* device: 0, val: 0, 表示重复码 */
			//PutKeyToBuf(0);
			//PutKeyToBuf(0);
			/* 写队列 */
			data.dev = 0;
			data.val = 0;
			DispatchKey(&data);
			g_IRReceiverIRQ_Cnt = 0;
		}
	}
	if (g_IRReceiverIRQ_Cnt == 68)
	{
		IRReceiver_IRQTimes_Parse();
		g_IRReceiverIRQ_Cnt = 0;
	}

        IRReceiver_IRQ_Callback:主要负责记录中断发生的时间,并决定何时调用解析函数

        IRReceiver_IRQTimes_Parse:负责解析记录下来的时间序列,将其转换为具体的按键信息,并通过队列传递给其他部分程序

        DispatchKey:将解析出来的按键数据写入所有已注册的队列中,用于任务间通信。

旋转编码器

         旋转编码器的初始化函数、创建队列


	/* 硬件驱动初始化中创建队列,解耦 */
	g_xQueueRotary   = xQueueCreateStatic(ROTARY_QUEUE_LEN, sizeof(struct rotary_data), g_ucQueueRotaryBuf, &g_xQueueRotaryStaticStruct);

        旋转编码器的中断回调函数、记录数据写入队列


	/* 写队列 /*  
 * 往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞 
 */
	rdata.cnt   = g_count;
	rdata.speed = g_speed;
	xQueueSendFromISR(g_xQueueRotary, &rdata, NULL);
}

MPU6050

         MPU6050通过创建一个新任务,读取I2C、写队列,旋转编码器和红外在中断中获得数据写队列

        创建MPU6050队列、创建事件组,中断回调通过事件组唤醒 MPU6050任务,读I2C、写队列

		/*创建MPU6050队列*/
	g_xQueueMPU6050 = xQueueCreate(MPU6050_QUEUE_LEN, sizeof(struct mpu6050_data));
	
				/*创建事件组*/
	g_xEventMPU6050 = xEventGroupCreate();

        中断回调函数设置事件组        

void MPU6050_Callback(void)
{
	/* 设置事件组: bit0 */
	xEventGroupSetBitsFromISR(g_xEventMPU6050, (1<<0), NULL);
}

MPU6050任务,等待事件组,然后循环读取MPU6050并把数值写入队列


	while(1)
	{
		/*等待事件*/
		xEventGroupWaitBits(g_xEventMPU6050,(1<<0),pdTRUE,pdFALSE,portMAX_DELAY);
		/* 读数据 */
//		while (bInUsed);
//		bInUsed = 1;
		GetI2C();
		ret = MPU6050_ReadData(&AccX, NULL, NULL, NULL, NULL, NULL);
		PutI2C();
		//		bInUsed = 0;
		if (0 == ret)
		{
			/* 解析数据 */
			MPU6050_ParseData(AccX, 0, 0, 0, 0, 0, &result);
			/* 写队列 */
			xQueueSend(g_xQueueMPU6050, &result, 0);
		}
		/* delay */
		vTaskDelay(50);
	}

队列集

        创建队列集,通过队列集管理多个队列

g_xQueueSetInput = xQueueCreateSet(IR_QUEUE_LEN + ROTARY_QUEUE_LEN + MPU6050_QUEUE_LEN);

        将三个队列添加进队列集

/*将队列或信号量添加到一个队列集合中*/
	xQueueAddToSet(g_xQueueIR, g_xQueueSetInput);
	xQueueAddToSet(g_xQueueRotary, g_xQueueSetInput);
	xQueueAddToSet(g_xQueueMPU6050, g_xQueueSetInput);

输入任务

读队列集,检测输入队列,并调用对应的数据处理函数

static void InputTask(void *params)
{
	/*QueueSetMemberHandle_t 用于标识和操作队列集合中的单个队列或信号量成员。*/
	QueueSetMemberHandle_t xQueueHandle;
	while(1)
	{
	/* 读队列集, 得到有数据的队列句柄 */
		xQueueHandle = xQueueSelectFromSet(g_xQueueSetInput, portMAX_DELAY);
		if(xQueueHandle)
		{
		/* 读队列句柄得到数据,处理数据 */
			if (xQueueHandle == g_xQueueIR)
			{
				ProcessIRData();
			}
			else if (xQueueHandle == g_xQueueRotary)
			{
				ProcessRotaryData();
			}
			else if (xQueueHandle == g_xQueueMPU6050)
			{
				ProcessMPU6050Data();
			}
			
		}
	}
}

数据处理函数

static void ProcessIRData(void)

static void ProcessRotaryData(void)

static void ProcessMPU6050Data(void)

读取各自队列中数据,并处理数据将数据转换成游戏按键值,即控制挡球板移动的值,然后将处理后数据写入挡球板队列

       

你可能感兴趣的:(c语言,stm32)