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

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

时间:2024-08-24 14:50:47浏览次数:15  
标签:编码器 队列 void 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() 会返回对应的队列或信号量的句柄,任务可以根据这个句柄进行相应处理。

应用场景

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

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

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

项目框架

源码分析

主要用户任务

核心用户任务:

       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)

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

       

标签:编码器,队列,void,MPU6050,信号量,任务,砖块,数据
From: https://blog.csdn.net/dcq715/article/details/141421343

相关文章

  • 打砖块小游戏html小游戏
    这里提供一个打砖块小游戏html代码,有需要的小伙伴可以自己试试。body内容点击查看代码<selectid="difficulty"><optionvalue="easy">简单</option><optionvalue="medium">中等</option><optionvalue="hard">困难&l......
  • 合宙Air780E开发板集成EC11旋转编码器实战指南
    合宙Air780E开发板,作为一款基于Cat.1技术的物联网通信模组开发板,依托移芯EC618平台,以其低功耗、全网通及丰富的接口支持特性,它支持AT指令和LuatOS二次开发,在物联网领域展现出了强大的竞争力。今天我们来讲解一个基于Air780E开发板,集成ec11旋转编码器的实例。 合宙支持LuatO......
  • x264 编码器像素运算系列:satd 函数
    x264编码器中像素运算在x264编码器中有多种像素间的运算,如下:sad计算:SAD(SumofAbsoluteDifferences,绝对差值和)是一种在图像处理和视频编码中常用的度量,用于计算两个图像块之间的差异。SAD值越小,表示两个图像块越相似。hadamard_ac计算:用于计算Hadamard变换后非零......
  • VL16 使用8线-3线优先编码器Ⅰ实现16线-4线优先编码器
    `timescale1ns/1nsmoduleencoder_83(input[7:0]I,inputEI,outputwire[2:0]Y,outputwireGS,outputwireEO);assignY[2]=EI&(I[7]|I[6]|I[5]|I[4]);assignY[......
  • VL13 优先编码器电路
     `timescale1ns/1nsmoduleencoder_0(  input   [8:0]    I_n ,    outputreg[3:0]    Y_n );always@(*)begin  casex(I_n)  9'b1_1111_1111:Y_n=4'b1111;  9'b0_xxxx_xxxx:Y_n=4'b0110;  9'b1_0xxx......
  • 机器学习笔记:编码器与解码器
    目录介绍组成结构代码实现编码器解码器合并编码器-解码器思考介绍在机器翻译中,输入的序列与输出的序列经常是长度不相等的序列,此时,像自然语言处理这种直接使用循环神经网络或是门控循环单元的方法就行不通了。因此,我们引入一个新的结构,称之为“编码器-解码器”(Enco......
  • 医学图像分割的基准:TransUnet(用于医学图像分割的Transformer编码器)器官分割
    1、TransUnet介绍TransUnet是一种用于医学图像分割的深度学习模型。它是基于Transformer模型的图像分割方法,由AI研究公司HuggingFace在2021年提出。医学图像分割是一项重要的任务,旨在将医学图像中的不同结构和区域分离出来,以便医生可以更好地诊断和治疗疾病。传统的医学......
  • 编码器和解码器
    编码器在编码器的接口中,我们只指定长度可变的序列作为编码器的输入X。任何继承自Encoder基类的模型将完成代码实现。fromtorchimportnn#@saveclassEncoder(nn.Module):"""编码器-解码器架构的基本编码器接口"""def__init__(self,**kwargs):super(En......
  • 一维变分自动编码器的错误重建
    我想实现一个变分自动编码器,它将一维Numpy数组(声音文件的波形)作为输入。运行该文件不会引发错误,但损失收敛到2000左右,并且重建看起来像纯粹的噪声。我使用了Goffinet等人的代码并尝试重写它以采用一维输入,因为我之前使用过他们的(二维)VAE。这是网络和转发功能的......
  • stm32入门-----硬件I2C读写MPU6050
     目录前言 一、stm32中I2C库函数介绍(stm32f10x_i2c.h)1.初始化2.使能操作3.生成起始位和结束位标志4.发送I2C从机地址5.发送数据和接收数据6.发送应答位7.状态检测二、硬件I2C读取MPU60501.电路连线图2.主要工程文件 3.MPU6050.c代码剖析(1)检测步骤超时操作(2)指......