目录

  1. 环境准备
  2. 安装 ESP32 开发环境
  3. 安装 micro_ros_arduino 库
  4. Micro-ROS Agent 配置
  5. 第一个程序:Publisher
  6. Subscriber 示例
  7. 常见问题排查

环境准备

硬件清单

硬件 说明
ESP32-S3 开发板 推荐 DevKitC-1,带 USB OTG
USB-C 数据线 能传数据的,不是仅充电线
LED(可选) 用于状态指示

软件版本要求

软件 版本 备注
Arduino IDE ≥ 2.0 或 VS Code + PlatformIO
ESP32 Board Package 2.0.11 不要用 3.x!
micro_ros_arduino humble 分支 必须 Git 克隆完整版
ROS2 Humble Ubuntu 22.04
micro-ROS Agent humble 通过 Snap 安装

ESP32-S3 机器人控制器 · 使用文档

适用固件版本:v1.0.0
已验证环境:Arduino IDE 2.x + esp32 core 2.0.11 + micro_ros_arduino 2.0.8-humble + ROS 2 Humble
实测构建结果:Flash 790,785 B(60%)、静态 RAM 111,140 B(33%)


0. 30 秒快速上手

1
2
3
4
5
6
① 接线          → 见 §2(电机 UART1 / 雷达 UART2 / 共地)
② 改 3 个参数 → config.h: WIFI_SSID / WIFI_PASSWORD / AGENT_IP
③ PC 起 Agent → ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888 -v6
④ 编译烧录 → 开发板选 "ESP32S3 Dev Module"
⑤ 架空车轮标定 → 见 §6(不做这步就上车,大概率原地打转)
⑥ 发速度 → ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.2}}"

先读 §9 安全须知。这台机器会动。


1. 硬件清单

部件 规格 说明
主控 ESP32-S3(DevKitC-1 等,N8R2/N8R8 均可) 双核,本工程用了 Core0/Core1 分工
电机 JGB37-520 ×4,12V 减速比 1:13,编码器 11 PPR(霍尔 A/B 相)
驱动 串口四路电机驱动板 ASCII 协议 $指令:参数#,板载速度闭环 PID + 编码器采集 + 电池电压采集
雷达 YDLIDAR X2 三角测距,5V 约 400mA,TTL 3.3V 串口 115200
轮子 麦克纳姆轮 ×4,直径 60mm 也支持四轮差速(改一个矩阵即可,见 §5.6)
电池 12V(3S 锂电等) 驱动板直接供电;雷达需 5V

2. 接线

2.1 引脚表(默认值,可在 config.h §2 修改)

信号 ESP32-S3 引脚 对端 备注
电机串口 RX GPIO4 驱动板 TX 交叉接线
电机串口 TX GPIO5 驱动板 RX 交叉接线
雷达串口 RX GPIO6 雷达 TX 交叉接线
雷达串口 TX GPIO7 雷达 RX X2 正常扫描时不需要,仅供查询
状态 LED GPIO48 板载 WS2812 STATUS_LED_IS_WS2812 1
电池 ADC GPIO1 分压电路 默认关闭,见 §2.4

2.2 电源与共地(最容易忽略、后果最严重)

  • 所有 GND 必须连在一起:ESP32、驱动板、雷达、电池负极。串口电平以地为参考,不共地会出现”时好时坏的乱码”。
  • 雷达单独确认 5V 能力:X2 启动瞬间电流较大,若从驱动板的 5V 取电后雷达转不起来或反复重启,改用独立 5V/1A 模块。
  • 电机电源与逻辑电源:电机启停会在电源上产生尖峰。若出现 ESP32 随机重启,优先怀疑电源跌落,在电机电源端并 470µF 以上电解电容。
  • ESP32-S3 的 GPIO0/3/45/46 是 strapping 脚、GPIO19/20 是原生 USB、GPIO26~37 可能被 Flash/PSRAM 占用,本工程已避开,不要随意改到这些脚上。

2.3 X2 雷达接口

X2 引脚 接到
5V 5V 电源
GND 共地
TX ESP32 GPIO6
RX ESP32 GPIO7(可悬空)

