一、所选主题和项目介绍
1.1 项目背景
在智能穿戴和物联网应用场景中,手势控制是一种直观、自然的交互方式。传统的遥控器需要双手操作,不够便捷。本项目使用M5Stack M5StickC PLUS2智能手表开发套件(K016-H2),实现一款手腕运动状态检测控制器,通过检测手腕的倾斜姿态来识别运动意图,为后续遥控小车控制提供手势输入接口,实现单手便捷控制。
1.2 项目目标
本项目使用M5Stack M5StickC PLUS2 (K016-H2) 智能手表套装(含表带配件),实现一款手腕运动状态检测控制器,主要功能如下:
- 姿态检测:通过六轴IMU传感器(MPU6886)实时检测手腕倾斜角度
- 状态识别:识别前进、后退、左转、右转、停止五种手势状态
- 实时显示:在1.14寸TFT屏幕上实时显示当前状态、方向箭头和传感器数据
- 方向指示:使用三角形箭头图形直观指示当前运动方向
- 命令输出:为后续遥控小车控制提供运动命令接口
- 可穿戴设计:配合表带配件,实现便携式可穿戴控制
1.3 创新点
- 手势控制:通过手腕姿态实现直观的运动控制,无需按键操作
- 实时反馈:屏幕实时显示姿态角度、运动状态和方向箭头
- 可调节阈值:支持调节检测灵敏度,适应不同用户习惯
- 方向指示器:使用三角形箭头图形,直观显示当前运动方向
- 模块化设计:代码采用类封装,便于扩展到其他控制场景
- 可穿戴形态:配合表带配件,实现真正的可穿戴控制器
二、硬件介绍
2.1 主控设备
M5StickC PLUS2 (SKU: K016-H2)
M5StickC PLUS2是M5StickC Plus的迭代版本,采用ESP32-PICO-V3-02芯片作为主控。在小巧的机身内部,集成了丰富多样的硬件资源,例如红外、RTC、麦克风、LED、IMU、按键、蜂鸣器等。它采用由ST7789V2驱动的1.14寸TFT屏幕,分辨率为135×240。电池容量增加到了200mAh,接口方面同样支持HAT与Unit系列产品。
核心参数
参数 | 规格 |
|---|---|
型号 | K016-H2 (M5StickC PLUS2 Watch Kit) |
主控芯片 | ESP32-PICO-V3-02 |
CPU | 双核 Xtensa LX7,240MHz |
Flash | 8MB |
PSRAM | 2MB |
Wi-Fi | 2.4 GHz Wi-Fi |
USB | USB Type-C |
人机交互
参数 | 规格 |
|---|---|
显示屏 | ST7789V2,1.14英寸,135×240像素 |
按键 | 自定义按键×3 (A/B/C) |
LED | 绿色LED×1 (休眠指示),红色LED×1 (与红外共用) |
蜂鸣器 | 板载无源蜂鸣器 |
麦克风 | MEMS麦克风 SPM1423 |
传感器
参数 | 规格 |
|---|---|
IMU | MPU6886 (六轴姿态传感器) |
RTC | BM8563 (实时时钟) |
扩展接口
参数 | 规格 |
|---|---|
Grove接口 | 1× HY2.0-4P (I2C+I/O+UART) |
外接引脚 | G0,G25/G26,G36,G32,G33 |
电源参数
参数 | 规格 |
|---|---|
电池 | |
输入电压 | 5V@500mA |
工作温度 | 0~40°C |
物理参数
参数 | 规格 |
|---|---|
产品尺寸 | 48.0 × 24.0 × 13.5 mm |
产品重量 | 16.7 g |
2.2 手表配件
M5StickC Plus2 Watch Kit (SKU: K016-H2)
手表套装包含M5StickC PLUS2开发板和一系列表带配件,可将设备佩戴在手腕上,实现可穿戴应用。
配件 | 数量 |
|---|---|
StickC-Plus2 | 1 |
LEGO适配件 | 1 |
Wall/1515 | 1 |
表带 | 1 |
Type-C USB(50cm) | 1 |
2.3 IMU传感器
MPU6886 六轴姿态传感器
参数 | 规格 |
|---|---|
传感器类型 | 3轴加速度计 + 3轴陀螺仪 |
通信接口 | I2C |
I2C地址 | 0x68 |
加速度量程 | ±2g / ±4g / ±8g / ±16g |
陀螺仪量程 | ±250 / ±500 / ±1000 / ±2000 °/s |
分辨率 | 16位ADC |
2.4 硬件框图

