【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;
        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.mfbz.cn/a/871727.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CronTab及定时任务

目录 CronTab及定时任务 一、定时任务的基本原理 二、Cron定时任务 但是 三、其他补充命令 CronTab及定时任务 一、定时任务的基本原理 # 每5秒钟向文本中输出一次时间#for i in {1..10}; do while [ 1 < 2 ]; dodate "%Y-%m-%d %H:%M:%S" >> /opt/lea…

Prism-学习笔记1-安装Prism

安装Prism 在VS2022中安装如下图&#xff1a; 2. 搜索Prism&#xff0c;安装Prism&#xff1a;&#xff08;ps&#xff1a;如果安装很慢&#xff0c;直接往上搜关键字 Prism template Pack 下载&#xff0c;或者这里我下载好的Prism包&#xff0c;提取码&#xff1a;bi7c&…

普通高校普通教师如何应对智能时代的冲击

前篇 艰难求生的转型之路-CSDN博客 背景 增量发展阶段&#xff0c;大部分人生活随着个人努力都会出现改善&#xff1b; 存量博弈阶段&#xff0c;大部分人&#xff0c;不展开&#xff0c;求生欲。 增量→“蛋糕”越来越大&#xff1b; 存量→“蛋糕”(*^_^*)凸(艹皿艹 ) …

将 hugo 博客搬迁到服务器

1. 说明 在 Ubuntu 22.04 上使用 root 账号&#xff0c;创建普通账号&#xff0c;并赋予 root 权限。 演示站点&#xff1a;https://woniu336.github.io/ 魔改hugo主题: https://github.com/woniu336/hugo-magic 2. 服务器配置 建立 git 用户 adduser git安装 git sudo apt …

C/C++ 多线程[1]---线程创建+线程释放+实例

文章目录 前言1. 多线程创建2. 多线程释放3. 实例总结 前言 说来惭愧&#xff0c;写了很久的代码&#xff0c;一个单线程通全部。可能是接触的项目少吧&#xff0c;很多多线程的概念其实都知道&#xff0c;但是实战并没有用上。前段时间给公司软件做一个进度条&#xff0c;涉及…

亲测解决Verifying shim SBAT data failed: Security Policy Violation

在小虎用u盘安装ubuntu系统的时候&#xff0c;笔记本出现了这个问题&#xff0c;解决方法是管关闭security boot。 解决方法 利用F2\F10\F12进入Bios设置&#xff0c;关闭security boot即可。 Use F2 to enter the bios security settings, close it. 参考 Verifying shim…

揭秘Semantic Kernel:用AI自动规划和执行用户请求

在我们日益高效的开发世界中&#xff0c;将任务自动化并智能规划变得越来越必要。今天&#xff0c;我要给大家介绍一个强大的概念——Semantic Kernel中的planner功能。通过这篇文章&#xff0c;我们会学习到planner的工作原理以及如何实现智能任务规划。 什么是planner&#x…

Spring GateWay自定义断言工厂

文章目录 概要整体架构流程最终的处理效果小结 概要 我们在线上系统部署了&#xff0c;灰度环境和生产环境时&#xff0c;可以通过自定义断言工厂去将请求需要路由到灰度环境的用户调用灰度的的服务集群&#xff0c;将正常的用户调用正常集群。 这样&#xff0c;我们可以在上线…

R语言论文插图模板第7期—分组散点图

在之前的文章中&#xff0c;分享过R语言折线图的绘制模板&#xff1a; 柱状图的绘制模板&#xff1a; 本期再来分享一下散点图&#xff08;分组&#xff09;的绘制方法。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;本期内容『数据代码』已上传资源群中&#xff0c;…

碰撞检测 | 基于ROS Rviz插件的多边形碰撞检测仿真平台