标准 X2 是 4pin,上电即自动旋转并连续输出数据,不需要任何启动命令。
若你的是 X2L(多一个电机控制脚),把 config.h 的 LIDAR_MOTOR_PIN 填上对应 GPIO——
注意 X2L 是「悬空=转,拉高=停」,与常见的高电平使能相反,驱动里已按此处理。

2.4 电池电压:两种方式二选一

方式 A(推荐,默认):让驱动板自己测。固件每秒发一次 $read_vol#,解析回包。
什么都不用接。

方式 B:ESP32 自己分压采样。改 config.h:

1
2
3
#define BATTERY_ADC_ENABLE      1
#define BATTERY_ADC_PIN 1
#define BATTERY_DIVIDER_RATIO 5.0f /* = (R上+R下)/R下 */

⚠️ 上电前必须算清楚分压比。ESP32-S3 的 ADC 在 11dB 衰减下满量程约 3.1V,
12V 电池经 5:1 分压后是 2.4V,安全。直接接 12V 会立刻烧掉引脚。
分压电阻建议 100k / 24k(比值 5.17),并在 ADC 脚对地并 0.1µF 电容。


3. 软件环境准备

3.1 版本要求(重要)

组件 必须的版本 原因
esp32 core 2.0.x(实测 2.0.11) micro_ros_arduino 2.0.8-humble 的预编译库 libmicroros.a 是针对 2.0.x 编译的。换成 core 3.x 会出现大量未定义符号,必须同时更换 micro_ros_arduino 版本
micro_ros_arduino 2.0.8-humble 与 ROS 2 Humble 的 Agent 配套
ROS 2 Humble Agent 与客户端版本需一致

查看已装 core 版本:开发板管理器里搜 esp32。

3.2 安装 micro_ros_arduino 库

库必须放在用户库目录:<你的文档目录>\Arduino\libraries\micro_ros_arduino\

安装后自检这三项必须都存在(缺任何一项都会编译失败):

1
2
3
libraries\micro_ros_arduino\library.properties
libraries\micro_ros_arduino\src\micro_ros_arduino.h
libraries\micro_ros_arduino\src\esp32s3\libmicroros.a ← 本板型的预编译库

装完重启 Arduino IDE(IDE 只在启动时扫描库)。

3.3 PC 端启动 micro-ROS Agent

1
2
3
4
5
6
# 方式一:原生安装(推荐)
sudo apt install ros-humble-micro-ros-agent
ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888 -v6

# 方式二:docker
docker run -it --rm --net=host microros/micro-ros-agent:humble udp4 --port 8888 -v6
  • -v6 是详细日志,排障时一定要加,能看到 ESP32 的握手过程。
  • 放行 UDP 8888:Windows 首次运行会弹防火墙询问,必须选「允许」。
  • 确认 PC 的局域网 IP:Windows ipconfig,Linux ip addr。

WSL2 用户的常见坑:若 ROS 2 装在 WSL2 里,Agent 也在 WSL2 里,
那么 AGENT_IP 要填 WSL2 的 IP(ip addr 里的 eth0 地址,通常 172.x.x.x),
而不是 Windows 的 IP。并且 WSL2 默认 NAT 模式下 ESP32 无法直接访问 WSL2,
需要配置镜像网络(.wslconfig 里 networkingMode=mirrored)或做端口转发。
如果一直连不上,先把 Agent 放到能直连的机器/树莓派上验证。


4. 配置 config.h

config.h 是整个工程唯一需要按硬件修改的文件。按下面四档处理:

4.1 必须改(不改就连不上)

宏 说明
WIFI_SSID WiFi 名(ESP32 只支持 2.4GHz,不支持 5GHz)
WIFI_PASSWORD WiFi 密码
AGENT_IP PC 的局域网 IP,不是 ESP32 的 IP,也不是 127.0.0.1
AGENT_PORT 默认 8888,与 Agent 启动参数一致

4.2 首次上车建议先收紧(跑通再放开)

