摘要
Micro-Wheeled_leg-Robot 是一个桌面级双轮腿机器人开源项目(GitHub 3000+ Star),覆盖机械结构(STP 模型)、四块定制 PCB(ESP32 + L6234 + AS5600 + MPU6050)、以及基于 Arduino + SimpleFOC 的完整控制软件。本文从控制算法核心切入,逐层拆解其 LQR 平衡控制的四分量解耦实现、自适应腿高增益调度、轮离地检测与跳跃状态机,并梳理硬件拓扑与 WebSocket 遥控架构。
一、项目概览:为什么值得拆解?
轮腿机器人(Wheeled-Legged Robot)结合了轮式的高效移动和腿式的地形适应能力。但大多数开源项目要么只有仿真没有实物,要么硬件门槛极高。这个项目的特殊价值在于:
- 全栈开源:机械 CAD(STP)、PCB 设计(嘉立创 EDA)、固件源码(Arduino)、Web 遥控界面全部公开
- 桌面级尺寸:号称"最小的双轮腿机器人",3D 打印 + CNC 即可复刻
- 控制算法可读性强:LQR 平衡控制被拆分为四个独立 PID 分量,适合学习和调参

图 1:Micro-Wheeled_leg-Robot 实物 -- 桌面级双轮腿机器人,ESP32 主控,SimpleFOC 无刷驱动。来源:GitHub 仓库,仅供学习

图 2:机器人 3D 渲染模型 -- 展示五连杆腿部机构与轮毂电机布局。来源:GitHub 仓库,仅供学习
二、硬件架构
2.1 电子系统拓扑
四块定制 PCB 构成完整电控系统:
| PCB | 核心芯片 | 功能 | 通信方式 |
|---|---|---|---|
| 主控板 | ESP32 | 控制核心 + WiFi AP | -- |
| 电机驱动板 | L6234PD013TR | 无刷电机 FOC 驱动 | PWM (6路) |
| 编码器板 x2 | AS5600 | 磁编码器,电机角度反馈 | I2C (双总线) |
| IMU 板 | MPU6050 | 姿态角 + 角速度 | I2C (与右编码器共线) |
| 舵机调试板 | -- | 串口时分复用 | Serial2 @ 1Mbaud |

图 3:硬件连接拓扑 -- 四块 PCB 间通过 GH1.25 4PIN 线缆互联。重点关注 I2C 总线的双路分配:Bus0 给左编码器,Bus1 给右编码器+MPU6050。来源:GitHub 仓库,仅供学习
2.2 关键硬件参数
cpp
// 电机驱动引脚
Motor1: PWM{32, 33, 25}, EN=22
Motor2: PWM{26, 27, 14}, EN=12
// I2C 双总线 @400kHz
Bus0: SDA=18, SCL=19 // 左编码器 AS5600
Bus1: SDA=23, SCL=5 // 右编码器 AS5600 + MPU6050
// 舵机总线
Serial2: TX=17, RX=16, 1Mbaud // FEETECH STS3032
// 电池 ADC
Pin 35, 12bit, 11dB attenuation
battery_v = (esp_adc_cal_raw_to_voltage(raw) * 3.97) / 1000.0
三、核心控制算法
3.1 LQR 平衡控制:四分量解耦
这是系统最核心的部分。经典 LQR 通过状态反馈 u = − K x u = -Kx u=−Kx 计算控制量,其中状态向量 x = θ , θ ˙ , s , s ˙ ⊤ x = \\theta, \\dot{\\theta}, s, \\dot{s}^\top x=θ,θ˙,s,s˙⊤ 包含倾斜角、角速度、轮位移和轮速度。本项目将其拆分为四个独立 PID 通道,每个通道对应一个状态变量:
u LQR = u angle + u gyro + u distance + u speed u_{\text{LQR}} = u_{\text{angle}} + u_{\text{gyro}} + u_{\text{distance}} + u_{\text{speed}} uLQR=uangle+ugyro+udistance+uspeed
各分量的计算:
u angle = K θ ⋅ ( θ − θ 0 ) , K θ = 1.0 u_{\text{angle}} = K_{\theta} \cdot (\theta - \theta_0), \quad K_{\theta} = 1.0 uangle=Kθ⋅(θ−θ0),Kθ=1.0
u gyro = K θ ˙ ⋅ θ ˙ , K θ ˙ = 0.06 u_{\text{gyro}} = K_{\dot{\theta}} \cdot \dot{\theta}, \quad K_{\dot{\theta}} = 0.06 ugyro=Kθ˙⋅θ˙,Kθ˙=0.06
u distance = K s ⋅ ( s − s 0 ) , K s = 0.5 u_{\text{distance}} = K_s \cdot (s - s_0), \quad K_s = 0.5 udistance=Ks⋅(s−s0),Ks=0.5
u speed = K s ˙ ⋅ ( s ˙ − v ref ) , K s ˙ = 0.5 ∼ 0.7 u_{\text{speed}} = K_{\dot{s}} \cdot (\dot{s} - v_{\text{ref}}), \quad K_{\dot{s}} = 0.5 \sim 0.7 uspeed=Ks˙⋅(s˙−vref),Ks˙=0.5∼0.7
其中:
- θ \theta θ 从 MPU6050 的
getAngleY()获取 - θ ˙ \dot{\theta} θ˙ 从
getGyroY()获取 - s = − 0.5 × ( ϕ 1 + ϕ 2 ) s = -0.5 \times (\phi_1 + \phi_2) s=−0.5×(ϕ1+ϕ2), ϕ \phi ϕ 为电机轴角度
- s ˙ = − 0.5 × ( ϕ ˙ 1 + ϕ ˙ 2 ) \dot{s} = -0.5 \times (\dot{\phi}_1 + \dot{\phi}_2) s˙=−0.5×(ϕ˙1+ϕ˙2)
- v ref = 0.1 × lpf ( joyy ) v_{\text{ref}} = 0.1 \times \text{lpf}(\text{joyy}) vref=0.1×lpf(joyy),遥控速度指令经低通滤波

