一、所选主题和项目介绍
1.1 项目背景
在智能监控、远程巡检、直播拍摄等场景中,云台(Pan-Tilt)控制系统是实现摄像头精准定位的核心设备。传统的专业云台系统成本高昂,且控制方式复杂。本项目使用M5Stack CoreS3开发板,配合GRBL Module 13.2步进电机驱动器,实现一款低成本、易操作的双轴云台控制系统,可搭载摄像头和激光笔,广泛应用于安防监控、工业巡检、智能拍摄等领域。
1.2 项目目标
本项目使用M5Stack CoreS3开发板,配合GRBL Module 13.2步进电机驱动器,实现一款二维云台控制系统,主要功能如下:
- 双轴云台控制:通过GRBL Module驱动两路步进电机,实现水平(Pan 0~180°)和垂直(Tilt 0~90°)双轴控制
- 按键手动操控:通过CoreS3内置的BtnA/B/C三个按键调节云台角度,按键切换控制模式
- 预设位置管理:支持4个预设位置的存储和快速定位
- 激光笔控制:通过GPIO3控制激光笔开关,BtnA长按1秒切换,辅助定位和指示
- 实时状态显示:320x240屏幕实时显示云台角度、激光状态和预设信息
1.3 创新点
- GRBL开源固件集成:将GRBL开源运动控制固件集成到M5Stack生态中,实现高性能步进电机控制
- 按键即控:利用CoreS3三个按键实现直观的云台角度调节,无需外接复杂控制设备
- 角度-毫米映射算法:建立云台角度与GRBL坐标之间的精准映射模型,简化控制逻辑
- 多模式切换:支持Pan控制、Tilt控制、Preset选择三种模式一键切换
- 按键长按复用:BtnA短按切换模式、长按切换激光,实现单一按键的多功能复用
- 预设快速定位:支持4个预设位置的存储,通过按键选择和触发定位
二、硬件介绍
2.1 主控设备
M5Stack CoreS3
M5Stack CoreS3是一款采用ESP32-S3主控芯片的开发板,搭载320x240 ILI9341 2.0英寸LCD彩色触摸屏,板内集成16MB Flash和8MB PSRAM。设备正面提供三个物理按键(BtnA/BtnC)和1个复位按键(BtnB),内置AXP2101电源管理芯片,支持USB-C供电和电池管理,丰富的GPIO和I2C接口可用于外设扩展。GRBL Module 13.2可直接堆叠在CoreS3顶部。
参数 | 规格 |
|---|---|
型号 | CoreS3 |
主控芯片 | ESP32-S3,双核240MHz |
Flash | 16MB |
PSRAM | 8MB |
显示屏 | 2.0英寸 ILI9341,320×240彩色 |
按键 | BtnA/BtnC(功能键)、BtnB(复位) |
扩展接口 | HY2.0-4P Grove (PORT.A/I2C) |
Wi-Fi/BT | 2.4 GHz Wi-Fi + BLE 5.0 |
电池 | 内置锂电池 |
2.2 扩展模块
GRBL Module 13.2 (SKU: M035)
GRBL Module 13.2是一款基于ESP32和GRBL开源固件的步进电机驱动模块,支持2轴步进电机控制(可扩展至3轴)。模块堆叠在CoreS3上,通过I2C接口(地址0x70)通信,支持G-Code指令集。模块需要外部DC 9~24V供电,驱动电流可达2A/轴。
参数 | 规格 |
|---|---|
SKU | M035 |
主控 | ESP32 + GRBL固件 |
电机轴数 | 3轴(本项目使用2轴) |
通信接口 | I2C @ 0x70 |
供电 | DC 9~24V 外部供电 |
驱动电流 | 2A/轴 |
安装方式 | 堆叠连接 |
2.3 传感器/执行器模块
双轴云台机构
双轴云台采用两个步进电机驱动,实现水平(Pan)和垂直(Tilt)两个自由度的运动控制。平台可搭载摄像头和激光笔,适用于监控、巡检等应用场景。
参数 | 规格 |
|---|---|
水平行程(Pan) | 0~180°(映射X轴 0~200mm) |
垂直行程(Tilt) | 0~90°(映射Y轴 0~100mm) |
步进电机细分 | 80步/mm |
移动速度 | 300 mm/min |
激光笔
通过CoreS3的GPIO3端口控制,用于辅助定位和指示。使用高电平(1)开启、低电平(0)关闭。
GRBL Module 13.2堆叠在CoreS3上,通过I2C总线通信。激光笔通过GPIO3控制。
接口分配
设备 | 接口/引脚 | 说明 |
|---|---|---|
GRBL Module 13.2 | I2C 0x70 | 步进电机驱动(堆叠连接) |
激光笔 | GPIO3 | 激光控制输出 |
BtnA | GPIO内置 | 短按切换模式,长按切换激光 |
BtnB | GPIO内置 | 减少当前模式值/上一个预设 |
BtnC | GPIO内置 | 增加当前模式值/执行预设 |
2.4 硬件框图

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

