单片机开发---ESP32S3移植NES模拟器(二)

news2024/11/25 20:51:37

书接上文

《单片机开发—ESP32-S3模块上手》
《单片机开发—ESP32S3移植lvgl+触摸屏》
《单片机开发—ESP32S3移植NES模拟器(一)》

暖场视频,小时候称这个为—超级曲线射门!!!!!!!!!!

ESP32上天使之翼游戏

继续优化

看门狗

源码中有两处看门狗的喂狗操作,前期都被注释掉了。
在这里插入图片描述
因为开始经常出现看门狗报警的重启。然后我将看门狗都关闭之后就不再重启了
在这里插入图片描述
问题如果不再出现,那它还是问题吗
在这里插入图片描述

分区表

前面如果需要使用分区存储rom数据的时候,需要使用定制的分区表
在(Top) → Partition Table → Partition Table 配置下,选择第四项
在这里插入图片描述
根目录下放置文件,内容如下
在这里插入图片描述
如果直接用内存,就不需要修改这些。
如果有多个应用的话,可以在这里选择配置,从不同位置启动程序。

I2S声音输出

有了声音,才能更好的玩游戏
在这里插入图片描述

所以又斥资购买的外置模块,接线图如下

在这里插入图片描述
I2S有3个主要信号,各种叫法,反正就这个意思

各种昵称说明
SCLK 、BCLK串行时钟SCLK,也叫位时钟(BCLK),即对应数字音频的每一位数据,SCLK都有1个脉冲。SCLK的频率=2×采样频率×采样位数。
LRCK、LRC、WS帧时钟LRCK,(也称WS),用于切换左右声道的数据。LRCK为“1”表示正在传输的是右声道的数据,为“0”则表示正在传输的是左声道的数据。LRCK的频率等于采样频率。
SDATA、DIN串行数据SDATA,就是用二进制补码表示的音频数据。

增加了声音的驱动,将原来写在一起的部分分离开,方便以后移植。

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_log.h"
#include <math.h>
#include "drv_pin.h"
#include "drv_sound.h"

#if CONFIG_SOUND_ENABLED

void sound_init(void)
{
    i2s_config_t i2s_config = {
        .mode = I2S_MODE_MASTER | I2S_MODE_TX ,
        .sample_rate = AUDIO_SAMPLERATE,
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
        .communication_format = I2S_COMM_FORMAT_I2S_MSB,
        .dma_buf_count = 8,
        .dma_buf_len = 64,
        .use_apll = false,
        .intr_alloc_flags = ESP_INTR_FLAG_INTRDISABLED   //Interrupt level 1
    };
    i2s_pin_config_t pin_config = {
        .mck_io_num = I2S_PIN_NO_CHANGE,
        .bck_io_num = I2S_BCK_IO,
        .ws_io_num = I2S_WS_IO,
        .data_out_num = I2S_DO_IO,
        .data_in_num = I2S_DI_IO    //Not used
    };
    i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
    i2s_set_pin(I2S_NUM, &pin_config);
}

void sound_send(const void *src, size_t size, size_t *bytes_written, TickType_t ticks_to_wait)
{
	i2s_write(I2S_NUM, src, size, bytes_written, ticks_to_wait);
}
void sound_stop(void)
{
	i2s_stop(I2S_NUM);
}
void sound_clear(void)
{
	i2s_zero_dma_buffer(I2S_NUM);
}
#endif

用这些函数代替之前的操作。
不过为什么波特率配置为这个44.1k的一半,还不太清楚,后续可以研究一下。

按照这样配置的时候,会有很大的杂音。需要修改一下声道。

 .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,

这里也要注意,模块电压是5V,等我回去试试电压5V是不是更好一些。
在这里插入图片描述

手柄适配

声音有了,还是需要用手柄玩,更贴心。
在这里插入图片描述

常用的九孔插头,里面有5根线有用。
在这里插入图片描述
七孔插头
在这里插入图片描述
还有一种
在这里插入图片描述

引脚含义
VCC5V供电
GND地线
LATCH锁存信号,由主机发送
CLOCK时钟信号,有些文档会叫PULSE,由主机发送
DATA串行数据线 低电平有效。

时序图
在这里插入图片描述
先普及个基础知识。日版美版FC主机均为NTSC制式,画面为60Hz。欧版以及中国的仿制机为PAL-D制式,50Hz。港版正规机以及某些地区是PAL-60制式,60Hz。下面的说明都是基于60Hz来解释,50Hz和60Hz时间参数有点差异。

当游戏机启动后,游戏机会每16.67ms(60Hz,1/60秒)读取一次手柄的状态。这个过程通过两个步骤来实现。

