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

news2024/11/24 14:49:42

目录

  • 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;
        else
            cnt = 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; //右移
    else
        idata.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;
        else
            cnt = 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;
		else
			cnt = 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);    //这里要么成功\要么失败,不能阻塞
        }
    }
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2058481.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SpringBoot+Vu e.js校园疫情防控系统的设计与实现

文章目录 前言具体实现截图详细视频演示技术栈系统测试为什么选择我官方认证玩家&#xff0c;服务很多代码文档&#xff0c;百分百好评&#xff0c;战绩可查&#xff01;&#xff01;入职于互联网大厂&#xff0c;可以交流&#xff0c;共同进步。有保障的售后 代码参考数据库参…

轮换IP与固定IP,如何抉择?

IP地址相信大家都知道&#xff0c;它是标识我们网络身份的重要凭证。从访问网站到数据抓取&#xff0c;都能看到IP地址的身影。那么&#xff0c;轮换IP和固定IP该怎么理解呢&#xff1f;本文将详细介绍这两种IP类型&#xff0c;旨在帮助你根据需求做出合适的选择。 什么是固定I…

VM Ubuntu22.04 ROS2 从头安装

目录 前言安装步骤1 设置编码2 添加ROS2软件源&#xff08;从哪去下载ros2相关软件&#xff09;报错解决方法 3 安装报错解决方法1解决方法2 报错 4 设置环境变量5 Ros2 测试Hello World 发送和监听小海龟键盘控制 成功 Hello World 发送和监听界面成功控制小海龟界面 前言 本…

OpenAI发布微调功能 允许企业客户定制AI模型

当地时间8月20日&#xff0c;OpenAI发布了一项新功能&#xff0c;允许企业客户使用他们自己的公司数据来定制其最强大的模型GPT-4o&#xff0c;这将大大提高应用程序的性能和准确性。此举出台之际&#xff0c;初创企业在人工智能&#xff08;AI&#xff09;产品上面临着日益激烈…

MySQL 高阶三 (索引性能分析)

执行过程 Explain explain select * from student s, course c , student_coure sc where s.id sc.studentid and c.id sc.courseid;EXPLAIN执行计划各字段含义: 【ld】 id相同&#xff0c;执行顺序从上到下; id不同&#xff0c;值越大&#xff0c;越先执行)。 【select_type…

【论文学习与撰写】快捷搜索指令filetype:pdf,搜索引擎关键词搜索pdf格式文件或者word格式文件。文献搜索方法大全。

1、使用快捷搜索指令 在搜索框中输入&#xff1a;关键词空格filetype英文冒号文件格式 &#xff08;如&#xff1a;关键词 filetype:pdf&#xff09;。 通过这种方式&#xff0c;搜索引擎会限定搜索结果只显示 PDF 格式的文件。 比如搜索“2018 年考研英语真题 filetype:pd…

使用 Docker 安装 Ollama 部署本地大模型并接入 One-API

Ollama是一款开源工具&#xff0c;它允许用户在本地便捷地运行多种大型开源模型&#xff0c;包括清华大学的ChatGLM、阿里的千问以及Meta的llama等。目前&#xff0c;Ollama兼容macOS、Linux和Windows三大主流操作系统。本文将介绍如何通过Docker安装Ollama&#xff0c;并将其部…

苹果手机如何备份通讯录?4个方法手把手教你备份

苹果手机通讯录是我们联系亲朋好友的重要工具。然而&#xff0c;如果苹果手机出现损坏或者是丢失的情况&#xff0c;那手机通讯录存储的联系方式也会随之消失。为了避免这种情况的发生&#xff0c;定期备份通讯录变得至关重要&#xff0c;那么&#xff0c;苹果手机如何备份通讯…

基于Nginx进行服务器隐私保护:隐藏真实的服务器IP地址或主机名( 转发代理、服务器的别名)

文章目录 引言I 隐藏站点请求API的真实服务器IP和端口查看主文件配置服务的端口和站点目录的映射配置proxy_pass代理转发代理转发的其他配置【可选】II 服务器主机名处理隐藏真实的服务器主机名判断API请求是哪个服务器处理的III GLC日志中心新增用户信息扩展:在Linux中配置主…

趋势分享|Gartner解读中国企业容器管理新挑战:混合环境、容器安全、AI支持

