FastBond4挑战部分-M5Dial外接Unit-Joystick2霍尔摇杆的ESP-NOW双端画面同步系统
该项目使用了M5Stack M5Dial V1.1开发板,实现了一套基于ESP-NOW协议的双终端图片无线联动切换系统的设计,它的主要功能为:摇杆按键输入:通过Joystick2的Z轴数字按键(LEFT-SW-B)驱动画面切换;自动无线组网:上电后借助MASTER\_PING / SLAVE\_JOIN 协议自主协商主从角色;跨端同步切换:LOCAL端按下摇杆按钮后本地立即切换画面,同时通过ESP-NOW广播SYNC消息驱动对端同步切换。
标签
m5dial
FastBond4
ESP-NOW
Unit-Joystick2
LEFT-SW-B
无线联动
量子回
更新2026-07-07
8

一、所选主题和项目介绍

1.1 项目背景

伴随物联网领域多终端协作场景的普及,设备间无线联动控制的需求日益迫切。传统方案往往依赖专用遥控硬件或繁琐的网络预配置,增加了部署难度和使用门槛。为解决这一问题,本设计选取M5Stack M5Dial V1.1为主控核心,外接Unit-Joystick2霍尔摇杆作为人机交互入口,借助ESP-NOW广播协议实现两台设备间的自动发现与画面联动。使用者只需按压任意一台Joystick2的摇杆轴按键(LEFT-SW-B),两端连接的M5Dial即可同步完成画面切换,形成"一按双响"的无线协同体验。

1.2 项目目标

本方案基于M5Dial V1.1 (K130-V11) 与 Unit-Joystick2 (U024-V2) 构建,核心功能规划如下:

  1. 摇杆按键输入:通过Joystick2的Z轴数字按键(LEFT-SW-B)驱动画面切换
  2. 自动无线组网:上电后借助MASTER_PING / SLAVE_JOIN 协议自主协商主从角色
  3. 跨端同步切换:LOCAL端按下摇杆按钮后本地立即切换画面,同时通过ESP-NOW广播SYNC消息驱动对端同步切换
  4. LED多态指示:利用Joystick2板载WS2812 RGB灯珠呈现连接状态与画面状态
  5. 随机延迟避让:引入随机延时机制,从根本上杜绝双设备同时升格为Master的可能性

1.3 创新点

  1. 纯广播自动配对:基于ESP-NOW广播信道实现Master/Slave自动协商,无需预配任何地址或网络信息
  2. 霍尔磁感应触发:选用非接触式霍尔电磁摇杆作为交互元件,具备耐磨、高精度、长寿命优势
  3. WS2812联动反馈:将Joystick2的RGB灯珠与连接进展、画面状态绑定,提供直观的光效指示
  4. 毫秒级低延迟:利用ESP-NOW广播特性将同步延迟压缩至毫秒量级
  5. 开箱即配对:双设备上电后自动完成角色协商与连接,使用者无需任何手动干预

二、硬件介绍

2.1 主控设备

主控型号:M5Stack Dial V1.1 (产品编码 K130-V11)

以ESP32-S3为核心驱动的圆形智能交互终端,集成了1.28吋圆形TFT彩屏、电容触摸层、旋转编码器与Grove扩展座,同时具备WiFi与ESP-NOW无线能力。

  • 处理器:ESP32-S3FN8,双核240MHz
  • 存储:8MB Flash + 2MB PSRAM
  • 显示:1.28" 圆形 TFT,分辨率240×240,驱动IC ST7789V2
  • 触控:电容式触摸面板
  • 扩充:Grove HY2.0-4P (Port.A,I2C)
  • 无线:WiFi 802.11 b/g/n + BLE 5.0
  • 编码器:20脉冲/圈,内嵌按压开关

2.2 传感器模块

传感单元:M5Stack Unit-Joystick2 (产品编码 U024-V2)