首先主机发送一个LATCH锁存信号脉冲,这个脉冲的宽度为12us。告诉手柄开始检查按键状态。

在LATCH的脉冲发送后间隔6us,CLOCK(PULSE)线开始发送周期为12us,占空比50%的脉冲信号,一共发8次。每次的脉冲的上升沿对DATA线采样,检查DATA线是否在该位置被拉低。按键被检查的顺序是固定的(游戏机设计时候设计人员固定的),按键顺序为A,B,SEL,START,上下左右。上图DATA线上标注的就是每个按键时序所在位置。如果按键被按下,那么对于位置的DATA是低电平。

这里找到了一个原理图,感觉可以自己做一个了。
在这里插入图片描述
在这里插入图片描述

引脚初始化,一定要注意上拉和下拉的使用
在这里插入图片描述

读取代码如下,时间可以严格按照时序图中的要求来定义,记住在上升沿的时候,读取data值。

	int b2b1 = 65535;
	
	gpio_set_level(INPUT_HW_JS1_LATCH_PIN, 1);
    ets_delay_us(12);
    gpio_set_level(INPUT_HW_JS1_LATCH_PIN, 0);
    for(int i = 0; i < 8; i++)
    {
		ets_delay_us(6);
		if(gpio_get_level(INPUT_HW_JS1_DATA_PIN) == 0)
		{
			b2b1 -= sfc_ps_button_info[i];
			//printf("%s ",sfc_ps_button_va[i]);
		}
		
        gpio_set_level(INPUT_HW_JS1_CLOCK_PIN, 1);
		ets_delay_us(6);
        gpio_set_level(INPUT_HW_JS1_CLOCK_PIN, 0);
    }

一定要注意,这种手柄的电压,至少要达到4.8V,否则可能出现如下问题
1.延迟必须增大才能读取按键
2.在读取按键的时候,一次如果按下超过两个按键,就会识别为全部按下。

这也是我灵光一现,才破解了这个问题。
在这里插入图片描述

双手柄支持

这里需要重新增加一个手柄
在这里插入图片描述

void osd_getinput2(void)
{
	// Note: These are in the order of PSX controller bitmasks (see psxcontroller.c)
	const int ev[16] = 
	{
		event_joypad2_select, 
		0, 
		0, 
		event_joypad2_start, 
		event_joypad2_up, 
		event_joypad2_right, 
		event_joypad2_down, 
		event_joypad2_left,
		0, 
		0, 
		0, 
		0, 
		0, 
		event_joypad2_a, 
		event_joypad2_b, 
		0
	};
	
	static int oldb = 0xffff;
	int b = input2_read();
	int chg = b ^ oldb;
	int x;
	oldb = b;
	event_t evh;
	//	printf("Input: %x\n", b);
	for (x = 0; x < 16; x++)
	{
		if (chg & 1)
		{
			evh = event_get(ev[x]);
			if (evh)
				evh((b & 1) ? INP_STATE_BREAK : INP_STATE_MAKE);
		}
		chg >>= 1;
		b >>= 1;
	}
}

主要就是注意选择事件。不过改归改,还么测试
在这里插入图片描述

游戏名称

注意复制到SD卡中的游戏,名字不能过长,否则会出现死机的问题,导致重启。
另外可以增加如下判断,只显示rom名称,屏蔽其他文件
在这里插入图片描述
这个后续可以替换成其他界面,毕竟连汉字都不支持,低端
在这里插入图片描述

游戏兼容性

测试了一些过关游戏,基本都可以,不过在测试一些智能卡的游戏的时候,会出现重启现象,打印输出

GUI: Mapper 74 not yet implemented

因为本身模拟器支持的mapper有限,并没有支持到74号,这个游戏就是《天使之翼》,
还有164号mapper,游戏是《三国志2》。

后续一定要解决这个问题,加上mapper。
至于这个mapper是什么

mapper,这个概念来源于 memory mapping,又叫做 Memory Management Chip,它是解决地址映射的一种电路,简单来说就是决定物理内存如何映射到 CPU 或者 PPU 的地址空间。
mapper 可以用来支持增加卡带的 RAM 甚至支持额外的音频通道,但更一般的目的就是控制物理内存到地址空间的映射,突破游戏 40KB 的限制。
为什么说是 40KB 的限制,因为早期一般的游戏最大就是 的 PRG,以及 的 CHR,加起来就是 40KB,而更复杂的 mapper 硬件可以使得游戏突破这个限制。

软件重启

增加了手柄远端重启机器,其实就是在按键的时候判断一下,如果同时按下select和start,重启设备
在这里插入图片描述
这样测试就比较方便了。

效果展示