图 4:系统控制架构 -- LQR 四分量解耦平衡控制 + Yaw 转向叠加 + 腿部高度伺服。重点关注轮离地检测对控制分量的切换逻辑。重绘自 design skill
3.2 非线性补偿
LQR 输出经过一个额外的 PI 控制器做非线性补偿:
cpp
pid_lqr_u: P=1, I=15, D=0, limit=8
// 当误差 < 5 时启用
LQR_u = pid_lqr_u(LQR_u);
这个 PI 环的作用是消除小信号区域的稳态误差 -- 当机器人接近平衡点时,纯比例的四分量输出可能不足以克服摩擦和模型误差,积分项 I = 15 I=15 I=15(相对较大)负责消除这个残差。
3.3 轮离地检测
当机器人跳跃或被抬起时,轮子失去地面接触,位移和速度反馈失效。检测条件:
∣ ϕ ¨ ∣ > 10 rad/s 2 OR ∣ ϕ ˙ ∣ > 50 rad/s |\ddot{\phi}| > 10 \, \text{rad/s}^2 \quad \text{OR} \quad |\dot{\phi}| > 50 \, \text{rad/s} ∣ϕ¨∣>10rad/s2OR∣ϕ˙∣>50rad/s
触发后切换为纯姿态平衡模式(去掉 distance 和 speed 项):
u LQR liftoff = u angle + u gyro u_{\text{LQR}}^{\text{liftoff}} = u_{\text{angle}} + u_{\text{gyro}} uLQRliftoff=uangle+ugyro
3.4 Yaw 转向控制
转向通过差速实现,独立于平衡控制:
θ yaw + = joyx × 0.002 \theta_{\text{yaw}} \mathrel{+}= \text{joyx} \times 0.002 θyaw+=joyx×0.002
u yaw = K yaw , θ ⋅ θ yaw + K yaw , θ ˙ ⋅ θ ˙ yaw u_{\text{yaw}} = K_{\text{yaw},\theta} \cdot \theta_{\text{yaw}} + K_{\text{yaw},\dot{\theta}} \cdot \dot{\theta}_{\text{yaw}} uyaw=Kyaw,θ⋅θyaw+Kyaw,θ˙⋅θ˙yaw
其中 K yaw , θ = 1.0 K_{\text{yaw},\theta} = 1.0 Kyaw,θ=1.0, K yaw , θ ˙ = 0.04 K_{\text{yaw},\dot{\theta}} = 0.04 Kyaw,θ˙=0.04。
最终电机输出叠加转向:
τ 1 = − 0.5 × ( u LQR + u yaw ) \tau_1 = -0.5 \times (u_{\text{LQR}} + u_{\text{yaw}}) τ1=−0.5×(uLQR+uyaw)
τ 2 = − 0.5 × ( u LQR − u yaw ) \tau_2 = -0.5 \times (u_{\text{LQR}} - u_{\text{yaw}}) τ2=−0.5×(uLQR−uyaw)
倾角安全限制: ∣ θ ∣ > 25 ° |\theta| > 25° ∣θ∣>25° 时输出归零。
#mermaid-svg-X5vdL4NHmQYeftFf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-X5vdL4NHmQYeftFf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-X5vdL4NHmQYeftFf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-X5vdL4NHmQYeftFf .error-icon{fill:#552222;}#mermaid-svg-X5vdL4NHmQYeftFf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-X5vdL4NHmQYeftFf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-X5vdL4NHmQYeftFf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-X5vdL4NHmQYeftFf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-X5vdL4NHmQYeftFf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-X5vdL4NHmQYeftFf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-X5vdL4NHmQYeftFf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-X5vdL4NHmQYeftFf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-X5vdL4NHmQYeftFf .marker.cross{stroke:#333333;}#mermaid-svg-X5vdL4NHmQYeftFf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-X5vdL4NHmQYeftFf p{margin:0;}#mermaid-svg-X5vdL4NHmQYeftFf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-X5vdL4NHmQYeftFf .cluster-label text{fill:#333;}#mermaid-svg-X5vdL4NHmQYeftFf .cluster-label span{color:#333;}#mermaid-svg-X5vdL4NHmQYeftFf .cluster-label span p{background-color:transparent;}#mermaid-svg-X5vdL4NHmQYeftFf .label text,#mermaid-svg-X5vdL4NHmQYeftFf span{fill:#333;color:#333;}#mermaid-svg-X5vdL4NHmQYeftFf .node rect,#mermaid-svg-X5vdL4NHmQYeftFf .node circle,#mermaid-svg-X5vdL4NHmQYeftFf .node ellipse,#mermaid-svg-X5vdL4NHmQYeftFf .node polygon,#mermaid-svg-X5vdL4NHmQYeftFf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-X5vdL4NHmQYeftFf .rough-node .label text,#mermaid-svg-X5vdL4NHmQYeftFf .node .label text,#mermaid-svg-X5vdL4NHmQYeftFf .image-shape .label,#mermaid-svg-X5vdL4NHmQYeftFf .icon-shape .label{text-anchor:middle;}#mermaid-svg-X5vdL4NHmQYeftFf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-X5vdL4NHmQYeftFf .rough-node .label,#mermaid-svg-X5vdL4NHmQYeftFf .node .label,#mermaid-svg-X5vdL4NHmQYeftFf .image-shape .label,#mermaid-svg-X5vdL4NHmQYeftFf .icon-shape .label{text-align:center;}#mermaid-svg-X5vdL4NHmQYeftFf .node.clickable{cursor:pointer;}#mermaid-svg-X5vdL4NHmQYeftFf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-X5vdL4NHmQYeftFf .arrowheadPath{fill:#333333;}#mermaid-svg-X5vdL4NHmQYeftFf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-X5vdL4NHmQYeftFf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-X5vdL4NHmQYeftFf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-X5vdL4NHmQYeftFf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-X5vdL4NHmQYeftFf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-X5vdL4NHmQYeftFf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-X5vdL4NHmQYeftFf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-X5vdL4NHmQYeftFf .cluster text{fill:#333;}#mermaid-svg-X5vdL4NHmQYeftFf .cluster span{color:#333;}#mermaid-svg-X5vdL4NHmQYeftFf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-X5vdL4NHmQYeftFf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-X5vdL4NHmQYeftFf rect.text{fill:none;stroke-width:0;}#mermaid-svg-X5vdL4NHmQYeftFf .icon-shape,#mermaid-svg-X5vdL4NHmQYeftFf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-X5vdL4NHmQYeftFf .icon-shape p,#mermaid-svg-X5vdL4NHmQYeftFf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-X5vdL4NHmQYeftFf .icon-shape .label rect,#mermaid-svg-X5vdL4NHmQYeftFf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-X5vdL4NHmQYeftFf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-X5vdL4NHmQYeftFf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-X5vdL4NHmQYeftFf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes
No
MPU6050: theta, gyro_y
angle_control
gyro_control
AS5600 x2: phi1, phi2
distance_control
speed_control
Wheel Liftoff?
LQR_u = angle + gyro
LQR_u = angle + gyro + distance + speed
PI Nonlinear Comp
Joystick X
YAW Control
Motor1 = -0.5 * LQR_u + YAW
Motor2 = -0.5 * LQR_u - YAW
3.5 腿部高度控制与跳跃
舵机(FEETECH STS3032)控制五连杆腿高。高度到舵机位置的映射:
P servo = 2048 ± 12 ± 8.4 × ( h − 32 ) − Δ P roll P_{\text{servo}} = 2048 \pm 12 \pm 8.4 \times (h - 32) - \Delta P_{\text{roll}} Pservo=2048±12±8.4×(h−32)−ΔProll
其中 h h h 为目标高度(默认 38), Δ P roll \Delta P_{\text{roll}} ΔProll 来自横滚角 PID 补偿( K p = 8 K_p = 8 Kp=8,限幅 450 步)。
自适应增益 :腿高 h h h 影响速度环增益 K s ˙ K_{\dot{s}} Ks˙,高腿位( h > 50 h > 50 h>50)时减小到 0.5,低腿位时增大到 0.7。
跳跃状态机(约 240 个循环周期):
| 阶段 | 周期范围 | 动作 |
|---|---|---|
| 蓄力 | 1 | 伸腿到 80mm |
| 起跳 | 30-35 | 缩腿到 40mm |
| 滞空 | 35-200 | 纯姿态平衡模式 |
| 恢复 | 200-240 | 回到正常高度 |
四、通信与遥控架构
4.1 WiFi + WebSocket
ESP32 以 AP 模式启动热点(SSID 前缀 "WL"),WebSocket 服务运行在端口 81。Web 界面存储在 ESP32 Flash 中,手机/PC 浏览器访问 192.168.1.11 即可遥控。
数据格式:JSON,包含 {direction, height, roll, linear, angular} 字段。
4.2 串口调参
通过 SimpleFOC 的 Commander 接口,支持运行时调整所有 PID 参数:
cpp
Commander commander = Commander(Serial);
// 运行时修改 pid_angle.P, pid_speed.P 等