三、方案框图和项目设计思路
3.1 系统架构

3.2 手腕姿态检测原理
通过加速度计数据计算手腕倾斜角度:
- Pitch角 (俯仰角):前后倾斜角度
- 正值:手腕向上抬起(手背向上) → 后退 (BACKWARD)
- 负值:手腕向下压(手心向下) → 前进 (FORWARD)
- Roll角 (横滚角):左右倾斜角度
- 正值:手腕向右倾斜 → 右转 (RIGHT)
- 负值:手腕向左倾斜 → 左转 (LEFT)
注意:由于设备佩戴在右手上,且屏幕朝外,当手腕自然水平时屏幕几乎朝天。因此抬腕(Pitch增大)对应的是手腕向后翻转,映射为后退;压腕(Pitch减小)对应手腕向前翻转,映射为前进。这与左手佩戴或桌面放置时的方向定义相反。
- 角度计算公式:
Pitch = atan2(ay, sqrt(ax² + az²))
Roll = atan2(-ax, az)
3.3 状态检测流程

3.4 运动控制映射
手腕姿态 | Pitch角度 | Roll角度 | 控制命令 | 小车动作 |
|---|---|---|---|---|
向下压 | < -15° | - | FORWARD | 前进 |
向上抬起 | > 15° | - | BACKWARD | 后退 |
向左倾斜 | - | < -15° | LEFT | 左转 |
向右倾斜 | - | > 15° | RIGHT | 右转 |
水平 | ±15°内 | ±15°内 | STOP | 停止 |
四、原理图和PCB展示及介绍
4.1 原理图展示

I2C适配板原理图
4.2 PCB布局展示

