【FreeRTOS】队列实验-分发数据给多个任务(赛车游戏)

2024-08-23 09:20

本文主要是介绍【FreeRTOS】队列实验-分发数据给多个任务(赛车游戏),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 0 前言
  • 1 队列实验_分发数据给多个任务(赛车游戏)
  • 2 赛车游戏
    • 2.1 game.c
    • 2.2 注册队列
    • 2.3显示汽车
    • 2.4隐藏汽车
    • 2.5 CarTask
    • 2.6 car_game
    • 2.7 MX_FREERTOS_Init
  • 3 总结


0 前言

学习视频:
【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS(FreeRTOS教程 基于STM32,以实际项目为导向)】 【精准空降到 01:25】 https://www.bilibili.com/video/BV1Jw411i7Fz/?p=38&share_source=copy_web&vd_source=8af85e60c2df9af1f0fd23935753a933&t=85

参考《FreeRTOS入门与工程实践(基于DshanMCU-103).pdf》


1 队列实验_分发数据给多个任务(赛车游戏)

本节源码:在"16_queueset_game_mpu6050"的基础上,改出"17_queue_car_dispatch"
红外遥控器的中断函数解析出按键值后,写入3个队列:3个赛车任务读取其中一个队列得到按键数据

任务:
写一个简陋版赛车游戏,在红外的中断函数里,我们解析按键值,以前是写一个队列,现在是把键值写三个队列,一共有三个任务,分别控制三辆车。

在这里插入图片描述


2 赛车游戏

2.1 game.c

编写game2.c,先测试图像是否能正常显示

/** Project: N|Watch* Author: Zak Kemble, contact@zakkemble.co.uk* Copyright: (C) 2013 by Zak Kemble* License: GNU GPL v3 (see License.txt)* Web: http://blog.zakkemble.co.uk/diy-digital-wristwatch/*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>#include "cmsis_os.h"
#include "FreeRTOS.h"                   // ARM.FreeRTOS::RTOS:Core
#include "task.h"                       // ARM.FreeRTOS::RTOS:Core
#include "event_groups.h"               // ARM.FreeRTOS::RTOS:Event Groups
#include "semphr.h"                     // ARM.FreeRTOS::RTOS:Core#include "draw.h"
#include "resources.h"#include "driver_lcd.h"
#include "driver_ir_receiver.h"
#include "driver_rotary_encoder.h"
#include "driver_mpu6050.h"#include "game2.h"#define NOINVERT	false
#define INVERT		true#define CAR_COUNT	3
#define CAR_WIDTH	12
#define CAR_LENGTH	15
#define ROAD_SPEED	6static uint32_t g_xres, g_yres, g_bpp;
static uint8_t *g_framebuffer;struct car {int x;int y;int control_key;
};struct car g_cars[3] = {{0, 0, IR_KEY_1},{0, 17, IR_KEY_2},{0, 34, IR_KEY_3},
};static const byte carImg[] ={0x40,0xF8,0xEC,0x2C,0x2C,0x38,0xF0,0x10,0xD0,0x30,0xE8,0x4C,0x4C,0x9C,0xF0,0x02,0x1F,0x37,0x34,0x34,0x1C,0x0F,0x08,0x0B,0x0C,0x17,0x32,0x32,0x39,0x0F,
};static const byte clearImg[30] ={0};static const byte roadMarking[] ={0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
};#if 0
void car_test(void)
{g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);draw_init();draw_end();draw_bitmap(0, 0, carImg, 15, 16, NOINVERT, 0);draw_flushArea(0, 0, 15, 16);draw_bitmap(0, 16, roadMarking, 8, 1, NOINVERT, 0);draw_flushArea(0, 16, 8, 1);while (1);
}
#endifstatic void ShowCar(struct car *pcar)
{draw_bitmap(pcar->x, pcar->y, carImg, 15, 16, NOINVERT, 0);draw_flushArea(pcar->x, pcar->y, 15, 16);
}static void HideCar(struct car *pcar)
{draw_bitmap(pcar->x, pcar->y, clearImg, 15, 16, NOINVERT, 0);draw_flushArea(pcar->x, pcar->y, 15, 16);
}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);}}draw_bitmap(0, 0, carImg, 15, 16, NOINVERT, 0);draw_flushArea(0, 0, 15, 16);
}

然后,将这个car_game放到初始化代码MX_FREERTOS_Init

观察现象:
在这里插入图片描述

现在,我们就绘制出来了第一辆赛车三条分割线

显示三辆汽车

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);}}/* 创建3个汽车任务 */for (i = 0; i < 3; i++){draw_bitmap(g_cars[i].x, g_cars[i].y, carImg, 15, 16, NOINVERT, 0);draw_flushArea(g_cars[i].x, g_cars[i].y, 15, 16);}
}

在这里插入图片描述

