1. 项目概述:当复古掌机遇上现代无线操控
如果你和我一样,对Arduboy那个小巧精致的开源掌机念念不忘,同时又对ESP32强大的无线连接能力着迷,那么“Arduboy TV on ESP32 with PS3 Remote Control”这个项目绝对会让你眼前一亮。简单来说,这是一个将经典的Arduboy游戏引擎移植到ESP32微控制器上,并通过HDMI输出到电视或显示器,最终用一只闲置的PS3手柄进行无线操控的“魔改”项目。它本质上是一个自制的、基于电视输出的复古游戏主机,核心体验是把那些像素风小游戏从2.5英寸的OLED屏幕搬到了大屏幕上,并用更舒适的游戏手柄来玩。
这个项目的魅力在于它的“混搭”与“再造”。Arduboy本身基于Arduino,游戏以C++编写,资源占用极低;ESP32则提供了Wi-Fi、蓝牙、充足的内存和强大的处理能力,足以流畅模拟Arduboy环境并处理视频输出;而PS3手柄通过蓝牙连接,提供了成熟、低延迟且手感优秀的操控方案。最终,你得到的不再是一个揣在口袋里的迷你玩具,而是一个可以连接客厅电视、与朋友分享的怀旧游戏站。它适合那些喜欢硬件DIY、对复古游戏有情怀、并且希望将手中闲置开发板和配件“变废为宝”的创客和玩家。整个过程涉及固件移植、显示驱动适配、蓝牙协议破解和输入映射,充满了动手的乐趣和解决问题的成就感。
2. 核心方案设计与硬件选型解析
2.1 为什么是ESP32?核心平台的优势与考量
选择ESP32作为本项目的主控芯片,绝非偶然,而是经过多重因素权衡后的最优解。首先,性能足够。ESP32系列(尤其是ESP32-S3)主频可达240MHz,拥有520KB SRAM和外部PSRAM支持,其计算能力远超Arduboy原装的ATmega32u4(16MHz, 2.5KB SRAM)。运行Arduboy游戏这种级别的代码绰绰有余,同时还留有充足余量来处理视频编码和蓝牙协议栈。
其次,无线连接是刚需。本项目需要稳定的蓝牙连接来配对PS3手柄。ESP32集成了经典的蓝牙4.2 BR/EDR和BLE,其蓝牙堆栈成熟稳定,社区支持完善,有大量关于连接PS3、PS4甚至Switch手柄的开源代码和库可供参考,极大降低了开发门槛。
第三,视频输出能力。这是将游戏“搬上电视”的关键。许多ESP32开发板(如ESP32-S3-USB-OTG、M5Stack系列特定型号)或者通过附加模块(如I2S音频+RGB565转HDMI芯片)支持Bit-Bang模拟VGA或通过I2S接口驱动RGB LCD,进而可以转换或直接输出视频信号。虽然ESP32原生没有HDMI控制器,但通过软件模拟和外部DAC芯片组合,实现640x480@60Hz的显示输出是完全可行的,这正好匹配了大多数老式游戏和Arduboy游戏放大后的显示需求。
最后,生态与成本。ESP32拥有极其庞大的Arduino和ESP-IDF开发生态,资料丰富,价格低廉。一个NodeMCU-32S开发板仅需二三十元,使得项目成本可控。相比之下,使用树莓派Zero等Linux板卡虽然更简单,但成本、功耗和启动速度都处于劣势,失去了微型嵌入式设备的那种“即开即玩”的纯粹感。
注意:并非所有ESP32开发板都方便进行视频输出。在选择硬件时,务必确认板子是否有足够的空闲GPIO用于模拟视频信号(通常需要8-16个并口数据线+同步信号),或者是否集成了专用的LCD接口(如ESP32-S3-LCD-EV-Board)。对于新手,建议从集成度较高的板卡开始,避免在硬件层耗费过多精力。
2.2 PS3手柄作为输入设备的可行性分析
使用PS3手柄(DualShock 3)作为遥控器,是一个兼顾了手感、易得性和技术可行性的选择。从技术层面看,PS3手柄通过蓝牙与主机配对,通信协议虽然并非完全公开,但早已被开源社区(如ps3-arduino库、ESP32-BT-Gamepad库)逆向工程并封装成库。ESP32可以模拟成PS3主机,向手柄发送连接请求,并解析手柄返回的蓝牙HID数据包,从而获取按键、摇杆和六轴传感器的数据。
其优势很明显:手感优秀,远超大多数廉价的蓝牙手柄;存量巨大,很多人家里都有闲置的PS3手柄;功能齐全,提供了双摇杆、方向键、四个肩键、四个功能键以及开始选择键,完全覆盖甚至超越了Arduboy游戏所需(通常只需要方向键和A/B两个键)。此外,PS3手柄的蓝牙连接相对稳定,功耗也控制得不错。
当然,也有挑战。最主要的挑战是配对过程。PS3手柄与原生主机的配对依赖于一个蓝牙MAC地址和链路密钥的交换过程。在ESP32上,我们需要事先知道手柄的蓝牙MAC地址,并在代码中将其设置为“主机”地址,同时模拟整个配对流程。这通常需要一次性的“有线配对”步骤:先用USB线将手柄连接到ESP32(如果ESP32板子有USB Host功能)或一台电脑,通过一个辅助程序获取手柄的MAC地址和链路密钥,然后将其硬编码到ESP32的固件中。之后,手柄就可以通过蓝牙正常连接了。这个过程稍显繁琐,但一劳永逸。
2.3 显示输出方案:从Bit-Bang到专用芯片
将ESP32的图形数据输出到大屏幕,是本项目的视觉核心。这里主要有两种技术路径,各有优劣。
方案一:软件Bit-Bang模拟VGA。这是最“硬核”也最灵活的方法。原理是利用ESP32的高速GPIO,通过精确的时序控制,直接模拟出VGA信号所需的行同步(HSync)、场同步(VSync)和RGB色彩数据。你需要将8根GPIO(用于R、G、B各3位,共9位,常用8位256色)配置为输出,并编写一个高优先级的定时器中断服务程序,在固定的时间点(如每行31.77us for 640x480@60Hz)切换这些GPIO的电平,从而“画”出每一帧图像。
- 优点:成本极低,只需要几个电阻进行电平转换(ESP32是3.3V,VGA需要0.7Vpp)即可连接到VGA接口。对内存需求灵活,可以实现多种分辨率。
- 缺点:极度消耗CPU资源。生成VGA信号本身就是一个不间断的、高精度的实时任务,会占用几乎一个CPU核心的全部算力。留给游戏逻辑和蓝牙处理的时间就非常紧张了,可能导致游戏卡顿或蓝牙连接不稳定。此外,实现和调试时序非常复杂,对新手不友好。
方案二:使用专用LCD驱动芯片或模块。这是更推荐、更稳定的方案。ESP32通过I2S或SPI接口,将帧缓冲区中的数据以像素流的形式发送给外部的LCD驱动芯片(如ILI9341、ST7789等),这些芯片再负责生成标准的RGB或LVDS信号。为了输出到电视,我们可以选择带有HDMI输出的LCD驱动板,或者使用一个RGB565转HDMI的转换板(小芯片方案)。
- 优点:解放CPU。图形数据的搬运由DMA(直接内存访问)和专用硬件完成,ESP32的CPU只需更新帧缓冲区中的像素值即可,负担大大减轻。稳定性高,信号质量好。开发简单,通常有成熟的库(如
TFT_eSPI)支持,只需配置引脚和初始化参数。 - 缺点:需要额外的硬件成本,并且需要仔细阅读芯片和转换板的说明书,进行正确的连接和配置。
对于本项目的Arduboy游戏(128x64分辨率放大显示),方案二显然是更优的选择。我们可以选择一个支持RGB接口并自带HDMI输出的LCD驱动板,或者采用“ESP32 + RGB转HDMI芯片(如TFP401)”的组合。在软件上,我们可以在内存中开辟一个虚拟的128x64的缓冲区(对应Arduboy原生分辨率),游戏引擎只更新这个缓冲区。然后,通过一个缩放算法(如最近邻插值),将这个小缓冲区放大到目标分辨率(如640x480),再填充到实际的显示缓冲区中,由驱动芯片输出到HDMI。
3. 软件架构与核心代码实现
3.1 Arduboy游戏引擎的移植与适配
Arduboy游戏本质上是为ATmega32u4芯片和特定硬件(SSD1306 OLED,特定按键引脚)编写的Arduino程序。将其移植到ESP32,并非直接编译那么简单,而是一个“硬件抽象层(HAL)”的重写过程。
首先,我们需要一个ESP32兼容的Arduboy核心库。幸运的是,开源社区已有类似项目(如Arduboy2库的ESP32分支)。如果没有现成的,我们需要自己创建一个。核心工作是实现以下几个关键类:
Arduboy2Base类:这是所有功能的基类。我们需要重写其初始化函数,将原本针对AVR的端口操作,改为ESP32的digitalWrite/pinMode。但更重要的是,要替换掉其中对SPI或I2C传输OLED数据的部分。因为我们现在不驱动OLED,而是写入一个内存中的帧缓冲区。- 显示驱动:创建一个名为
Display的类,内部维护一个uint8_t数组作为帧缓冲区(sBuffer),大小是128 * 64 / 8 = 1024字节。Arduboy原生的drawPixel、drawLine、drawBitmap等函数,最终都会操作这个缓冲区。然后,我们需要提供一个display()函数。这个函数在原生库中会通过I2C将sBuffer发送给OLED。在我们的移植版中,display()函数需要做两件事:a) 将sBuffer中的1位像素数据,转换为目标显示缓冲区(比如640x480的16位RGB565缓冲区)的像素值;b) 触发一次屏幕更新(例如,通知DMA开始传输)。 - 输入系统:重写
buttonsState()函数。原本这个函数直接读取几个特定GPIO的电平来判断按键。现在,我们需要将其改为从一个全局变量中读取状态,而这个全局变量由我们的PS3手柄蓝牙任务来更新。例如,当蓝牙任务解析到手柄的“上”键被按下,就将一个代表“UP_BUTTON”的位设置为1。buttonsState()函数只需返回这个全局变量即可。 - 系统函数:如
delay()、millis()等,可以直接使用Arduino for ESP32核心提供的版本,它们是兼容的。
一个简化的核心适配代码框架如下:
// 自定义的显示驱动类 class ESP32Display : public Arduboy2Base { protected: uint8_t sBuffer[1024]; // 继承自基类,或自己声明 uint16_t tvBuffer[640 * 480]; // 用于HDMI输出的RGB565缓冲区 public: void begin() { // 初始化TV输出硬件(如I2S LCD驱动) tft_init(); // 清空缓冲区 clear(); } void display() { // 将1bpp的sBuffer放大并转换为RGB565,填充到tvBuffer scaleAndConvertToRGB565(sBuffer, tvBuffer, 128, 64, 640, 480); // 将tvBuffer通过DMA发送到显示设备 tft_push_frame(tvBuffer); } // 重写绘图函数(如果需要,这里可以优化性能) void drawPixel(int16_t x, int16_t y, uint8_t color) { // ... 原有的位操作逻辑,操作sBuffer ... } }; // 全局输入状态变量 uint8_t gButtonState = 0; #define UP_BUTTON_BIT 0x01 #define DOWN_BUTTON_BIT 0x02 // ... 其他按键位定义 uint8_t buttonsState() { // 直接返回由蓝牙任务更新的全局状态 return gButtonState; }3.2 PS3手柄蓝牙连接与数据解析
实现PS3手柄的连接,我们可以借助现有的库,例如ESP32-BT-Gamepad库。但为了更深入理解,我们剖析一下关键步骤。
第一步:获取手柄MAC地址并配对。这是最棘手的一步。通常需要一个一次性步骤:
- 使用USB线将PS3手柄连接到电脑。
- 在电脑上运行一个工具(如
sixaxispairer或ds3pair),它会读取手柄的蓝牙MAC地址和生成链路密钥。 - 将这个MAC地址(如
01:23:45:67:89:ab)硬编码到ESP32的代码中。
第二步:ESP32模拟主机并初始化蓝牙。我们需要以经典蓝牙(BR/EDR)模式启动,并设置本地设备的名称和可被发现模式。
#include <BluetoothSerial.h> BluetoothSerial SerialBT; void initBluetooth() { if (!SerialBT.begin("ESP32-ArduboyTV", true)) { // 设备名称, true表示主模式 Serial.println("蓝牙初始化失败!"); while(1); } SerialBT.setPin("1234"); // 可选的配对码 Serial.println("蓝牙已启动,正在等待配对..."); }第三步:建立连接并解析HID报告。我们需要主动连接手柄,并设置一个回调函数来处理接收到的数据包。PS3手柄的数据报告通常是48字节或49字节长的HID报告。
// 假设我们已经知道了手柄的MAC地址 const char *ps3Addr = "01:23:45:67:89:ab"; void connectToPS3() { // 注意:实际库函数可能不同,这里展示逻辑 if (SerialBT.connect(ps3Addr)) { Serial.println("成功连接到PS3手柄!"); // 注册接收回调 SerialBT.register_callback(btCallback); } else { Serial.println("连接失败!"); } } void btCallback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) { if (event == ESP_SPP_DATA_IND_EVT) { // 有数据到达 // param->data_ind.len 是数据长度 // param->data_ind.data 是指向数据的指针 uint8_t *report = param->data_ind.data; if (param->data_ind.len == 49) { // 典型的PS3报告长度 parsePS3Report(report); } } } void parsePS3Report(uint8_t* report) { // 报告结构需要参考文档。这里是一个简化的示例: // report[2] & 0x0F 可能包含方向键状态 // report[3] 包含方形、交叉、圆形、三角等按钮 // report[4] 包含L1, R1, L2, R2等 // report[6], report[7] 是左摇杆X,Y // report[8], report[9] 是右摇杆X,Y gButtonState = 0; // 先清零 // 解析方向键 (假设报告格式,具体需调整) uint8_t hat = report[2] & 0x0F; if (hat == 0) gButtonState |= UP_BUTTON_BIT; if (hat == 4) gButtonState |= DOWN_BUTTON_BIT; if (hat == 6) gButtonState |= LEFT_BUTTON_BIT; if (hat == 2) gButtonState |= RIGHT_BUTTON_BIT; // 解析动作键 (例如,PS3的“交叉”键映射为Arduboy的A键) if (!(report[3] & 0x40)) { // 假设交叉键对应位 gButtonState |= A_BUTTON_BIT; } // 解析B键... }实操心得:不同版本的
ESP32-BT-Gamepad库或PS3手柄固件,其报告格式可能有细微差别。最好的方法是先建立一个简单的测试程序,连接手柄后,将所有接收到的报告数据以十六进制形式打印出来,然后逐个按下按键,观察哪些字节发生了变化,从而反推出正确的映射关系。这是一个必经的调试过程。
3.3 主循环与多任务协同
在ESP32上,我们有双核CPU和FreeRTOS实时操作系统,这允许我们优雅地安排多个任务。
任务一:游戏逻辑与渲染(高优先级,运行在Core 1)。这个任务包含一个经典的游戏循环:
void gameTask(void *parameter) { ESP32Display display; display.begin(); // 初始化你的游戏 gameSetup(); while(1) { // 1. 处理输入(gButtonState已被蓝牙任务更新) // 2. 运行游戏逻辑(update) gameUpdate(); // 3. 渲染到sBuffer(draw) gameDraw(); // 4. 将sBuffer放大并输出到电视 display.display(); // 5. 控制帧率,例如等待16ms以达到~60FPS delay(16); } }任务二:蓝牙通信(中优先级,运行在Core 0或Core 1)。这个任务负责维护蓝牙连接、读取数据并解析。它可以是一个独立的FreeRTOS任务,或者在loop()函数中非阻塞地运行。关键是解析手柄数据后更新全局变量gButtonState的操作必须是原子操作,或者使用信号量、队列来同步数据,避免游戏任务在读取状态时发生错乱。
任务三:显示刷新(由DMA和硬件中断驱动)。如果我们使用TFT_eSPI这类库,通常库内部会利用DMA在后台自动搬运数据到LCD驱动芯片。我们只需要在display()函数中调用pushImage()或类似函数,它内部会启动DMA传输,然后立即返回,不阻塞CPU。这是效率最高的方式。
通过这样的多任务架构,游戏逻辑、输入处理和显示输出可以并行不悖,确保游戏运行流畅且响应迅速。
4. 系统集成、调试与性能优化
4.1 硬件连接与系统搭建
假设我们选择了一个带有RGB接口和HDMI输出的LCD驱动板(例如基于ILI9341或类似芯片的板子)。硬件连接清单如下:
- ESP32开发板x1(建议选择GPIO充足的型号,如ESP32 DevKit V1或ESP32-S3-DevKitC)
- RGB转HDMI驱动板x1
- PS3手柄x1及USB数据线(用于初次配对)
- HDMI线x1
- 杜邦线若干
- 5V/2A电源(为ESP32和驱动板供电,电视USB口供电可能不足)
连接步骤:
- 电源连接:将5V电源的正负极分别连接到ESP32的
VIN和GND,同时也连接到驱动板的5V和GND。确保共地。 - 信号连接:根据驱动板说明书,将ESP32的GPIO与驱动板的RGB数据线、同步信号线连接。典型连接可能包括:
GPIO18->CLK(像素时钟)GPIO19->VSYNC(垂直同步)GPIO21->HSYNC(水平同步)GPIO4->DE(数据使能)GPIO0, 2, 12, 13, 14, 15, 16, 17->D0-D7(8位数据总线,具体顺序需查手册)GPIO23, 22->I2S音频输出(可选,如果需要游戏音效)
- HDMI输出:用HDMI线连接驱动板和电视或显示器。
- 初次上电:先不要上传复杂代码,上传一个简单的LCD测试程序(如
TFT_eSPI库中的示例),确认屏幕能正常点亮并显示测试图案。这一步能排除硬件连接错误。
4.2 软件配置与关键参数调校
软件配置主要集中在显示库和蓝牙库。
显示库配置(以TFT_eSPI为例): 在Arduino IDE的库文件夹中找到TFT_eSPI库,编辑其下的User_Setup.h文件。你需要:
- 选择正确的驱动芯片(如
ILI9341_DRIVER)。 - 定义你使用的ESP32引脚。这通常是通过取消注释和修改一些宏定义来完成,例如:
务必根据你的驱动板原理图和实际接线来修改这些引脚定义!一个错误的引脚定义会导致花屏或无显示。#define TFT_CS -1 // 未使用片选 #define TFT_DC 2 // 数据/命令线 #define TFT_RST -1 // 连接复位引脚,或使用-1通过软件复位 // 对于并行接口,需要定义一组数据线引脚 #define TFT_D0 0 #define TFT_D1 2 // ... 一直到 TFT_D7 #define TFT_WR 4 // 写使能 #define TFT_RD -1 // 读使能,通常不用
蓝牙与手柄配置:
- 在代码中正确填写你从配对工具获取的PS3手柄MAC地址。
- 根据你使用的蓝牙库,正确初始化并设置回调。确保ESP32的蓝牙天线区域没有被金属遮挡。
- 仔细调试
parsePS3Report函数中的按键映射。建议编写一个简单的测试程序,在电视上显示当前按下的按键名称,以验证映射是否正确。
性能关键参数:
- 帧缓冲区:使用双缓冲区可以避免撕裂。但ESP32内存有限,一个640x480的RGB565双缓冲区需要
640*480*2*2 ≈ 1.2MB,这超出了内部RAM,必须使用外部PSRAM。如果你的板子没有PSRAM,可以考虑降低分辨率(如320x240),或者使用单缓冲区+垂直同步。 - 游戏帧率:在
gameTask的循环中,使用delay()控制帧率并不精确。更好的方法是使用vTaskDelayUntil()或记录上一帧的时间戳来计算需要延迟的时间,实现更稳定的帧率。 - CPU核心绑定:将游戏渲染任务绑定到一个核心(如Core 1),将蓝牙、Wi-Fi等任务绑定到另一个核心(Core 0),可以减少任务切换的开销,提升性能。
4.3 常见问题排查与实战技巧
在集成过程中,你几乎一定会遇到下面这些问题。这里是我的排查清单和解决思路:
问题1:电视显示“无信号”或黑屏。
- 检查电源:确保驱动板和ESP32供电充足且稳定。单独使用手机充电器供电测试。
- 检查接线:逐一核对每根信号线是否连接牢固,是否接错了引脚。特别是时钟和同步信号线。
- 检查配置:确认
User_Setup.h中的驱动芯片型号和引脚定义100%正确。可以先用库自带的“graphicstest”示例测试。 - 降低频率:有些驱动板对像素时钟频率敏感。尝试在代码中降低SPI或并口的时钟频率。
问题2:PS3手柄无法连接或连接后立即断开。
- 确认MAC地址:再次检查代码中填写的MAC地址是否与配对工具读取的一致,格式是否正确(冒号分隔,小写字母)。
- 重新配对:尝试清除ESP32和手柄的配对记录。对于ESP32,可以在代码开始时尝试
SerialBT.disconnect()和SerialBT.erase()(如果库支持)。对于手柄,用针戳一下背后的复位孔。 - 供电干扰:蓝牙对电源噪声敏感。尝试给ESP32的电源引脚并联一个100uF的电解电容。
- 库版本:尝试更换或更新
ESP32-BT-Gamepad库,不同版本对协议的支持可能不同。
问题3:游戏运行卡顿,帧率低下。
- 查看CPU占用:使用
Serial.println(uxTaskGetStackHighWaterMark(NULL));打印任务堆栈高水位线,或者使用更专业的性能分析工具,检查是否有任务阻塞或堆栈溢出。 - 优化显示:
- 确认是否使用了DMA传输。在
TFT_eSPI中,调用tft.initDMA()来启用。 - 缩小帧缓冲区。如果游戏原生是128x64,输出到640x480,缩放是必须的。确保你的缩放函数是高效的。避免在缩放中使用浮点运算,使用整数运算和查表法。
- 只更新变化区域。如果游戏画面变化不大,可以只重绘脏矩形区域,而不是全屏刷新。
- 确认是否使用了DMA传输。在
- 优化游戏逻辑:审查移植的Arduboy游戏代码,看是否有低效的循环或大量的浮点运算。在嵌入式环境下,整数运算和查表法远快于浮点。
问题4:按键响应延迟或失灵。
- 检查数据解析:在
parsePS3Report函数中增加调试输出,确保每次手柄报告到来时,gButtonState都被正确更新。 - 任务优先级:提高蓝牙处理任务的优先级,确保手柄数据能被及时处理。
- 去抖动处理:在游戏逻辑中,可以对读取的
gButtonState进行简单的软件去抖动,避免因蓝牙信号微小波动导致的误触发。
实战技巧:
- 分阶段开发:不要试图一次性完成所有功能。先让ESP32点亮屏幕并显示一个色块;再让PS3手柄连接并在串口打印按键数据;然后移植一个最简单的Arduboy游戏(比如一个移动的小方块);最后将所有部分整合。每完成一步,就巩固一步。
- 善用版本控制:使用Git管理你的代码。每次重大更改或调试出一个稳定版本后,做一个提交。这样当你不小心改乱了代码,可以轻松回退。
- 准备调试工具:一个逻辑分析仪(即使是便宜的)对于调试视频时序和信号问题有奇效。它可以帮助你确认HSync、VSync和像素时钟的波形是否正确。
5. 项目扩展与进阶玩法
当基础功能实现后,这个项目平台还有巨大的扩展潜力。
扩展一:添加音频输出。Arduboy游戏虽然简单,但很多都有经典的芯片音乐。ESP32可以通过I2S接口连接一个简单的音频DAC芯片(如MAX98357),驱动一个小喇叭或接入电视的音频输入。你需要将Arduboy游戏中基于tone()函数的声音调用,映射到ESP32的I2S音频库上。这需要对游戏源码进行一些修改,或者拦截Arduboy库的音频输出函数。
扩展二:构建游戏菜单与ROM加载器。目前我们只能烧录一个游戏。可以开发一个简单的图形菜单系统,让ESP32从SD卡中读取多个.hex(Arduboy游戏文件)文件,并动态加载运行。这涉及到在ESP32上实现一个简单的Arduino程序加载器(bootloader),技术难度较高,但完成后就真正变成一个“多合一”游戏机了。
扩展三:支持更多手柄。PS3手柄只是开始。同样的蓝牙HID协议,可以扩展支持PS4、PS5手柄,甚至Xbox手柄(需要不同的库)。你可以在代码中实现一个手柄自动检测和配置映射的功能,让系统更加通用。
扩展四:网络功能。ESP32有Wi-Fi。可以增加一个功能,通过网页服务器上传新的游戏ROM到SD卡,或者实现简单的分数排行榜功能。
扩展五:外壳与便携化。使用3D打印设计一个漂亮的外壳,将ESP32、驱动板、电池集成进去,做成一个真正的便携式TV游戏机。甚至可以加入一个小屏幕,实现双屏输出(一个作为调试信息显示)。
这个项目的终点远不止于让Arduboy游戏跑在电视上。它更像一个练手平台,让你深入理解了嵌入式图形、蓝牙HID、实时系统、硬件接口等多个领域。每一次调试,每一次功能扩展,都是对技能树的扎实点亮。当你最终拿着PS3手柄,在电视上流畅地玩着经典的《Arduboy》游戏时,那种亲手打造一个完整系统的满足感,是任何现成商品都无法替代的。