单片机开发---ESP32S3移植NES模拟器(二)
书接上文
《单片机开发—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_ENABLEDvoid 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根线有用。
七孔插头
还有一种
引脚 | 含义 |
---|---|
VCC | 5V供电 |
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很火,国内外各种模仿争相出现,国内的还是老样子,不该问的别问。救媳妇还是救妈妈,豆腐脑吃甜的还是辣的,是吧
反正豆腐脑我吃咸的。
相关文章:

单片机开发---ESP32S3移植NES模拟器(二)
书接上文 《单片机开发—ESP32-S3模块上手》 《单片机开发—ESP32S3移植lvgl触摸屏》 《单片机开发—ESP32S3移植NES模拟器(一)》 暖场视频,小时候称这个为—超级曲线射门!!!!!&am…...

微信小程序nodej‘s+vue警局便民服务管理系统
本文首先介绍了设计的背景与研究目的,其次介绍系统相关技术,重点叙述了系统功能分析以及详细设计,最后总结了系统的开发心得在Internet高速发展的今天,我们生活的各个领域都涉及到计算机的应用,其中包括“最多跑一次”微信小程序的网络应用,在外国小程序的使用已经是很普遍的方…...

第18章 MongoDB $type 操作符教程
MongoDB $type 操作符 描述 在本章节中,咱们将继续讨论MongoDB中条件操作符 $type。 $type操作符是基于BSON类型来检索集合中匹配的数据类型,并return 结果。 MongoDB 中可以使用的类型如下表所示: 类型数字备注Double1 String2 Object3…...

【MySQL主从复制】快速配置
本文配置环境Windows和Linux。 windows主 Linux 从 一、主库配置 首先保证Linux和防火墙开启3306端口或关闭防火墙。 登录Mysql管理员账户: GRANT REPLICATION slave,reload,super ON *.* TO root@从库ip地址 IDENtIFIED BY root; flush privileges; 本地的mysql可以被:…...

Typescript - interface 关键字(通俗易懂的详细教程)
前言 简单来说,Interface 就是一种描述对象或函数的东西。 您可以把 interface 理解为形状,真实开发情况下,一个对象需要有什么样的属性,函数需要什么参数或返回什么样的值,数组应该是什么样子的,一个类和继…...