同一个键值,写三个队列,这三个任务都可以分别读各自的队列,得到同一份数据

在这里插入图片描述

由驱动程序分发给不同的队列


2.2 注册队列

void RegisterQueueHandle(QueueHandle_t queueHandle)
{if (g_queue_cnt < 10){g_xQueues[g_queue_cnt] = queueHandle;g_queue_cnt++;}
}static void DispatchKey(struct ir_data *pidata)
{
#if 0	extern QueueHandle_t g_xQueueCar1;extern QueueHandle_t g_xQueueCar2;extern QueueHandle_t g_xQueueCar3;xQueueSendFromISR(g_xQueueCar1, pidata, NULL);xQueueSendFromISR(g_xQueueCar2, pidata, NULL);xQueueSendFromISR(g_xQueueCar3, pidata, NULL);
#elseint i;for (i = 0; i < g_queue_cnt; i++){xQueueSendFromISR(g_xQueues[i], pidata, NULL);}
#endif	
}

这个注册队列,就是把底层驱动程序的某一个数组里,以后等硬件捕获到数据之后,它会自动分发数据
在这里插入图片描述

在中断函数里,检测到重复码之后,就会调用这个分发函数,把一个数据,写入多个队列

void IRReceiver_IRQ_Callback(void)
{uint64_t time;static uint64_t pre_time = 0;struct ir_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; //如果有重复码,就上报上一次的按键值DispatchKey(&data);g_IRReceiverIRQ_Cnt = 0;}}if (g_IRReceiverIRQ_Cnt == 68){IRReceiver_IRQTimes_Parse();g_IRReceiverIRQ_Cnt = 0;}
}

在这里插入图片描述

或者是解析到实际的键值之后,也写多个队列

static int IRReceiver_IRQTimes_Parse(void)
{uint64_t time;int i;int m, n;unsigned char datas[4];unsigned char data = 0;int bits = 0;int byte = 0;struct ir_data idata;   //修改成 ir_data/* 1. 判断前导码 : 9ms的低脉冲, 4.5ms高脉冲  */time = g_IRReceiverIRQ_Timers[1] - g_IRReceiverIRQ_Timers[0];if (time < 8000000 || time > 10000000){return -1;}time = g_IRReceiverIRQ_Timers[2] - g_IRReceiverIRQ_Timers[1];if (time < 3500000 || time > 55000000){return -1;}/* 2. 解析数据 */for (i = 0; i < 32; i++){m = 3 + i*2;n = m+1;time = g_IRReceiverIRQ_Timers[n] - g_IRReceiverIRQ_Timers[m];data <<= 1;bits++;if (time > 1000000){/* 得到了数据1 */data |= 1;}if (bits == 8){datas[byte] = data;byte++;data = 0;bits = 0;}}/* 判断数据正误 */datas[1] = ~datas[1];datas[3] = ~datas[3];if ((datas[0] != datas[1]) || (datas[2] != datas[3])){g_IRReceiverIRQ_Cnt = 0;return -1;}//PutKeyToBuf(datas[0]);    //写环形缓冲区//PutKeyToBuf(datas[2]);/* 写队列 */idata.dev = datas[0];idata.val = datas[2];DispatchKey(&idata);xQueueSendToBackFromISR(g_xQueueIR, &idata, NULL);return 0;
}

在这里插入图片描述


2.3显示汽车

这是显示一辆汽车的代码:

static void ShowCar(struct car *pcar)
{draw_bitmap(pcar->x, pcar->y, carImg, 15, 16, NOINVERT, 0);draw_flushArea(pcar->x, pcar->y, 15, 16);
}

2.4隐藏汽车

这是隐藏一辆汽车的代码:

static void HideCar(struct car *pcar)
{draw_bitmap(pcar->x, pcar->y, clearImg, 15, 16, NOINVERT, 0);draw_flushArea(pcar->x, pcar->y, 15, 16);
}

2.5 CarTask

编写CarTask任务函数

static void CarTask(void *params)
{struct car *pcar = params;struct ir_data idata;/* 创建自己的队列 */QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));/* 注册队列 */RegisterQueueHandle(xQueueIR);/* 显示汽车 */ShowCar(pcar);while (1){/* 读取按键值:读队列 */xQueueReceive(xQueueIR, &idata, portMAX_DELAY);/* 控制汽车往右移动 */if (idata.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);}}}
}

2.6 car_game

在car_game里实现所有的操作

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);}}/* 创建3个汽车任务 */
#if 0for (i = 0; i < 3; i++){draw_bitmap(g_cars[i].x, g_cars[i].y, carImg, 15, 16, NOINVERT, 0);draw_flushArea(g_cars[i].x, g_cars[i].y, 15, 16);}
#endifxTaskCreate(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);	
}

最后修改初始化的部分


2.7 MX_FREERTOS_Init

