基于Arduino Nano ESP32构建一个无线蓝牙游戏手柄
这是我在使用Arduino Nano ESP32基础上开发的无线蓝牙游戏手柄原型。
本项目的核心理念是,最终打造一个完全开源的游戏手柄,从电子元件、PCB板到外壳和固件,让任何人都能自行组装、修改或以该设计为起点进行个性化开发。
这个原型是实现这一目标的第一步。我首先想测试核心电路,确保在重新设计完整的硬件之前,能够成功实现定制化无线控制功能。
在制作原型时,我重用了此前自制有线游戏手柄的PCB板。该电路板最初是基于Arduino Pro Micro和ATmega32U4设计的。通过将Pro Micro更换为Arduino Nano ESP32,我得以利用ESP32-S3内置的蓝牙低功耗(BLE)功能,将原本简单的控制器硬件转变为一款无线游戏手柄。
原型机已经完全具备功能,可通过蓝牙连接电脑,作为普通游戏手柄使用。在测试硬件和固件期间,我一直在用它来玩《赛博朋克2077》和《尼尔》等游戏。
这还不是最终版本的手柄。最终版本将采用完全定制的PCB板、专门设计的外壳以及更加精良的设计。目前,这个原型机主要目的是验证概念可行性,并为开源手柄项目打下基础。
本文将详细介绍该原型机的完整制作过程,现在让我们开始动手吧。
所需材料
以下是本次制作所使用的组件:
•定制PCB板(来自之前项目的回收材料)
•Arduino Nano ESP32开发板
•直角按钮
•水平方向按钮
•模拟摇杆
•移动电源
•USB转Type-C数据线
•双面胶
硬件——Arduino游戏控制器
如果Arduino Nano ESP32是这个项目的“大脑”,那么之前制作的游戏控制器PCB无疑就是“肌肉”。
在本项目中,我重用了之前Arduino游戏控制器项目中的主摇杆和按键PCB。在原始版本中,一个Arduino Pro Micro连接到该PCB上,作为控制器与电脑之间的接口。通过利用ATmega32U4原生的USB HID支持,我能够控制游戏,并将这块电路板当作普通的游戏控制器使用。
然而,这款控制器最有趣的地方在于,仅用一个I/O引脚就能控制电路板上的全部10个按钮。我通过巧妙的电阻分压电路实现这一功能,使得微控制器可以通过读取单个模拟引脚的不同电压值,来识别哪个按钮被按下。接下来我会详细说明其工作原理。
此外,我还在PCB设计时预留了两个方向键的位置,这在无线版本中得到了充分利用。
如需详细了解原始控制器的构造及其背后的设计决策,请查看其项目页面。
PCB设计
在本项目中,我设计了一个相当传统且简单的游戏手柄PCB,将按键连接到支持HID协议的微控制器的GPIO引脚上,因为最初的版本是作为有线游戏手柄设计的。
有趣的是,我如何用单个I/O引脚连接了10个按键。
为此,我在VCC与按键输入之间串联了十二个1 kΩ电阻,构成一个电阻阶梯结构。每个按键连接到电阻阶梯的不同位置,而每个开关的另一端则连接到地。
当按键被按下时,根据其在电阻阶梯中的位置,共享的模拟输入端会产生不同的电压。微控制器通过ADC读取该电压,并据此确定哪个按键被按下。
这种方案使得可以将大量按键连接到单个模拟I/O引脚上,而无需为每个按键单独设置一个GPIO引脚。实际上,根据电阻值、ADC分辨率以及可接受的公差范围,你可以使用此技术将10到20个甚至更多的按钮集成到单个输入上。
PCBWAY 服务
在完成设计后,我生成了PCB Gerber文件,并发送给PCBWay进行制作。我选择了白色PCB板,搭配黑色焊盘掩膜。
电压分压按钮设置的工作原理
为了获取每个按钮的数值,我们使用了示例菜单中的模拟串行读取代码,并据此获取每个按钮的数值。
A0模拟引脚用于读取电阻阶梯在按钮被按下时产生的不同电压值。
例如,按下“向上”按钮会产生大约0的模拟读数,而按下“向下”按钮则产生约511的读数。每个按钮都会产生其独有的数值。
在代码中,我为每个按钮定义了预期值以及一个较小的容差范围。控制器会持续读取A0的模拟值,并将其与这些预设值进行比较。如果读取值落在某个按钮的预期范围内,则认为该按钮被按下;否则,视为释放。
这使得控制器能够通过单一模拟输入识别多个按钮。
即使将Pro Micro更换为Arduino Nano ESP32,原始控制器的这一部分仍然完全保持不变。电阻阶梯和A0输入继续处理所有按钮输入,只是负责处理这些读数的微控制器发生了变化。
硬件——Arduino Nano ESP32
本项目的核心是Arduino Nano ESP32,它作为游戏控制器的主要微控制器。
在本次原型中,我基本上用Arduino Nano ESP32取代了之前有线控制器所使用的Arduino Pro Micro克隆板。虽然Pro Micro基于ATmega32U4,但Nano ESP32则采用ESP32-S3芯片,从而显著提升了控制器的处理能力、内存和连接性能。
Arduino Pro Micro使用的是运行在16 MHz频率的8位ATmega32U4 AVR微控制器,拥有32 KB闪存、2.5 KB SRAM和1 KB EEPROM。对于原始控制器而言,其主要优势在于原生支持USB,使其能够作为USB HID设备与计算机通信。
Nano ESP32采用ESP32-S3芯片,这是一种基于Xtensa LX7架构的32位双核微控制器,最高时钟频率可达240 MHz。ESP32-S3配备512 KB SRAM、384 KB ROM以及额外的实时时钟(RTC)存储空间。Nano ESP32板还提供了外部闪存用于程序存储。
然而,该项目最大的优势在于连接性。ESP32-S3内置了2.4 GHz Wi-Fi无线电和蓝牙低功耗(BLE)模块,无需额外的蓝牙模块。
接线
这款板的接线非常简单。我使用一个电阻网络,通过单个模拟引脚同时处理A、B、X、Y按钮以及D-pad输入,其中A0负责组合输入。
其余控制键的连接方式如下:
•右摇杆Y轴:A1
•右摇杆X轴:A2
•左摇杆Y轴:A3
•左摇杆X轴:D4
•右摇杆开关:D2
•左摇杆开关:D3
•LT(左摇杆触发):D6
•RT(右摇杆触发):D7
最初我在LT和RT输入上遇到了一些问题,因此最终将它们分别直接连接到D6和D7。为了连接,我使用单芯银铜线。
代码
这是我在本项目中使用的主代码,非常简单。
整个过程相当直接,大部分蓝牙HID功能都由BleGamepad库来处理。
蓝牙游戏手柄库
BleGamepad库负责项目中的蓝牙HID部分,使Nano ESP32能够识别自己为无线游戏手柄。
第一行包含库文件,第二行创建了游戏手柄实例。我将该手柄命名为“Open-Game-Controller”,制造商名称设为Arnov,并将报告的电池电量设置为100%。
手柄配置
这些变量定义了手柄的一些基本行为。
SAMPLES决定了读取模拟输入时采集多少次ADC读数。我使用八个采样值并取平均值,以降低噪声。
STICK_DEADZONE定义了在操纵杆中心位置周围忽略多少范围的移动。这可以防止操纵杆微小的波动被误认为是移动。
INVERT变量允许我反转某个轴的方向,如果操纵杆的实际朝向导致其运动被解释为反向的话。
MATCH_TOLERANCE 用于检测通过电阻阶梯连接的按钮,而 DEBOUNCE_MS 则可防止短时间波动被误认为是多次按键。
引脚映射
本节定义了每个物理控制键与Nano ESP32的连接位置。
A、B、X、Y按钮和方向键通过电阻分压器共享A0引脚。两个摇杆分别使用四个模拟输入来对应其X轴和Y轴。
摇杆点击开关LT和RT则连接到数字GPIO引脚。
物理按键映射
在此处,我将物理控制键映射到BleGamepad库所期望的按键ID。
例如,按下物理上的A键最终会触发:
这对应于HID游戏手柄报告中的BUTTON_1。
我为控制器采用了类似Xbox风格的按键布局,使其能够兼容需要标准游戏手柄的各类游戏。
电阻分压器
此时电阻分压器变得非常有用。
无需为每个按键单独分配一个GPIO引脚,每个按键会产生不同的电压,该电压可通过单个模拟引脚A0进行测量。
例如,ADC读数约为852表示A键,约为900表示B键。
控制器会将测得的ADC值与该表进行比较,并选择最接近匹配的按键。
这使得八个不同的输入可以共享一个模拟引脚,从而节省大量GPIO。
平均化模拟读数
由于模拟读数并非完全稳定,因此我不会只取一次ADC读数,而是取八次并计算它们的平均值。
例如,如果ADC返回:
固件会在使用前对这些数值进行平均处理。
这样无论是摇杆还是电阻阶梯,都能获得更稳定的读数。
检测按钮阶梯
该函数从A0获取ADC值,并判断哪个按钮被按下。
如果读数高于空闲阈值,控制器则认为没有按钮被按下。
否则,它会检查电阻阶梯表中的每一个值,并计算测量值与预期值之间的距离。
距离最接近的那个值即为检测到的按钮。
容差值可防止完全无关的ADC读数被误认为是有效的按键操作。
转换摇杆输入
该函数将摇杆原始的ADC值转换为蓝牙HID游戏手柄所期望的范围。
Nano ESP32 提供一个 0 到 1023 的 10 位 ADC 值,而 HID 轴则使用更大的有符号范围来表示。
函数首先减去已校准的中心位置,应用死区,然后对剩余的移动量进行缩放。
结果大约为:
invert 参数允许在必要时反转轴的方向。
初始化控制器
setup() 函数在控制器上电时运行一次。
在此处,我用于调试初始化串口连接,配置 ADC,并通过 ESP32 内部的上拉电阻将数字按钮引脚设置为输入模式。
使用 INPUT_PULLUP 模式时,按钮在释放时读取高电平,在按下时读取低电平。
摇杆校准
启动时,控制器会测量四个摇杆轴的静止位置。
与假设中心点精确为 512 不同,实际每个摇杆的位置会被测量并存储下来。
这很有用,因为真实的摇杆模块并不完全相同,其中心值可能会略有差异。
唯一的注意事项是,在控制器启动期间,必须保持摇杆处于未触碰的状态。
蓝牙游戏手柄配置
在这里,我配置蓝牙HID设备本身。
该控制器提供了14个按钮和所需的模拟轴。我还通过setAutoReport(false)禁用了自动报告功能。
这意味着固件决定何时发送当前控制器状态,而不是每次输入发生改变时都发送一次报告。
配置完成后,bleGamepad.begin() 便会启动蓝牙游戏手柄。
主循环
loop() 函数是控制器持续读取输入信息的地方。
首先,它会检查是否已连接蓝牙设备。如果没有连接,则直接等待。
一旦成功连接,固件将读取按钮和摇杆的位置,处理输入数据,更新游戏手柄状态,最后通过以下方式发送完整的报告:
该循环大约每10毫秒重复一次。
读取摇杆
四个摇杆轴的数据被读取、平均并转换为HID值,然后分别分配给对应的游戏手柄轴。
左侧摇杆控制X轴和Y轴,右侧摇杆则对应Z轴和RZ轴。
此时,摇杆的物理运动已被转换为蓝牙游戏手柄所期望的数字值。
发送最终报告
最后,所有当前的按钮和摇杆状态将以蓝牙HID报告的形式发送到已连接的设备。
因此,计算机无需了解任何关于单个按钮、电阻电位器、摇杆ADC值或Arduino本身的细节。至于操作系统,它只是与一个标准的蓝牙游戏手柄进行通信。
整个过程可以概括为:
物理按键 → ADC/GPIO → 输入处理 → HID值 → BleGamepad → 蓝牙 → 电脑
这基本上就是原型机将一组按钮和模拟摇杆转化为功能完整的无线游戏手柄的方式。
电源
刷入固件后,游戏手柄已准备就绪。但还有一个问题:它仍然需要有线连接。
手柄需要供电,目前这意味着必须用USB Type-C数据线连接到Nano ESP32上。
为解决这一临时限制,我决定使用一个MagSafe移动电源。并不是因为这个电源具备MagSafe功能或其他什么特别之处,只是恰好身边有一台。
该移动电源支持USB Power Delivery(PD)输出,但手柄并不需要如此复杂的供电方式。对于这个原型来说,我们只需要一个普通的5V电源,能够提供高达2A的电流即可。
实际上,手柄的功耗远低于2A。根据本原型所使用的硬件,实际功耗大约在几百毫安左右,甚至可能低于200mA,具体取决于工作环境和连接的外设。
不过,对于一个原型机而言,一个能提供5V、2A输出的移动电源已经足够满足需求,且留有充足的余量。
将移动电源安装在背面后,我们现在拥有了一个完全无线的控制器,至少直到移动电源不可避免地成为整个装置中结构上最重要的部分。
装配
组装过程从在移动电源背面贴上双面泡沫胶带开始。
取下保护盖后,我将游戏控制器的PCB板对准移动电源,用力将两部分紧密压合,并固定好位置。
接着,在电路的另一侧贴上一小块泡沫胶带,用它来安装Arduino Nano ESP32。只需稍加压力,就能将Arduino板和控制器PCB板牢固地固定到位。
最后,我使用一根USB线将移动电源连接到Arduino,一端接在移动电源上,另一端连接到Arduino的USB Type-C接口。
就这样,临时的组装完成了。
结果
这就是这种临时组装方法的最终成果,一个能正常工作的控制器,看起来简直就像詹姆斯频道制作的那样。
抛开玩笑不谈,这是一个完全功能正常的无线游戏手柄项目。虽然外观略显粗糙,但它证明了这个概念是可行的,并为我下一次改进提供了坚实的基础。
设置控制器
要测试控制器是否真的能用,首先我们需要将其与电脑配对。
打开蓝牙设置,查找我们的控制器,它应该显示为“开放的游戏控制器”。一旦出现,就将其与电脑配对,并确认连接已建立。
当控制器未连接或尚未连接到设备时,RGB指示灯会保持红色。一旦通过蓝牙成功连接,指示灯就会变为绿色,简单地提示控制器已连接并可使用。
接下来,我们打开浏览器,进入Gamepad Tester(游戏手柄测试器),这是一款非常实用的工具,可用于检测游戏手柄。在这里,我们可以逐一测试每个按键,确保输入信号被正确识别。我们还可以检查两个摇杆,确保它们的运动和行程被正确记录。
在检查完所有按钮和摇杆并确认一切正常后,我们进入下一步,实际使用控制器与Steam进行连接。
STEAM
现在,这从技术上讲并不是一个XInput控制器,这意味着Windows可能不会立即识别它为标准的Xbox风格游戏手柄。部分游戏仍能检测到它,但很多游戏则无法识别。
因此,这里有一个巧妙的解决方案。
我们以大图模式打开Steam,进入手柄设置,创建一个新的手柄配置。接着,Steam允许我们手动映射输入操作。我们将逐一检查各个控制按钮、扳机键、方向键和摇杆。
完成所有映射后,Steam会自动为我们处理输入转换,我们的手柄就能在支持标准控制器输入的游戏里正常使用了。
虽然这并非原生的XInput,但对于原型测试来说,效果相当不错。
玩游戏
我们首先测试的是《尼尔》。选择它的原因并不特别,我只是非常喜欢《尼尔》,想看看我最喜爱的游戏之一如何与我的自定义游戏手柄配合使用。
说实话,我完全没有遇到任何问题。一切运行流畅,几乎完全符合预期。
与Xbox手柄上的摇杆相比,这款原型机所用的摇杆质量确实有明显差异。我目前使用的这些摇杆并不算高端,因此精度和整体手感尚不理想。如果使用更高精度的霍尔效应摇杆,这种问题完全可以得到改善,但这一点我将在后续版本中考虑。
接下来我试玩的是《赛博朋克2077》,这时我才真正体会到一款真正的Xbox手柄有多么出色,而我目前的手柄表现则显得相当可疑。
在《赛博朋克》这类游戏中使用手柄时,体验明显更差。摇杆不够精准,难以进行瞄准,我也完全无法稳定地击中或追踪敌人。与真正的Xbox手柄相比,操控感觉略显迟缓且笨拙。
但最重要的是,它还是能正常工作的。
无线连接的控制器,输入信号被正确检测到,我甚至能用它玩完整个游戏。这次更新的目标正是实现这一点。
本文编译自hackster.io





