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


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》


1 任务

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

  • “13_queue_game”: 使用队列读取红外遥控器按键
  • “14_queue_game_multi_input”: 在"13_queue_game"的基础上,增加旋转编码控制功能

实验目的:使用红外遥控器旋转编码器玩游戏。

实现方案:

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

2 新增

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;
		}

勘误,旋转编码器不好用

  • 16
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北国无红豆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值