Flipper Zero Wii 扩展控制器协议分析器(wii_ec_anal)完全指南:接线、i2c 协议、屏幕场景与校准体系
2026/9/14 11:48:55 网站建设 项目流程

Flipper Zero Wii 扩展控制器协议分析器(wii_ec_anal)完全指南:接线、i2c 协议、屏幕场景与校准体系

【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper

导读

本文围绕 Flipper Zero 上的Wii Extension Controller Protocol Analyser(wii_ec_anal)插件展开,全面讲解如何将任天堂 Wii 双节棍(Nunchuck)、经典手柄(Classic Controller)等扩展控制器接入 Flipper Zero,通过 i2c 总线完成协议解析、实时数据显示、原始数据转储与软件校准。读完本文你将掌握:扩展控制器 6 针脚接线规范、i2c 初始化握手与寄存器布局、插件各屏幕场景的操作逻辑、完整的模拟量校准流程,以及如何基于源码结构扩展支持新的控制器类型。

插件定位:一个完整的"测试 + 校准"协议分析系统

该插件(Flipper 应用 ID:wii_ec_anal,参见 application.fam)不是一个简单的"读数据"工具,而是一套全功能的 Wii 扩展控制器测试与校准系统

  • 实时解析:将原始 i2c 字节流解码为可读的摇杆、按钮、加速度计数据;
  • 场景化显示:为 Nunchuck、Classic Controller 提供专属可视化界面(SCENE_NUNCHUCKSCENE_CLASSIC),未知设备则统一进入原始数据 DUMP 界面;
  • 软件校准:可在运行时对模拟量(摇杆、加速度计、扳机键)进行中心点与行程极值的重新校准;
  • 调试支撑:内置 DEBUG 屏幕与串口日志系统,便于协议逆向与问题排查。

需要强调的是,原文档明确给出免责声明:使用本插件(尤其是将扩展控制器连接到 Flipper Zero)的风险完全由使用者自行承担。截至文档写作时,插件仅在官方任天堂 Nunchuck 与 Classic Controller 上验证过。

关联文档:README.md,本文所有实操细节均以此为主干,并结合仓库源码进行展开。

硬件接线:WAIT 屏幕与 6 针脚映射

插件启动后首先进入SPLASH 启动画面(可按键清除,或 3.5 秒后自动消失),随后进入WAIT 等待画面。WAIT 屏幕会直接绘制出 Flipper Zero 与 Wii 扩展控制器之间的接线示意:

对照扩展控制器插头(从暴露面观察、凹口朝下),引脚定义如下:

EC 引脚 #EC 位置EC 引脚标识引脚功能FZ GPIO 名称FZ GPIO 编号
1左上+3v3电源3v39
2左下SCLi2c 时钟C016
3上中EN疑似"存在检测"
4下中-x-无连接
5右上SDAi2c 数据C115
6右下Gnd电源地Gnd18

接线要点

  • 实际必须接线的只有 4 根:3v3、Gnd、SCL(C0)、SDA(C1)
  • 上中引脚(EN)被作者推测为"存在检测"(presence detect)功能,但尚未验证,且本插件并不需要它——设备检测是通过 i2c 握手完成的,见下文"初始化握手"一节;
  • 设备连接成功后会被立即识别。若识别失败,通常有两种原因:控制器未正确连接(可能只是断线),或控制器内部电路板损坏(超出本插件排查范围)。

WAIT 画面按键

  • Left(左键)— 回到 SPLASH 启动画面;
  • Back(返回键)— 退出插件。

适配器(WiiChuck / Nunchucky)与方向警示

对于大多数玩家,最省事的连接方式是使用WiiChuckNunchucky一类的转接板(将扩展控制器插头转为杜邦线排针)。但这两类适配器都没有防反插机制——一旦插反,会以错误的极性向控制器供电。

作者给出的经验法则(适用于见过的所有 WiiChuck):

  • WiiChuck 一侧有 3 个连接器,另一侧有 2 个;
  • 有 2 个连接器的一侧应对准控制器插头上带大凹槽的一侧
  • 插头示意图(注意缺失的引脚与凹槽):
