当前位置: 首页 > news >正文

【FreeRTOS】队列实验-多设备玩游戏(旋转编码器)

目录

  • 0 前言
  • 1 任务
    • 1.1 本节源码
    • 1.2实验目的
    • 1.3实现方案
  • 2 code
    • 2.1 创建队列
    • 2.2 写队列
    • 2.3 创建任务
  • 3 勘误


0 前言

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


参考《FreeRTOS入门与工程实践(基于DshanMCU-103).pdf》
https://rtos.100ask.net/zh/FreeRTOS/DShanMCU-F103/chapter11.html


1 任务

1.1 本节源码

在"03_参考的源码/DshanMCU-F103/02_nwatch_game_freertos.7z"的基础上

  • “14_queue_game_multi_input”: 在"13_queue_game"的基础上,增加旋转编码控制功能

1.2实验目的

使用红外遥控器旋转编码器玩游戏。

1.3实现方案

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

2 code

2.1 创建队列

在game1.c的void game1_task(void *params)中添加

    /* 创建队列:平台任务从里面读到旋转编码器数据,... */g_xQueueRotary   = xQueueCreateStatic(10,                         // 长度 10 随意给的sizeof(struct rotary_data), // 大小g_ucQueueRotaryBuf,         // 保存数据的buffer&g_xQueueRotaryStaticStruct // 结构体的地址);// 静态创建

定义结构体

struct rotary_data {int32_t cnt;	//设备int32_t speed;	//值uint8_t dir;    //方向
};

定义队列

QueueHandle_t g_xQueueRotary;   /* 旋转编码器队列 */
static uint8_t g_ucQueueRotaryBuf[10*sizeof(struct rotary_data)];   //存放10个rotary_data结构体这么大小的buf
static StaticQueue_t g_xQueueRotaryStaticStruct;                    //队列的结构体

后面还需要创建旋转编码器的任务,这个任务就读取本队列,处理数据,再写队列

在这里插入图片描述


2.2 写队列

在旋转编码器的中断服务函数里写队列,写哪个队列? >>> g_xQueueRotary

在14_queue_game_multi_input\Drivers\DshanMCU-F103\driver_rotary_encoder.c路径下写
先添加头文件

#include "FreeRTOS.h"
#include "queue.h"
#include "typedefs.h"

再外部声明这个队列

extern QueueHandle_t g_xQueueRotary;   /* 旋转编码器队列 */

在中断回调函数添加写队列的代码,后面注释了很多加号的就是新增的部分

/*********************************************************************** 函数名称: RotaryEncoder_IRQ_Callback* 功能描述: 旋转编码器的中断回调函数* 输入参数: 无* 输出参数: 无* 返 回 值: 无* 修改日期:      版本号     修改人	      修改内容* -----------------------------------------------* 2023/08/04	     V1.0	  韦东山	      创建***********************************************************************/
void RotaryEncoder_IRQ_Callback(void)
{uint64_t time;static uint64_t pre_time = 0;struct rotary_data rdata;   //定义这个结构体++++++++++++++++++++++++++++++++++++++++++++++/* 1. 记录中断发生的时刻 */	time = system_get_ns();/* 上升沿触发: 必定是高电平 * 防抖*/mdelay(2);if (!RotaryEncoder_Get_S1())return;/* S1上升沿触发中断* S2为0表示逆时针转, 为1表示顺时针转*/g_speed = (uint64_t)1000000000/(time - pre_time);if (RotaryEncoder_Get_S2()){g_count++;}else{g_count--;g_speed = 0 - g_speed;}pre_time = time;/* 写队列 */   //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++rdata.cnt   = g_count;rdata.speed = g_speed;xQueueSendFromISR(g_xQueueRotary, &rdata, NULL);    //把这个结构体写入队列g_xQueueRotary
}

2.3 创建任务

创建旋转编码器的任务

    /* 创建旋转编码器的任务 */xTaskCreate(RotaryEncoderTask, "RotaryEncoderTask", 128, NULL, osPriorityNormal, NULL);

编写旋转编码器任务函数

static void RotaryEncoderTask(void *params)
{struct rotary_data rdata;   //定义结构体  读取struct input_data  idata;   //定义结构体  写入uint8_t left;uint8_t i, cnt;while(1){/* 1.读取旋转编码器队列 */xQueueReceive(g_xQueueRotary,&rdata, portMAX_DELAY);    //读到的数据保存到rdata结构体里/* 2.处理队列 */// 判断速度:-表示左移,+表示右移if (rdata.speed < 0){left = 1;rdata.speed = 0 - rdata.speed;  //负数,改成正数}else{left = 0;}if (rdata.speed > 100)cnt = 5;else if (rdata.speed > 100)cnt = 3;elsecnt = 1;/* 3.写入挡球板队列 */idata.dev = 1;idata.val = left ? 0xe0 : 0x90;for (i = 0; i < cnt; i ++)  //速度快就多写几遍{xQueueSend(g_xQueuePlatform, &idata, 0);    //这里要么成功\要么失败,不能阻塞}}
}

烧录运行这个代码是有bug的,旋转编码器只能右移
而且左右移的数据和红外的数据是关联的,我们需要对他们两个解耦,分别控制

在这里插入图片描述
队列A里用的是红外的格式,我们修改一下即可实现解耦

在这个函数里static int IRReceiver_IRQTimes_Parse(void),写队列的时候,我们修改一下,不写红外的键值了,写左右

 /* 写队列 */idata.dev = datas[0];if (datas[2] == 0xe0)idata.val = UPT_MOVE_LEFT;  //左移else if (datas[2] == 0x90)idata.val = UPT_MOVE_RIGHT; //右移elseidata.val = UPT_MOVE_NONE;g_last_val = idata.val;			// 重复码处理xQueueSendToBackFromISR(g_xQueuePlatform, &idata, NULL);

旋转编码器的左右

static void RotaryEncoderTask(void *params)
{struct rotary_data rdata;   //定义结构体  读取struct input_data  idata;   //定义结构体  写入uint8_t left;uint8_t i, cnt;while(1){/* 1.读取旋转编码器队列 */xQueueReceive(g_xQueueRotary,&rdata, portMAX_DELAY);    //读到的数据保存到rdata结构体里/* 2.处理队列 */// 判断速度:-表示左移,+表示右移if (rdata.speed < 0){left = 1;rdata.speed = 0 - rdata.speed;  //负数,改成正数}else{left = 0;}if (rdata.speed > 100)cnt = 5;else if (rdata.speed > 100)cnt = 3;elsecnt = 1;/* 3.写入挡球板队列 */idata.dev = 1;idata.val = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT;      //+++++++++++++++++++++++for (i = 0; i < cnt; i ++)  //速度快就多写几遍{xQueueSend(g_xQueuePlatform, &idata, 0);    //这里要么成功\要么失败,不能阻塞}}
}

判断重复码部分,如果有重复码,就上报上一次的按键值

/* 是否重复码 */if (isRepeatedKey()){/* device: 0, val: 0, 表示重复码 *///PutKeyToBuf(0);//PutKeyToBuf(0);/* 写队列 */idata.dev = 0;idata.val = g_last_val; //如果有重复码,就上报上一次的按键值xQueueSendToBackFromISR(g_xQueuePlatform, &idata, NULL);g_IRReceiverIRQ_Cnt = 0;}

3 勘误

bug:旋转编码器不好用,旋转编码器只能控制挡球板右移,移到最右边,就不能动了,不能往左移动!

错误代码:

void RotaryEncoder_IRQ_Callback(void)
{uint64_t time;static uint64_t pre_time = 0;struct rotary_data rdata;   //定义这个结构体++++++++++++++++++++++++++++++++++++++++++++++/* 1. 记录中断发生的时刻 */	time = system_get_ns();/* 上升沿触发: 必定是高电平 * 防抖 */mdelay(2);if (!RotaryEncoder_Get_S1())return;/* S1上升沿触发中断* S2为0表示逆时针转, 为1表示顺时针转*/g_speed = (uint64_t)1000000000/(time - pre_time);if (RotaryEncoder_Get_S2()){g_count++;}else{g_count--;g_speed = 0 - g_speed;}pre_time = time;/* 写队列 */   //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++rdata.cnt   = g_count;rdata.speed = g_speed;xQueueSendFromISR(g_xQueueRotary, &rdata, NULL);    //把这个结构体写入队列g_xQueueRotary
}

消抖用的mdelay(2),跳过2ms,这样是错误的,假设抖动大于2ms,后面的抖动的数据也被采集进去了,这样就错误了
在这里插入图片描述

修改之后的代码,具体看注释
当前时间 - 上次中断的时间 < 2ms 认为是抖动!就返回,不处理!

/*********************************************************************** 函数名称: RotaryEncoder_IRQ_Callback* 功能描述: 旋转编码器的中断回调函数* 输入参数: 无* 输出参数: 无* 返 回 值: 无* 修改日期:      版本号     修改人	      修改内容* -----------------------------------------------* 2023/08/04	     V1.0	  韦东山	      创建***********************************************************************/
void RotaryEncoder_IRQ_Callback(void)
{uint64_t time;static uint64_t pre_time = 0;struct rotary_data rdata;   //定义这个结构体++++++++++++++++++++++++++++++++++++++++++++++/* 1. 记录中断发生的时刻 */	time = system_get_ns();/* 上升沿触发: 必定是高电平 * 防抖*///mdelay(2);  //没有意义if (time - pre_time < 2000000)   //当前时间 - 上次中断的时间 < 2ms 认为是抖动!{pre_time = time;return; //就返回,不处理}if (!RotaryEncoder_Get_S1())return;/* S1上升沿触发中断* S2为0表示逆时针转, 为1表示顺时针转*/g_speed = (uint64_t)1000000000/(time - pre_time);if (g_speed == 0)   // 如果后面的时间足够大,这个speed可能=0,0很小,不能移动,我们给赋值1,很小的速度移动!g_speed = 1;if (RotaryEncoder_Get_S2()){g_count++;}else{g_count--;g_speed = 0 - g_speed;  //反转吗}pre_time = time;/* 写队列 */   //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++rdata.cnt   = g_count;rdata.speed = g_speed;xQueueSendFromISR(g_xQueueRotary, &rdata, NULL);    //把这个结构体写入队列g_xQueueRotary
}

我们继续优化一下队列的处理部分

static void RotaryEncoderTask(void *params)
{struct rotary_data rdata;   //定义结构体  读取struct input_data  idata;   //定义结构体  写入
//    uint8_t left;uint8_t i, cnt;while(1){/* 1.读取旋转编码器队列 */xQueueReceive(g_xQueueRotary,&rdata, portMAX_DELAY);    //读到的数据保存到rdata结构体里/* 2.处理队列 *//* 判断速度: 负数表示向左转动, 正数表示向右转动 */if (rdata.speed < 0){left = 1;rdata.speed = 0 - rdata.speed;}else{left = 0;}if (rdata.speed > 50)	//队列处理的优化,挡球板移动的更细腻了cnt = 5;else if (rdata.speed > 40)cnt = 4;else if (rdata.speed > 30)cnt = 3;else if (rdata.speed > 20)cnt = 2;elsecnt = 1;/* 3.写入挡球板队列 */idata.dev = 1;idata.val = left ? UPT_MOVE_RIGHT : UPT_MOVE_LEFT;      //+++++++++++++++++++++++for (i = 0; i < cnt; i ++)  //速度快就多写几遍{xQueueSend(g_xQueuePlatform, &idata, 0);    //这里要么成功\要么失败,不能阻塞}}
}

现在就好使了!
在这里插入图片描述

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • SQL 时间盲注 (injection 第十五关)
  • java.sql.SQLException: txn too large, size: 104857606.
  • SQLALchemy 分组过滤、子查询
  • QT网络编程: 实现UDP通讯设置
  • java使用itext 直接生成pdf
  • 2025ICASSP Author Guidelines
  • 宠物空气净化器推荐买吗?清除浮毛的效果好吗
  • 改变自己·心情治愈
  • [000-01-030].Zookeeper学习大纲
  • node版本8.x→16.x,前端维护火葬场,问题及解决方案总结
  • 如何将本地组件库上传到npm上
  • 【区块链+金融服务】甘肃股权交易中心企业金融服务平台 | FISCO BCOS应用案例
  • DQL-案例
  • 第10章 无持久存储的文件系统 (3)
  • 【hot100篇-python刷题记录】【和为 K 的子数组】
  • 【刷算法】从上往下打印二叉树
  • centos安装java运行环境jdk+tomcat
  • css选择器
  • Javascripit类型转换比较那点事儿,双等号(==)
  • PHP的Ev教程三(Periodic watcher)
  • Wamp集成环境 添加PHP的新版本
  • Web标准制定过程
  • 创建一个Struts2项目maven 方式
  • 吴恩达Deep Learning课程练习题参考答案——R语言版
  • 一些css基础学习笔记
  • 《天龙八部3D》Unity技术方案揭秘
  • ​渐进式Web应用PWA的未来
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • ​油烟净化器电源安全,保障健康餐饮生活
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • #我与Java虚拟机的故事#连载12:一本书带我深入Java领域
  • $emit传递多个参数_PPC和MIPS指令集下二进制代码中函数参数个数的识别方法
  • (1)(1.9) MSP (version 4.2)
  • (20)docke容器
  • (javaweb)Http协议
  • (Java入门)学生管理系统
  • (Matlab)基于蝙蝠算法实现电力系统经济调度
  • (Ruby)Ubuntu12.04安装Rails环境
  • (二开)Flink 修改源码拓展 SQL 语法
  • (二十九)STL map容器(映射)与STL pair容器(值对)
  • (附源码)ssm高校社团管理系统 毕业设计 234162
  • (十五)使用Nexus创建Maven私服
  • (算法)区间调度问题
  • (转)AS3正则:元子符,元序列,标志,数量表达符
  • (转)人的集合论——移山之道
  • .net core 调用c dll_用C++生成一个简单的DLL文件VS2008
  • .NET 的程序集加载上下文
  • .NET框架设计—常被忽视的C#设计技巧
  • .net实现客户区延伸至至非客户区
  • .NET文档生成工具ADB使用图文教程
  • @Service注解让spring找到你的Service bean
  • []sim300 GPRS数据收发程序
  • [8-27]正则表达式、扩展表达式以及相关实战
  • [Angular] 笔记 18:Angular Router
  • [Big Data - Kafka] kafka学习笔记:知识点整理