宏 默认 建议首次
MAX_LINEAR_SPEED_MPS 0.80 0.30
MAX_ANGULAR_SPEED_RPS 2.00 1.00
MAX_LATERAL_SPEED_MPS 0.60 0.20
CMD_TIMEOUT_MS 500 500(别调大,这是失联保护)

4.3 标定时改(见 §6)

宏 默认 什么时候改
WHEEL_DIAMETER_MM 60.0 实测轮径后
ENCODER_EDGES_PER_PPR 4.0 确认编码器倍频后(1/2/4)
MOTOR_WIRE_FL/FR/RL/RR 0/1/2/3 接线顺序与丝印不一致时
MOTOR_DIR_SIGN_FL/FR/RL/RR 全 +1 某个轮子转向相反时
WHEEL_TRACK_MM 200.0 实测左右轮中心距后
KINEMATICS_MATRIX 麦克纳姆 X 型 横移/自转方向不对时
MOTOR_DEADZONE 1650 电机低速抖动或起步困难时(0~3600)
LIDAR_REVERSE_DIRECTION 0 RViz 里地图左右镜像时改 1

4.4 一般不用改

引脚、波特率、发布频率、看门狗阈值、LIDAR_SCAN_BINS、调试等级 DEBUG_LEVEL。

调试输出等级:DEBUG_LEVEL 0=静默 1=只错误 2=常规(默认)3=每帧收发明细。
查协议问题时设 3。


5. 编译、烧录、上电自检

5.1 Arduino IDE 板子设置

选项 值
Board ESP32S3 Dev Module
Flash Size 按模组(常见 4MB / 8MB / 16MB)
PSRAM 按模组(N8R8 选 OPI PSRAM,N8R2 选 QSPI)
Partition Scheme 默认(Default 4MB with spiffs)→ App 空间 1,310,720 B
Upload Speed 921600

5.2 编译

直接点编译即可。占用(实测):

1
2
Sketch uses 790785 bytes (60%) of program storage space. Maximum is 1310720 bytes.
Global variables use 111140 bytes (33%) of dynamic memory, leaving 216540 bytes for local variables.

其中约 21KB 是雷达的双缓冲和原始点缓存。RAM 余量充足。

5.3 上电后应该看到什么

打开串口监视器(115200),正常启动日志长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
==================================================================
ESP32-S3 机器人控制器 v1.0.0
四路串口电机 + YDLIDAR X2 + micro-ROS(WiFi)
==================================================================
【参数传递链】
编码器 11PPR × 减速比 13:1 × 4倍频 = 572 计数/圈
轮径 60.0mm → 周长 188.50mm → 每计数 0.3295mm
额定 366RPM → 轮子满量程 1.150 m/s(=速度指令±1000)
底盘: 轴距 200mm 轮距 200mm 旋转臂 K=200mm
------------------------------------------------------------------
电机串口 UART RX=GPIO4 TX=GPIO5 @115200
雷达串口 UART RX=GPIO6 TX=GPIO7 @115200
Agent 192.168.1.100:8888 节点名 esp32s3_robot
==================================================================
[I] [1/4] 初始化电机驱动板 ...
[I] 电机串口就绪: UART RX=GPIO4 TX=GPIO5 @115200
[I] ---- 底盘参数自检 ----
[I] 减速比 13:1 编码器 11PPR ×4倍频 = 572 计数/圈
[I] 轮径 60.0mm 周长 188.50mm 每计数 0.3295mm
[I] 额定转速 366RPM → 轮子满量程 1.150 m/s(速度指令 ±1000 对应)
[I] 轴距 200mm 轮距 200mm 旋转臂 K=200.0mm
[I] ----------------------
[I] 配置驱动板参数(写 flash)...
[I] [2/4] 初始化 YDLIDAR X2 ...
[I] 雷达串口就绪: UART RX=GPIO6 TX=GPIO7 @115200(X2 上电自动扫描,无需启动命令)
[I] 雷达角度分箱 720 格(0.50°/格),量程 0.10~8.00m
[I] [3/4] 创建任务 (motor@core1, lidar@core0, ros@core0) ...
[I] [4/4] 外设就绪,等待 micro-ROS 连接 ...
[I] 尝试建立 micro-ROS 连接 (Agent=192.168.1.100:8888) ...
[I] 连接 WiFi: 你的WiFi名 ...
[I] WiFi 已连接 IP=192.168.1.50 RSSI=-52 dBm 信道=6
[I] ping micro-ROS Agent @ 192.168.1.100:8888 ...
[I] Agent 已响应(第 1 次握手)
[I] ROS 时间同步成功,epoch=...
[I] ROS2 节点已创建: esp32s3_robot
[I] ROS 接口就绪
...
[I] micro-ROS 已连接,进入运行状态

