编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

做了个ESP32游戏机,也是个手柄!DIY一个很轻松的

wxchong 2024-07-06 00:42:54 开源技术 37 ℃ 0 评论

前言

自制了一个ESP32游戏机,可以随手放兜里,想玩就玩!

最实用的地方是,它还可以作为手柄使用。连接电视、电脑、手机都可以。

本文就记录一下它的软硬件设计思路。

看看它是如何从0到1实现一个成品的!有些什么技巧?

看过的网友都表示——“学习过游戏机的源代码后,写程序都简单了!”


电路设计

由于游戏机比较小,所以电路也尽量设计得比较简单,重点都在软件。

另外元件都是0603,如图所示,铁板烧就能焊!

编译

RachelSDKPIO 工程, VS Code 下载 PlatformIO 插件,用 VS Code 打开文件夹即可。

SDK 目录树

.
├── apps
│   ├── app_ble_gamepad               BLE 手柄
│   ├── app_music                     音乐播放器
│   ├── app_nofrendo                  NES 模拟器
│   ├── app_raylib_games              Raylib 游戏
│   ├── app_screencast                WiFi 投屏
│   ├── app_settings                  设置
│   ├── app_genshin                   __,__!
│   ├── app_template                  App 模板
│   ├── launcher                      启动器
│   ├── utils                         通用组件库
│   ├── assets                        公共资源
│   ├── tools                         App 相关工具(脚本)
│   └── apps.h                        App 安装回调
├── hal
│   ├── hal.cpp                       HAL 基类
│   ├── hal.h                         HAL 基类
│   ├── hal_rachel                    HAL Rachel 派生类
│   ├── hal_simulator                 HAL PC 模拟器派生类
│   └── lgfx_fx                       lgfx 派生类(拓展图形API)
├── rachel.cpp
└── rachel.h                          RachelSDK 入口

SD卡目录树

NES 模拟器、音乐播放器等会尝试加载SD卡里指定目录的资源文件。

.
├── buzz_music                        蜂鸣器音乐
│   ├── harrypotter.json
│   ├── nokia.json
│   ...
├── fonts                             字体
│   └── font_text_24.vlw
└── nes_roms                          NES ROM 文件
    ├── Kirby's Adventure (E).nes
    ├── Snow Bros (U).nes
	...

font_text_24.vlw 这个字体我用的是Zpix很嗨好看,可以替换任何自己喜欢的。

NES ROM 直接丢进去就行,不是很大的应该都能玩。

SDK 结构


创建 App

自动创建

写了个 python 脚本用来简化 App 创建:

python3 ./src/rachel/apps/tools/app_generator.py

$ Rachel app generator > <

$ app name:

hello_world

$ file names:

$ - ../app_hello_world/app_hello_world.cpp

$ - ../app_hello_world/app_hello_world.h

$ app class name: AppHello_world

$ install app hello_world

$ done

App 创建好了, 重新编译上传:

新创建的 App 基本模板如下,详细的生命周期和API可以参考 Mooncake 项目。

// Like setup()...void AppTemplate::onResume()
{
    spdlog::info("{} 启动", getAppName());
}// Like loop()...void AppTemplate::onRunning()
{    
    spdlog::info("咩啊");
    HAL::Delay(1000);

    _data.count++;    if (_data.count > 5)
        destroyApp();
}

Mooncake 框架内部集成了 spdlog 日志库,当然你也可以继续用 cout, printf, Serial...

手动创建


常用的 App API

destroyApp()

关闭 App,调用后会告诉框架你不玩了,框架会把你的 App 销毁释放,所以在 onRunning() 被阻塞的情况下是无效的。

// 有效void AppTemplate::onRunning()
{
    destroyApp();
}// 无效void AppTemplate::onRunning()
{    
    destroyApp();
    HAL::Delay(66666666666);
}

getAppName()

获取 App 名字,会返回你设置的 App 名字。

// 你的 App 头文件里:class AppHello_world_Packer : public APP_PACKER_BASE
{    // 这里修改你的 App 名字:
    std::string getAppName() override { return "文明讲礼外乡人"; }
    ...
}

getAppIcon()

获取 App 图标,启动器在渲染画面时会调用。

// 你的 App 头文件里:class AppHello_world_Packer : public APP_PACKER_BASE
{
    ...    // 这里修改你的 App 图标(有默认图标)
    void* getAppIcon() override { return (void*)image_data_icon_app_default; }
    ...
}

mcAppGetDatabase()

获取数据库实例,是一个简单的 RAMKV 数据库,可以用于 App 退出数据保存、多 App 间的数据共享(当然断电没)。

void AppTemplate::onResume()
{    // 看看数据库里有没有这个 key
    if (mcAppGetDatabase()->Exist("开了?"))
    {        // 数据库里拿出来, 看看开了几次
        int how_many = mcAppGetDatabase()->Get("开了?")->value();
        spdlog::info("开了 {} 次", how_many);		
        // 加上这一次, 写进数据库
        how_many++;
        mcAppGetDatabase()->Put("开了?", how_many);
    }    // 没有就创建一个
    else
        mcAppGetDatabase()->Add("开了?", 1);
}

