ps2-controller源码解读:PS2I2C类如何把6字节I2C数据变成16个按键和4根摇杆
【免费下载链接】ps2-controller源师兄扩展项目: PS2 | 由源师兄组织创建项目地址: https://gitcode.com/yuanshixiong/ps2-controller
ps2-controller是源师兄开源的 PS2 手柄扩展项目:通过 I2C 总线把 PS2 无线手柄接入「大师兄板」,提供一套完整的 I2C 驱动与积木块。这篇文章做一次源码解读,带你一步步看懂PS2I2C类是如何把 6 字节 I2C 数据"拆解"成 16 个按键和 4 个摇杆轴值的,适合刚入门 I2C 和位运算的读者。
一、30秒认识项目:文件都藏在哪
整个仓库非常精简,核心驱动其实全部内嵌在一个文件里:
| 文件 | 作用 |
|---|---|
| blocksdef.js | 定义积木块,并把完整的 Python 驱动(PS2I2C类)作为代码模板内嵌其中 |
| category.json | 声明「PS2手柄」积木分类及其下的 3 个积木 |
| config.json | 项目元信息:适配设备为大师兄板,名称「PS2手柄」 |
也就是说,blocksdef.js 里pythonGenerator.definitions_["ps2_i2c_driver"]这段模板,就是本文要拆解的"主角"——一个标准的 PS2 无线手柄 I2C 驱动。
二、先看数据帧:6 字节里装着什么
驱动假设手柄以7 位地址0x13挂在 I2C 总线上,其中数据寄存器REG_PS2_DATA(0xBB)一次返回6 个字节:
字节0 字节1 字节2 字节3 字节4 字节5 方向键/系统 肩键/动作键 RX轴 RY轴 LX轴 LY轴 (8个按键) (8个按键) 0~255 0~255 0~255 0~255规律一目了然:
- 字节0、字节1各是一个 8 位"位图",每个 bit 代表一个按键,两个字节正好 16 键;
- 字节2~5是 4 根摇杆轴的模拟量,取值 0~255。
这就是标题的答案雏形:6 字节 = 16 个按键位 + 4 个摇杆通道。
三、源码逐段拆解:PS2I2C 类的 4 个核心方法
1. 初始化:一条 400kHz 的 I2C 总线
def __init__(self, address=PS2_I2C_ADDRESS): self._bus = I2C(1, freq=400000) self._addr = address self._buf = bytearray(6)见 blocksdef.js。创建I2C(1)总线(400kHz 快速模式),并预分配一个 6 字节的缓冲区_buf用来缓存手柄数据。
2._ensure_data():10ms 缓存,避免刷屏式读取
now = self._ticks_ms() if self._last_update_ms and (now - self._last_update_ms) < 10: return self._bus.writeto(self._addr, bytearray([REG_PS2_DATA])) data = self._bus.readfrom(self._addr, 6)见 blocksdef.js。逻辑是:先读寄存器0xBB选通数据,再读 6 字节写入缓存;10ms 内的重复查询直接复用缓存。这对游戏循环(通常每秒几十次轮询)非常关键——既保证响应灵敏,又不会把 I2C 总线打满。
3.is_button_pressed():位运算查按键,"按下 = 0"
value = self._buf[byte_index] return (value & (1 << bit_index)) == 0见 blocksdef.js。关键有两点:
- 按键→位置映射表:类属性
_BUTTON_MAP把每个按键名映射为(byte_index, bit_index),例如LEFT是字节0 的第7位、SQUARE是字节1 的第7位,共 16 项,见 blocksdef.js; - 低电平有效:该硬件约定"按下的位为 0",所以用
&屏蔽后判断是否等于 0。
另外它还内置了一张形状符号兼容表:传"△"、"○"、"×"、"□"(及其变体)也能正确识别,对小朋友写积木程序非常友好。
4.get_axis():4 个摇杆通道一次查表拿走
if a == "LX": return self._buf[4] if a == "LY": return self._buf[5] if a == "RX": return self._buf[2] if a == "RY": return self._buf[3]见 blocksdef.js。摇杆没有位运算,直接按字节下标取 0~255 的模拟值即可。
四、从源码到积木:3 个积木块让新手零门槛使用
PS2I2C驱动对积木层是"透明"的,category.json 里声明了 3 个积木,与源码方法一一对应:
| 积木(类型) | 生成的 Python 代码 |
|---|---|
| 手柄(I2C) 初始化(ohcode_extend_ps2_i2c_init) | 自动引入驱动并执行ps2 = PS2I2C() |
| PS2 按键是否按下(ohcode_extend_ps2_i2c_button_pressed) | ps2.is_button_pressed("CROSS") |
| PS2 摇杆数值(ohcode_extend_ps2_i2c_get_axis) | ps2.get_axis("LX") |
初始化积木下拉 16 个按键选项、摇杆积木下拉 LX/LY/RX/RY 四轴——拼积木的过程,本质上就是调用上文拆解过的两个方法,这就是"积木化封装"的价值:新手不用懂 I2C 和位运算也能驱动手柄。
五、速查表:16 个按键的位位置
| 字节 | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
|---|---|---|---|---|---|---|---|---|
| 字节0 | LEFT | DOWN | RIGHT | UP | START | R3 | L3 | SELECT |
| 字节1 | SQUARE | CROSS | CIRCLE | TRIANGLE | R1 | L1 | R2 | L2 |
摇杆:字节2=RX,字节3=RY,字节4=LX,字节5=LY(均 0~255)。
六、新手避坑指南
- 🎮按下是 0 不是 1:判断逻辑是
(value & (1 << bit)) == 0,改代码时千万别写反; - 🧩未知按键安全返回 False:
is_button_pressed对不认识的按键名直接返回False,不会抛异常; - ⏱️善用 10ms 缓存:在快速循环里连续调用查询接口是安全的,缓存机制已兜底;
- 🔌地址别写错:7 位地址是
0x13,寄存器0xAA读模式 ID(get_id,见 blocksdef.js),0xBB读 6 字节数据。
写在最后
ps2-controller用一个不足 100 行的 Python 类,完整演示了 I2C 从设备的典型套路:固定寄存器 + 位图按键 + 模拟量轴 + 短时缓存,再用 3 个积木块把它变成新手友好的可视化接口。读懂 blocksdef.js 这一个文件,你也就拿到了自己的 I2C 外设驱动模板。🚀
【免费下载链接】ps2-controller源师兄扩展项目: PS2 | 由源师兄组织创建项目地址: https://gitcode.com/yuanshixiong/ps2-controller
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考