PCB
设计过程
电路设计采用KiCad完成,包括原理图设计和PCB布线两个环节。在具体实现上,在指导老师的帮助下完成了电路设计的审阅和优化,方便用于增加外设控制。
五、关键代码介绍
5.1 IMU数据读取API
MPU6886通过内部I2C接口连接。在UiFlow2 MicroPython固件中,IMU通过全局Imu对象访问,无需手动初始化I2C:
# 通过全局Imu对象读取6轴数据
accel = Imu.getAccel() # 返回 (ax, ay, az),单位G
gyro = Imu.getGyro() # 返回 (gx, gy, gz),单位°/s
注意:Imu是全局单例对象,由from M5 import *自动导入,不需要创建实例。不支持单轴读取方法,必须通过getAccel()一次读取三轴数据。
5.2 主程序结构
"""
文件名: wrist_motion_controller.py
功能: 手腕状态检测与显示控制器
硬件: M5StickC PLUS2 (K016-H2)
用途: 检测手腕姿态运动,用于控制遥控小车前进后退
"""
5.2.1 import导入
import M5
from M5 import *
import time
import math
关键点:
from M5 import *会导入全局Imu对象(IMU传感器)和Widgets(屏幕控件)、Lcd(底层绘图)等模块- 不需要额外导入
hardware模块或手动初始化I2C
5.3 手腕运动检测类
class WristMotionDetector:
"""
手腕运动检测器类
基于MPU6886六轴传感器检测手腕姿态:
- Pitch > +15度: 抬腕 -> BACKWARD (后退)
- Pitch < -15度: 压腕 -> FORWARD (前进)
- Roll > +15度: 右倾 -> RIGHT (右转)
- Roll < -15度: 左倾 -> LEFT (左转)
- 其他: STOP (停止)
"""
def __init__(self):
self.ax = 0.0
self.ay = 0.0
self.az = 0.0
self.pitch = 0.0
self.roll = 0.0
self.state = "STOP"
self.threshold_pitch = 15.0
self.threshold_roll = 15.0
def read_imu(self):
"""通过全局Imu对象读取6轴数据"""
accel = Imu.getAccel() # 返回三轴加速度
gyro = Imu.getGyro() # 返回三轴角速度
self.ax, self.ay, self.az = accel
self.gx, self.gy, self.gz = gyro
return (self.ax, self.ay, self.az,
self.gx, self.gy, self.gz)
def calculate_angles(self):
"""计算俯仰角(pitch)和横滚角(roll)"""
self.read_imu()
ax, ay, az = self.ax, self.ay, self.az
norm = math.sqrt(ax*ax + ay*ay + az*az)
if norm > 0:
ax, ay, az = ax/norm, ay/norm, az/norm
self.pitch = math.degrees(
math.atan2(ay, math.sqrt(ax*ax + az*az)))
self.roll = math.degrees(
math.atan2(-ax, az))
return (self.pitch, self.roll)
def detect_state(self):
"""检测手腕状态(右手佩戴,抬腕=后退)"""
self.calculate_angles()
if self.pitch > self.threshold_pitch:
self.state = "BACKWARD" # 抬腕 = 后退
elif self.pitch < -self.threshold_pitch:
self.state = "FORWARD" # 压腕 = 前进
elif self.roll > self.threshold_roll:
self.state = "RIGHT"
elif self.roll < -self.threshold_roll:
self.state = "LEFT"
else:
self.state = "STOP"
return self.state
def get_motion_command(self):
"""将手腕状态转换为电机控制命令"""
self.detect_state()
commands = {}
commands["FORWARD"] = {"motor_left": 100, "motor_right": 100, "direction": "Forward"}
commands["BACKWARD"] = {"motor_left": -100, "motor_right": -100, "direction": "Backward"}
commands["LEFT"] = {"motor_left": -50, "motor_right": 50, "direction": "Left"}
commands["RIGHT"] = {"motor_left": 50, "motor_right": -50, "direction": "Right"}
commands["STOP"] = {"motor_left": 0, "motor_right": 0, "direction": "Stop"}
return commands.get(self.state, commands["STOP"])
IMU API说明:
Imu是UiFlow2固件提供的全局单例对象,由from M5 import *自动创建Imu.getAccel()返回三元组(ax, ay, az),单位GImu.getGyro()返回三元组(gx, gy, gz),单位度/秒- 不支持和
Imu.getAccX()等单轴方法,也不支持构造IMU()或M5.IMU访问 - 数据无需转换,可直接用于角度计算
方向说明:设备戴在右手上、屏幕朝外时,抬腕(手背向上)对应Pitch正值,此时手腕实际姿势是向后翻转,因此映射为BACKWARD(后退);压腕(手心向下)映射为FORWARD(前进)。
5.4 显示管理类
class DisplayManager:
"""
显示管理器类
管理M5StickC PLUS2的TFT屏幕(135x240竖屏)显示内容
- 状态文字 + 方向箭头 + 传感器数据
"""
def __init__(self):
Widgets.fillScreen(0x000000)
# 标题 (DejaVu12, 白色)
self.title_label = Widgets.Label("Wrist Ctrl", 35, 3, 1.0,
0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu12)
# 状态 (DejaVu18, 黄色)
self.state_label = Widgets.Label("STOP", 5, 26, 1.0,
0xFFFF00, 0x000000, Widgets.FONTS.DejaVu18)
# 传感器数据 (DejaVu12)
self.pitch_label = Widgets.Label("Pitch: 0.0", 5, 74, 1.0,
0xFFFF00, 0x000000, Widgets.FONTS.DejaVu12)
self.roll_label = Widgets.Label("Roll: 0.0", 5, 92, 1.0,
0xFFFF00, 0x000000, Widgets.FONTS.DejaVu12)
self.ax_label = Widgets.Label("AX: 0.00", 5, 116, 1.0,
0x00FFFF, 0x000000, Widgets.FONTS.DejaVu12)
self.ay_label = Widgets.Label("AY: 0.00", 5, 134, 1.0,
0x00FFFF, 0x000000, Widgets.FONTS.DejaVu12)
self.az_label = Widgets.Label("AZ: 0.00", 5, 152, 1.0,
0x00FFFF, 0x000000, Widgets.FONTS.DejaVu12)
self.last_direction = "STOP"
def _draw_arrow(self, cx, cy, direction, color):
"""在指定位置绘制方向箭头"""
s = 12 # 三角形半边长
if direction == "FORWARD":
# 向上三角形
Lcd.fillTriangle(cx, cy-s, cx-s, cy+s, cx+s, cy+s, color)
elif direction == "BACKWARD":
# 向下三角形
Lcd.fillTriangle(cx, cy+s, cx-s, cy-s, cx+s, cy-s, color)
elif direction == "LEFT":
# 向左三角形
Lcd.fillTriangle(cx-s, cy, cx+s, cy-s, cx+s, cy+s, color)
elif direction == "RIGHT":
# 向右三角形
Lcd.fillTriangle(cx+s, cy, cx-s, cy-s, cx-s, cy+s, color)
else: # STOP
Lcd.fillRect(cx-s, cy-s, s*2, s*2, color)
def update_display(self, detector):
"""更新显示内容和方向箭头"""
state = detector.state
# 各状态对应颜色: 绿色/红色/蓝色/紫色/黄色
state_colors = {}
state_colors["FORWARD"] = 0x00FF00
state_colors["BACKWARD"] = 0xFF0000
state_colors["LEFT"] = 0x0000FF
state_colors["RIGHT"] = 0xFF00FF
state_colors["STOP"] = 0xFFFF00
color = state_colors.get(state, 0xFFFFFF)
self.state_label.setColor(color, 0x000000)
self.state_label.setText(state)
self.pitch_label.setText("Pitch: {:.1f}".format(detector.pitch))
self.roll_label.setText("Roll: {:.1f}".format(detector.roll))
self.ax_label.setText("AX: {:.2f}".format(detector.ax))
self.ay_label.setText("AY: {:.2f}".format(detector.ay))
self.az_label.setText("AZ: {:.2f}".format(detector.az))
# 方向变化时清除旧箭头并绘制新箭头
if state != self.last_direction:
clear_x = 56 # 箭头区域居中
clear_y = 46 # 状态文字正下方
clear_size = 24
Lcd.fillRect(clear_x, clear_y, clear_size, clear_size, 0x000000)
arrow_cx = clear_x + clear_size // 2
arrow_cy = clear_y + clear_size // 2
self._draw_arrow(arrow_cx, arrow_cy, state, color)
self.last_direction = state
显示API说明:
Widgets.Label()是文本标签控件,构造参数为:(文字, x, y, 缩放, 前景色, 背景色, 字体)Widgets.FONTS.DejaVu12和DejaVu18是可用字体,不支持中文字符Lcd.fillTriangle()用于绘制方向箭头三角形- 不支持
M5TextBox或M5.Lcd.FONT_DejaVu等旧API - 屏幕分辨率为135x240竖屏,需使用
Widgets.setRotation(0)设置正常方向
5.5 主循环逻辑
def main():
"""主程序入口"""
M5.begin()
Widgets.setRotation(0) # 竖屏正常方向
print("=" * 40)
print("Wrist Motion Controller")
print("M5StickC PLUS2 (K016-H2)")
print("=" * 40)
# 初始化IMU
try:
accel = Imu.getAccel()
gyro = Imu.getGyro()
print("[OK] IMU init success")
except Exception as e:
print("[ERROR] IMU init failed:", e)
return
detector = WristMotionDetector()
display = DisplayManager()
last_state = ""
update_counter = 0
paused = False
while True:
M5.update()
# 按键A: 暂停/恢复检测
if M5.BtnA.wasPressed():
paused = not paused
time.sleep_ms(200)
if paused:
time.sleep_ms(100)
continue
# 检测手腕状态
current_state = detector.detect_state()
# 每5帧更新一次显示(减少刷新频率)
if update_counter % 5 == 0:
display.update_display(detector)
# 状态变化时输出命令
if current_state != last_state:
cmd = detector.get_motion_command()
print("[STATE] {} -> {}".format(last_state, current_state))
last_state = current_state
update_counter += 1
time.sleep_ms(50)
主循环说明:
- 使用
update_counter % 5控制屏幕刷新频率为10Hz(主循环50ms×5=250ms) - BtnA实现暂停/恢复功能,而非切换阈值
M5.update()必须在每次循环中调用,用于处理按键事件和系统任务
六、硬件功能展示图及说明
6.1 显示界面
6.1.1 显示布局
屏幕为1.14寸ST7789V2竖屏显示,分辨率135x240像素。使用Widgets.setRotation(0)设置正常方向。


