【FreeRTOS】队列实验-多设备玩游戏(红外改造)

news2024/9/21 14:52:21

目录

  • 0 前言
  • 1. 队列实验_多设备玩游戏
  • 2 回顾程序
  • 3 程序改进
    • 3.1 创建队列
      • 3.1.1 方法
      • 3.1.2 实践
    • 3.2 读队列
      • 3.2.1 方法
      • 3.2.2 实践
    • 3.3 写队列
      • 3.3.1 方法
      • 3.3.2 实践


0 前言

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


参考《FreeRTOS入门与工程实践(基于DshanMCU-103).pdf》


1. 队列实验_多设备玩游戏

本次实验现象:一边听音乐,一边打游戏!
在这里插入图片描述


2 回顾程序

这个任务不断读取环形Buffer,很占CPU资源,导致音乐播放很卡顿
在这里插入图片描述

我们现在要改造这代码,改成读取队列的形式

在这里插入图片描述


3 程序改进

3.1 创建队列

3.1.1 方法

队列的创建有两种方法:动态分配内存静态分配内存

  • 动态分配内存xQueueCreate,队列的内存在函数内部动态分配

函数原型如下:

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
参数说明
uxQueueLength队列长度,最多能存放多少个数据(item)
uxItemSize每个数据(item)的大小:以字节为单位
返回值非0:成功,返回句柄,以后使用句柄来操作队列 NULL:失败,因为内存不足
  • 静态分配内存:xQueueCreateStatic,队列的内存要事先分配好

函数原型如下:

QueueHandle_t xQueueCreateStatic(*
              		UBaseType_t uxQueueLength,*
              		UBaseType_t uxItemSize,*
              		uint8_t *pucQueueStorageBuffer,*
              		StaticQueue_t *pxQueueBuffer*
           		 );
参数说明
uxQueueLength队列长度,最多能存放多少个数据(item)
uxItemSize每个数据(item)的大小:以字节为单位
pucQueueStorageBuffer如果uxItemSize非0,pucQueueStorageBuffer必须指向一个uint8_t数组, 此数组大小至少为"uxQueueLength * uxItemSize"
pxQueueBuffer必须执行一个StaticQueue_t结构体,用来保存队列的数据结构
返回值非0:成功,返回句柄,以后使用句柄来操作队列 NULL:失败,因为pxQueueBuffer为NULL

示例代码:

// 示例代码
 #define QUEUE_LENGTH 10
 #define ITEM_SIZE sizeof( uint32_t )
 
 // xQueueBuffer用来保存队列结构体
 StaticQueue_t xQueueBuffer;

// ucQueueStorage 用来保存队列的数据

// 大小为:队列长度 * 数据大小
 uint8_t ucQueueStorage[ QUEUE_LENGTH * ITEM_SIZE ];

 void vATask( void *pvParameters )
 {
	QueueHandle_t xQueue1;

	// 创建队列: 可以容纳QUEUE_LENGTH个数据,每个数据大小是ITEM_SIZE
	xQueue1 = xQueueCreateStatic( QUEUE_LENGTH,
							ITEM_SIZE,
                            ucQueueStorage,
                            &xQueueBuffer ); 
  }

3.1.2 实践

在哪里创建队列???

  1. 先定义一个全局指针
QueueHandle_t g_xQueuePlatform; /* 挡球板队列 */
  1. 再创建队列
	/* 创建队列:平台任务从里面读到红外数据,... */
	 g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data));	// 队列有多少个数据(大概10个),每个数据有多大
	 // 这里为了精简代码,并没有判断返回值

在13_queue_game\nwatch\typedefs.h路径下定义结构体

struct input_data {
	uint32_t dev;	//设备
	uint32_t val	//值
};

3.2 读队列

3.2.1 方法

使用 xQueueReceive() 函数读队列,读到一个数据后,队列中该数据会被移除。这个函数有两个版本:在任务中使用、在ISR中使用。函数原型如下:

BaseType_t xQueueReceive( QueueHandle_t xQueue,
                          void * const pvBuffer,
                          TickType_t xTicksToWait );

BaseType_t xQueueReceiveFromISR(
                                    QueueHandle_t    xQueue,
                                    void             *pvBuffer,
                                    BaseType_t       *pxTaskWoken
                                );

参数说明如下:

参数说明
xQueue队列句柄,要读哪个队列
pvBufferbufer指针,队列的数据会被复制到这个buffer 复制多大的数据?在创建队列时已经指定了数据大小
xTicksToWait如果队列空则无法读出数据,可以让任务进入阻塞状态, xTicksToWait表示阻塞的最大时间(Tick Count)。 如果被设为0,无法读出数据时函数会立刻返回; 如果被设为portMAX_DELAY,则会一直阻塞直到有数据可写
返回值pdPASS:从队列读出数据入 errQUEUE_EMPTY:读取失败,因为队列空了。