3.2 设备启动流程

3.3 角度-坐标映射算法
云台控制的核心是将角度转换为GRBL步进电机坐标:
Pan: 角度(0~180°) → 坐标(0~200mm)
公式: mm = angle / 180 * 200
Tilt: 角度(0~90°) → 坐标(0~100mm)
公式: mm = angle / 90 * 100
def angle_to_mm(angle, axis_range_mm, angle_max):
"""将角度转换为步进电机位置(毫米)"""
return angle / angle_max * axis_range_mm
def mm_to_angle(mm, axis_range_mm, angle_max):
"""将步进电机位置(毫米)转换为角度"""
return mm / axis_range_mm * angle_max
四、原理图和PCB展示及介绍
4.1 原理图

4.2 PCB布局

4.3 设计过程
原理图和PCB均使用KiCad软件进行设计。在实际动手环节,作为电路设计的新手,这次从零开始画原理图、布PCB的经历让我学到了很多。电路设计上采用直通方式,各路I2C信号线直接并联,依靠设备地址区分不同外设,结构简洁可靠。
4.4 硬件连接与实物展示
┌─────────────────┐
│ │
GRBL堆叠 ──────┤ I2C SDA/SCL │ I2C总线至GRBL Module
│ │
激光笔 ────────┤ GPIO3 │ 开关控制
│ │
BtnA ──────────┤ GPIO内置 │ 模式切换/激光切换
│ │
BtnB ──────────┤ GPIO内置 │ 角度减少/上一个预设
│ │
BtnC ──────────┤ GPIO内置 │ 角度增加/执行预设
│ │
GND ───────────┤ GND │
│ │
5V ────────────┤ 5V │
│ │
└───────────────────┘
│
本项目设计了专用接口转接板,用于连接 CoreS3 主控、GRBL Module 与激光笔模块。转接板通过自定义 PCB 走线将 GPIO3 信号引出至激光笔控制引脚,实现了稳定的电气连接。
五、软件流程和关键代码介绍
5.1 主程序结构
"""
M5Stack CoreS3 - 二维云台控制程序 (PTZ Camera/Laser Control)
功能特性:
1. 使用GRBL Module 13.2控制双轴步进电机(云台)
2. CoreS3按键控制云台角度 (BtnA/B/C)
3. 激光笔开关控制 (BtnA长按切换)
4. 预设位置快速定位
5. 实时显示云台角度和状态
"""
5.2 硬件初始化
def init_hardware():
"""初始化硬件设备:GRBL模块、激光笔GPIO"""
global grbl_0, laser
try:
# 初始化GRBL Module (I2C地址: 0x70)
grbl_0 = GRBLModule(address=0x70)
grbl_0.init(PAN_STEPS_PER_MM, TILT_STEPS_PER_MM, 400, 500)
grbl_0.set_mode(GRBLModule.MODE_ABSOLUTE)
grbl_0.unlock()
grbl_0.g_code("G92 X0 Y0 Z0") # 软归零
grbl_0.wait_idle()
# 移动到初始位置 (Pan: 90°, Tilt: 45°)
pan_mm = angle_to_mm(90, PAN_RANGE_MM, PAN_ANGLE_MAX)
tilt_mm = angle_to_mm(45, TILT_RANGE_MM, TILT_ANGLE_MAX)
grbl_0.turn(pan_mm, tilt_mm, None, DEFAULT_SPEED)
grbl_0.wait_idle()
except Exception as e:
grbl_0 = None
return False
# 初始化激光笔GPIO3
try:
laser = Pin(LASER_PIN, Pin.OUT)
laser.value(LASER_OFF)
except Exception:
laser = None
return True
5.3 云台运动控制
def move_to_angle(pan_angle, tilt_angle):
"""同时移动云台到指定水平/垂直角度"""
global current_pan, current_tilt
# 限幅保护
pan_angle = max(PAN_ANGLE_MIN, min(PAN_ANGLE_MAX, pan_angle))
tilt_angle = max(TILT_ANGLE_MIN, min(TILT_ANGLE_MAX, tilt_angle))
current_pan = pan_angle
current_tilt = tilt_angle
# 角度转毫米坐标
pan_mm = angle_to_mm(pan_angle, PAN_RANGE_MM, PAN_ANGLE_MAX)
tilt_mm = angle_to_mm(tilt_angle, TILT_RANGE_MM, TILT_ANGLE_MAX)
# 通过GRBL发送移动指令
grbl_0.turn(pan_mm, tilt_mm, None, DEFAULT_SPEED)
5.4 按键控制
def handle_buttons():
"""处理CoreS3按键输入"""
global control_mode, selected_preset, btn_a_press_start, btn_a_long_triggered
# BtnA长按检测(>800ms) → 切换激光笔
if BtnA.isPressed():
if btn_a_press_start == 0:
btn_a_press_start = time.ticks_ms()
btn_a_long_triggered = False
elif not btn_a_long_triggered and \
time.ticks_ms() - btn_a_press_start > BTN_LONG_PRESS_MS:
toggle_laser()
btn_a_long_triggered = True
# BtnA短按释放 → 切换控制模式
if BtnA.wasReleased():
if not btn_a_long_triggered:
control_mode = (control_mode + 1) % 3
btn_a_press_start = 0
btn_a_long_triggered = False
return
# BtnB: 减少角度 / 上一个预设
if BtnB.wasPressed():
if control_mode == 0: # Pan模式
new_pan = current_pan - 5
move_pan(new_pan)
elif control_mode == 1: # Tilt模式
new_tilt = current_tilt - 5
move_tilt(new_tilt)
elif control_mode == 2: # Preset选择模式
selected_preset = (selected_preset - 1) % 4
# BtnC: 增加角度 / 执行预设
if BtnC.wasPressed():
if control_mode == 0:
new_pan = current_pan + 5
move_pan(new_pan)
elif control_mode == 1:
new_tilt = current_tilt + 5
move_tilt(new_tilt)
elif control_mode == 2:
go_to_preset(selected_preset) # 执行预设定位
5.5 预设位置管理
# 预设位置定义
presets = [
{"name": "Home", "pan": 90, "tilt": 45}, # 居中
{"name": "Left", "pan": 0, "tilt": 45}, # 最左
{"name": "Right", "pan": 180, "tilt": 45}, # 最右
{"name": "Top", "pan": 90, "tilt": 0 }, # 最上
]
def go_to_preset(index):
"""移动到预设位置"""
if index < 0 or index >= len(presets):
return
preset = presets[index]
move_to_angle(preset["pan"], preset["tilt"])
grbl_0.wait_idle()
5.6 显示管理
def display_status():
"""显示云台状态界面,适配CoreS3 320x240横向屏幕"""
Widgets.fillScreen(0x000000)
# 显示控制模式
mode_label = MODE_NAMES[control_mode] # "Pan"/"Tilt"/"Preset"
Widgets.Label("=== PTZ Control ===", 10, 10, 1.0, ..., Widgets.FONTS.DejaVu12)
Widgets.Label("Mode: " + mode_label, 10, 35, 1.0, 0xFFFF00, ...)
# 显示当前角度
pan_str = "Pan: {:3d} deg".format(int(current_pan))
tilt_str = "Tilt: {:2d} deg".format(int(current_tilt))
# 显示激光状态
laser_str = "Laser: " + ("ON" if laser_on else "OFF")
laser_color = 0x00FF00 if laser_on else 0xFF0000
# 显示预设位置列表
Widgets.Label("Presets:", 10, 130, 1.0, 0xFFFFFF, ...)
for i, preset in enumerate(presets):
marker = ">" if i == selected_preset else " "
color = 0x00FF00 if i == selected_preset else 0xAAAAAA
text = "{}{}: {} {}deg".format(marker, preset["name"],
preset["pan"], preset["tilt"])
5.7 紧急停止
def emergency_stop():
"""紧急停止:锁定所有电机、关闭激光"""
if grbl_0 is not None:
try:
grbl_0.lock()
except Exception:
pass
set_laser(False)
Widgets.fillScreen(0xFF0000)
Widgets.Label("EMERGENCY STOP", 10, 10, 1.0, 0xFFFFFF, 0xFF0000,
Widgets.FONTS.DejaVu18)
5.8 主程序入口
def setup():
"""系统初始化"""
M5.begin()
if init_hardware():
display_status()
else:
Widgets.Label("Init Failed!", 10, 120, 1.0, 0xFF0000, ...)
def loop():
"""主循环:更新系统状态、处理按键输入、刷新显示"""
M5.update()
handle_buttons()
display_status()
time.sleep_ms(50)
if __name__ == '__main__':
try:
setup()
while True:
loop()
except (Exception, KeyboardInterrupt) as e:
emergency_stop()
print("Error:", e)
六、硬件功能展示及说明
6.1 显示
6.1.1 实物图
实物展示:

6.1.2 实物图

6.1.3 运行状态(Preset模式,激光开启)
=== PTZ Control ===
Mode: Preset
Pan: 90 deg
Tilt: 45 deg
Laser: ON
Presets:
Home: 90 45deg
>Left: 0 45deg
Right: 180 45deg
Top: 90 0deg
操作提示:
BtnA:切换模式 BtnB:- BtnC:+
6.2 系统运行日志
==================================================
CoreS3 PTZ Control System v1.0
==================================================
[GRBL] Module init OK (0x70)
[GRBL] Steps/mm: X=80, Y=80, Z=400
[GRBL] Mode: ABSOLUTE
[GRBL] Unlocked
[GRBL] Soft home set (G92 X0 Y0 Z0)
[GRBL] Move to initial: X=100, Y=50
[LASER] GPIO3 init OK
==================================================
[Main] Running...
[PAN] Move to 120 deg (X=133.3mm)
[TILT] Move to 30 deg (Y=33.3mm)
[MODE] Switch to Tilt control
[LASER] ON (BtnA long press)
[PRESET] Go to "Left": Pan=0, Tilt=45
==================================================
[EMERGENCY STOP] All motors locked, laser off
==================================================
七、设计中遇到的难题和解决方法
7.1 GRBL模块I2C通信
问题描述:GRBL Module 13.2使用I2C通信,需要正确配置地址和初始化参数,否则无法正常驱动步进电机。
解决方案:
- 确认GRBL Module I2C地址为0x70(可通过模块跳线配置为0x71)
- 按照步进电机参数配置Steps/mm(80步/mm对应常用42步进电机)
- 先解锁电机再执行移动指令
# GRBL模块初始化
grbl_0 = GRBLModule(address=0x70)
grbl_0.init(80, 80, 400, 500) # X, Y, Z步/mm
grbl_0.set_mode(GRBLModule.MODE_ABSOLUTE)
grbl_0.unlock() # 必须先解锁
7.2 无限位开关的归零问题
问题描述:云台机械结构没有安装限位开关,上电后系统不知道当前位置。
解决方案:
- 使用G92软归零指令,将当前位置设定为坐标系原点
- 上电后手动将云台移动到机械零点位置
- 软归零后自动移动到初始工作位置(Pan 90°,Tilt 45°)
# 软归零 - 将当前位置设为原点
grbl_0.g_code("G92 X0 Y0 Z0")
# 移动到初始位置
pan_mm = angle_to_mm(90, 200, 180) # X=100mm
tilt_mm = angle_to_mm(45, 100, 90) # Y=50mm
grbl_0.turn(pan_mm, tilt_mm, None, 300)
7.3 角度到毫米坐标的映射
问题描述:用户期望以角度控制云台,但GRBL模块以毫米为单位进行运动控制。
解决方案:
- 建立角度-毫米的线性映射模型
- Pan轴:0~180° 映射到 0~200mm
- Tilt轴:0~90° 映射到 0~100mm
- 提供双向转换函数
def angle_to_mm(angle, axis_range_mm, angle_max):
"""角度转毫米坐标"""
return angle / angle_max * axis_range_mm
def mm_to_angle(mm, axis_range_mm, angle_max):
"""毫米坐标转角度"""
return mm / axis_range_mm * angle_max
7.4 按键长按与短按的区分
问题描述:BtnA需要同时支持短按切换模式和长按切换激光,两种操作需要准确区分。
解决方案:
- 使用
isPressed()持续检测按键按下状态 - 记录按下起始时间,超过800ms阈值触发长按事件
- 设置
btn_a_long_triggered标志防止长按后再次触发短按 - 在
wasReleased()中检查标志,仅当未触发长按时才执行短按逻辑
# BtnA长按检测
if BtnA.isPressed():
if btn_a_press_start == 0:
btn_a_press_start = time.ticks_ms()
btn_a_long_triggered = False
elif not btn_a_long_triggered and \
time.ticks_ms() - btn_a_press_start > BTN_LONG_PRESS_MS:
toggle_laser()
btn_a_long_triggered = True
# BtnA释放 - 区分短按和长按
if BtnA.wasReleased():
if not btn_a_long_triggered:
control_mode = (control_mode + 1) % 3 # 短按切换模式
7.5 多模式控制的交互设计
问题描述:需要利用3个按键实现Pan调节、Tilt调节、Preset选择和激光控制等多种功能。
解决方案:
- 通过BtnA短按循环切换三种模式(Pan/Tilt/Preset)
- 通过BtnA长按切换激光开关(独立于模式)
- Mode 0 (Pan):BtnB/BtnC调节水平角度(步进5°)
- Mode 1 (Tilt):BtnB/BtnC调节垂直角度(步进5°)
- Mode 2 (Preset):BtnB选择预设,BtnC执行定位
BUTTON_STEP = 5 # 每次按键调节5°
# 按键控制映射
BtnA: 短按→切换模式, 长按→切换激光
BtnB: Pan-5° / Tilt-5° / 上一个预设
BtnC: Pan+5° / Tilt+5° / 执行预设
八、心得体会
8.1 项目收获
- 步进电机控制技术:深入理解了步进电机驱动原理和GRBL开源固件的使用方法
- 运动控制系统设计:掌握了从按键输入到电机运动的完整控制链路设计
- 嵌入式开发:熟悉了M5Stack MicroPython开发和GRBL Module的I2C通信协议
- 用户交互设计:实现了CoreS3按键多模式控制和320x240屏幕显示界面
- 系统集成:完成了硬件堆叠、I2C通信和GPIO控制的系统集成
8.2 技术亮点
- GRBL开源集成:将GRBL运动控制固件无缝集成到M5Stack生态
- 按键复用:通过长按/短按区分实现单键多功能(模式切换+激光控制)
- 角度-坐标映射:建立精准的云台角度与步进电机坐标的映射模型
- 预设管理:支持4个预设位置的存储和快速定位
- 容错设计:各硬件模块独立初始化,单一模块故障不影响整体运行
- 紧急停止:异常时自动锁定电机并关闭激光,确保安全
8.3 改进建议
- 远程控制:添加WiFi网络功能,实现Web/APP远程云台控制
- 自动巡航:实现预设位置的自动轮巡功能
- 轨迹记忆:记录并回放云台运动轨迹
- 摄像头集成:添加摄像头实时图像采集和显示功能
- 限位保护:安装机械限位开关,增加安全保护
- 速度调节:支持按键组合实时调节运动速度
- 更多预设:扩展预设数量,支持SD卡存储预设配置
- OTA升级:支持远程固件更新
- 激光功率调节:支持激光笔PWM调光
创意方向关联
从本项目的技术探索中,我们可以发现以下创意方向的发展潜力:
1. 人工智能在嵌入式系统中的应用 / 计算机视觉
本项目的云台控制技术为AI视觉应用提供了硬件基础:
- 目标跟踪:结合计算机视觉算法,实现动态目标自动跟踪
- 智能巡检:AI识别异常情况并自动控制云台聚焦目标区域
- 图像识别:云台搭载摄像头进行图像采集,配合AI进行识别分析
2. 楼宇自动化 / 智能安防
云台控制系统是智能安防的核心组件:
- 智能监控:预设巡航路线,实现全方位安防监控
- 访客识别:云台联动门禁系统,自动追踪访客
- 能源管理:结合人员检测,智能调节监控区域照明
3. 机械臂 / 工业自动化
双轴云台控制技术可扩展至更广泛的工业自动化领域:
- 工业机器人:步进电机控制模式可迁移至小型机械臂设计
- 自动化检测:搭载传感器进行工业产品质量检测
- 精准定位:GRBL高精度运动控制在数控领域的应用
技术迁移价值
本项目开发的技术方案可迁移到:
- 人工智能应用:目标跟踪、智能巡检、图像识别平台
- 楼宇自动化:安防监控、访客识别、智能照明
- 机械臂/自动化:工业检测、精准定位、运动控制
致谢
感谢 DigiKey 和 电子森林 提供的FastBond4活动支持,本次活动链接:https://www.eetree.cn/page/digikey-fastbond