+-------------+ | _________ | | | = = = | | | |_=_____=_| | <-- notice missing pin | ___ | | | | | <-- notice indent +----+ +----+

作者同时强调:强烈建议在接线前自行核对适配器引脚定义,因为接反电压可能永久损坏控制器。上方的缺失引脚(即 EN 检测脚)在插头上并不连通。

i2c 协议基础:从寄存器布局到初始化握手

理解了接线后,我们来剖析插件底层真正的核心——wii_i2c.c中实现的 i2c 通信层(wii_i2c.c)。

总线参数与地址

  • 总线:Flipper Zero 的外部 i2c 总线furi_hal_i2c_handle_external,见 wii_i2c.h);
  • 设备地址0x52,注意 FZ 的 read/write 函数需要传入(7bitAddress << 1),即实际调用时使用0x52 << 1
  • 超时i2cTimeout = 3ms读等待i2cReadWait = 300µs(在写地址与读数据之间插入延时,部分设备对读请求响应较慢)。

寄存器映射

Wii 扩展控制器的寄存器布局(读取时寄存器地址自动递增):

寄存器段长度方向含义
0x00..0x056 字节控制器实时数据(摇杆/按钮/加速度原始值)
0x20..0x2F16 字节出厂校准数据
0x30..0x3F16 字节出厂校准数据的副本
0x40..0x4F16 字节加密密钥(PSK,2×8 字节)
0xFA..0xFF6 字节外设 ID(PID,用于识别设备类型)

对应到源码中的常量(见 wii_ec.h):

#define ENC_LEN (2*8) // 加密密钥长度,寄存器 0x40..0x4F #define JOY_LEN (6) // 控制器状态数据,寄存器 0x00..0x05 #define CAL_LEN (16) // 校准数据,寄存器 0x20..0x2F #define PID_LEN (6) // 控制器 ID,寄存器 0xFA..0xFF

初始化握手(encryption-bypass 策略)

ecInit()(wii_i2c.c)的完整流程为:

  1. 探测设备furi_hal_i2c_is_device_ready()检查0x52是否在线;
  2. 发送两条初始化命令(这是让控制器进入"未加密直通模式"的关键):
static const uint8_t regInit1 = 0xF0; static const uint8_t regInit2 = 0xFB; static const uint8_t cmdInit1[] = {regInit1, 0x55}; // i2c_write(0xf0, 0x55) static const uint8_t cmdInit2[] = {regInit2, 0x00}; // i2c_write(0xfb, 0x00)

即文档中所说的i2c_write(0xf0, 0x55); i2c_write(0xfb, 0x00)——这是本插件正常工作的前提

  1. 读取 6 字节 PID(寄存器0xFA),与已知设备表比对,确定控制器类型;
  2. 读取 16 字节出厂校准数据(寄存器0x20),并调用ecCalibrate(pec, CAL_RESET | CAL_FACTORY)加载工厂校准;
  3. 初始化解码双缓冲并做首次读取,使新旧两份解码数据一致,便于后续"变化检测"。

加密(Encryption)的现状与限制

Wii 官方加密协议使用位于0x40..0x4F的 2×8 字节 PSK 密钥,解密算法为:

// decrypted_byte = (encrypted_byte XOR encKey[1][address%8]) + encKey[2][address%8] *p = (*p ^ encKey[(reg + (p - buf)) % 8]) + encKey[8 + ((reg + (p - buf)) % 8)];