3.2.2 实践

先在static void platform_task(void *params)中定义结构体

struct input_data idata;

然后再读队列

        /* 读取红外遥控器 */
		//if (0 == IRReceiver_Read(&dev, &data))
		if (pdPASS == xQueueReceive(g_xQueuePlatform, &idata, portMAX_DELAY))
		{
			data = idata.val;
			// …… 其他代码都一致

被注释掉的就是读取环形Buff,新的代码就是读队列!


3.3 写队列

3.3.1 方法

更新中…………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………

3.3.2 实践

代码路径:13_queue_game\Drivers\DshanMCU-F103\driver_ir_receiver.c
先声明外部变量

extern QueueHandle_t g_xQueuePlatform; /* 挡球板队列 */

修改后的代码如下:

void IRReceiver_IRQ_Callback(void)
{
    uint64_t time;
    static uint64_t pre_time = 0;
	  struct input_data idata;	// 定义

        
	/* 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);
			
            /* 写队列 */
			idata.dev = 0;
			idata.val = 0;
			xQueueSend(g_xQueuePlatform, &idata, 0);
			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 input_data idata;

	/* 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];
    xQueueSend(g_xQueuePlatform, &idata, 0);
    return 0;
}

编译运行,代码被卡死了,不能正常运行

……
……
……

写队列,我们是在中断里写的,要调用xQueueSendToBackFromISR函数,替换写队列的函数

xQueueSendToBackFromISR(g_xQueuePlatform, &idata, NULL);

开启的两个任务

  extern void PlayMusic(void *params);
  xTaskCreate(PlayMusic, "MusicTask", 128, NULL, osPriorityNormal, NULL);
  xTaskCreate(game1_task, "GameTask", 128, NULL, osPriorityNormal, NULL);

再次烧写运行,没有问题

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

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

相关文章

WPS真题题库导入刷题小程序:百思考个人使用经验分享

这篇文章的诞生&#xff0c;是因为我即将踏上一场超级有趣的挑战——备考全国计算机等级二级WPS Office高级应用与设计的冒险之旅&#xff01; WPS的分值&#xff1a; 单项选择题20分(含公共基础知识部分10分)。 WPS处理文字文档操作题30分。 WPS处理电子表格操作题30分。 …

Taro+Vue 创建微信小程序

TaroVue 创建微信小程序 一、全局安装 tarojs/cli二、项目初始化三、现在去启动项目吧 一、全局安装 tarojs/cli npm install -g tarojs/cli //安装 npm info tarojs/cli //查看安装信息 如果正常显示版本说明成功了&#xff0c;就直接跳到第二步吧官网说&#xff1a;…

如何快速自学开源项目?试试我的诀窍

大家好&#xff0c;我是程序员鱼皮。上周二晚上我直播带大家学习了一个 GitHub 上不错的开源聊天室项目 MallChat &#xff0c;大家表示学到了很多&#xff0c;所以我专门剪出了一期项目导学视频&#xff0c;分享给大家&#xff1a; https://www.bilibili.com/video/BV1Q142147…

Redis 高可用性如何实现?

Redis 高可用性如何实现&#xff1f; 1、主备切换2、哨兵集群 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; Redis&#xff0c;作为广泛使用的内存数据结构存储系统&#xff0c;通过一系列机制和策略&#xff0c;如主备切换、哨兵&#xf…

vue脚手架配置代理(解决跨域问题)

vue配置代理 一.介绍二.方法一三.方法二 一.介绍 跨域问题是指协议&#xff0c;主机&#xff0c;端口有一个以上不同 解决方法&#xff1a; 1&#xff0c;cors(最标准&#xff09; 2&#xff0c;jsonp&#xff08;script src&#xff09;&#xff08;比较巧妙&#xff09; 3&a…

使用MongoDB构建AI:Story Tools Studio将生成式AI引入Myth Maker AI游戏

Story Tools Studio利用先进的生成式AI技术&#xff0c;打造沉浸式、个性化、无穷尽的情景体验。 Story Tools Studio创始人兼首席执行官Roy Altman表示&#xff1a;“我们的旗舰游戏Myth Maker AI采用的是我们自主研发的、以AI为驱动的专家指导型故事生成器MUSE&#xff0c;它…

跨境电商新手必知:轻松解决商品详情图一键翻译难题!

在做跨境电商&#xff0c;商品详情图的翻译是至关重要的一环。 对于刚刚踏上跨境电商之旅的个人创业者来说&#xff0c;如何实现一键翻译商品详情图&#xff0c;可能是一个令人头疼的问题。 别担心&#xff0c;现在有许多实用的工具和方法可以帮助您轻松应对。 比如&#xff…

使用Windows11搭建代理服务器

一、问题引入二、下载并安装apache服务器三、配置Apache服务四、安装服务并测试五、文中提到的下载文件集合一、问题引入 使用ccproxy总是有很多限制,而且总是中断,因此就想自己用windows搭建一个。 二、下载并安装apache服务器 进入apache官网https://httpd.apache.org/do…

C/C++实现蓝屏2.0

&#x1f680;欢迎互三&#x1f449;&#xff1a;程序猿方梓燚 &#x1f48e;&#x1f48e; &#x1f680;关注博主&#xff0c;后期持续更新系列文章 &#x1f680;如果有错误感谢请大家批评指出&#xff0c;及时修改 &#x1f680;感谢大家点赞&#x1f44d;收藏⭐评论✍ 前…

在使用JMeter做负载测试遇到过的坑

1、本人做的是HTTP测试。遇到一个错误提示 415 not suported mediatype 注意&#xff0c;需要添加一个HTTP信息头管理信息&#xff0c;在运行的时候&#xff0c;请保持它启动是启用状态。 添加步骤如下图。 相关配置信息如下图。因为采用了JWTTOKEN进行权限验证&#xff0c…

SQL面试题练习 —— 有序行转列

目录 1 题目2 建表语句3 题解 1 题目 有学生各学科分数表&#xff0c;记录了学生的各科分数&#xff0c;请按照学生粒度&#xff0c;生成两列数据分别为学科和分数&#xff0c;要求学科内的顺序与分数顺序一致。 样例数据 期望结果 2 建表语句 --建表语句 create table if not…

偶然遇到了scanf输入字符时,前面与要加上空格

任务描述 本关任务&#xff1a;给定一个含有n个学生数据元素的数组a&#xff0c;用头插法来快速创建整个单链表。 相关知识 创建单链表有两种方法&#xff1a; 先初始化一个单链表&#xff0c;然后向其中一个一个地插入元素&#xff0c;通过调用基本运算算法来创建单链表。…

基于springboot-vue的毕业论文管理系统

TOC springboot251基于springboot-vue的毕业论文管理系统 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化…

海康VisionMaster使用学习笔记1-本地图像导入及参数设置

前言 原计划直接学习海康相机二次开发,发现先学习使用海康VisionMaster很有必要,与其他相机工具还有用法不太相同.开始记录一下海康VisionMaster使用学习笔记. 本地图像导入及参数设置 本地图像导入 在工具箱中找到采集 拖取图像源模块到流程编辑区域 双击图像源,选择图像源…

STM32cubeMX配置Systick的bug

STM32cubeMX版本&#xff1a;6.11.0 现象 STM32cubeMX配置Systick的时钟&#xff0c;不管选择不分频 还是8分频。 生成的代码都是一样的&#xff0c;代码都是不分频。 即不管选择不分频还是8分频&#xff0c;Systick都是使用的系统时钟 函数调用 HAL_Init() → HAL_Init…

【深度学习】单层神经网络

单层神经网络 神经元感知机 1943年&#xff0c;心理学家McCulloch和数学家Pitts共同发表了神经网络的开山之作A Logical Calculus of the Ideas Immanent in Nervours Activity1&#xff0c;提出了神经网络的第一个数学模型——MP模型。该模型也成为了人工神经网络的基础。 神经…

ELK学习笔记

ElasticStack分布式日志系统概述 Elasticsearch: 一个分布式搜索引擎&#xff0c;能够快速存储、搜索和分析大量数据。核心概念包括索引&#xff08;Index&#xff09;、文档&#xff08;Document&#xff09;和分片&#xff08;Shard&#xff09;。使用 RESTful API 进行数据操…

滑动窗口记录左右的最大值

前言&#xff1a;看到这个题目的时候分析了一下&#xff0c;就是最大值问题&#xff0c;但是要注意分类讨论 以后遇到离散化的问题&#xff0c;还可以开一个map来记录存在的点&#xff0c;免得二分查找的点不存在 #include<bits/stdc.h> using namespace std;const int …

欧科云链研究院对话:风浪越大鱼越贵—链上数据洞悉加密市场规律

作者 Hedy 出品 OKG Research “我们从来就不是理性人。但可以用最简单的工具——链上数据做‘最猛’的分析。” 在经历了超级宏观周之后&#xff0c;金融市场产生了巨大的震荡&#xff0c;加密市场的表现也越来越受到宏观经济因素的影响。欧科云链研究院OKG Research 集结多…

【多模态大模型】LLaMA in arXiv 2023

一、引言 论文&#xff1a; LLaMA: Open and Efficient Foundation Language Models 作者&#xff1a; Meta AI 代码&#xff1a; LLaMA 特点&#xff1a; 该方法在Transformer的基础上增加了Pre-normalization (RMSNorm)、SwiGLU activation function (SwiGLU)、Rotary Embed…