Wii Nunchuk 手柄接入 Arduino 完全指南:I2C 通信、数据解析与卡尔曼滤波
项目简介
Wii Nunchuk 是极其廉价且功能丰富的输入设备:双轴摇杆、两键 (C/Z)、三轴加速度计,通过 I2C (400kHz Fast Mode) 通信。本项目提供一套完善的 Arduino 库 (nunchuk.h),封装初始化、加密关闭、校准、原始/校准数据读取、摇杆角度、俯仰/横滚角计算,并附带 Processing 可视化示例。解决了网络上代码片段碎片化、加密处理不一、副本兼容性差的问题。
硬件准备
- Wii Nunchuk (原装或副本,原装屏蔽线、摇杆分辨率、加速度计质量更好)
- Arduino Uno/Nano/Mega (任意型号,I2C 引脚 A4/A5)
- 电平转换模块 (强烈推荐):Nunchuk 工作电压 3.3V,Arduino 5V 逻辑直连会缩短寿命
- 可选:Nunchuk 转接板 (保留原插头,免剪线)
接线方案
方案 A:剪线直连 + 电平转换 (推荐)
| Nunchuk 线色 (原装) | 功能 | 电平转换 HV 侧 (5V) | 电平转换 LV 侧 (3.3V) |
|---|---|---|---|
| 红 | 3.3V | — | LV 供电输入 |
| 白 | GND | — | GND |
| 绿 | SDA | HV1 (Arduino A4) | LV1 (Nunchuk SDA) |
| 黄 | SCL | HV2 (Arduino A5) | LV2 (Nunchuk SCL) |
副本线色可能不同,**必须拆开插头确认实物对应关系**,切勿盲信线色。
方案 B:WiiChuck 转接板 (A2/A3 供电)
- 转接板插入 Arduino A2 (3.3V)、A3 (GND)、A4 (SDA)、A5 (SCL)
- 代码调用
nunchuk_init_power()启用 A2/A3 供电
I2C 协议深度解析
地址
设备地址 0x52 (7-bit)
初始化序列 (关闭加密,兼容副本)
START, 0xF0, 0x55, STOP
START, 0xFB, 0x00, STOP
关闭加密后,读取数据无需解密 (x ^ 0x17) + 0x17,直接使用原始字节。
读取 6 字节测量数据 (地址 0x00)
START, 0x00, STOP
READ 6 bytes
数据位定义
| Byte | Bit 7-0 |
|---|---|
| 1 | Joystick X [7:0] |
| 2 | Joystick Y [7:0] |
| 3 | Accel X [9:2] |
| 4 | Accel Y [9:2] |
| 5 | Accel Z [9:2] |
| 6 | Az[1:0] Ay[1:0] Ax[1:0] ¬Bc ¬Bz |
- 按键值为反逻辑:
¬Bz(Z键),¬Bc(C键),0=按下,1=释放 - 加速度计低 2 位打包在 Byte 6,库函数自动合成 10-bit 值
校准数据 (地址 0x20/0x30, 16 字节)
包含 0g/1g 参考值、摇杆极值/中心值,库中预留解析接口,当前版本使用硬编码默认校准,建议按文档自行标定。
库函数速查表
| 函数 | 返回值 | 说明 |
|---|---|---|
nunchuk_init() | bool | 初始化 I2C、发送关闭加密序列 |
nunchuk_read() | bool | 读取 6 字节并更新内部缓存,成功返回 true |
nunchuk_buttonZ() | uint8_t | Z 键状态 (1=按下) |
nunchuk_buttonC() | uint8_t | C 键状态 (1=按下) |
nunchuk_joystickX() / Y() | int | 校准后摇杆值 (约 -128~127) |
nunchuk_joystickX_raw() / Y_raw() | int | 原始 0-255 值 |
nunchuk_joystick_angle() | float | 摇杆角度 (弧度, -π~π) |
nunchuk_accelX() / Y() / Z() | int | 校准后加速度 (约 -512~512, 1g≈200) |
nunchuk_pitch() | float | 俯仰角 (弧度, ±π/2) |
nunchuk_roll() | float | 横滚角 (弧度, ±π),俯仰>90°时翻转 |
无 Yaw (偏航角):加速度计测重力矢量,无法感知绕竖直轴旋转。
Arduino 示例代码
#include <Wire.h>
#include "nunchuk.h"
void setup() {
Serial.begin(9600);
Wire.begin();
// nunchuk_init_power(); // 若用 WiiChuck 转接板取消注释
nunchuk_init();
}
void loop() {
if (nunchuk_read()) {
Serial.print("Joy X: "); Serial.print(nunchuk_joystickX());
Serial.print(" Y: "); Serial.print(nunchuk_joystickY());
Serial.print(" | Accel X: "); Serial.print(nunchuk_accelX());
Serial.print(" Y: "); Serial.print(nunchuk_accelY());
Serial.print(" Z: "); Serial.print(nunchuk_accelZ());
Serial.print(" | Pitch: "); Serial.print(nunchuk_pitch());
Serial.print(" Roll: "); Serial.println(nunchuk_roll());
Serial.print(" Btn Z: "); Serial.print(nunchuk_buttonZ());
Serial.print(" C: "); Serial.println(nunchuk_buttonC());
}
delay(10);
}
Processing 可视化演示
库附带 NunchukDemo.pde,运行后屏幕显示一个圆锥,随 Nunchuk 姿态倾斜,直观验证俯仰/横滚计算。
校准指南
- 运行示例,观察串口输出原始值
nunchuk_joystickX_raw()等 - 静止水平放置手柄,记录加速度计 X/Y/Z 原始零点值
- 摇杆推至四个极限,记录最大/最小值
- 修改库文件开头的常量定义:
JOY_X_CENTER,JOY_X_MIN,JOY_X_MAX等 - 重新编译上传,校准后数据更准确
常见问题
- 读取全 0 / 0xFF:检查 I2C 接线、电平转换、供电 (3.3V)、上拉电阻 (电平转换板通常自带)
- 副本手柄无响应:尝试标准初始化序列
START, 0x40, 0x00, STOP(启用加密),或尝试不同初始化延时 - 数据抖动大:库未内置滤波,建议应用层加一阶互补滤波或卡尔曼滤波 (作者提到已应用卡尔曼平滑副本差异)
- 俯仰/横滚角异常:确认手柄安装朝向与坐标系定义一致 (X 前后, Y 左右, Z 垂直)
资源链接
制造工艺
- 树莓派无需 LIRC 捕获红外遥控指令:Python UART 方案
- Arduino Mega 按键 PWM 调光:三阶段渐变 LED 实战教程
- 用 Arduino 打造绚丽显示器 Ambilight 系统
- 基于 Arduino 与 Python 的可靠 RFID 考勤系统
- K30 CO₂ 传感器:室内空气质量实时监测
- 基于 1Sheeld 与 Arduino 的盲聋人士沟通辅助系统
- 智能存钱罐:Arduino Nano 控制硬币识别器实现余额显示
- Arduino 与智能手机打造智能电压表——简单易做的 DIY 项目
- 基于 Arduino 与 MAX30100 的实时物联网心率监测系统
- Arduino + HC-05 蓝牙控制 LED:MIT App Inventor 手机 App 开发全攻略
- Arduino Mega 驱动 Wii Nunchuk 控制 6 自由度机械臂
- MAX30100脉搏血氧传感器与Arduino接入指南:获取PPG波形与SpO2