【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

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd

hdu1180(广搜+优先队列)

此题要求最少到达目标点T的最短时间,所以我选择了广度优先搜索,并且要用到优先队列。 另外此题注意点较多,比如说可以在某个点停留,我wa了好多两次,就是因为忽略了这一点,然后参考了大神的思想,然后经过反复修改才AC的 这是我的代码 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

烟火目标检测数据集 7800张 烟火检测 带标注 voc yolo

一个包含7800张带标注图像的数据集,专门用于烟火目标检测,是一个非常有价值的资源,尤其对于那些致力于公共安全、事件管理和烟花表演监控等领域的人士而言。下面是对此数据集的一个详细介绍: 数据集名称:烟火目标检测数据集 数据集规模: 图片数量:7800张类别:主要包含烟火类目标,可能还包括其他相关类别,如烟火发射装置、背景等。格式:图像文件通常为JPEG或PNG格式;标注文件可能为X

pandas数据过滤

Pandas 数据过滤方法 Pandas 提供了多种方法来过滤数据,可以根据不同的条件进行筛选。以下是一些常见的 Pandas 数据过滤方法,结合实例进行讲解,希望能帮你快速理解。 1. 基于条件筛选行 可以使用布尔索引来根据条件过滤行。 import pandas as pd# 创建示例数据data = {'Name': ['Alice', 'Bob', 'Charlie', 'Dav