不少企业都使用容器管理类软件/平台&#xff0c;方便容器环境的部署和运维。而随着应用系统的运行环境逐渐多元化&#xff0c;IT 运维人员仅依靠容器管理产品&#xff0c;已难以同时兼顾多种 IT 基础设施上的多个应用运行环境。同时&#xff0c;AI 等高性能应用场景的兴起&…

探索802.1X:构筑安全网络的认证之盾

在现代网络安全的世界里&#xff0c;有一个极其重要但又常常被忽视的角色&#xff0c;它就是802.1x认证协议。这个协议可以被称作网络安全的守护者&#xff0c;为我们提供了强有力的防护。今天&#xff0c;我们就来深入探讨一下802.1x的原理、应用和测试&#xff0c;看看它是如…

做谷歌seo如何创建良好的用户体验?

Google 希望排名靠前的页面能够为用户提供良好的体验&#xff0c;所以网站提升用户体验很重要。以下是一些实用的小建议&#xff0c;让你的网站更受用户欢迎&#xff0c;并且有助于提升你的 SEO 排名。 现代化设计&#xff1a;确保你的网站设计符合当前的审美和功能趋势。使用高…

基于ssm的毕业回忆录系统的设计与实现

获取源码联系方式请查看文章结尾&#x1f345; 摘 要 随着信息化时代的到来&#xff0c;系统管理都趋向于智能化、系统化&#xff0c;毕业回忆录也不例外&#xff0c;但目前国内的有些学校仍然都使用人工管理&#xff0c;学校规模越来越大&#xff0c;同时信息量也越来越庞大&a…

6.画面渲染及背景-《篮球比赛展示管理系统》现场管理员角色操作手册

通过[特效实验室]及[更换背景] 对整个展示界面的底部图层进行动画渲染。此功能是平台的一大特色。一般用在选手上场或颁奖等。用户可以根据现场情况&#xff0c;妥善发挥。背景图片及其特效&#xff0c;应该在比赛之前设置好。

【信创】Linux操作系统上安装软件包提示依赖不足的解决办法 _ 统信 _ 麒麟 _ 方德

原文链接&#xff1a;【信创】Linux操作系统上安装软件包提示依赖不足的解决办法 | 统信 | 麒麟 | 方德 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在Linux操作系统上安装软件包时遇到依赖不足问题的解决办法的文章。软件包依赖是Linux系统中常见的问题&…

聊聊最近很火的后端即服务

最近&#xff0c;你可能经常听到“后端即服务”&#xff08;Backend as a Service, BaaS&#xff09;这个词。不论是在技术论坛上&#xff0c;还是在开发者社区&#xff0c;BaaS都成了大家讨论的热点。究竟是什么让这个概念如此火爆&#xff1f;今天我们就来聊聊这个话题&#…

CPU内部专用数据通路各阶段的微操作序列利控制信号

1.主存与寄存器之间的数据传送 取指令的示例&#xff1a; (PC)→MARC0有效(MAR)→主存C1有效1→RCU发送读命令M(MAR)→MDRC2有效(MDR)→IRC3有效OP(IR)→CUC4有效(PC)1→PC 由于是专用数据通路&#xff0c;只要两个元器件之间有数据流动就需要一条数据通路&#xff0c;造价昂贵…

基础数据结构——二分算法及时间复杂度

这个算法的前提是&#xff0c;数组是升序排列的 算法描述&#xff1a; i和j是指针可以表示查找范围 m为中间值 当目标值targat比m大时&#xff0c;设置查找范围在m右边&#xff1a;i m-1 当目标值targat比m小时&#xff0c;设置查找范围在m左边&#xff1a;j m1 当targat…

ISO 26262中的失效率计算:IEC 61709-Clause 10_Resistors and resistor networks

目录 概要 1 元器件分类和基准温度 2 失效率的计算 2.1 失效率预测模型 2.2 温度应力系数 2.2.1 温度应力系数计算模型 3.2.2 温度应力系数计算 结语 概要 IEC 61709是国际电工委员会&#xff08;IEC&#xff09;制定的一个标准&#xff0c;即“电子元器件 可靠性 失效…

STM32学习记录-02-GPIO通用输入输出口

mm 1 GPIO简介 GPIO&#xff08;General Purpose Input Output&#xff09;通用输入输出口 可配置为8种输入输出模式 引脚电平&#xff1a;0V~3.3V&#xff0c;部分引脚可容忍5V 输出模式下可控制端口输出高低电平&#xff0c;用以驱动LED、控制蜂鸣器、模拟通信协议输出时…