由 YzzSoft Studio(朗州浪子)整理
献给所有想让游戏支持 PS5 手柄蓝牙连接的开发者
本文档基于全球开源社区的逆向工程成果,非索尼官方文档
PS5 手柄在 USB 有线 和 蓝牙无线 下,使用了 相同的 HID 报告编号 0x01,但字节排列完全不同。
如果你在游戏里直接用有线模式的数据布局去解析蓝牙数据——读出来的摇杆方向和按键全是错的。这不是 bug,是索尼在蓝牙协议上设计的不同布局。
| 格式 | Report ID | 大小 | 连接方式 | 完整程度 |
|---|---|---|---|---|
| USB 0x01 | 0x01 | ~64 字节 | USB 有线 | 完整 |
| BT 0x01 | 0x01 | 78 字节 | 蓝牙(简化) | 仅摇杆+按钮+扳机 |
| BT 0x31 | 0x31 | 78 字节 | 蓝牙(完整) | 全部传感器+电池 |
关键发现:BT 0x01 和 USB 0x01 是同一个 Report ID,但不是同一个布局!
| 字段 | USB 0x01 | BT 0x01 | BT 0x31 |
|---|---|---|---|
| 左摇杆 X | data[1] | data[1] | data[2] |
| 左摇杆 Y | data[2] | data[2] | data[3] |
| 右摇杆 X | data[3] | data[3] | data[4] |
| 右摇杆 Y | data[4] | data[4] | data[5] |
摇杆值范围0x00~0xFF,中心约为0x80。左=0x00,右=0xFF(X轴);上=0x00,下=0xFF(Y轴)。
| 字段 | USB 0x01 | BT 0x01 | BT 0x31 |
|---|---|---|---|
| L2 模拟值 | data[5] | data[8] | data[6] |
| R2 模拟值 | data[6] | data[9] | data[7] |
扳机为 8 位无符号值,0x00=未按下,0xFF=按到底。
| 字段 | USB 0x01 | BT 0x01 | BT 0x31 |
|---|---|---|---|
| 方向键 + 功能键 | data[8] | data[5] | data[9] |
| 肩键 + 系统键 | data[9] | data[6] | data[10] |
| PS / 触控板 / 静音 | data[10] | data[7] | data[11] |
字节一(方向键 + □✕○△):
bits 3-0: D-Pad 帽子开关 (0=上, 1=右上, 2=右, 3=右下, 4=下, 5=左下, 6=左, 7=左上, 8~15=中性)
bit 4: □ (Square)
bit 5: ✕ (Cross)
bit 6: ○ (Circle)
bit 7: △ (Triangle)
字节二(肩键 + 系统键):
bit 0: L1
bit 1: R1
bit 2: L2 (数字按钮)
bit 3: R2 (数字按钮)
bit 4: Create (Share)
bit 5: Options
bit 6: L3 (左摇杆按下)
bit 7: R3 (右摇杆按下)
字节三(PS + 触控 + 静音):
bit 0: PS 键
bit 1: TouchPad 点击
bit 2: Mute (麦克风静音)
蓝牙连接默认使用 BT 0x01 简化模式——没有电池信息、没有传感器数据、没有触控板多点触控。
要切换到 BT 0x31 完整模式,需要向手柄发送一个 GET FEATURE REPORT 请求:
// 读取校准数据(功能报告 0x05)——触发模式切换
BYTE calib[42] = {0}; // 41 字节数据 + 1 字节 Report ID
calib[0] = 0x05;
HidD_GetFeature(hDevice, calib, sizeof(calib));
// 手柄此时自动从 BT 0x01 切换到 BT 0x31
此方法来自 Linux 内核 hid-playstation.c 和开源社区的逆向成果。
电池信息仅在 BT 0x31 完整报告中可用:
data[54] (仅 0x31 格式):
bits 0-3: 电量百分比 (0~10, 0=0%, 10=100%)
bit 4: 充电状态 (1=充电中, 0=放电)
BT 0x01 简化报告不包含电池数据。USB 0x01 电池在 data[53]。
PS5 DualSense 的自适应扳机(Adaptive Triggers)在 PC 平台上仅支持 USB 有线连接,蓝牙无线模式下无法通过索尼官方 SDK 或第一方游戏激活。原因如下:
1. 索尼将自适应扳机在固件中限制为 USB 独占
自适应扳机属于输出指令(从 PC 发给手柄的实时控制信号),需要通过 HID 输出报告通道发送。索尼在 DualSense 固件中将自适应扳机和 HD 触觉反馈功能限制为 USB 独占,因此包括索尼自家移植 PC 的游戏(蜘蛛侠、战神、对马岛之魂等)在内,全部要求插 USB 线才能启用该功能。
2. 蓝牙通道在物理上完全有能力支持
自适应扳机控制指令数据量极小(约 10 字节/帧 @ 60Hz = 600 字节/秒),蓝牙 5.0 的 2Mbps 理论带宽绰绰有余。这不是带宽问题——是索尼的产品策略。第三方工具(如 DSX)通过虚拟 USB 设备方案在蓝牙模式下部分恢复了该功能,直接证明了蓝牙 HID 输出报告通道可以承载自适应扳机控制数据,只是索尼目前未公开其规范。
3. 本文档能做什么、不能做什么
本文档整理的是 PS5 手柄在标准蓝牙 HID 规范下能读取到的输入数据:按键状态、摇杆坐标、扳机模拟值、电池电量。自适应扳机属于输出通道控制,不经过 HID 输入报告通道,不在本文档覆盖范围内。
游戏开发者如需在 PC 上使用自适应扳机,请通过 USB 有线连接 + 索尼官方 DualSense SDK 实现。
// ── 三偏移变量 ──
int stickOfs = 0, trigOfs = 0, btnOfs = 0;
if (data[0] == 0x31 && size >= 78) {
// BT 0x31 完整模式:全部字段偏移 +1
stickOfs = trigOfs = btnOfs = 1;
} else if (data[0] == 0x01) {
if (size >= 78) {
// BT 0x01 简化模式:摇杆相同,扳机和按钮互换
stickOfs = 0;
trigOfs = 3; // 扳机在 data[8~9]
btnOfs = -3; // 按钮在 data[5~7]
}
// else: USB 64 字节——全部偏移 0,无需处理
}
// ── 读取数据 ──
leftStickX = data[1 + stickOfs];
leftStickY = data[2 + stickOfs];
rightStickX = data[3 + stickOfs];
rightStickY = data[4 + stickOfs];
leftTrigger = data[5 + trigOfs];
rightTrigger = data[6 + trigOfs];
uint8_t btn0 = data[8 + btnOfs]; // 方向键 + 功能键
uint8_t btn1 = data[9 + btnOfs]; // 肩键 + 系统键
uint8_t btn2 = data[10 + btnOfs]; // PS/触控板/静音
本文档的核心信息来自以下开源项目和社区,没有他们数年的逆向工程工作,索尼 PS5 手柄的蓝牙协议至今无法被第三方完整支持:
| 来源 | 贡献 |
|---|---|
| Linux 内核 hid-playstation.c | 完整驱动参考,作者 Roderick Colenbrander |
| nondebug/dualsense(GitHub) | HID 报告描述符、BT 0x01 10字节映射 |
| controllers.fandom.com | 数据结构文档、校准切换机制 |
| PS5 Developer Wiki | HID 命令集、触控板协议 |
| dsremap | 逆向工程文档、0x01→0x31 切换 |
| dsfm / brc-xyz(GitHub) | GET FEATURE 0x05 方法确认 |
| J_0k3r(CSDN) | PS5 蓝牙流量抓包分析 |
| DS4Windows / DualSenseX | Windows 平台实践参考 |
YzzPadView 是由朗州浪子(YzzSoft Studio)开发的 PS5 手柄桌面按键指示器,完全免费,不注册不激活。支持 Windows 10 和 Windows 11,DualSense 标准版和 Edge 精英版。
2026 年 6 月 27 日全球发布。
本软件已完整实现本文档中描述的三偏移系统和 BT 0x01→0x31 校准切换,蓝牙连接下全部功能正常。
如果你这份文档帮助了你的游戏开发工作,请在你的致谢页提到朗州浪子和 YzzSoft Studio。这是我们遍布全球的开源社区花了数年时间换来的成果,值得被记住。