基于STM32G030F6P6的高精度霍尔磁感摇杆,通过I2C总线与主控通信,提供三轴信号(X/Y模拟位移 + Z轴数字按键),并搭载可编程全彩LED。

  • 内嵌MCU:STM32G030F6P6 @ Cortex-M0+,64MHz
  • 通信方式:I2C,设备地址 0x63
  • X/Y输出:16bit ADC 原始值,范围 0~65535
  • Z轴按键:LEFT-SW-B,数字电平 0/1
  • 指示灯:WS2812C 全彩LED × 1
  • 外形尺寸:40.0 × 24.0 × 23.9 mm

2.3 对外接线

侧(M5Dial Port.A)

侧(Joystick2)

信号描述

GND (黑)

GND

电源回路地

5V (红)

5V

电源供电正

SDA (白)

SDA

I2C 数据线

SCL (黄)

SCL

I2C 时钟线

2.4 Joystick2管脚分配

STM32引脚

用途

说明

PA1

LEFT-SW-X

X轴模拟量输入

PA2

LEFT-SW-Y

Y轴模拟量输入

PA3

LEFT-SW-B

Z轴数字按键(本方案使用)

PA4

RGB-LED

WS2812C 灯珠控制信号

三、方案框图和项目设计思路

3.1 系统架构

系统架构图

Master 端工作流

Slave 端工作流

3.2 设备启动流程

设备启动流程

3.3 Master/Slave角色协商协议

整个自动配对过程由三条消息驱动:

  1. MASTER_PING(Master → 广播域)
    Master 端每 2 s 发出一次 PING 声明自身存在;尚未配对的设备收到后自动降为 Slave 并回复 JOIN。
  2. SLAVE_JOIN(Slave → 广播域)
    Slave 端收到 PING 后立即广播 JOIN 回执;Master 收到后建立 peer 表项并以 SYNC 确认。
  3. SYNC(双向同步触发)
    连接建成后,任一端的摇杆按键按下即广播 SYNC;对端收到后执行与本地相同的画面切换动作。

四、原理图和PCB展示及介绍

4.1 原理图展示


4.2 PCB布局展示

4.3 设计过程

使用KiCad EDA工具完成了原理图设计和PCB布局布线。作为电路设计的新手,这次从零开始画原理图、布PCB的经历让我学到了很多。

4.4 硬件接线图

本项目使用自设计的I2C适配板(PCB)连接 M5Dial 主控与 Joystick2 外设模块。适配板通过自定义 PCB 走线将 M5Dial Port.A 接口的 I2C 信号引出至 Joystick2 的对应引脚,实现了稳定的电气连接与信号传输。

M5Dial Port.A 与 Joystick2 之间通过 Grove 线缆进行连接,具体引脚对应关系如下:

  • 红线(VCC):M5Dial Port.A 5V 输出 → Joystick2 5V 输入,为模块提供电源;
  • 黑线(GND):M5Dial Port.A GND → Joystick2 GND,构成电源回路;
  • 黄线(SCL):M5Dial Port.A SCL → Joystick2 SCL,传输 I2C 时钟信号;
  • 白线(SDA):M5Dial Port.A SDA → Joystick2 SDA,传输 I2C 数据信号。

Joystick2 内部采用 STM32G030F6P6 作为从机 MCU,I2C 设备地址为 0x63。其管脚功能分配如下:

  • PA1:X 轴模拟量输入(LEFT-SW-X),输出 16bit ADC 值;
  • PA2:Y 轴模拟量输入(LEFT-SW-Y),输出 16bit ADC 值;
  • PA3:Z 轴数字按键(LEFT-SW-B),为本方案使用的画面切换触发源;
  • PA4:WS2812C 全彩 LED 控制信号,用于输出连接状态与画面状态指示。

4.5 ESP-NOW无线配对时序