状态 LED(GPIO48 板载 WS2812)一眼判断工况:

颜色 含义
白色常亮 启动中
蓝色慢闪(1Hz) 等待 ROS 连接
绿色常亮 正常运行
黄色快闪(5Hz) ROS 掉线,降级模式(电机已停,后台自动重连)
红色急促闪(10Hz) 故障(通常是编码器遥测丢失)

5.4 关键自检项

每 5 秒打印一次状态摘要,重点看这几行:

1
2
3
编码器: 1000 -2000 3000 -4000  (12ms 前)     ← 有数据且在刷新 = 驱动板通信正常
电池: 11.85 V ← 有电压 = $read_vol# 有响应
雷达: 在线 包 8421/0(坏) 失步 3 点 25340 圈 68 8.02Hz 帧头 AA55
  • 帧头 AA55 或 55AA 都正常,未同步 才是问题。
  • 包 x/y(坏) 里 y 应该接近 0(偶发几个是正常的)。

6. 标定流程(★ 必须做,约 20 分钟 ★)

全程把车架空,轮子离地。 标定过程中电机会突然转动。

① 编码器倍频确认

轮子上贴个标记,手动缓慢转 10 圈,看串口摘要里 编码器 第 1 个数的增量 Δ。

1
ENCODER_EDGES_PER_PPR = Δ / (11 × 10)
实测 Δ 结论 填写
≈ 5720 四倍频 ENCODER_EDGES_PER_PPR 4.0f(默认)
≈ 2860 二倍频 2.0f
≈ 1430 单相单沿 1.0f

这项错了,里程计会按整数倍偏差——这是最常见的”走 1 米显示 0.25 米”的原因。

② 轮径实测

让车直线走 2 米(地上画线),记录 encoder_data 的总计数 Δc。

1
2
实际轮周长(mm) = 2000 / (Δc / ENCODER_COUNTS_PER_REV)
实际轮径(mm) = 轮周长 / π

把算出的轮径填回 WHEEL_DIAMETER_MM。

③ 速度满量程标定

1
ros2 topic pub --once /motor_speeds std_msgs/msg/Int32MultiArray "{data: [500, 500, 500, 500]}"

用秒表+卷尺实测车速 v。理论值 = 0.5 × WHEEL_MAX_LINEAR_MPS = 0.575 m/s。
若实测偏小,按比例调小 MOTOR_RATED_RPM(例:实测 0.46 m/s → 366 × 0.46/0.575 ≈ 293)。

注意:/motor_speeds 是直通模式,不受 MAX_LINEAR_SPEED_MPS 限制,且 500ms
内没有新指令就会自动停车。测试时用 --once 发一次即可,安全。

④ 轮序与方向(四步测试法)

逐个轮子发指令,记录哪个物理轮子转、朝哪个方向转:

1
2
3
4
ros2 topic pub --once /motor_speeds std_msgs/msg/Int32MultiArray "{data: [100, 0, 0, 0]}"
ros2 topic pub --once /motor_speeds std_msgs/msg/Int32MultiArray "{data: [0, 100, 0, 0]}"
ros2 topic pub --once /motor_speeds std_msgs/msg/Int32MultiArray "{data: [0, 0, 100, 0]}"
ros2 topic pub --once /motor_speeds std_msgs/msg/Int32MultiArray "{data: [0, 0, 0, 100]}"