mcAppGetFramework()

获取 Mooncake 框架实例,一般用来写启动器。

// 看看安装了几个 Appauto installed_app_num = mcAppGetFramework()->getAppRegister().getInstalledAppNum();
spdlog::info("安装了 {} 个 App", installed_app_num);// 看看他们都叫什么for (const auto& app_packer : mcAppGetFramework()->getAppRegister().getInstalledAppList())
{
    spdlog::info("{}", app_packer->getAppName());
}


HAL 硬件抽象层

HAL为单例模式,SDK初始化时会注入一个HAL实例。

  • 对于 HAL Rachel ,按住 按键A 开机,会暂停在初始化界面,可以查看详细的HAL初始化log。
  • 如果有不同底层硬件需求,只需派生新的HAL对象,重写 API 方法 (override) 并在初始化时注入即可。

Include

#include "{path to}/hal/hal.h"

显示 API

显示驱动使用 LovyanGFX。

// 获取屏幕驱动实例HAL::GetDisplay();// 获取全屏Buffer实例HAL::GetCanvas();// 推送全屏buffer到显示屏HAL::CanvasUpdate();// 渲染FPS面板HA::RenderFpsPanel();

系统 API

HAL Rachel 在初始化时会以RTC时间调整系统时间,所以时间相关的POSIX标准API都可以正常使用。

// 延时(毫秒)HAL::Delay(unsigned long milliseconds);// 获取系统运行毫秒数HAL::Millis();// 关机HAL::PowerOff();// 重启HAL::Reboot();// 设置RTC时间HAL::SetSystemTime(tm dateTime);// 获取当前时间HAL::GetLocalTime();// 优雅地抛个蓝屏HAL::PopFatalError(std::string msg);

外设 API

// 刷新IMU数据HAL::UpdateImuData();// 获取IMU数据HAL::GetImuData();// 蜂鸣器开始哔哔HAL::Beep(float frequency, uint32_t duration);// 蜂鸣器别叫了HAL::BeepStop();// 检查SD卡是否可用HAL::CheckSdCard();// 获取按键状态HAL::GetButton(GAMEPAD::GamePadButton_t button);// 获取任意按键状态HAL::GetAnyButton();

系统配置 API

// 从内部FS导入系统配置HAL::LoadSystemConfig();// 保存系统配置到内部FSHAL::SaveSystemConfig();// 获取系统配置HAL::GetSystemConfig();// 设置系统配置HAL::SetSystemConfig(CONFIG::SystemConfig_t cfg);// 以系统配置刷新设备HAL::UpdateSystemFromConfig();


通用组件库

一些比较有用的通用封装库放在了这里 rachel/apps/utils/system

选择菜单

创建一个选择菜单。

Include

#include "{path to}/utils/system/ui/ui.h"

Example

using namespace SYSTEM::UI;// 创建选择菜单auto select_menu = SelectMenu();// 创建选项列表std::vector items = {    "[WHAT 7 TO PLAY]",    "Jenshin Import",    "Light Soul",    "Grand Cop Manual",    "Super Maliao",    "Quit"};// 等待选择auto selected_index = select_menu.waitResult(items);
spdlog::info("selected: {}", items[selected_index]);

进度条窗口

创建一个带有进度条的窗口(u1s1, 现在应该算是页面)。

Include

#include "{path to}/utils/system/ui/ui.h"

Example

using namespace SYSTEM::UI;for (int i = 0; i < 100; i++)
{
    ProgressWindow("正在检测智商..", i);
    HAL::CanvasUpdate();
    HAL::Delay(20);
}

蜂鸣器音乐播放器

参考 arduino-songs 的 json 格式蜂鸣器音乐播放器。

Include

#include "{path to}/utils/system/audio/audio.h"

Example

using namespace SYSTEM::AUDIO;// 播放SD路径上的json音乐文件BuzzMusicPlayer::playFromSdCard("/buzz_music/nokia.json");

Include

#include "{path to}/utils/system/inputs/inputs.h"

Example

using namespace SYSTEM::INPUTS;auto button_a = Button(GAMEPAD::BTN_A);while (1)
{    if (button_a.pressed())
        spdlog::info("button a was pressed");    if (button_a.released())
        spdlog::info("button a was released");    if (button_a.toggled())
        spdlog::info("button a was toggled");
    HAL::Delay(20);
}


深入

需要源码,或更深入的了解具体框架实现方式,可以自行去文末参考资料处查看文献~都开源了的。

也欢迎收藏本文,保姆级教学,不愁学不到真技术!


参考资料:

[1]https://oshwhub.com/eedadada/mason

— 完 —

嘉立创EDA·头条号

关注我,看一手优质开源项目

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表