Master 与 Slave 两端的角色协商与同步过程由以下四个步骤组成:

  1. Master 声明存在(MASTER_PING):Master 端以 2 秒为周期向广播域发送 MASTER_PING 消息,声明自身已就绪。尚未建立配对的设备收到该消息后,自动检测到已有 Master 存在,随即将自身角色降为 Slave。
  2. Slave 回执加入(SLAVE_JOIN):Slave 端收到 MASTER_PING 后,立即向广播域回复 SLAVE_JOIN 回执消息。Master 端收到该回执后,在本地建立对端 peer 表项,并以 SYNC 消息确认连接建立。
  3. Master 确认连接(SYNC):Master 端发送 SYNC 消息确认连接已就绪;Slave 端收到后也在本地建立 peer 表项。至此双方 peer 表项均建立完毕,连接正式就绪。
  4. 对端同步触发(按键 → SYNC):连接建成后,用户按下任意一端的摇杆按键(LEFT-SW-B)。按下端首先在本地切换当前显示画面,同时立即向广播域发送 SYNC 消息。对端收到该消息后执行与本地相同的画面切换动作,从而实现"一按双响"的无线联动效果。

五、软件流程图和关键代码介绍

代码已全部收录于 joystick2_sync.py,以下仅展示核心流程。

5.1 入口与 setup()

开机 → random_delay(0~5s)M5.begin()I2C(SCL=1,SDA=2)Joystick2Unit(0x63) → load_image → M5ESPNow(callback)
def setup():
time.sleep_ms(random.randint(0, 5000))
M5.begin()
Widgets.fillScreen(0x000000)

i2c = I2C(1, scl=Pin(1), sda=Pin(2), freq=100000)
joystick = Joystick2Unit(i2c, address=0x63)
joystick.fill_color(0x000000) # 初始熄灭灯珠

image0 = Widgets.Image("/flash/res/img/crazy-eyes-open.jpg", 0, 0)

now = M5ESPNow(0)
now.set_irq_callback(espnow_recv_callback)

初始化要点:

  1. 随机休眠 0~5 s — 错开双设备的启动时间窗口
  2. I2C 总线选择 I2C(1),Pin(1)=SCL、Pin(2)=SDA,适应 M5Dial Port.A 的物理连接
  3. Joystick2 的 I2C 地址固定为 0x63
  4. 灯珠默认熄灭,后续会根据连接进度和画面状态动态变色

5.2 组网回调

收到 SLAVE_JOIN → 升 Master,记录 peer,广播 SYNC 确认
收到 MASTER_PING → 降 Slave,记录 peer,广播 JOIN 回执
收到 SYNC → 直接调用 toggle_image()
def espnow_recv_callback(espnow_obj):
try:
mac, data = espnow_obj.recv_data()
msg = data.decode() if isinstance(data, bytes) else str(data)
mac_hex = mac_bytes_to_hex(mac) if isinstance(mac, bytes) else mac

if mac_hex == mac_bytes_to_hex(now.get_mac(0)): # 过滤自包
return

if msg == "SLAVE_JOIN" and peer_mac is None:
peer_mac, peer_id, is_master, connected = mac_hex, 1, True, True
now.set_add_peer(peer_mac, peer_id, 0, False)
now.broadcast_data("SYNC")

elif msg == "MASTER_PING":
if peer_mac is None:
peer_mac, peer_id, is_master, connected = mac_hex, 1, False, True
now.broadcast_data("SLAVE_JOIN")
now.set_add_peer(peer_mac, peer_id, 0, False)

elif msg == "SYNC":
toggle_image()
except Exception as e:
print("espnow error:", e)

三条消息覆盖完整生命周期:发现(PING)→ 注册(JOIN)→ 同步(SYNC)。

5.3 主循环与按键

def loop():
M5.update()

