FreeRTOS 队列游戏挡球板

本文介绍了如何使用FreeRTOS在嵌入式系统中创建多个任务,如游戏任务、音乐播放任务和控制任务,通过队列进行数据通信。文章详细描述了如何处理红外遥控、旋转编码器和MPU6050数据,以及信号量在控制任务同步中的作用。
摘要由CSDN通过智能技术生成

概要

通过freeRTOS创建多个任务。

  • 旋转编码器任务
    • 它的中断函数里面解析出旋转编码器的状态,写队列B
    • 他的任务函数里面,读取队列B,构造好数据后写·队列A
  • 游戏任务
    • 读取队列A 获取控制信息,用来控制游戏
  • 红外遥控器驱动任务
    • 中断函数里面解析出按键之后,写队列A

关键代码

MX_FREERTOS_Init 任务初始化( 创建了游戏任务 ,音乐播放任务)

void MX_FREERTOS_Init(void) {
  /* USER CODE BEGIN Init */
  LCD_Init();
  LCD_Clear();
  
  IRReceiver_Init();
  LCD_PrintString(0, 0, "Starting");
  /* 创建任务: 声 */
  extern void PlayMusic(void *params);
  xTaskCreate(game1_task, "GameTask", 128, NULL, osPriorityNormal, NULL);
  xTaskCreate(PlayMusic, "MusicTask", 128, NULL, osPriorityNormal, NULL);

}

1 . 游戏任务

这里我们创建了挡球平板任务: 任务就是一直读取队列A ( 读取红外遥控 )


void game1_task(void *params)
{		
    uint8_t dev, data, last_data;
    
    g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
    draw_init();
    draw_end();
	
	/* 创建队列,创建旋转编码器的任务 */
	g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data));
	g_xQueueRotary   = xQueueCreateStatic(10, sizeof(struct rotary_data), g_ucQueueRotaryBuf, &g_xQueueRotaryStaticStruct);
	    
	uptMove = UPT_MOVE_NONE;

	ball.x = g_xres / 2;
	ball.y = g_yres - 10;
        
	ball.velX = -0.5;
	ball.velY = -0.6;
	blocks = pvPortMalloc(BLOCK_COUNT);
    memset(blocks, 0, BLOCK_COUNT);
	
	lives = lives_origin = 3;
	score = 0;
	platformX = (g_xres / 2) - (PLATFORM_WIDTH / 2);

    xTaskCreate(platform_task, "platform_task", 128, NULL, osPriorityNormal, NULL);

    while (1)
    {
        game1_draw();
        //draw_end();
        vTaskDelay(50);
    }
}

1.0 挡球板任务函数:

[ 挡球板的任务就是控制它的左右移动 ,它需要一直读取队列A ]

【 写队列的是:红外遥控的中断服务程序】

QueueHandle_t g_xQueuePlatform; /* 挡球板队列 */
QueueHandle_t g_xQueueRotary;   /* 旋转编码器队列 */

/* 挡球板任务 */
static void platform_task(void *params)
{
    byte platformXtmp = platformX;    
    uint8_t dev, data, last_data;
	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);
		
		data = idata.val;
		{
            if (data == 0x00)
            {
                data = last_data;
            }
            
            if (data == 0xe0) /* Left */
            {
                btnLeft();
            }

            if (data == 0x90)  /* Right */
            {
                btnRight();
            }
            last_data = data;

            
            // 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;
            
		}
    }
}

2. 红外中断服务程序写队列:

/**********************************************************************
 * 函数名称: IRReceiver_IRQ_Callback
 * 功能描述: 红外接收器的中断回调函数,记录中断时刻
 * 输入参数: 无
 * 输出参数: 无
 * 返 回 值: 无
 * 修改日期:      版本号     修改人	      修改内容
 * -----------------------------------------------
 * 2023/08/04	     V1.0	  韦东山	      创建
 ***********************************************************************/