这是初始化部分,我们在初始化中,调用car_game();这一个函数即可!

void MX_FREERTOS_Init(void) {/* USER CODE BEGIN Init */LCD_Init();LCD_Clear();IRReceiver_Init();RotaryEncoder_Init();LCD_PrintString(0, 0, "Starting");/* USER CODE END Init *//* USER CODE BEGIN RTOS_MUTEX *//* add mutexes, ... *//* USER CODE END RTOS_MUTEX *//* USER CODE BEGIN RTOS_SEMAPHORES *//* add semaphores, ... *//* USER CODE END RTOS_SEMAPHORES *//* USER CODE BEGIN RTOS_TIMERS *//* start timers, add new ones, ... *//* USER CODE END RTOS_TIMERS *//* USER CODE BEGIN RTOS_QUEUES *//* add queues, ... *//* USER CODE END RTOS_QUEUES *//* Create the thread(s) *//* creation of defaultTask */defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);/* USER CODE BEGIN RTOS_THREADS *//* add threads, ... *//* 创建任务 */extern void PlayMusic(void *params);xTaskCreate(PlayMusic, "MusicTask", 128, NULL, osPriorityNormal, NULL);car_game();   //使用这个函数来创建任务/* USER CODE END RTOS_THREADS *//* USER CODE BEGIN RTOS_EVENTS *//* add events, ... *//* USER CODE END RTOS_EVENTS */}

3 总结

我们在驱动程序里面,想使用同一个输入数据控制多个任务!
我们可以在这个驱动程序里面,写多个队列,写哪些队列呢?这个决定权可以交给应用程序,应用程序调用一个注册函数,把它的句柄告诉驱动程序,驱动程序会把它记录下来,这样就可以实现分发数据给多个任务~

在这里插入图片描述

在这里插入图片描述

这篇关于【FreeRTOS】队列实验-分发数据给多个任务(赛车游戏)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1098976

相关文章

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

Python MySQL如何通过Binlog获取变更记录恢复数据

《PythonMySQL如何通过Binlog获取变更记录恢复数据》本文介绍了如何使用Python和pymysqlreplication库通过MySQL的二进制日志(Binlog)获取数据库的变更记录... 目录python mysql通过Binlog获取变更记录恢复数据1.安装pymysqlreplicat

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

使用SQL语言查询多个Excel表格的操作方法

《使用SQL语言查询多个Excel表格的操作方法》本文介绍了如何使用SQL语言查询多个Excel表格,通过将所有Excel表格放入一个.xlsx文件中,并使用pandas和pandasql库进行读取和... 目录如何用SQL语言查询多个Excel表格如何使用sql查询excel内容1. 简介2. 实现思路3

Oracle数据库使用 listagg去重删除重复数据的方法汇总

《Oracle数据库使用listagg去重删除重复数据的方法汇总》文章介绍了在Oracle数据库中使用LISTAGG和XMLAGG函数进行字符串聚合并去重的方法,包括去重聚合、使用XML解析和CLO... 目录案例表第一种:使用wm_concat() + distinct去重聚合第二种:使用listagg,

Redis延迟队列的实现示例

《Redis延迟队列的实现示例》Redis延迟队列是一种使用Redis实现的消息队列,本文主要介绍了Redis延迟队列的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习... 目录一、什么是 Redis 延迟队列二、实现原理三、Java 代码示例四、注意事项五、使用 Redi

Python实现将实体类列表数据导出到Excel文件

《Python实现将实体类列表数据导出到Excel文件》在数据处理和报告生成中,将实体类的列表数据导出到Excel文件是一项常见任务,Python提供了多种库来实现这一目标,下面就来跟随小编一起学习一... 目录一、环境准备二、定义实体类三、创建实体类列表四、将实体类列表转换为DataFrame五、导出Da

Python Invoke自动化任务库的使用

《PythonInvoke自动化任务库的使用》Invoke是一个强大的Python库,用于编写自动化脚本,本文就来介绍一下PythonInvoke自动化任务库的使用,具有一定的参考价值,感兴趣的可以... 目录什么是 Invoke?如何安装 Invoke?Invoke 基础1. 运行测试2. 构建文档3.

Python实现数据清洗的18种方法

《Python实现数据清洗的18种方法》本文主要介绍了Python实现数据清洗的18种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录1. 去除字符串两边空格2. 转换数据类型3. 大小写转换4. 移除列表中的重复元素5. 快速统

Python数据处理之导入导出Excel数据方式

《Python数据处理之导入导出Excel数据方式》Python是Excel数据处理的绝佳工具,通过Pandas和Openpyxl等库可以实现数据的导入、导出和自动化处理,从基础的数据读取和清洗到复杂... 目录python导入导出Excel数据开启数据之旅:为什么Python是Excel数据处理的最佳拍档