请务必注意:原文档明确声明——"本插件有部分加密处理代码,但未使用、未测试,且已知其中部分无法工作"。插件目前仅支持实现加密绕过策略(encryption-bypass)的扩展控制器。如果你确实需要加密通信支持,应通过 Issue 或 Pull Request 参与完善(源码中ecInit()的加密分支已标注多处//! this encryption code fails注释)。

设备识别:已知控制器表与 info.sh

设备识别基于ecId[]查找表(定义于 wii_ec.c,类型定义见 wii_ec.h)。每个条目由 6 字节 ID、可读名称、默认场景以及一组函数指针(init/decode/check/calib/show/keys)组成。

运行./info.sh(info.sh)可查看当前支持的控制器,输出如下:

[PID_UNKNOWN ] = { {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, "Unknown Perhipheral", SCENE_DUMP, [PID_NUNCHUCK ] = { {0x00, 0x00, 0xA4, 0x20, 0x00, 0x00}, "Nunchuck", SCENE_NUNCHUCK, [PID_CLASSIC ] = { {0x00, 0x00, 0xA4, 0x20, 0x01, 0x01}, "Classic Controller", SCENE_CLASSIC, [PID_BALANCE ] = { {0x00, 0x00, 0xA4, 0x20, 0x04, 0x02}, "Balance Board", SCENE_DUMP, [PID_GH_GUITAR ] = { {0x00, 0x00, 0xA4, 0x20, 0x01, 0x03}, "Guitar Hero Guitar", SCENE_DUMP, [PID_GH_DRUMS ] = { {0x01, 0x00, 0xA4, 0x20, 0x01, 0x03}, "Guitar Hero World Tour Drums", SCENE_DUMP, [PID_TURNTABLE ] = { {0x03, 0x00, 0xA4, 0x20, 0x01, 0x03}, "DJ Hero Turntable", SCENE_DUMP, [PID_TAIKO_DRUMS] = { {0x00, 0x00, 0xA4, 0x20, 0x01, 0x11}, "Taiko Drum Controller)", SCENE_DUMP,

结合源码可以看到,ecId[]实际包含的条目更多、也更完整:除上述设备外,还有PID_NUNCHUCK_R2(Nunchuck rev2,ID 首字节为0xFF)、PID_CLASSIC_PRO(Classic Controller Pro,ID 首字节为0x01)、PID_UDRAW(uDraw 平板,仅有udraw_init而无 decode/scene)、PID_ERROR(读错误)与PID_NULL(表尾哨兵)。设备类型枚举ecPid及场景枚举scene分别定义于 wii_ec.h 与 wii_anal.h。

识别结论:共 8 个已知 ID(PID_UNKNOWN为未知设备兜底;7 个具名设备中,只有 Nunchuck 与 Classic Controller 两个拥有专属场景SCENE_NUNCHUCK/SCENE_CLASSIC),其余设备统一落入SCENE_DUMP原始数据界面。匹配逻辑位于ecInit():从PID_FIRST遍历到PID_ERRORmemcmp比对 6 字节 ID,未命中则标记为PID_UNKNOWN

屏幕场景体系与按键操作

插件采用多场景 GUI 架构,场景枚举scene_t(wii_anal.h)包含:SCENE_NONESCENE_SPLASHSCENE_RIPSCENE_WAITSCENE_DEBUGSCENE_DUMPSCENE_CLASSICSCENE_CLASSIC_NSCENE_NUNCHUCKSCENE_NUNCHUCK_ACC

SPLASH 启动画面

插件启动时显示,按任意键清除,否则 3.5 秒后自动消失。

WAIT 等待画面

已在上文"硬件接线"一节详述,显示引脚映射并等待控制器接入。

NUNCHUCK 主界面

连接 Nunchuck 后进入,实时显示:

  • 加速度计Accelerometer{X,Y,Z}值;
  • 摇杆Joystick{X,Y}值与摇杆位置图形;
  • 按钮Button{C,Z}状态。

按键映射:

按键功能
Left进入 DUMP 原始数据界面
Right进入 NUNCHUCK_ACC 加速度计界面
Up / Down / OK参见下文"Peak Meters"
短按 Back重置控制器
长按 Back退出插件

NUNCHUCK 加速度计界面(NUNCHUCK_ACC)

轴方向约定:

运动方向较低读数较高读数
X左 / 右
Y前 / 后
Z下 / 上

解读规则:

  • 沿某轴平动 → 改变该轴读数;
  • 绕某轴转动/倾斜 → 改变另外两轴的读数;
  • 例:向左平移(沿 X 轴)只影响 X;向左转(绕 Y 轴旋转)则同时影响 X 与 Z。

按键映射:

按键功能
Left返回 NUNCHUCK 主界面
UpAuto-Pause 已禁用 → 启用;页面末尾暂停时 → 重启扫描;Auto-Pause 已启用时运行中 → 禁用
Nunchuck-Z切换暂停(Pause)
Nunchuck-C切换自动暂停(Auto-Pause)
长按 OK进入软件校准模式(加速度计界面下仅校准加速度计)
短按 OK退出软件校准模式,并校准 CENTRE 中心位置
短按 Back重置控制器
长按 Back退出插件

说明:源码中确实存在屏幕滚动显示代码,但 LCD 刷新率过低、效果不佳,因此未启用(见 notes.txt 中的波形绘制代码注释)。

CLASSIC 经典手柄界面

连接 Classic Controller(Pro)后,屏幕绘制一个经典手柄图形,并随控制器事件实时动画。扫描率设定为 30fps,但受 LCD 延迟影响实际体验因人而异。

按键功能
Left进入 DUMP 界面
Right显示模拟量读数(再次按 Left 隐藏)
Up / Down / OK参见"Peak Meters"
短按 Back重置控制器
长按 Back退出插件

DUMP 原始数据界面

所有未知设备(以及无专属_decode()的设备)只能看到此界面。它展示:

  • SID:String ID,设备可读名称(来自ecId表);
  • PID:Peripheral ID,识别设备的 6 字节 ID;
  • Cal:16 字节出厂校准数据;
  • 底部六字节:控制器实时数据,每字节十六进制上方是对应的二进制位图形。例如连接 Nunchuck 时按下 Z 按钮,可观察最右侧位的变化(Z 按钮位于 joy[5] 的 bit0,见 wii_ec_nunchuck.c)。

按键:

按键功能
Right返回控制器专属界面(若存在)
短按 Back重置控制器
长按 Back退出插件

Peak Meters:峰值/谷值视图

在任何带 Peak/Trough 菜单的控制器专属界面上:

按键功能
Up切换:仅显示峰值(peak)
Down切换:仅显示谷值(trough)
长按 OK进入软件校准模式
短按 OK退出软件校准模式 / 校准 CENTRE 中心位置

校准体系:工厂校准与软件校准

为什么需要校准

  • 数字按钮无需校准
  • 部分控制器带出厂校准数据(疑似存储于控制器 OTP 中),例如 Classic Controller出厂校准,而 Classic Controller Pro没有
  • 不同设备对校准数据的解读方式不同:Nunchuck 是 1 摇杆 + 加速度计,Classic Controller 是 2 摇杆 + 2 模拟扳机;
  • 作者实测发现出厂校准数据可能不准确("控制器用久了会漂移")。若工厂值限制了行程,可通过在运行时扩展来解决;但若工厂数据给出的行程超出摇杆物理可达范围,就必须对控制器做完整重校准。

推荐的校准方法

  1. 控制器静止、水平放置时采集读数作为基准;
  2. 将控制器推到所有方向极限,记录各轴的极值(peak/trough)。

背景知识(文档提及):任天堂据称会在控制器刚连接时采集"静止"读数,且在任何时刻同时按住 {A、B、+、-} 至少 3 秒可触发"重校准",但文档作者并未掌握其具体行为细节。

本工具的实际校准流程

  • 设备首次识别时:使用工厂校准数据决定每个模拟控制的中心/中间位置与极值(如最左、最右);
  • 长按 OK(FlipperZero 端)——进入软件校准模式,注意按下期间不要触碰任何模拟控制
    1. 校准按钮开始闪烁;
    2. 取当前读数作为中心位置;
    3. 将范围极限设为"无范围";
    4. 现在需要在各控制的两个极限之间移动,让代码记录新的校准/范围/峰谷值;
    5. 完成后按短按 OK退出软件校准模式;
  • 短按 OK(FlipperZero 端)——同样不要触碰模拟控制:
    1. 停止校准按钮闪烁;
    2. 校准所有模拟控制的中心位置(加速度计暂不支持中心校准)。

从源码看,校准策略由ecCalib枚举(wii_ec.h)驱动:

typedef enum ecCalib { CAL_FACTORY = 0x01, // (re)set to factory defaults CAL_TRACK = 0x02, // track maximum and minimum values seen CAL_RESET = 0x04, // initialise ready for software calibration CAL_RANGE = 0x08, // perform software calibration step CAL_CENTRE = 0x10, // reset centre point of joystick CAL_NOTJOY = 0x20, // do NOT calibrate the joystick } ecCalib_t;

软件校准数据在每个设备中以 5 个槽位组织(0=最低见过值;1=min;2=mid;3=max;4=最高见过值,见 wii_ec.h 的ecCal联合体注释)。Nunchuck 的校准实现(wii_ec_nunchuck.c)在CAL_RESET时会将 LO 置为最大值(10bit 值上限)、HI 置为零,以便后续通过扫描逐步收敛到真实行程。

事件驱动架构:从轮询到消息队列

插件并非简单死循环读寄存器,而是由定时器驱动的事件驱动架构

  • 状态结构体state_t(wii_anal.h)包含定时器(timer/timerHz/fps)、当前/前一场景、校准状态、暂停标志、通知队列(用于拍打背光看门狗)与wiiEC_t控制器状态;
  • 轮询核心是ecPoll()(wii_ec.c):
    • 未初始化时尝试ecInit(),成功后向消息队列投递WIIEC_CONN连接事件;
    • 已初始化时调用ecRead(),根据返回值处理:2= 设备断开,投递WIIEC_DISCONN0= 读取成功,调用设备专属check()函数;3= 瞬时读失败,直接忽略;其余为不应发生的 bug 分支;
  • 事件类型枚举wiiEcEventType(wii_ec.h):WIIEC_NONEWIIEC_CONNWIIEC_DISCONNWIIEC_PRESSWIIEC_RELEASEWIIEC_ANALOG(摇杆/扳机变化)、WIIEC_ACCEL(加速度变化);
  • Nunchuck 的nunchuck_msg()通过宏BUTTON()/ANALOG()/ACCEL()将新老解码数据逐项比对,仅在变化时投递对应事件(wii_ec_nunchuck.c)。

Nunchuck 原始字节的解码逻辑(nunchuck_decode())很值得参考:6 字节中,joy[0]/joy[1]直接对应摇杆 XY,joy[5]的低 2 位是按钮 C/Z(取反,低电平有效),而加速度计的 10bit 精度由高 8 位字节与joy[5]中的高位拼接而来

p->accX = ((uint16_t)joy[2] << 2) | ((joy[5] >> 2) & 0x03); // {10} p->accY = ((uint16_t)joy[3] << 2) | ((joy[5] >> 4) & 0x03); // {10} p->accZ = ((uint16_t)joy[4] << 2) | ((joy[5] >> 6) & 0x03); // {10}

DEBUG 屏幕与日志系统

在除 SPLASH 外的任意屏幕长按 Down进入 Debug 模式:

  • 进入后实时扫描停止
  • Up— 尝试初始化已连接的控制器;
  • OK— 从控制器读取一次数据;
  • 长按 Down— 重启实时扫描并返回 WAIT 屏幕。

日志查看方式:通过 USB 连接 Flipper Zero,使用串口终端(minicomputty等)启动log功能即可看到调试消息。

日志级别可在编译期通过LOG_LEVEL限制(bc_logging.h),也可在运行期通过FZ -> Settings -> System -> LogLevel调整:

  • FURI 日志共 6 级(1=None,2=Errors,3=Warnings,4=Information,5=Debug,6=Trace);
  • LOG_LEVEL < N时对应宏会被替换为空操作,从而减小插件体积(文档特别提示:自 FAP 支持引入后,编译期裁剪对体积的意义可能已经不大);
  • 源码默认#define LOG_LEVEL 4,且注释警告:若同时将编译期与运行期日志都开到 TRACE(6),插件退出时可能导致 FZ 崩溃。

./info.sh可一键查看源码中所有LOG_LEVEL的使用点与 TODO 标记。

源码结构:从文件清单到"新增一款控制器"

仓库 wii_ec_anal 目录(开发笔记见 README.txt)组织如下:

文件职责
README.md /_images/用户手册正文与配图
application.famFAP 清单:appid="wii_ec_anal",类别GPIO_Extra,图标WiiEC.png,源码通配wii_*.c+gfx/*.c,栈 2KB
wii_anal.c /.h主应用:场景机、事件循环、状态管理
wii_anal_ec.c /.h扩展控制器相关动作
wii_anal_keys.c /.h按键处理
wii_anal_lcd.c /.hLCD 绘制函数
wii_i2c.c /.hi2c 通信层(初始化/读取/解密)
wii_ec.c /.h扩展控制器通用函数与ecId[]设备表
wii_ec_nunchuck.c /.hNunchuck 专属场景
wii_ec_classic.c /.hClassic Controller Pro 专属场景
wii_ec_udraw.c /.huDraw 场景(未完成)
i2c_workaround.hFZ i2c 库 bug 的临时绕行方案
bc_logging.h / err.h日志宏与错误定义
info.sh从源码提取支持列表/日志级别/TODO

为插件添加新的扩展控制器类型

开发笔记(README.txt)给出了标准扩展流程(以新增 "mydev" 为例):

  1. 新建wii_ec_mydev.cwii_ec_mydev.h,实现以下函数(含原型):
    • bool mydev_init(wiiEC_t*)— 额外初始化代码;
    • void mydev_decode(wiiEC_t*)— 解码控制器输入数据;
    • void mydev_msg(wiiEC_t*, FuriMessageQueue*)— 向事件队列投递消息;
    • void mydev_calib(wiiEC_t*, ecCalib_t)— 校准函数;
    • void mydev_show(Canvas*, state_t*)— 场景 LCD 绘制;
    • bool mydev_key(const eventMsg_t*, state_t*)— 场景按键处理;
  2. 在 wii_ec.h 中#include "wii_ec_mydev.h",并在enum ecPid增加PID_MYDEV
  3. 在 wii_anal.h 的enum scene中增加SCENE_MYDEV
  4. 在 wii_ec.c 的ecId[]表中注册设备:
[PID_MYDEV] = { {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, "My Device", SCENE_MYDEV, mydev_init, mydev_decode, mydev_msg, mydev_calib, mydev_show, mydev_key },

这就是ecId_t中那组函数指针(init/decode/check/calib/show/keys)的实际用法——插件正是通过这张"设备驱动表"实现了解码、消息、校准、绘制与按键的多态分发(顶层ecDecode()/ecCalibrate()先判断对应函数指针是否存在再调用,见 wii_ec.c)。

已知问题与限制(TODO)

原文档在 TODO 一节记录了一个重要问题,并在源码中留有对应证据:

  • FZ i2c bug:写作当时 FlipperZero 固件的 i2c 库存在缺陷,本插件通过 i2c_workaround.h 提供临时绕行——所有 i2c 调用被包装为"acquire → 操作 → release"的形式(furi_hal_Wi2c_is_device_ready/tx/rx/trx),并额外提供带读延时的furi_hal_i2c_trxd()notes.txt中还保留了完整的修复参考代码与"region locking"的固件定位线索。

其余已知限制(文档与源码交叉确认):

  • 加密功能未使用、未测试且部分不可用,仅支持 encryption-bypass 控制器;
  • 仅官方 Nunchuck 与 Classic Controller 得到实际验证;uDraw 设备仅注册了udraw_init,场景未编写;
  • LCD 滚动显示因刷新率不足被禁用;
  • 加速度计暂不支持软件校准中心点(CAL_CENTRE注释为 "accelerometers not supported (yet)")。

结语:一份可复用的 i2c 外设分析范本

wii_ec_anal 的价值不仅在于"能读双节棍数据":它的多场景 GUI + 事件驱动轮询 + 设备驱动表 + 双缓冲解码 + 工厂/软件双层校准设计,为在 Flipper Zero 上逆向与调试任何 i2c 外设提供了一个高完成度的参考范本。无论你是想直接用它测试手中的 Nunchuck/Classic 手柄,还是想照葫芦画瓢为自己的扩展控制器写一个专属场景,本文结合 README.md 与源码给出的接线表、寄存器映射、初始化握手与校准流程,都能作为你起步的地图。

【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询