void IRReceiver_IRQ_Callback(void)
{
    uint64_t time;
    static uint64_t pre_time = 0;
	
	struct input_data data;
        
	/* 1. 记录中断发生的时刻 */	
	time = system_get_ns();
    
    /* 一次按键的最长数据 = 引导码 + 32个数据"1" = 9+4.5+2.25*32 = 85.5ms
     * 如果当前中断的时刻, 举例上次中断的时刻超过这个时间, 以前的数据就抛弃
     */
    if (time - pre_time > 100000000) 
    {
        g_IRReceiverIRQ_Cnt = 0;
    }
    pre_time = time;
    
	g_IRReceiverIRQ_Timers[g_IRReceiverIRQ_Cnt] = time;

	/* 2. 累计中断次数 */
	g_IRReceiverIRQ_Cnt++;

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


程序架构

在这里插入图片描述


如果增加了一个mpu6050 六轴控制。那架构就变成如下:

在这里插入图片描述


缺点
有一个设备就有一个任务,就需要用到栈,会比较消耗内存


架构优化

效果并不好
在这里插入图片描述


优化:使用队列集合

在这里插入图片描述

我们使用队列集来管理,各个硬件队列的数据。

比如队列A,写入队列A的时候,顺带吧队列A的句柄写入队列集S,任务B也是一样

架构变成如下所示:

在这里插入图片描述


分割线

在这里插入图片描述
并且非常容易得添加硬件

在InputTask 处理的时候,要判断是否是新添加的设备


队列集

在使用队列集的时候,需要先创建队列集。

然后吧需要加入队列集的队列通过下面函数设置:

	xQueueAddToSet(g_xQueueIR, g_xQueueSetInput);

参数1:队列的句柄
参数2:队列集句柄


也就是说当前的队列集中收集了队列,后续队列集中对应的队列,只要有人写队列了,就会把当前队列的句柄写入队列集中。

InputTask 扫描队列集中的任务,取出队列句柄,处理数据,然后写档球板队列。

1. 创建队列集

队列集在创建的时候,使用如下函数

QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength )

在这里插入图片描述

2. 把队列加入队列集

BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore,

                QueueSetHandle_t xQueueSet );

在这里插入图片描述

3 . 读取队列集

QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,

                        TickType_t const xTicksToWait );

在这里插入图片描述


代码架构分析

在这里插入图片描述

游戏game 任务

在游戏任务里面,主要是死循环里面是负责画 小球的位置。

而在游戏初始化里面,我们创建了挡球板任务(负责:读取挡球板队列)

而挡球板队列中( 写队列的人是:InputTask,它根据队列集中的队列句柄,扫描,然后取出队列句柄中的数据,[ 也就是要去对设备的队列获取数据 ],转换成游戏控制数据后,写入挡球板队列中 )

void game1_task(void *params)
{		    
	g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
	draw_init();
	draw_end();
	
	/* 创建队列,队列集,创建输入任务InputTask */
	g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data));
	g_xQueueSetInput = xQueueCreateSet(IR_QUEUE_LEN + ROTARY_QUEUE_LEN + MPU6050_QUEUE_LEN);
	
	g_xQueueIR = GetQueueIR();
	g_xQueueRotary = GetQueueRotary();
	g_xQueueMPU6050 = GetQueueMPU6050();
	
	xQueueAddToSet(g_xQueueIR, g_xQueueSetInput);
	xQueueAddToSet(g_xQueueRotary, g_xQueueSetInput);
	xQueueAddToSet(g_xQueueMPU6050, g_xQueueSetInput);
	
    xTaskCreate(MPU6050_Task, "MPU6050Task", 128, NULL, osPriorityNormal, NULL);
    xTaskCreate(InputTask, "InputTask", 128, NULL, osPriorityNormal, NULL);
    
	uptMove = UPT_MOVE_NONE;

	ball.x = g_xres / 2;
	ball.y = g_yres - 10;
        
	ball.velX = -0.5;
	ball.velY = -0.6;
//	ball.velX = -1;
//	ball.velY = -1.1;

	blocks = pvPortMalloc(BLOCK_COUNT);
	memset(blocks, 0, BLOCK_COUNT);
	
	lives = lives_origin = 3;
	score = 0;
	platformX = (g_xres / 2) - (PLATFORM_WIDTH / 2);

    xTaskCreate(platform_task, "platform_task", 128, NULL, osPriorityNormal, NULL);

    while (1)
    {
        game1_draw();
        //draw_end();
        vTaskDelay(50);
    }
}

InputTask 任务

/**********************************************************************
 * 函数名称: InputTask
 * 功能描述: 输入任务,检测多个输入设备并调用对应处理函数
 * 输入参数: params - 未使用
 * 输出参数: 无
 * 返 回 值: 无
 * 修改日期:      版本号     修改人	      修改内容
 * -----------------------------------------------
 * 2023/09/02	     V1.0	  韦东山	      创建
 ***********************************************************************/
static void InputTask(void *params)
{
	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();
			}			
		}
	}
}

设备处理数据:红外IR数据

读取队列集,获得队列句柄,然后根据队列中的数据,写挡球板队列

读取IR红外 队列,然后 判断硬件数据,写挡球板队列

/**********************************************************************
 * 函数名称: ProcessIRData
 * 功能描述: 读取红外遥控器键值并转换为游戏控制键,写入挡球板队列
 * 输入参数: 无
 * 输出参数: 无
 * 返 回 值: 无
 * 修改日期:      版本号     修改人	      修改内容
 * -----------------------------------------------
 * 2023/09/02	     V1.0	  韦东山	      创建
 ***********************************************************************/