冒险岛系列
在这里插入图片描述
绿色兵团
在这里插入图片描述

热血系列,这么激烈打斗的游戏,非常流畅。在这里插入图片描述
快打旋风
在这里插入图片描述
激龟忍者传视频

ESP32S3-nes上的《激龟忍者传3》

参考资料

《FC游戏机手柄工作原理 》
《小霸王游戏机手柄(一)——硬件破解》
《NES 模拟器开发教程》
《童年神机小霸王(七) Mapper》
这篇文章的作者写了几篇相关的介绍,感兴趣的可以学习一下。
在这里插入图片描述

结束语

这个83年推出的产品,到现在快四十年了,承载了无数80后的儿童时光,几年玩的游戏加起来,估计也没有几十兆的空间,里面的技术可想而知,把硬件软件的性能压榨到了极点了。
在这里插入图片描述

最近这chatGPT很火,国内外各种模仿争相出现,国内的还是老样子,不该问的别问。救媳妇还是救妈妈,豆腐脑吃甜的还是辣的,是吧
在这里插入图片描述

反正豆腐脑我吃咸的。
在这里插入图片描述

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

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

相关文章

Linux 安装jenkins和jdk11

Linux 安装jenkins和jdk111. Install Jdk112. Jenkins Install2.1 Install Jenkins2.2 Start2.3 Error3.Awakening1.1 Big Data -- Postgres4. Awakening1. Install Jdk11 安装jdk11 sudo yum install fontconfig java-11-openjdk 2. Jenkins Install 2.1 Install Jenkins 下…

八、Linux文件 - 文件IO与标准IO的区别

目录 1.Linux标准文件描述符 2.缓存的概念 1.Linux标准文件描述符 文件描述符缩写描述0STDIO标准输入1STDOUT标准输出2STDERR标准错误输出文件IO&#xff1a;是直接调用内核提供的系统调用函数&#xff0c;头文件时unistd.h标准IO&#xff1a;是间接调用系统调用函数&#x…

C语言经典编程题100例(21-40)

21、练习3-2 计算符号函数的值对于任一整数n&#xff0c;符号函数sign(n)的定义如下&#xff1a;请编写程序计算该函数对任一输入整数的值。输入格式:输入在一行中给出整数n。输出格式:在一行中按照格式“sign(n) 函数值”输出该整数n对应的函数值。输入样例1:10输出样例1:sig…

LeetCode刷题系列 -- 54. 螺旋矩阵

给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。示例 1&#xff1a;输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]]输出&#xff1a;[1,2,3,6,9,8,7,4,5]示例 2&#xff1a;输入&#xff1a;matrix [[1,2,3,4],[…

Nextcloud通过不被信任的域名访问解决方法 Nextcloud 您正在访问来自不信任域名的服务器

windows电脑在网页端输入“http://192.168.xxx.xxx:8080/login”访问远程ubuntu18.04服务器&#xff0c;访问其docker镜像的Nextcloud&#xff0c;提示“”Nextcloud通过不被信任的域名访问解决方法 Nextcloud 您正在访问来自不信任域名的服务器“”&#xff0c;如下图&#xf…

Jetpack Compose中的绘制流程和自定义布局

Jetpack Compose中绘制流程的三个阶段 与大多数其他界面工具包一样&#xff0c;Compose 会通过几个不同的“阶段”来渲染帧。如果我们观察一下 Android View 系统&#xff0c;就会发现它有 3 个主要阶段&#xff1a;测量、布局和绘制。Compose 和它非常相似&#xff0c;但开头…

2023年java面试题之kafka(5道)

一. kafka介绍&#xff1a; 1.消息分类按不同类别,分成不同的Topic,Topic⼜拆分成多个partition,每个partition均衡分散到不同的服务器(提⾼并发访问的能⼒) 2.消费者按顺序从partition中读取,不⽀持随机读取数据,但可通过改变保存到zookeeper中的offset位置实现从任意位置开…

golang 优雅关闭服务

文章目录一、什么是优雅关闭服务二、使用函数三、使用案例一、什么是优雅关闭服务 先把在执行的任务执行完成&#xff0c;再关闭服务&#xff0c;防止中断服务造成程序错误&#xff0c;数据异常等影响。 二、使用函数 语法&#xff1a; Shutdown(ctx context.Context) error作…

深度学习之循环神经网络(RNN)实现股票预测

深度学习训练营之循环神经网络&#xff08;RNN&#xff09;实现股票预测原文链接环境介绍前置工作设置GPU数据加载划分数据集模型训练数据预处理归一化对样本进行构建构建模型激活模型对模型进行训练结果可视化预测模型评估原文链接 &#x1f368; 本文为&#x1f517;365天深度…