【计组】内存和总线
课程链接:深入浅出计算机组成原理_组成原理_计算机基础-极客时间 一、虚拟内存和内存保护 日常使用的操作系统下,程序不能直接访问物理内存。内存需要被分成固定大小的页(Page),再通过虚拟内存地址(Virtu…...

CUDA中的数学方法
CUDA中的数学方法 文章目录CUDA中的数学方法1. Standard FunctionsSingle-Precision Floating-Point FunctionsDouble-Precision Floating-Point Functions2. Intrinsic FunctionsSingle-Precision Floating-Point FunctionsDouble-Precision Floating-Point Functions参考手册…...

Elasticsearch基本概念和索引原理
一、Elasticsearch是什么? Elasticsearch是一个基于文档的NoSQL数据库,是一个分布式、RESTful风格的搜索和数据分析引擎,同时也是Elastic Stack的核心,集中存储数据。Elasticsearch、Logstash、Kibana经常被用作日志分析系统&…...

《NFL橄榄球》:堪萨斯城酋长·橄榄1号位
堪萨斯城酋长队(Kansas City Chiefs)是位于密苏里州堪萨斯城的职业美式橄榄球队;目前在全国橄榄球联盟隶属于美国橄榄球联合会(AFC)西区;其夏季训练营在威斯康星大学河瀑校区举行。 酋长队的前身是达拉斯得州佬队,这支…...

python+django在线教学网上授课系统vue
随着科技的进步,互联网已经开始慢慢渗透到我们的生活和学习中,并且在各个领域占据着越来越重要的部分,很多传统的行业都将面临着巨大的挑战,包括学习也不例外。现在学习竞争越来越激烈,人才的需求量越来越大࿰…...

二叉搜索树之AVL树
AVL树的概念二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年 发明了一种解决上…...

全栈自动化测试技术笔记(二):准备工作的切入点
自动化测试技术笔记(二):准备工作的切入点 上篇整理的技术笔记,聊了自动化测试的前期调研工作如何开展,最后一部分也提到了工作的优先级区分。 这篇文章,接上篇文章的内容,来聊聊自动化测试前期的准备工作࿰…...

57 长短期记忆网络(LSTM)【动手学深度学习v2】
57 长短期记忆网络(LSTM)【动手学深度学习v2】 深度学习学习笔记 学习视频:https://www.bilibili.com/video/BV1JU4y1H7PC/?spm_id_fromautoNext&vd_source75dce036dc8244310435eaf03de4e330 长短期记忆网络(LSTM)…...
算法第十五期——动态规划(DP)之各种背包问题
目录 0、背包问题分类 1、 0/1背包简化版 【代码】 2、0/ 1背包的方案数 【思路】 【做法】 【代码】 空间优化1:交替滚动 空间优化2:自我滚动 3、完全背包 【思路】 【代码】 4、分组背包 核心代码 5、多重背包 多重背包解题思路1:转化…...

实现复选框全选和全不选的切换
今天,复看了一下JS的菜鸟教程,发现评论里面都是精华呀!! 看到函数这一节,发现就复选框的全选和全不选功能展开了讨论。我感觉挺有意思的,尝试实现了一下。 1. 全选、全不选,两个按钮ÿ…...

React hooks之useState用法(一)
系列文章目录 学习React已经有很长的一段时间了,今天决定重新回顾一下跟React相关的一些知识点 文章目录系列文章目录结构如下一、hooks是什么?useState可以能做什么二、如何使用useState()第一步:创建【函数组件&…...

spring的简单理解
目录 1 .ioc容器(控制反转) 2. Aop面向切面编程 3. 事务申明 4. 注解的方式启动 5. spring是什么与他的优势 6. 代理设计模式(比如aop) 7. springmvc中相应json数据 8. 使用lombok来进行对代码的简化 9. 使用logback记录…...

Docker调用Intel集显实现FFmpeg硬解码
文章目录Docker调用Intel集显实现FFmpeg硬解码参考FFmpeg 集成qsv方式一 容器完成所有步骤方式二 容器完成部分步骤方式三 dockerfile部署Docker调用Intel集显实现FFmpeg硬解码 参考 ffmpeg_qsv_docker拉取该镜像可以实现FFmpeg集成vaapi的硬加速,通过dockerfile文…...

端到端模型(end-to-end)与非端到端模型
一、端到端(end to end) 从输入端到输出端会得到一个预测结果,将预测结果和真实结果进行比较得到误差,将误差反向传播到网络的各个层之中,调整网络的权重和参数直到模型收敛或者达到预期的效果为止,中间所…...

uniApp封装一个滑块组件
最近 项目中有一个需求 PC端动态设计的表单 移动端要能渲染出来 那么 就要去找到对应的组件 而其中 没有的 就包括滑块 没有又能怎么办 只能自己封装一个 我们直接上代码 <template><view class"u-slider" tap"onClick" :class"[disabled…...

运动基元(二):贝塞尔曲线
贝塞尔曲线是我第一个深入接触并使用于路径规划的运动基元。N阶贝塞尔曲线具有很多优良的特性,例如端点性、N阶可导性、对称性、曲率连续性、凸包性、几何不变性、仿射不变性以及变差缩减性。本章主要介绍贝塞尔曲线用于运动基元时几个特别有用的特性。 一、贝塞尔曲线的定义 …...

Android 11.0 关于Launcher3中调用截图功能总是返回null的解决方案
1.1概述 在11.0的系统产品开发中,在某些时候需要调用截图接口来进行截屏功能实现,而在Launcher3中发现调用系统截屏接口SurfaceControl.screenshot进行截图的时候始终为null, 获取不到系统当前页面的截屏功能,所以需要找到当前截屏失败的原因然后来实现截屏功能的实现,下面来…...

random随机数
random随机数 1.概述 random用来生成一些随机数,下面介绍random模块提供的方法根据需求生成不同的随机数。 2.random常用操作 2.1.random默认随机数 random()函数返回一个随机的浮点值,默认返回值范围在0 < n < 1.0区间 import randomfor i …...

【金三银四系列】Spring面试题-上(2023版)
Spring面试专题 1.Spring应该很熟悉吧?来介绍下你的Spring的理解 有些同学可能会抢答,不熟悉!!! 好了,不开玩笑,面对这个问题我们应该怎么来回答呢?我们给大家梳理这个几个维度来回答 1.1 Spring的发展历程 先介绍…...

linux基本功系列之tar命令实战
文章目录前言一. tar命令介绍二. 语法格式及常用选项三. 参考案例3.1 仅打包不压缩3.2 打包后使用调用压缩命令进行压缩3.3 列出文件的内容3.4 追加文件到tar命令中3.5 释放文件到指定的目录四 . 各种压缩方式的比较总结前言 大家好,又见面了,我是沐风晓…...

Prometheus服务发现
Prometheus服务发现介绍 Prometheus默认是采用pull的方式拉取监控数据的,每一个被抓取的目标都要暴露一个HTTP接口,prometheus通过这个接口来获取相应的指标数据,这种方式需要由prometheus-server决定采集的目标服务器有哪些,通过…...

【Spring6源码・MVC】请求处理流程源码解析
上一篇《【Spring6源码・MVC】初始化registry,完成url和controller的映射关系》我们知道,在IOC容器加载的同时,初始化了registry这个HashMap,这个HashMap中存放了请求路径和对应的方法。当我们请求进来,会通过这个regi…...

elasticsearch term match 查询
1. 准备数据 PUT h1/doc/1 {"name": "rose","gender": "female","age": 18,"tags": ["白", "漂亮", "高"] }PUT h1/doc/2 {"name": "lila","gender&quo…...

canal使用说明:MySQL、Redis实时数据同步
1. canal简介 canal是阿里开源的数据同步工具,基于bin log可以将数据库同步到其他各类数据库中,目标数据库支持mysql,postgresql,oracle,redis,MQ,ES等 canal分成服务端deployer和客户端adapter,我们可以部署多个,同时为了方便管…...

计算机视觉框架OpenMMLab开源学习(三):图像分类实战
前言:本篇主要偏向图像分类实战部分,使用MMclassification工具进行代码应用,最后对水果分类进行实战演示,本次环境和代码配置部分省略,具体内容建议参考前一篇文章:计算机视觉框架OpenMMLab开源学习&#x…...