数组下标约定固定为 [0]=左前 1=右前 2=左后 3=右后(车头朝前、从上往下看)。

  • 第 i 个位置转的不是对应轮子 → 改 MOTOR_WIRE_FL/FR/RL/RR(逻辑轮位 → 驱动板 M 口,0 基)。
  • 某个轮子「正指令时朝后转」→ 把该轮的 MOTOR_DIR_SIGN_* 改成 -1.0f。

改完复测,直到 4 个位置各自对应正确轮子且都朝前。

⑤ 运动学矩阵校验

三个单向测试,每个都必须”纯”:

1
2
3
4
5
6
7
8
# 只发前进 → 必须严格直行,不能斜着走
ros2 topic pub -r 10 /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.2}}"

# 只发左移 → 必须严格横移,车头方向不变
ros2 topic pub -r 10 /cmd_vel geometry_msgs/msg/Twist "{linear: {y: 0.2}}"

# 只发自转 → 必须原地转,中心不动
ros2 topic pub -r 10 /cmd_vel geometry_msgs/msg/Twist "{angular: {z: 0.5}}"

哪个不对,就改 config.h §4 的 KINEMATICS_MATRIX 对应行的 vy 或 wz 列符号。
辊子朝向不同,试这几种组合即可(不用改算法代码):

1
[1,+1,-1] / [1,-1,+1] / [1,+1,+1] / [1,-1,-1]

⑥ 原地转 360° 验证

1
2
3
ros2 topic pub -r 10 /cmd_vel geometry_msgs/msg/Twist "{angular: {z: 0.5}}"
# 转够一圈后 Ctrl-C,然后看里程计:
ros2 topic echo /odom --field pose.pose.orientation

实际转过的角度与 /odom 的偏航角应基本一致(差 10% 以内)。
差得多 → 回 ④⑤ 检查;系统性偏大/偏小 → 检查 WHEEL_TRACK_MM 与 KINEMATICS_WZ_SCALE_MM。


7. ROS 2 接口

7.1 话题总览

订阅(PC → ESP32)

话题 类型 元素含义 说明
/cmd_vel geometry_msgs/Twist linear.x 前后 m/s,linear.y 横移 m/s,angular.z rad/s 正常导航用这个,走完整运动学
/motor_speeds std_msgs/Int32MultiArray 4 个 ±1000,顺序 FL,FR,RL,RR 直通,不经运动学、不限速。标定/排障用
/motor_pwm std_msgs/Int32MultiArray 4 个 ±3600 开环 PWM,绕过驱动板速度闭环。警告见 §7.4
/motor_config std_msgs/Float32MultiArray 见 §7.3 下标表 运行期改参数

发布(ESP32 → PC)

话题 类型 频率 说明
/encoder_data std_msgs/Int32MultiArray 20 Hz 4 个轮子累计编码器计数(顺序 FL,FR,RL,RR)
/battery_voltage std_msgs/Float32 1 Hz 电池电压 V
/odom nav_msgs/Odometry 20 Hz 里程计位姿与速度
/scan sensor_msgs/LaserScan 10 Hz 720 格,0.5°/格,量程 0.1~8m
/lidar_points sensor_msgs/PointCloud2 10 Hz x/y/z float32,2D 平面点云

7.2 模式仲裁规则(重要)

四个订阅话题都能控制电机,规则是「谁最后到,谁生效」:

  • 任意模式下,超过 CMD_TIMEOUT_MS(默认 500ms)没有新指令 → 自动停车并回到空闲。
  • /cmd_vel 与直通模式互斥:发了 /motor_speeds 之后,下一帧 /cmd_vel 会夺回控制权。
  • 遥测故障(4 秒收不到编码器)会强制进入故障态停车,需要遥测恢复 + 收到新指令才自动解除。

7.3 /motor_config 下标表

发 Float32MultiArray,按需填前缀,没填的下标保持不变(值为 0 表示”本次不改”,PID 用 -1 表示不改):