# ── 未连接:转发 PING 或自升 Master ──
if not connected:
t = time.ticks_ms()
if not is_master:
if t >= listen_timeout:
is_master = True
send_ping_time = t + 100
else:
if t >= send_ping_time:
now.broadcast_data("MASTER_PING")
send_ping_time = t + 2000
joystick.fill_color(0x0000FF) # 蓝灯待连
return

# ── 已连接 ──
joystick.fill_color(0x00FF00) # 绿灯就绪

# 上升沿检测
if joystick.get_button_status() and not prev_button:
toggle_image()
send_sync()
prev_button = joystick.get_button_status() # 保留当前值用于下次比对

上升沿判别:只响应 False→True 的跳变,按住不放不会重复触发。

5.4 画面切换

def toggle_image():
current_image_idx = 1 - current_image_idx
if current_image_idx == 0:
image0.setImage("/flash/res/img/crazy-eyes-open.jpg")
joystick.fill_color(0x00FF00) # 睁眼 → 绿
else:
image0.setImage("/flash/res/img/crazy-eyes-close.jpg")
joystick.fill_color(0xFF0000) # 闭眼 → 红

画面与灯色联动:睁眼=绿灯,闭眼=红灯,双端效果完全一致。

六、硬件功能展示图及说明

6.1 屏幕画面

6.2 LED 灯色含义

灯色

阶段

含义

熄灭

上电初始化

Joystick2 尚未就绪

蓝色

搜索中

未找到配对设备,持续监听或广播 PING

绿色

已连接 / 睁眼

连接正常,当前画面为睁眼

红色

已连接 / 闭眼

连接正常,当前画面为闭眼

实物展示1

6.3 典型启动日志

==================================================
M5Dial + Unit-Joystick2 Wireless Sync
==================================================
[Boot] Random delay: 2341 ms
[I2C] Init OK (SCL=1, SDA=2)
[Joystick2] Init OK (I2C addr 0x63)
[ESP-NOW] Callback registered
[Phase1] Waiting for MASTER_PING...
[Recv] MASTER_PING from d0cf130f446c
[Role] Became Slave
[Send] SLAVE_JOIN (broadcast)
[Peer] Added d0cf130f446c
[Recv] SYNC from d0cf130f446c
[Image] Switched to close
==================================================
[Loop] Sync ready — press LEFT-SW-B to toggle

实物展示

实物展示

实物展示

实物展示

七、设计中遇到的难题和解决方法

7.1 两端同时升 Master

现象:双设备几乎同时上电时可能各自升为 Master,相互只发 PING 不收 JOIN,无法连通。

对策

  1. 启动阶段插入 0~5 s 随机 sleep_ms,破坏时间同步性
  2. 先监听 5 s,期间仅收不发,收不到 PING 才自升 Master
  3. "先占"原则:先成为 Master 的一方持续广播,等待对方加入
delay = random.randint(0, 5000)
time.sleep_ms(delay)
# 5 秒后仍未收到 PING → 升 Master
if now_t >= listen_timeout:
is_master = True

7.2 广播自收干扰

现象:ESP-NOW 广播模式下本端也会收到自己发出的消息,导致回调误判。

对策:回调入口处用本机 MAC 过滤,自包直接丢弃。

if mac_hex == mac_bytes_to_hex(now.get_mac(0)):
return

7.3 按键机械抖动

现象:LEFT-SW-B 按下/释放瞬间触点抖动,单次按压可能被识别成多次。

对策

  1. 上升沿检测:仅记录从 False 变为 True 的那一次
  2. loop 以 ~20 ms 周期运行,天然具备低通滤波效果
btn = joystick.get_button_status()
if btn and not prev_button:
toggle_image()
send_sync()
prev_button = btn

7.4 I2C 引脚适配

现象:M5Dial 的 Port.A 引脚编号与其它 M5 设备不同,直接套用通用配置会通信失败。

对策:查阅 M5Dial V1.1 原理图,确认 Port.A 对应 SCL=Pin(1)、SDA=Pin(2),并在代码中显式指定。