6.1.2 各状态显示
状态 | 颜色 | 手势 | 小车动作 | 箭头方向 |
|---|---|---|---|---|
FORWARD | 绿色 | 手腕下压 | 前进 | 向上 |
BACKWARD | 红色 | 手腕上抬 | 后退 | 向下 |
LEFT | 蓝色 | 手腕左倾 | 左转 | 向左 |
RIGHT | 紫色 | 手腕右倾 | 右转 | 向右 |
STOP | 黄色 | 手腕水平 | 停止 | 方形 |
6.2 系统运行日志
========================================
Wrist Motion Controller
M5StickC PLUS2 (K016-H2)
========================================
[OK] IMU init success
[OK] Accel: 0.02, -0.01, 1.01
[OK] Gyro: 0.00, 0.00, 0.00
[OK] Starting main loop...
----------------------------------------
[STATE] STOP -> BACKWARD
[CMD] Direction: Backward, Motors: L:-100 R:-100
[STATE] BACKWARD -> STOP
[CMD] Direction: Stop, Motors: L:0 R:0
[STATE] STOP -> FORWARD
[CMD] Direction: Forward, Motors: L:100 R:100
[PAUSE] Detection paused
[RESUME] Detection resumed
七、设计中遇到的难题和解决方法
7.1 IMU API差异(全局单例 vs 构造函数)
问题描述:初次编写代码时使用了from hardware import IMU或M5.IMU.getAccX()方式访问IMU,导致运行时报错AttributeError或ImportError。
解决方案: UiFlow2 MicroPython固件中,IMU通过from M5 import *导入的全局单例Imu对象访问,而非通过构造函数创建。正确的API为:
from M5 import *
accel = Imu.getAccel() # 返回 (ax, ay, az)
gyro = Imu.getGyro() # 返回 (gx, gy, gz)
不支持Imu.getAccX()单轴方法或IMU()构造函数。
7.2 字典字面量初始化导致SyntaxError
问题描述:直接使用字典字面量state_colors = {"FORWARD": 0x00FF00, ...}会在设备上报错SyntaxError。
根本原因:某些版本的MicroPython对字典字面量语法解析有限制,特别是混合使用字符串键和非数字值时。
解决方案:改用空字典逐步赋值:
state_colors = {}
state_colors["FORWARD"] = 0x00FF00
state_colors["BACKWARD"] = 0xFF0000
# ...
同理,commands字典也使用相同的增量方式构建。
7.3 屏幕旋转方向校准
问题描述:屏幕初始显示方向错误,内容上下颠倒、左右反置。
解决方案: 使用Widgets.setRotation(n)进行校准,其中n为:
0:正常方向(USB口朝下,内容可读)2:180度翻转
由于M5StickC戴在右手上且屏幕朝外,最佳方向为setRotation(0)(竖屏模式)。经过多次测试确定该方向在手腕自然水平时显示内容可正常阅读。
7.4 前后方向映射反转(右手佩戴)
问题描述:初始逻辑将Pitch正值(抬腕)映射为"前进",但实际测试时抬腕感觉更像"后退"动作。
解决方案: 由于M5StickC戴在右手上、屏幕朝外:
- 抬腕(Pitch > 15度)= 手腕向后翻转 = BACKWARD(后退)
- 压腕(Pitch < -15度)= 手腕向前翻转 = FORWARD(前进)
这与桌面放置或左手佩戴时的方向感知相反,需要根据实际佩戴习惯调整映射。
7.5 方向箭头被状态文字遮挡
问题描述:在屏幕右侧绘制的方向箭头与状态文字"BACKWARD"/"FORWARD"在垂直方向重叠,导致箭头不可见或被文字覆盖。
解决方案:
- 将箭头从屏幕右侧(x=106)移至状态文字正下方并居中(x=56,y=46)
- 缩小箭头三角形尺寸(半边长s从14减小到12)
- 传感器数据标签整体下移4px,避免与箭头区域重叠
- 最终布局:状态文字 y=26,箭头 y=46,Pitch y=74,依次间隔4px
八、心得体会
8.1 项目收获
- IMU传感器应用:深入理解了六轴传感器的工作原理和姿态解算方法,掌握了通过加速度计计算Pitch/Roll角度的公式和归一化处理
- UiFlow2 MicroPython开发:掌握了ESP32平台上MicroPython固件的开发流程,包括设备连接、代码上传、串口调试等
- 显示API适配:实际验证了UiFlow2固件的
Widgets模块和Lcd绘图API,了解了与官方文档示例的差异 - 可穿戴方向映射:实践了右手佩戴场景下的方向映射逻辑(抬腕=后退、压腕=前进),理解了坐标系变换的重要性
- MicroPython陷阱排查:积累了MicroPython语法限制(字典字面量、UTF-8编码)的排查经验
- 迭代调试技巧:通过屏幕显示+串口输出双重反馈进行状态调试,大幅缩短了问题定位时间
8.2 技术亮点
- 直观交互:通过手腕姿态实现自然的运动控制,无需按键操作
- 实时反馈:屏幕实时显示姿态角度、运动状态和方向箭头
- 方向指示器:使用三角形箭头图形直观显示当前运动方向
- 模块化设计:采用WristMotionDetector(检测)+ DisplayManager(显示)双层架构,职责清晰
- 刷新优化:使用计数器控制屏幕刷新频率,降低CPU占用
8.3 改进建议
- 无线通信:添加Wi-Fi/蓝牙通信(如UDP或蓝牙SPP),实现真正的遥控功能
- 滤波算法:实现互补滤波或卡尔曼滤波,融合加速度计和陀螺仪数据提高角度精度
- 手势扩展:增加更多手势识别,如摇动(用于鸣笛或切换模式)、旋转等
- 低功耗设计:实现休眠唤醒机制,利用RTC定时唤醒,延长续航时间
- 阈值自适应:根据用户手腕抖动幅度自动调整检测阈值,减少误触发
- 振动反馈:利用板载蜂鸣器或外接振动马达,提供状态变化触觉反馈
- OTA升级:支持通过Wi-Fi远程固件升级
- 数据记录:记录运动轨迹到SD卡或通过Wi-Fi上传,用于后续数据分析
创意方向关联
本项目的技术探索为以下创意方向奠定了基础:
1. 人工智能在嵌入式系统中的应用
本项目的IMU姿态检测技术为AI应用提供了基础:
- 故障保护:人工智能判断:运动状态异常检测可发展为AI驱动的健康预警系统
- 计算机视觉:IMU数据与视觉融合实现更精确的姿态识别
- 图像识别:手势识别技术可扩展为基于视觉的手势交互系统
2. 机械臂 / 工业自动化 / 元宇宙
手势控制技术在工业领域有重要应用:
- 机械臂控制:手势控制可作为机械臂的示教输入方式
- 工业自动化:手势控制实现无接触设备操作
- 元宇宙:手势识别是元宇宙人机交互的核心技术
3. 楼宇自动化
手势控制技术在楼宇自动化中有广泛应用:
- 无接触开关:手势控制灯光、窗帘等设备
- 智能家电控制:手势控制电视、空调等家电
- 无障碍设施:为残障人士提供便捷控制方式
技术迁移价值
本项目开发的技术方案可迁移到:
- 人工智能应用:手势识别、运动健康分析
- 机械臂/自动化:手势示教、无接触控制
- 楼宇自动化:智能开关、无障碍控制
致谢
感谢 DigiKey 和 电子森林 提供的FastBond4活动支持,本次活动链接:https://www.eetree.cn/page/digikey-fastbond