下标 含义 单位 生效方式
0 最大线速度 m/s 立即
1 最大角速度 rad/s 立即
2 最大横移速度 m/s 立即
3 线加速度限幅 m/s² 立即
4 角加速度限幅 rad/s² 立即
5 指令超时 ms 立即
6 轮径 mm 写入驱动板 $wdiameter
7 减速比 — 写入驱动板 $mline
8 PWM 死区 — 写入驱动板 $deadzone
9 PID-P — 写入驱动板 $MPID ⚠️
10 PID-I — 同上 ⚠️
11 PID-D — 同上 ⚠️
1
2
# 例:把限速降到 0.3 m/s、角速度 1.0 rad/s
ros2 topic pub --once /motor_config std_msgs/msg/Float32MultiArray "{data: [0.3, 1.0]}"

⚠️ 下标 9/10/11 会让驱动板重启约 0.6 秒并把电机停掉(驱动板文档明确说明)。
这是正常现象,但不要带着目标速度去改 PID——固件会在写完后强制回到空闲态。

7.4 开环 PWM 的警告

/motor_pwm 绕过驱动板的速度闭环,没有速度反馈,坡道上会溜车。
只用于测死区、测电机极性、做阶跃响应实验。导航中不要使用。

7.5 RViz2 配置要点

1
rviz2
设置 值
Fixed Frame odom(或 base_footprint,取决于你补的 TF)
添加 LaserScan Topic /scan,Size 0.03,Style Points
添加 PointCloud2 Topic /lidar_points,Size 0.02
添加 Odometry Topic /odom

7.6 TF 树(必须自己补)

本固件只发布 /odom 数据,不发布 TF。 TF 由 PC 端提供,两条链:

1
odom ──(动态,随车移动)──> base_footprint ──(静态,安装位置)──> laser_frame

静态链(雷达安装位置,z 填雷达实际离地高度,Nav2 避障正确性依赖它):

1
2
3
ros2 run tf2_ros static_transform_publisher \
--x 0 --y 0 --z 0.10 --roll 0 --pitch 0 --yaw 0 \
--frame-id base_footprint --child-frame-id laser_frame

动态链(odom → base_footprint)二选一:

  • 方案 A(推荐,同时融合其他传感器):用 robot_localization 的 EKF,订阅 /odom,设 publish_tf: true。
  • 方案 B(最简):写一个 10 行的小节点订阅 /odom,用 tf2_ros TransformBroadcaster 转发。

为什么不在 ESP32 上直接发 TF:会额外占用带宽和消息类型,而且 TF 的权威源
通常应该在上位机(便于融合 IMU 等)。代码里 FRAME_ID_* 常量已经按这套约定命名。

7.7 接 Nav2

只要 /scan + /odom + TF 三条链齐全,Nav2 就能用。建议:

  • 先用 slam_toolbox 建图,确认 /scan 在 RViz 里方向正确(见 §8 镜像问题)。
  • 把 MAX_LINEAR_SPEED_MPS 调到与 Nav2 的 max_vel_x 匹配,避免指令被固件截断产生”跟不上路径”的假象。

8. 故障排查

8.1 编译类

报错 原因 处理
geometry_msgs/msg/twist.h: No such file or directory Arduino 的库依赖探测按源码顺序扫描,遇到解析不了的 include 就中断,导致 micro_ros_arduino 库没被识别 已修复。请确认 ESP32S3_Robot.ino 顶部保留了 #include <micro_ros_arduino.h> 且在所有头文件之前;motor_ros_bridge.h/lidar_ros_bridge.h 里也各有一行。不要删、不要调顺序
micro_ros_arduino.h: No such file or directory 库没装或没重启 IDE 见 §3.2,装完重启 IDE
大量 undefined reference to ... core 版本不是 2.0.x,或用了非 esp32s3 的预编译库 换回 core 2.0.11(见 §3.1)
region dram0_0_seg overflowed RAM 不够(一般是自己加了很大的静态数组) 减小 LIDAR_CLOUD_MAX_POINTS 或 LIDAR_SCAN_BINS

8.2 连接类