i2c = I2C(1, scl=Pin(1), sda=Pin(2), freq=100000)
joystick = Joystick2Unit(i2c, address=0x63)

八、心得体会

8.1 项目收获

  1. ESP-NOW 协议实践:通过实际编码理解了 ESP-NOW 广播的收发模型与回调机制,掌握了基于消息的 Master/Slave 角色协商方法
  2. I2C 外设驱动:掌握了 Joystick2Unit 的初始化、按钮读取、RGB LED 控制等 API,加深了对 I2C 总线时序的理解
  3. M5Dial + UiFlow2 开发:熟悉了 M5Dial 的屏幕驱动、图片加载、ESP-NOW 模块的使用流程
  4. 双端同步设计:从信号产生、本地响应到无线广播、对端执行,完整实践了一条端到端同步链路
  5. 轻量协议设计:用 PING/JOIN/SYNC 三条消息完成了自动发现、角色协商、数据同步三大功能

8.2 技术亮点

  1. 零手工配置:上电即自动配对,无需预存任何 MAC 或 SSID
  2. 霍尔按键输入:非接触式磁感摇杆,杜绝机械磨损隐患
  3. 灯色联动:WS2812 灯珠与连接状态、画面状态三者绑定一次到位
  4. 秒级同步:ESP-NOW 广播延迟在毫秒级,人眼几乎无感知
  5. 双重防冲突:随机延时 + 超时监听两道防线确保角色分配可靠

8.3 改进方向

  1. 信道加密:对 ESP-NOW 载荷做 AES 加密,防范空中窃听
  2. 多端组网:将 1 对 1 拓展为 1 对 N,实现多设备同步切换
  3. X/Y 轴利用:将摇杆的模拟位移数据纳入交互,支持更多操控维度
  4. 心跳保活:定期心跳检测,断开后自动重连
  5. 模式切换:长按按键切换同步模式(主从 / 对等 / 独立)
  6. OTA 远程升级:通过网络推送固件,免去物理刷机
  7. 低功耗睡眠:无操作时进入深度睡眠,延长续航
  8. 配对持久化:将已配对设备信息写入 Flash,下次开机直连
  9. 多图轮播:利用 X/Y 轴选择图片,扩展内容展示能力

创意方向关联

本设计涉及的关键技术可向以下方向延伸:

无线通信方向

ESP-NOW 的低延迟广播机制可用于智能楼宇内的设备联动场景:

  • 局域联动:建筑内多终端通过 ESP-NOW 实现状态同步,无需中心网关
  • 广播迁移:ESP-NOW 的组播模型可为 5G 物联网的群发指令提供参考
  • 高带宽扩展:将 WiFi 作为骨干网承载大批量设备的数据聚合

嵌入式 AI 方向

摇杆 + 无线同步的交互框架可作为 AI 终端的输入/输出管道:

  • 人机接口:摇杆用作 AI 设备的指令输入装置
  • 协同推理:AI 推理结果通过 ESP-NOW 分发至多个终端同步呈现
  • 场景联动:单次触发驱动多个节点协同响应,适合智能家居场景

楼宇自动化方向

双端同步控制是建筑自动化的基础模块:

  • 联动控制:一个触发源驱动多处执行器同时动作
  • 状态镜像:分布式节点间的状态实时保持一致
  • 应急广播:紧急情况下同步触发全部关联设备

技术迁移清单

  • 无线层:ESP-NOW 广播 + 自动发现协议
  • 输入层:霍尔摇杆按键检测 + 上升沿去抖
  • 硬件层:M5Dial + UiFlow2 MicroPython
  • 应用层:多设备无线联动控制模式

致谢

感谢 DigiKey电子森林 提供的FastBond4活动支持,本次活动链接:https://www.eetree.cn/page/digikey-fastbond

附件下载
adapter.zip
joystick2_sync.py
团队介绍
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号