图 5:LQR 四分量解耦平衡算法流程 -- 从传感器输入到电机输出的完整数据路径,含轮离地检测分支和跳跃状态机。重绘自 design skill
小结
设计亮点:
-
LQR 四分量解耦 -- 将经典 LQR 的矩阵增益拆分为四个独立 PID,降低了调参复杂度。每个通道独立可调,适合嵌入式环境下的实时调优。非线性 PI 补偿( I = 15 I=15 I=15)解决了小信号区域的稳态误差问题。
-
轮离地检测 -- 通过轮角加速度和角速度双阈值检测,在跳跃或被抬起时自动切换为纯姿态模式,避免位移积分器饱和导致的失控。
-
自适应腿高增益 -- 速度环增益随腿高变化( K s ˙ ∈ 0.5 , 0.7 K_{\dot{s}} \in 0.5, 0.7 Ks˙∈0.5,0.7),补偿了不同重心高度下的动力学差异。
改进空间:
- 当前 IMU 直接使用 MPU6050 的 DMP 输出,无显式互补滤波或 EKF 融合,高动态场景下可能有延迟
- LQR 增益为手动标定,未见自动辨识或在线优化流程
- 跳跃状态机为硬编码时序,无着地检测反馈
个人判断:作为教学和开源复刻项目,这个仓库的控制代码可读性远超同类项目。四分量解耦的 LQR 实现虽然不是理论最优,但在工程上更易调参和理解,适合作为轮腿机器人入门的参考实现。3000+ Star 的社区验证说明了其复刻可行性。