news 2026/9/10 8:43:00

两轮平衡车ADRC控制实战:从MATLAB仿真到STM32部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
两轮平衡车ADRC控制实战:从MATLAB仿真到STM32部署

简介:本资源是一套基于自抗扰控制(ADRC)算法实现的两轮自平衡车MATLAB仿真与嵌入式联合开发方案,面向自动控制、机器人学及智能车方向的本科生、研究生与工程实践者,解决经典倒立摆系统建模、控制器设计与实时闭环控制验证等核心问题。压缩包共28个文件,含11个C/C++头文件(如ADRC.h、ESO.h、TWIP.h)与9个源码文件(如ADRC.cpp、Motor.cpp、KalmanFilter相关实现),支撑状态观测、姿态解算与电机驱动;另有4个.zbak备份文件、2个说明类txt及.gitmodules配置,整体仅1.4MB,轻量易部署。目前已有66人学习下载,适合开展控制算法对比实验、从MATLAB仿真到嵌入式移植的进阶实践。读者可直接复现ADRC对扰动抑制与动态响应的优化效果,获取完整模块化代码结构、传感器融合逻辑(加速度计/陀螺仪)、以及AB平衡车与ADRC双方案对照参考。

1. 为什么两轮平衡车仿真不用PID而选ADRC?——MATLAB里跑通自抗扰控制的真实门槛

两轮平衡车不是玩具,是控制理论的试金石。很多初学者在MATLAB里搭完倒立摆模型、调好PID参数,一上真实小车就抖动失稳——不是模型不准,而是PID对电机延迟、编码器噪声、电池压降这些“看不见的扰动”束手无策。这套ADRC两轮平衡车MATLAB仿真资源,核心价值不在代码行数,而在它把自抗扰控制(ADRC)从论文公式落地为可调试、可替换、可对比的完整闭环:从扩张状态观测器(ESO)实时估计总扰动,到非线性反馈律生成控制量,再到与底层电机驱动接口的C++封装层(TWIP.cpp/Motor.cpp)。它不依赖Simulink物理建模,纯M文件+结构化C++混合架构,适合想吃透ADRC内核又需对接嵌入式硬件的工程师。如果你正卡在“仿真稳、实物飘”的阶段,或需要一份能拆解、能改参、能移植到STM32/FPGA的控制逻辑参考,这份资源比任何教程视频都更接近工程现场。


2. ADRC核心模块拆解:ESO如何实时估计总扰动?从MATLAB函数到C++类实现

ADRC的威力不在“抗扰”,而在“实时估计扰动”。传统PID靠经验凑Kp/Ki/Kd,ADRC则用扩张状态观测器(ESO)把系统内部摩擦、外部风阻、电源波动等所有未知扰动,统一建模为一个“总扰动量”并在线观测。这套资源中,ESO.hESO.cpp是理解ADRC本质的关键入口。

2.1 ESO数学结构与MATLAB验证脚本定位

资源包中的ADRC.m主函数调用eso_update()进行状态更新,其离散化形式为:

% ESO核心更新(简化示意,实际代码含滤波与饱和处理) z1(k) = z1(k-1) + h*(z2(k-1) - beta1*(z1(k-1)-y(k))); z2(k) = z2(k-1) + h*(-beta2*sign(z1(k-1)-y(k)));

其中z1是被控量(倾角)的估计值,z2是总扰动及其导数的综合估计。beta1beta2是观测器增益,直接决定响应速度与噪声敏感度。注意beta1过大导致高频噪声放大,beta2过小则扰动跟踪滞后——这不是理论参数,而是必须在MATLAB里反复试出来的工程值。

提示:打开main.m,找到% === ESO tuning section ===注释块。这里预置了三组beta参数组合([10,5]/[20,15]/[40,30]),运行时切换并观察scope_eso窗口中z2曲线的毛刺程度与跟踪延迟,比看论文推导更快建立直觉。

2.2 C++端ESO类与硬件耦合细节

MATLAB仿真最终要迁移到单片机,资源包中ESO.h定义了轻量级C++类:

class ESO { public: float z1, z2; // 状态估计 float beta1, beta2; // 观测器增益(需与MATLAB一致) float h; // 采样周期(单位:秒) float y; // 当前测量值(倾角,弧度) void update(float y_measured) { float e = z1 - y_measured; z1 += h * (z2 - beta1 * e); z2 += h * (-beta2 * sign(e)); // sign()函数在Motor.cpp中已重载 } };
2.2.1 为什么用sign()而非线性增益?

sign(e)实现的是非线性观测器,对大误差快速收敛,对小误差抑制抖振。但sign()在数字系统中易引发高频振荡,因此Motor.cpp中实际使用带死区的平滑符号函数:

float sign_smooth(float x, float deadzone=0.001f) { if (x > deadzone) return 1.0f; if (x < -deadzone) return -1.0f; return x / deadzone; // 线性过渡区 }

注意:此死区值0.001f对应倾角约0.057°,需根据你的编码器分辨率(如12位ADC对应0.0879°/LSB)调整,否则ESO在平衡点附近持续震荡。

2.3 MATLAB与C++参数一致性校验表

参数名MATLAB位置C++位置典型值修改影响
h(采样周期)main.mTs=0.01TWIP.h#define CONTROL_PERIOD_MS 100.01s周期变短→ESO响应快但CPU负载高;需匹配ADC采样率
beta1ADRC.mbeta1=20ESO.cpp构造函数赋值15~40过大→z1噪声大;过小→z1跟踪慢,角度超调
beta2ADRC.mbeta2=15ESO.cpp构造函数赋值10~30过大→z2剧烈跳变;过小→z2无法跟踪突加扰动(如推车)
b0(控制增益)ADRC.mb0=0.8ADRC.cppfloat b0 = 0.8f0.5~1.2直接缩放控制量,影响电机响应强度

实操验证:在MATLAB命令行执行test_eso_convergence.m(资源包内附),输入不同beta组合,观察z1收敛时间(理想<0.5s)与z2稳态波动(应<0.02 rad/s²)。若波动超标,优先降低beta2而非beta1——这是ADRC调试的第一铁律。


3. 从MATLAB仿真到硬件部署:TWIP结构体与电机驱动接口映射

仿真再准,不连电机就是纸上谈兵。这套资源的亮点在于TWIP.h定义的硬件抽象层(HAL),它把MATLAB里的theta_dotmotor_pwm等变量,映射到真实小车的传感器读取与PWM输出流程。理解这个映射,是避免“仿真OK、实物飞车”的关键。

3.1 TWIP结构体:控制环路的数据中枢

TWIP.h中定义的核心结构体:

struct TWIP { // 传感器数据(来自Encoders.cpp/Gyroscope.cpp) float theta; // 倾角(弧度),融合陀螺仪与编码器 float theta_dot; // 角速度(rad/s),主要来自陀螺仪微分 float x; // 水平位移(米),由编码器累计 float x_dot; // 水平速度(m/s) // 控制输出 int16_t left_pwm; // 左轮PWM占空比(-1000~+1000) int16_t right_pwm; // 右轮PWM占空比(-1000~+1000) // 状态标志 bool is_balanced; // 平衡态标志(theta < 0.1rad) uint32_t timestamp; // 控制周期计时(ms) };
3.1.1 为什么theta_dot不直接用编码器微分?

编码器每转脉冲数(如1000PPR)经齿轮比(如1:19)后,角度分辨率仅≈0.019°,微分后噪声极大。theta_dot实际由Gyroscope.cpp读取MPU6050的角速度原始值(gyro_z),经低通滤波(截止频率15Hz)后得到。验证方法:在main.cpp中临时添加printf("gyro:%.3f, enc_diff:%.3f\n", gyro_z, (theta-theta_prev)/h);,对比两者差异——你会看到编码器微分在静止时跳变±0.5 rad/s,而陀螺仪稳定在±0.02 rad/s。

3.2 电机驱动协议与PWM安全机制

Motor.cpp实现了双轮独立控制,并内置硬限幅:

void Motor::setPWM(int16_t left, int16_t right) { // 安全限幅:防止启动电流冲击 left = constrain(left, -800, 800); // 实际限制±800而非±1000 right = constrain(right, -800, 800); // PWM极性适配:部分驱动板高电平有效,部分低电平有效 if (MOTOR_POLARITY == 1) { analogWrite(LEFT_PWM_PIN, abs(left)); digitalWrite(LEFT_DIR_PIN, left >= 0 ? HIGH : LOW); } else { analogWrite(LEFT_PWM_PIN, 1023 - abs(left)); digitalWrite(LEFT_DIR_PIN, left >= 0 ? LOW : HIGH); } }

提示:MOTOR_POLARITY宏定义在Motor.h中,必须根据你所用驱动板(如L298N、TB6612FNG)的实际逻辑电平设置。错误会导致电机反转或不转——这是实物调试中最常卡住的环节。

3.3 MATLAB与C++数据流对照表

MATLAB变量C++来源更新时机注意事项
thetaTWIP.thetaFusion::update()每个控制周期(10ms)融合算法在Fusion.cpp中,权重alpha=0.98表示陀螺仪主导
theta_dotTWIP.theta_dotGyroscope::read()同上未使用编码器微分,避免噪声
left_pwmADRC::compute_control()Motor::setPWM()同上输出值需经constrain()限幅,否则烧毁MOSFET
is_balancedif (abs(theta) < 0.1) ...同上0.1rad≈5.7°,是平衡态判定阈值,过大会导致频繁启停

实操步骤

  1. 打开main.cpp,定位while(1)循环内的twip.update_sensors();adrc.compute(twip);motor.setPWM(twip.left_pwm, twip.right_pwm);
  2. adrc.compute()前插入Serial.print("theta:"); Serial.println(twip.theta, 3);
  3. 上电后用串口监视器观察theta值:静止时应在0±0.02rad内波动,若持续漂移>0.1rad,说明陀螺仪零偏未校准(见Gyroscope.cppcalibrate_gyro()函数)。

4. ADRC参数整定实战:三步法调出不抖不飘的平衡效果

ADRC参数不是“算出来”的,是“试出来”的。这套资源提供了完整的MATLAB/C++双平台调试路径,但新手常陷入盲目调参。以下三步法,基于真实调试记录提炼,可将调参时间从3天压缩到2小时。

4.1 第一步:冻结ESO,只调b0omega_c

ADRC.m中注释掉ESO更新,用理想扰动补偿:

% 注释掉原有ESO调用 % [z1,z2] = eso_update(y, z1, z2, beta1, beta2, h); z2 = 0; % 强制扰动为0,验证基础控制性能 u = b0 * ( -omega_c^2 * (y - y_ref) - 2*omega_c*y_dot ); % 二阶TD+NLSEF

此时系统退化为带微分的PD控制。目标:让小车在无扰动下稳定直立,且无明显低频晃动(<1Hz)。

  • 若小车缓慢倒下 → 增大omega_c(如从15→25)
  • 若小车高频抖动(>5Hz) → 减小b0(如从0.8→0.5)
  • 关键指标:用示波器抓取电机电流,纹波应<额定电流的15%,否则b0过大。

4.2 第二步:激活ESO,分频段调beta1/beta2

恢复ESO后,用test_disturbance.m注入阶跃扰动(模拟推车):

% 在t=2s时施加0.1rad阶跃扰动 if t > 2 && t < 2.01 y = y + 0.1; end

观察z2曲线:

  • 低频段(0~2Hz)z2应平滑跟踪扰动趋势 → 调beta1(增大加快跟踪)
  • 中频段(2~10Hz)z2出现锯齿 →beta2过大,减小10%再试
  • 高频段(>10Hz)z2剧烈毛刺 →beta1过大,需同步降低

注意:beta1beta2存在耦合关系。经验公式:beta2 ≈ 0.6 * beta1。若按此比例仍抖动,检查h是否过小(如5ms)——采样率过高会放大量化噪声。

4.3 第三步:硬件联调中的“抖动-飘移”诊断树

现象最可能原因快速验证解决方案
小车左右摇摆(1~3Hz)omega_c过小或b0过小在MATLAB中临时omega_c=30,观察仿真是否改善增大omega_c至25~35,同步微调b0
小车缓慢前倾/后仰(<0.1Hz)陀螺仪零偏漂移静止时读取gyro_z平均值,若≠0则修改Gyroscope.cppgyro_offset执行calibrate_gyro()并写入EEPROM
启动瞬间猛冲b0过大或theta初始值偏差断开电机,手动扶正小车,读取theta初始值main.cpp中添加if(!is_calibrated) theta = 0;强制归零
平衡时电机嗡嗡响PWM频率过低或ESO高频噪声用示波器测PWM波形,频率应≥10kHz修改Motor.cppanalogWriteFrequency()为31250

终极验证指令:在MATLAB命令行运行validate_adrc_stability.m,该脚本自动执行:

  1. 施加0.05rad正弦扰动(0.5Hz)→ 检查theta响应相位滞后<30°
  2. 突加0.1rad阶跃 → 检查超调量<15%,调节时间<1.2s
  3. 持续运行300s → 统计theta标准差,应<0.008rad(≈0.46°)

若三项全通过,你的ADRC参数已达工业级可用水平——此时再把ADRC.h中的参数固化到C++代码,即可烧录到STM32。


5. 进阶技巧:用MATLAB实时绘图监控ESO状态,替代万用表查信号

调试实物平衡车时,最耗时的不是写代码,而是“猜”信号在哪出错。与其用万用表测电机引脚,不如用MATLAB的udp通信实时绘图——这套资源已预留接口,只需三步启用。

5.1 启用串口数据转发

修改main.cppsetup()函数:

void setup() { Serial.begin(115200); // 确保波特率与MATLAB一致 // 添加:发送关键变量到MATLAB Serial.print("START:"); Serial.println(TWIP_VERSION); }

并在主循环末尾添加:

void loop() { // ...原有控制逻辑... // 每100ms发一次数据(避免串口拥塞) static unsigned long last_send = 0; if (millis() - last_send > 100) { Serial.print("DATA:"); Serial.print(twip.theta, 3); Serial.print(","); Serial.print(twip.theta_dot, 3); Serial.print(","); Serial.print(adrc.z2, 3); Serial.print(","); Serial.print(twip.left_pwm); Serial.println(); last_send = millis(); } }

5.2 MATLAB端实时接收与绘图

创建realtime_monitor.m

% 初始化UDP接收 u = udp('LocalHost', 12345, 'LocalPort', 12345); fopen(u); % 创建实时图 figure('Name','ADRC Realtime Monitor'); ax1 = subplot(2,1,1); hold on; grid on; h1 = plot(NaN, NaN, 'b-', 'LineWidth',1.5); xlabel('Time (s)'); ylabel('Angle (rad)'); title('Theta & Theta_dot'); ax2 = subplot(2,1,2); hold on; grid on; h2 = plot(NaN, NaN, 'r-', 'LineWidth',1.5); xlabel('Time (s)'); ylabel('Z2 (disturbance)'); title('ESO Estimated Disturbance'); t_data = []; theta_data = []; theta_dot_data = []; z2_data = []; tic; while isvalid(u) && toc < 60 % 运行60秒 try data = fscanf(u, '%s'); % 读取一行 if startsWith(data, 'DATA:') parts = strsplit(data(6:end), ','); if length(parts) >= 4 t_data(end+1) = toc; theta_data(end+1) = str2double(parts{1}); theta_dot_data(end+1) = str2double(parts{2}); z2_data(end+1) = str2double(parts{3}); % 动态更新图形 set(h1, 'XData', t_data, 'YData', theta_data); set(h2, 'XData', t_data, 'YData', z2_data); drawnow limitrate; end end catch break; end end fclose(u);
5.2.1 关键参数说明
  • fscanf(u, '%s'):按行读取,避免数据粘连
  • drawnow limitrate:限制绘图帧率,防止MATLAB卡死
  • t_datatoc而非millis():确保时间轴绝对准确

提示:若图形闪烁,将limitrate改为nodrawnow,并在循环末尾加pause(0.01)。实时性稍降,但界面更稳。

5.3 用Z2曲线诊断真实扰动源

当小车平衡时,观察z2曲线:

  • 平稳直线(≈0):系统干净,无持续扰动
  • 缓慢上升斜线:电池电压下降(电机负载增加)→ 检查电源纹波
  • 周期性尖峰(≈10Hz):机械共振(如轮轴松动)→ 拧紧螺丝或加阻尼
  • 随机毛刺:编码器接触不良或电源干扰 → 检查地线连接

案例:某次调试中z2出现2Hz正弦波动,幅度0.15 rad/s²。用手机慢镜头拍摄发现车轮有0.5mm偏心——更换轮毂后z2回归平稳。这比用示波器查电源纹波快10倍。

至此,你已掌握从MATLAB公式推导、到C++硬件部署、再到实时信号诊断的全链路ADRC调试能力。下一步,可尝试将ESO.cpp中的sign_smooth()替换为saturation()函数,或引入KalmanFilter替代陀螺仪融合——这些进阶改造,代码骨架已在KalmanFilter子目录中备好。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 8:40:19

5分钟跑通 Tracy Profiler:精准定位帧时间毛刺的元凶

5分钟跑通 Tracy Profiler&#xff1a;精准定位帧时间毛刺的元凶 【免费下载链接】tracy Frame profiler 项目地址: https://gitcode.com/GitHub_Trending/tr/tracy 你有没有遇到过&#xff1a;程序每十几秒卡顿一次&#xff0c;等打开事后采样工具时&#xff0c;现场早…

作者头像 李华
网站建设 2026/9/10 8:39:59

基于JSP的Java植物科普网站毕业设计:从需求到答辩完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 8:35:10

ty 泛型可调用对象全解析:PEP 484 传统语法下的类型推断与特化

ty 泛型可调用对象全解析&#xff1a;PEP 484 传统语法下的类型推断与特化 【免费下载链接】ruff An extremely fast Python linter and code formatter, written in Rust. 项目地址: https://gitcode.com/GitHub_Trending/ru/ruff 本篇技术指南以 Ruff 仓库内置的类型检…

作者头像 李华
网站建设 2026/9/10 8:34:41

OpenCore Legacy Patcher 实战指南:让老 Mac 免费装上最新版 macOS

OpenCore Legacy Patcher 实战指南&#xff1a;让老 Mac 免费装上最新版 macOS 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher OpenCore Legacy Patcher&…

作者头像 李华