static void ProcessIRData(void)
{
	struct ir_data idata;
	static struct input_data input;
	
	xQueueReceive(g_xQueueIR, &idata, 0);
	
	if (idata.val == IR_KEY_LEFT)
	{
		input.dev = idata.dev;
		input.val = UPT_MOVE_LEFT;
	}
	else if (idata.val == IR_KEY_RIGHT)
	{
		input.dev = idata.dev;
		input.val = UPT_MOVE_RIGHT;
	}
	else if (idata.val == IR_KEY_REPEAT)
	{
		/* 保持不变 */;
	}
	else
	{
		input.dev = idata.dev;
		input.val = UPT_MOVE_NONE;
	}
	
	/* 写挡球板队列 */
	xQueueSend(g_xQueuePlatform, &input, 0);
}

设备处理数据:旋转编码数据

读取队列集,获得队列句柄,然后根据队列中的数据,写挡球板队列

读取旋转编码队列,然后 判断硬件数据,写挡球板队列

/**********************************************************************
 * 函数名称: ProcessRotaryData
 * 功能描述: 读取旋转编码器数据并转换为游戏控制键,写入挡球板队列
 * 输入参数: 无
 * 输出参数: 无
 * 返 回 值: 无
 * 修改日期:      版本号     修改人	      修改内容
 * -----------------------------------------------
 * 2023/09/02	     V1.0	  韦东山	      创建
 ***********************************************************************/
static void ProcessRotaryData(void)
{
	struct rotary_data rdata;
	struct input_data idata;
	int left;
	int i, cnt;
	
	/* 读旋转编码器队列 */
	xQueueReceive(g_xQueueRotary, &rdata, 0);
			
	/* 处理数据 */
	/* 判断速度: 负数表示向左转动, 正数表示向右转动 */
	if (rdata.speed < 0)
	{
		left = 1;
		rdata.speed = 0 - rdata.speed;
	}
	else
	{
		left = 0;
	}
	
	//cnt = rdata.speed / 10;
	//if (!cnt)
	//	cnt = 1;
	if (rdata.speed > 100)
		cnt = 4;
	else if (rdata.speed > 50)
		cnt = 2;
	else
		cnt = 1;
			
	/* 写挡球板队列 */
	idata.dev = 1;
	idata.val = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT;
	for (i = 0; i < cnt; i++)
	{
		xQueueSend(g_xQueuePlatform, &idata, 0);
	}
}

设备处理数据:MPU6050数据

读取队列集,获得队列句柄,然后根据队列中的数据,写挡球板队列

读取MPU6050 队列,然后 判断硬件数据,写挡球板队列

/**********************************************************************
 * 函数名称: ProcessMPU6050Data
 * 功能描述: 读取MPU6050D的角度值并转换为游戏控制键,写入挡球板队列
 * 输入参数: 无
 * 输出参数: 无
 * 返 回 值: 无
 * 修改日期:      版本号     修改人	      修改内容
 * -----------------------------------------------
 * 2023/09/05	     V1.0	  韦东山	      创建
 ***********************************************************************/
static void ProcessMPU6050Data(void)
{
	struct mpu6050_data mdata;
	struct input_data idata;
	
	/* 读旋转编码器队列 */
	xQueueReceive(g_xQueueMPU6050, &mdata, 0);
			
	/* 处理数据 */
	/* 判断角度, 大于90度表示往左移动挡球板, 小于90度表示往右移动挡球板 */
	if (mdata.angle_x > 90)
	{
		idata.val = UPT_MOVE_LEFT;
	}
	else if(mdata.angle_x < 90)
	{
		idata.val = UPT_MOVE_RIGHT;
	}
	else
	{
		idata.val = UPT_MOVE_NONE;
	}
	
	/* 写挡球板队列 */
	idata.dev = 2;
	xQueueSend(g_xQueuePlatform, &idata, 0);
}

游戏框架

在这里插入图片描述


游戏2

OLED 上有3辆小车,一开始在最左侧的位置。

每一辆汽车都有自己的汽车任务:就是从最坐车跑到最右侧。

如果汽车想要进城、也就是发动汽车,那么此时需要信号量。

假设一开始的信号量有3个,那么3辆汽车分别同时重头的到位。

如果只有一个信号量,那么汽车1,发动之后,就消耗了信号量,此时汽车2、3 就无法发动汽车(阻塞了,永久等待),只能原地等待。

如果汽车1 到达终点之后,释放了信号量,那么任务2就获得了信号量,然后进城,到终点后,释放信号量。

此时,汽车3 获得信号量,发动汽车。

从上面的结果可以看出,信号量就是任务执行的一个资格,票据,没有票据就无法运行任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值