【Python案例实战】爬虫能做哪些很酷很有趣很有用的事情?爱了爱了,简直是神仙代码~(淘宝秒杀、VIP视频解析、wiwi破解等)

前言 &#x1f680; 作者 &#xff1a;“程序员梨子” &#x1f680; **文章简介 **&#xff1a;本篇文章主要是写了opencv的人脸检测、猫脸检测小程序。 &#x1f680; **文章源码免费获取 &#xff1a; 为了感谢每一个关注我的小可爱&#x1f493;每篇文章的项目源码都是无 偿…

Spring Cloud组件

1.服务治理 Spring Cloud Eureka 概念 Eureka提供了服务端组件&#xff0c;我们也称为注册中心。每个服务都向Eureka的服务注册中心&#xff0c;登记自己提供服务的元数据&#xff0c;包括服务的ip地址、端口号、版本号、通信协议等。 原理 服务注册中心&#xff0c;还会以心跳…

【DOCKER】容器概念基础

文章目录1.容器1.概念2.特点3.与虚拟机的对比2.docker1.概念2.命名空间3.核心概念3.命令1.镜像命令2.仓库命令1.容器 1.概念 1.不同的运行环境&#xff0c;底层架构是不同的&#xff0c;这就会导致测试环境运行好好的应用&#xff0c;到了生产环境就会出现bug&#xff08;就像…

DevOps在项目交付场景下的应用

DevOps介绍 DevOps一词是由development和operation两个单词组合而来&#xff0c;代表着研发和交付运营的一体化。DevOps在2009年就被提出&#xff0c;但在学术界和工业界还没有一个广泛认可的定义&#xff0c;一些有代表性的总结&#xff0c;比如John Willis从文化、自动化、度…

策略编制解决方案

策略编制 NetIQ 使您能够将 AD 策略扩展到整个 IT 生态系统&#xff0c;并集中管理和监控策略与配置更改。 一、优点 提高 IT 管理员效率 集中管理策略控制。 借助可见性降低风险 借助实时监控减少漏洞。 审计和合规性报告 采取措施以满足安全条例和策略的合规要求。 二、…

应用部署初探:微服务的3大部署模式

在之前的文章中&#xff0c;我们已经充分了解了应用部署的4种常见模式&#xff08;金丝雀部署、蓝绿部署、滚动部署及影子部署&#xff09;。随着云原生技术逐步成熟&#xff0c;企业追求更为灵活和可扩展的系统&#xff0c;微服务架构大行其道。 微服务固然有诸多优点&…

计算组合数Cnk即从n个不同数中选出k个不同数共有多少种方法math.comb(n,k)

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 计算组合数Cnk 即从n个不同数中选出k个不同数共有多少种方法 math.comb(n,k) 以下python代码输出结果是? import math print("【执行】print(math.comb(3,1))") print(math.comb(…

一文了解kafka消息队列,实现kafka的生产者(Producer)和消费者(Consumer)的代码,消息的持久化和消息的同步发送和异步发送

文章目录1. kafka的介绍1.2 Kafka适合的应用场景1.2 Kafka的四个核心API2. 代码实现kafka的生产者和消费者2.1 引入加入jar包2.2 生产者代码2.3 消费者代码2.4 介绍kafka生产者和消费者模式3. 消息持久化4. 消息的同步和异步发送5. 参考文档1. kafka的介绍 最近在学习kafka相关…

Ubuntu20.04+cuda11.2+cudnn8.1+Anaconda3安装tensorflow-GPU环境,亲测可用

(1)安装nvidia显卡驱动注意Ubuntu20.04和Ubuntu16.04版本的安装方法不同,安装驱动前一定要更新软件列表和安装必要软件、依赖&#xff08;必须&#xff09;sudo apt-get update #更新软件列表sudo apt-get install gsudo apt-get install gccsudo apt-get install make查看GP…

4.5.1 泛型

文章目录1.概述2.泛型的具体表现形式3.泛型的作用4.泛型示例5.练习&#xff1a;泛型测试一6.练习:泛型测试二1.概述 泛型不是指一种具体的类型&#xff0c;而是说&#xff0c;这里有个类型需要设置&#xff0c;那么具体设置成什么类型&#xff0c;得看具体的使用&#xff1b; …

RabbitMQ-持久化

一、介绍如何保证RabbitMQ服务停掉以后生产者发送过来的消息不丢失。默认情况下RabbitMQ退出或由于某种原因崩溃时&#xff0c;他将忽视队列和消息&#xff0c;除非告知它不要这样做。确保消息不丢失需要做两件事情&#xff1a;将队列和消息都标记为持久化二、队列持久化再声明…