现象 排查
LED 一直蓝色慢闪,串口停在「连接 WiFi」 SSID/密码错;路由器是 5GHz(ESP32 不支持);信号太弱
WiFi 连上了但「Agent 无响应」 ① Agent 没启动 → 在 PC 上跑 §3.3 的命令;② AGENT_IP 填错(必须是 PC 的局域网 IP,不是 127.0.0.1);③ PC 防火墙拦了 UDP 8888;④ PC 和 ESP32 不在同一网段
连上一次后频繁掉线(LED 黄闪) 信号弱;把 AGENT_PING_INTERVAL_MS 调大;检查是否有多个 Agent 抢同一个端口
一直提示「ROS 时间同步失败」 Agent 侧时钟异常;不影响控制,但 RViz 可能报时间戳过旧

8.3 电机类

现象 原因与处理
完全不动,/encoder_data 无数据 电机串口接线反了(TX/RX 要交叉);波特率不是 115200;驱动板没供电
上电后电机「嗡」一下或抖动 MOTOR_DEADZONE 不合适。从 1650 起,每次 ±100 试,直到低速不抖、起步顺畅
发前进车往后走 / 原地打转 轮序或方向没标定。做 §6 ④
发前进车斜着走 运动学矩阵与辊子朝向不符。做 §6 ⑤
发横移车不动或很慢 麦克纳姆横移效率天然低于纵向;确认 MAX_LATERAL_SPEED_MPS 不是 0;确认矩阵 vy 列符号
一给速度就进入故障态 驱动板不上报编码器。检查 $upload 设置、串口线、以及是否打开了 MOTOR_PROTOCOL_SNIFF 看板子到底发了什么
电机偶尔自己停一下 500ms 指令超时触发。用 -r 10 以上频率持续发指令;或网络卡顿
驱动板每次上电都重启一次 MOTOR_WRITE_PID_ON_BOOT 被设成了 1。写 PID 必然重启,确认参数后改回 0

8.4 雷达类

现象 原因与处理
串口提示「已连续收到 N 字节仍未同步」 ① 雷达 TX 没接到 GPIO6;② 波特率不是 115200;③ 少数固件帧头顺序不同 → 把 LIDAR_HEADER_ORDER 显式设为 1 或 2
帧头 未同步 但有点数 只是还没确认顺序,不影响;若 包 0/大量坏 才是真问题
扫描有规律的空洞 串口丢数据。X2 每秒 6.5KB,确认没有长时间关中断的操作;LIDAR_TASK_PRIO 不要降低
RViz 里地图左右镜像 LIDAR_REVERSE_DIRECTION 改 1(注意:这个开关同时作用于 /scan 与点云,改完两者仍然一致)
近处物体角度偏 距离相关角度校正默认开启(LIDAR_DIST_CORRECTION 1),若你自行标定过可关掉
雷达不转 5V 供电能力不足(X2 启动电流大);X2L 的电机脚被拉高了
/scan 没有数据但串口显示雷达在线 TF/坐标系没配,RViz 显示不出来;或 LIDAR_MIN_VALID_POINTS 门限(40 点)没达到

8.5 里程计类

现象 排查
走 1 米显示 1.25 / 0.8 米 编码器倍频错(§6 ①)或轮径不准(§6 ②)
转 360° 显示 300° WHEEL_TRACK_MM / KINEMATICS_WZ_SCALE_MM 不准;地面打滑
里程计不动 /encoder_data 没数据,或数据不刷新
里程计跳变 编码器数据间隔超过 1 秒(固件会重设基准并告警),检查通信质量

9. 安全须知(★ 必读 ★)

  1. 上电前把车架空,或放在足够空旷的地面并确认没人。
  2. 首次运行务必先收紧限速(§4.2),跑通后再逐步放开。
  3. 指令看门狗是您的主要保护:500ms 无指令自动停车。不要为了”跑得更顺”把它调大 —— WiFi 一断,ROS 就再也发不出停止指令了。
  4. 调试时用 --once 或低频 -r,不要用 -r 100 长时间狂发速度。
  5. 改 PID 会让驱动板重启停车,不要在车跑动时改。
  6. /motor_pwm 是开环的,坡道会溜车,仅用于台架测试。
  7. 串口/电源接线改动后必须重新上电,不要带电插拔电机线(反电动势可能损坏驱动板)。
  8. 出现异常行为时,最快的安全动作是直接断电池,而不是去点 RViz 的停止按钮。