目录 0 专栏介绍1 基于多边形的碰撞检测2 碰撞检测仿真平台搭建2.1 多边形实例2.2 外部服务接口2.3 Rviz插件化 3 案例演示3.1 功能介绍3.2 绘制多边形 0 专栏介绍 &#x1f525;课设、毕设、创新竞赛必备&#xff01;&#x1f525;本专栏涉及更高阶的运动规划算法轨迹优化实战…

【附源码】Python :PYQT界面点击按钮随机变色

系列文章目录 Python 界面学习&#xff1a;PYQT界面点击按钮随机变色 文章目录 系列文章目录一、项目需求二、源代码三、代码分析3.1 导入模块&#xff1a;3.2 定义App类&#xff1a;3.3 构造函数&#xff1a;3.4 初始化用户界面&#xff1a;3.5 设置窗口属性&#xff1a;3.6 …

基于距离度量学习的异常检测:一种通过相关距离度量的异常检测方法

异常通常被定义为数据集中与大多数其他项目非常不同的项目。或者说任何与所有其他记录(或几乎所有其他记录)显著不同的记录,并且与其他记录的差异程度超出正常范围,都可以合理地被认为是异常。 例如上图显示的数据集中,我们有四个簇(A、B、C和D)和三个位于这些簇之外的点:P1、P…

client网络模块的开发和client与server端的部分联动调试

客户端网络模块的开发 我们需要先了解socket通信的流程 socket通信 server端的流程 client端的流程 对于closesocket()函数来说 closesocket()是用来关闭套接字的,将套接字的描述符从内存清除,并不是删除了那个套接字,只是切断了联系,所以我们如果重复调用,不closesocket()…

Agentic Security:一款针对LLM模型的模糊测试与安全检测工具

关于Agentic Security Agentic Security是一款针对LLM模型的模糊测试与安全检测工具&#xff0c;该工具可以帮助广大研究人员针对任意LLM执行全面的安全分析与测试。 请注意 Agentic Security 是作为安全扫描工具设计的&#xff0c;而不是万无一失的解决方案。它无法保证完全防…

C++(11)类语法分析(2)

C(10)之类语法分析(2) Author: Once Day Date: 2024年8月17日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 源码分析_Once-Day的博客-CSDN博客 …

Python数据结构:集合详解(创建、集合操作)④

文章目录 1. Python集合概述2. 创建集合2.1 使用花括号 {} 创建集合2.2 使用 set() 函数创建集合2.3 创建空集合 3. 集合操作3.1 添加和删除元素3.2 集合的基本操作3.3 集合的比较操作3.4 不可变集合&#xff08;frozenset&#xff09; 4. 综合例子&#xff1a;图书管理系统 Py…

30秒内批量删除git本地分支

在开发过程中&#xff0c;我们经常需要对本地的 Git 分支进行管理。有时&#xff0c;由于各种原因&#xff0c;我们可能需要批量删除本地的分支。这可能是因为某些分支已经不再需要&#xff0c;或者是为了清理本地的分支列表&#xff0c;以保持整洁和易于管理。 要批量删除本地…

没有用的小技巧之---接入网线,有内网没有外网,但是可以登录微信

打开控制面板&#xff0c;找到网络和Internet 选择Internet选项 点击连接&#xff0c;选择局域网设置 取消勾选代理服务器

开放式耳机会打扰到别人吗?四款漏音处理做的好的蓝牙耳机

一般情况下&#xff0c;开放式耳机不会打扰到别人。 开放式耳机通常采用全开放设计&#xff0c;声音不会完全封闭在耳朵里&#xff0c;而是向四周扩散&#xff0c;相比封闭式耳机&#xff0c;其对外界环境的噪音影响更小 。而且现在的开放式耳机在技术上已经有了很大的进步&am…

[数据集][目标检测]工程机械车辆检测数据集VOC+YOLO格式3189张10类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;3189 标注数量(xml文件个数)&#xff1a;3189 标注数量(txt文件个数)&#xff1a;3189 标注…