【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

相关文章

一文详解MySQL如何设置自动备份任务

《一文详解MySQL如何设置自动备份任务》设置自动备份任务可以确保你的数据库定期备份,防止数据丢失,下面我们就来详细介绍一下如何使用Bash脚本和Cron任务在Linux系统上设置MySQL数据库的自... 目录1. 编写备份脚本1.1 创建并编辑备份脚本1.2 给予脚本执行权限2. 设置 Cron 任务2

SQL Server修改数据库名及物理数据文件名操作步骤

《SQLServer修改数据库名及物理数据文件名操作步骤》在SQLServer中重命名数据库是一个常见的操作,但需要确保用户具有足够的权限来执行此操作,:本文主要介绍SQLServer修改数据... 目录一、背景介绍二、操作步骤2.1 设置为单用户模式(断开连接)2.2 修改数据库名称2.3 查找逻辑文件名

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部

canal实现mysql数据同步的详细过程

《canal实现mysql数据同步的详细过程》:本文主要介绍canal实现mysql数据同步的详细过程,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的... 目录1、canal下载2、mysql同步用户创建和授权3、canal admin安装和启动4、canal

使用jenv工具管理多个JDK版本的方法步骤

《使用jenv工具管理多个JDK版本的方法步骤》jenv是一个开源的Java环境管理工具,旨在帮助开发者在同一台机器上轻松管理和切换多个Java版本,:本文主要介绍使用jenv工具管理多个JD... 目录一、jenv到底是干啥的?二、jenv的核心功能(一)管理多个Java版本(二)支持插件扩展(三)环境隔

使用SpringBoot整合Sharding Sphere实现数据脱敏的示例

《使用SpringBoot整合ShardingSphere实现数据脱敏的示例》ApacheShardingSphere数据脱敏模块,通过SQL拦截与改写实现敏感信息加密存储,解决手动处理繁琐及系统改... 目录痛点一:痛点二:脱敏配置Quick Start——Spring 显示配置:1.引入依赖2.创建脱敏

Java中常见队列举例详解(非线程安全)

《Java中常见队列举例详解(非线程安全)》队列用于模拟队列这种数据结构,队列通常是指先进先出的容器,:本文主要介绍Java中常见队列(非线程安全)的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一.队列定义 二.常见接口 三.常见实现类3.1 ArrayDeque3.1.1 实现原理3.1.2

详解如何使用Python构建从数据到文档的自动化工作流

《详解如何使用Python构建从数据到文档的自动化工作流》这篇文章将通过真实工作场景拆解,为大家展示如何用Python构建自动化工作流,让工具代替人力完成这些数字苦力活,感兴趣的小伙伴可以跟随小编一起... 目录一、Excel处理:从数据搬运工到智能分析师二、PDF处理:文档工厂的智能生产线三、邮件自动化:

Python数据分析与可视化的全面指南(从数据清洗到图表呈现)

《Python数据分析与可视化的全面指南(从数据清洗到图表呈现)》Python是数据分析与可视化领域中最受欢迎的编程语言之一,凭借其丰富的库和工具,Python能够帮助我们快速处理、分析数据并生成高质... 目录一、数据采集与初步探索二、数据清洗的七种武器1. 缺失值处理策略2. 异常值检测与修正3. 数据

pandas实现数据concat拼接的示例代码

《pandas实现数据concat拼接的示例代码》pandas.concat用于合并DataFrame或Series,本文主要介绍了pandas实现数据concat拼接的示例代码,具有一定的参考价值,... 目录语法示例:使用pandas.concat合并数据默认的concat:参数axis=0,join=