10. 目录结构与附加工具

10.1 草稿根目录(正好 12 个标签页)

文件 职责
ESP32S3_Robot.ino 主程序:系统状态机、三任务创建、状态 LED、卡死自恢复
config.h 全部可调参数(含参数传递链推导与标定流程说明)
micro_ros_handler.h/.cpp 通信层:连接状态机、4 订阅 / 5 发布、时间同步
motor_protocol.h/.cpp 驱动层:串口协议、物理量换算、麦克纳姆运动学、里程计
motor_ros_bridge.h/.cpp 语义层:速度策略、模式仲裁、双重看门狗
ydlidar_x2.h/.cpp 雷达驱动:X2 协议解析、分圈、分箱、双缓冲
lidar_ros_bridge.h/.cpp 语义层:LaserScan / PointCloud2 构造

10.2 附加目录(不是标签页,不参与编译)

路径 内容
data/docs/ 本使用文档 + 修改文档
data/host_test/ 宿主机单元测试(Arduino 替身头文件 + 76 项断言)
tools/check_compile.ps1 用真实 xtensa 工具链做语法/类型校验
tools/run_host_tests.ps1 在 PC 上编译并运行单元测试

为什么放在 data/ 里:Arduino 官方规范规定 data/ 目录不被编译、
不显示为标签页,而且 文件 > 另存为 时会连同一起复制。

10.3 改动代码后如何自查

1
2
3
4
5
# 语法/类型校验(需要本机装有 esp32 core 2.0.11 与对应工具链)
powershell -File tools\check_compile.ps1

# 逻辑单元测试(需要 g++,测 X2 协议解析与运动学)
powershell -File tools\run_host_tests.ps1

两个脚本都会打印逐项结果并给出总数,改完代码跑一遍能挡掉绝大多数低级错误。


11. 命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ---- 启动 Agent ----
ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888 -v6

# ---- 看数据 ----
ros2 topic list
ros2 topic hz /scan
ros2 topic echo /encoder_data
ros2 topic echo /battery_voltage
ros2 topic echo /odom --field pose.pose

# ---- 发指令 ----
ros2 topic pub -r 10 /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.2}, angular: {z: 0.3}}"
ros2 topic pub --once /cmd_vel geometry_msgs/msg/Twist "{}" # 停车
ros2 topic pub --once /motor_speeds std_msgs/msg/Int32MultiArray "{data: [100,0,0,0]}"
ros2 topic pub --once /motor_config std_msgs/msg/Float32MultiArray "{data: [0.3, 1.0]}"

# ---- TF ----
ros2 run tf2_ros static_transform_publisher --x 0 --y 0 --z 0.1 --roll 0 --pitch 0 --yaw 0 \
--frame-id base_footprint --child-frame-id laser_frame
ros2 run tf2_tools view_frames

12. 已知限制

  1. 驱动板的上传帧格式未在协议文档中给出。固件实现了容错解析(多别名 + “未知指令但恰好 4 个整数按编码器处理” + 裸 CSV 支持)并带嗅探日志。
    首次上电请看串口有没有 未知帧 #n: "xxx:yyy" 的告警——若有,把那个指令名加进 motor_protocol.cpp 的别名表即可(10 秒的事)。
  2. 不发布 TF,需在 PC 端补 odom → base_footprint(见 §7.6)。
  3. X2 不提供每点强度,LaserScan.intensities 填的是常数(协议本身没有该字段)。
  4. X2 的距离相关角度校正系数(基线 21.8mm / 焦距 155.3mm)取自 YDLidar SDK,对 X2 是通用值;若你需要更高的角度精度,可关掉该校正后自行标定。
  5. 固件只支持单 Agent,不支持多机命名空间隔离(ROS_NAMESPACE 可改但未做多实例验证)。