【Micro-WL Robot】桌面级轮腿机器人全栈解析:LQR平衡控制、SimpleFOC驱动与五连杆腿部机构源码深度拆解

摘要

Micro-Wheeled_leg-Robot 是一个桌面级双轮腿机器人开源项目(GitHub 3000+ Star),覆盖机械结构(STP 模型)、四块定制 PCB(ESP32 + L6234 + AS5600 + MPU6050)、以及基于 Arduino + SimpleFOC 的完整控制软件。本文从控制算法核心切入,逐层拆解其 LQR 平衡控制的四分量解耦实现、自适应腿高增益调度、轮离地检测与跳跃状态机,并梳理硬件拓扑与 WebSocket 遥控架构。


代码MuShibo/Micro-Wheeled_leg-Robot


一、项目概览:为什么值得拆解?

轮腿机器人(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

小结

设计亮点

  1. LQR 四分量解耦 -- 将经典 LQR 的矩阵增益拆分为四个独立 PID,降低了调参复杂度。每个通道独立可调,适合嵌入式环境下的实时调优。非线性 PI 补偿( I = 15 I=15 I=15)解决了小信号区域的稳态误差问题。

  2. 轮离地检测 -- 通过轮角加速度和角速度双阈值检测,在跳跃或被抬起时自动切换为纯姿态模式,避免位移积分器饱和导致的失控。

  3. 自适应腿高增益 -- 速度环增益随腿高变化( 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 的社区验证说明了其复刻可行性。

相关推荐
Agilex松灵机器人3 小时前
什么是具身智能底盘?4 类主流 AI 机器人底盘选型|VLA/ROS2 项目硬件指南
人工智能·机器人·具身智能·vla·aloha·松灵科研案例
拓朗工控5 小时前
实现机器人大小脑深度融合:拓朗工控与搭载英伟达平台Jetson Orin NX具身智能控制器
机器人
Cxiaomu8 小时前
MentorPi A1 底盘接入开发实践:让自研Web系统接管机器人底盘
前端·机器人
产品人卫朋8 小时前
一个开源桌面机器人的从 0 到 1
机器人·开源·产品经理·创业·ipd流程
咖啡星人k8 小时前
自然语言驱动开发(NLDD):全栈开发的新范式与实践指南
驱动开发
沫儿笙9 小时前
安川焊接机器人弧焊节气装置
人工智能·机器人
hujinyuan2016010 小时前
电子学会青少年机器人技术(一级)等级考试试卷-真题+答案(2026年3月)
机器人
想你依然心痛11 小时前
Isaac Sim vs MuJoCo vs PyBullet:机器人仿真器选型终极指南(2026版)
java·开发语言·机器人
想你依然心痛12 小时前
Diffusion Policy实战:让机械臂学会推方块——从论文复现到真机部署
人工智能·机器人·具身智能