简介:MATLAB双轮差速驱动移动机器人“走8字”仿真的附件包,面向机器人与自动控制方向的在校生、科研人员,以及希望用Simulink完成运动建模与轨迹控制的初学者。包内共有4个文件,其中3个为MATLAB脚本,主要承担机器人参数定义、差速驱动模型构建和运动控制算法实现;另有1个Word文档,系统梳理了差速驱动原理、运动学方程、建模步骤与仿真结果分析,可作为学习笔记和项目复盘参考。资源包整体仅541KB,轻量便于下载。已有2660人学习下载,适合需要参考完整代码与思路的读者。通过其中代码与文档,可以掌握差速驱动机器人的数学模型与PID调节方法,理解左右轮转速差如何改变行驶方向和速度,并借助Simulink环境观察“8字”轨迹动态效果;文档中的过程说明也能帮助快速搭建类似移动机器人仿真项目。
1. 双轮差速移动机器人走8字仿真:先把轮速曲线解开,再谈轨迹
做移动机器人课程设计时,很多人一开始都以为“走8字”最难的是画出一条8字曲线。真正动手用 MATLAB 做双轮差速驱动的移动机器人走8字仿真后才发现,难点根本不在曲线,而在左右两个驱动轮的速度配合。8字不是画出来的,是左轮和右轮沿着时间轴相互追赶又相互让开的节奏。这篇笔记把从运动学建模、8字参考轨迹生成、轮速反解,到脚本和 Simulink 两种实现方式的完整路径拆开讲,并把轮速饱和、轨迹不闭合、起点错位这几个让仿真翻车的点单独列出来。适合正在写课程作业、准备机器人入门仿真,或者想把运动学公式变成一份能运行的仿真附件的人。照着下面的章节搭,你也能交付一份能跑的“走8字”仿真附件.zip。
2. 差速驱动机器人运动学建模:轮距、轮半径和位姿更新,先于8字想清楚
2.1 双轮差速模型成立的前提:把理想条件写进代码注释
双轮差速驱动的移动机器人,常见布局是左右两个驱动轮同轴,外加一个万向轮支撑。做运动学仿真时,我们默认三个条件:车身是刚体;轮子与地面纯滚动,不发生侧滑;轮子半径恒定不变。这三个条件决定了你后面用的是运动学公式而不是动力学公式。如果加入轮胎打滑、车身变形或者加速度限制,就需要引入滑移率模型和动力学方程,那就不是通常意义上的“走8字最小仿真”了。很多从脚本开始学的人都忽略了这个前提,等到想把仿真结果迁移到真实小车时,才发现模型输出跟电机实际响应有差距。
坐标系也得提前定死:全局坐标系是地面上的 XY 平面,x 正方向任意设定;机器人局部坐标系以车体中心为原点,x 轴指向车头,y 轴指向左侧,θ 是车体朝向与全局 X 轴的夹角。左右轮间距 L 取两轮接地点之间的距离,轮半径 r 取驱动轮半径。单位建议统一用米、弧度、秒。我做这类仿真时,最怕的就是半径用了厘米、轮距用了毫米,算出来轮速全不对。
代码里我习惯把这些参数单独放在一个文件里:
% figure8_robot_params.m % 机器人几何与仿真参数:单位统一为 m / rad / s r = 0.05; % 驱动轮半径 L = 0.30; % 左右轮间距(同轴轮距) v_des = 0.3; % 期望线速度 m/s dt = 0.01; % 仿真步长 s pose0 = [1.0; 0; pi/2]; % 初始位姿 [x; y; theta],与参考轨迹起点对齐参数说明:r 和 L 决定了后面所有轮速和位姿的单位。r=0.05 m 是常见小车轮子半径,L=0.3 m 相当于一个 30 cm 宽的底盘。v_des=0.3 m/s 是让小车走得不快不慢的速度,dt=0.01 s 是仿真步长。pose0 是初始位姿,它的取值必须和你生成的 8 字参考轨迹起点完全一致,否则仿真的 8 字和参考 8 字会出现整体偏移。这个坑在后面第 5 章会专门展开。
2.2 正运动学三件套:轮速之和、轮速之差、位姿更新
差速驱动的核心是左右轮速的“和”与“差”。设右轮角速度为 wr,左轮角速度为 wl,单位都是 rad/s,则车体中心线速度 v 和角速度 omega 为:
v = r * (wr + wl) / 2 omega = r * (wr - wl) / L
这里要特别注意左右轮方向约定。按我定义的 y 轴朝左,当右轮比左轮快时,车体逆时针转,omega 为正;如果 y 轴定义朝右,符号需要反过来。这个细节是仿真轨迹沿中轴线镜像的常见原因。
有了 v 和 omega,位姿更新方程就是经典的:
x_dot = v * cos(theta) y_dot = v * sin(theta) theta_dot = omega
仿真里用离散步长 dt 做欧拉积分。写成 MATLAB 函数:
function pose_new = diff_drive_step(pose, wr, wl, dt, r, L) % 差速驱动机器人运动学单步更新 % pose 为 [x; y; theta] % wr, wl 为右、左轮角速度,单位 rad/s v = r * (wr + wl) / 2; omega = r * (wr - wl) / L; x_new = pose(1) + v * cos(pose(3)) * dt; y_new = pose(2) + v * sin(pose(3)) * dt; theta_new = pose(3) + omega * dt; pose_new = [x_new; y_new; theta_new]; end逻辑说明:先用轮速之和算线速度,用轮速之差算角速度,再用欧拉积分更新位姿。这个函数是整套仿真里被调用最多次的部分,后面主循环每一步都会调用它。如果你把 dt 改大一些,比如 0.1 s,轨迹会明显跑偏,因为欧拉积分每步只保留一阶精度,误差会随步数累积。
参数说明:wr 和 wl 的单位必须一致。如果轮速来自编码器脉冲,要先换算成 rad/s。theta 在长时间仿真后会超过 2π,画图时可以先用 wrapToPi 把它卷回 [-π, π],但不建议在积分过程中每次都卷,否则角速度连续性会被破坏。
2.3 欧拉积分不是万能药:从小步长到 RK4
很多人写完上面的 diff_drive_step 就跑仿真,等到发现 8 字闭合不上才回头调 dt。这里提前说清楚:欧拉法对匀速圆周运动已经有相位误差,更不用说曲率处处变化的 8 字轨迹。我一般把 dt 设为参考轨迹采样间隔的 1/2 或更小。比如期望线速度 0.3 m/s,dt=0.01 s 时每步走 3 mm,这个空间分辨率对 a=1 m 的 8 字轨迹是够的。如果 dt 取 0.1 s,每一步走 3 cm,8 字轮廓就开始发毛。
想要更高精度,可以换成四阶 Runge-Kutta。对双轮差速这个模型,手写 RK4 并不复杂:
function pose_new = diff_drive_rk4(pose, wr, wl, dt, r, L) % 四阶 Runge-Kutta 积分,比欧拉法更稳 k1 = diff_drive_dyn(pose, wr, wl, r, L); k2 = diff_drive_dyn(pose + dt/2 * k1, wr, wl, r, L); k3 = diff_drive_dyn(pose + dt/2 * k2, wr, wl, r, L); k4 = diff_drive_dyn(pose + dt * k3, wr, wl, r, L); pose_new = pose + dt/6 * (k1 + 2*k2 + 2*k3 + k4); end function dp = diff_drive_dyn(pose, wr, wl, r, L) % 连续时间动力学,返回位姿导数 v = r * (wr + wl) / 2; omega = r * (wr - wl) / L; dp = [v * cos(pose(3)); v * sin(pose(3)); omega]; end参数说明:k1 到 k4 是不同点处的导数,加权合并后相当于用四次采样拟合一条曲线。dt=0.01 s 时,欧拉法可能产生厘米级闭合误差,RK4 通常能压到毫米级。代价是每个步长多算几次函数,但对单个机器人来说计算开销可以忽略。我的习惯是:先用欧拉法快速验证模型结构,确认轮速方向、轨迹形状没有大问题后,再把积分器换成 RK4 或直接用 MATLAB 的 ode45,最后看闭合误差。
2.4 逆运动学:给定 v 和 omega,左右轮速怎么拆回去
走 8 字仿真里,我们通常是先在车体中心层面规划 v 和 omega,然后再反解每个轮子的转速。由上面两个方程可以直接求逆:
wl = (v - omega * L/2) / r wr = (v + omega * L/2) / r
注意符号:omega 为正时右轮更快,所以 wr 更大。写成函数:
function [wr, wl] = diff_drive_inv(v, omega, r, L) % 由期望线速度 v 和角速度 omega 反解左右轮角速度 wr = (v + omega * L/2) / r; wl = (v - omega * L/2) / r; end这个函数在后面生成参考轮速序列时会反复调用。它有一个很明显的物理限制:当 |omega * L/2| 大于 |v| 时,某个轮子会反转。8 字轨迹在曲率较大的位置角速度很高,如果线速度太低,就可能出现一个轮子正转、另一个反转的情况。这在仿真里看起来很正常,但在真实电机上可能已经超出工作范围。后续轮速饱和的处理都得从这里出发。
3. 走8字参考轨迹生成与轮速反解:从伯努利双纽线到一条能跑的轮速序列
3.1 用伯努利双纽线做8字:为什么不建议用两段圆弧拼
一个常见做法是用两个半圆弧拼一个 8 字,但圆弧连接点处曲率突变,机器人角速度会跳变,仿真曲线会出现明显的折角。伯努利双纽线是一条连续曲线,参数方程为:
x(t) = a * cos(t) / (1 + sin(t).^2) y(t) = a * sin(t) .* cos(t) / (1 + sin(t).^2) t 从 0 到 2π
其中 a 决定 8 字的大小。t 从 0 走到 2π,曲线先画右叶再画左叶,经过原点两次。这个参数方程的优点是没有尖角,切线方向连续,适合差速驱动机器人跟踪。
生成密集参考点的 MATLAB 代码:
% gen_figure8_trajectory.m a = 1.0; % 8字半宽,单位 m t_dense = linspace(0, 2*pi, 4001); den = 1 + sin(t_dense).^2; x_dense = a * cos(t_dense) ./ den; y_dense = a * sin(t_dense) .* cos(t_dense) ./ den;参数说明:linspace 里的 4001 是采样点数,点数越多曲线越光滑。这里的 t 只是参数,不是时间。如果把均匀 t 直接当时间用,机器人实际线速度会忽快忽慢,因为双纽线上相等 t 间隔对应的弧长并不相等。所以下一步必须做弧长均匀化重采样。
3.2 弧长重采样:让机器人匀速走完整个8字
计算相邻点之间的距离,累加得到弧长 s,再把 s 均匀插值,让每个参考点之间的弧长相等。这样当机器人以恒定线速度扫过这些点时,每个采样间隔对应固定的时间。
% 按弧长重新采样,保证匀速 seg = sqrt(diff(x_dense).^2 + diff(y_dense).^2); s_dense = [0 cumsum(seg)]; s_total = s_dense(end); num_steps = round(s_total / (v_des * dt)); s_interp = linspace(0, s_total, num_steps); x_ref = interp1(s_dense, x_dense, s_interp, 'pchip'); y_ref = interp1(s_dense, y_dense, s_interp, 'pchip');参数说明:v_des=0.3 m/s,dt=0.01 s 时,v_des * dt = 0.003 m,也就是每个仿真步走 3 mm。num_steps 是仿真总步数,由总弧长除以每步弧长得到。插值方法用 pchip 而不是 linear,因为 linear 会在点之间产生折角,导致后续曲率计算出现毛刺。
重采样之后,每个参考点在弧长方向上是均匀分布的。为了算角速度,还需要每点的切线角 theta_ref。因为每个点间隔的时间相等,可以用 gradient 直接对时间差分:
dx = gradient(x_ref, dt); % x 对时间的导数 dy = gradient(y_ref, dt); theta_ref = unwrap(atan2(dy, dx)); % unwrap 避免角度跳变逻辑说明:gradient(x_ref, dt) 把 x_ref 当成等间隔时间信号,得到的是 x 方向速度分量。atan2 返回的是 [-π, π] 之间的值,一路累计会在 -π 和 π 之间跳变,所以用 unwrap 把角度展开成连续变化的角度。如果你跳过 unwrap,后面偏差计算会出现 2π 的阶跃,画 theta 曲线时特别明显。
3.3 由轨迹曲率得到角速度,再反解左右轮速
匀速走一条轨迹时,线速度保持恒定,角速度等于线速度乘以轨迹曲率 kappa。曲率公式:
kappa = (dx .* d2y - dy .* d2x) ./ (dx.^2 + dy.^2).^(3/2)
MATLAB 计算:
% 计算曲率和参考角速度 d2x = gradient(dx, dt); d2y = gradient(dy, dt); kappa = (dx .* d2y - dy .* d2x) ./ (dx.^2 + dy.^2).^(3/2); omega_ref = v_des * kappa; % 反解左右轮角速度 [wr_ref, wl_ref] = diff_drive_inv(v_des, omega_ref, r, L);参数说明:kappa 在 8 字交叉点附近数值较大,说明那里曲率半径小,机器人需要较大的角速度。以 v_des=0.3 m/s、L=0.3 m、r=0.05 m 为例,反解出来的 wr_ref 和 wl_ref 可能到几十 rad/s。这个数值在真实电机上通常早已达到饱和,但仿真里如果不加限制,轨迹依然能走出来。加上饱和限制之后,8 字轨迹就会明显变形,这就是第 5 章的主要踩坑点。
这里有两点要说明。第一,这个轮速序列是开环参考值。严格来说应该加一个 PID 控制器去跟踪,但在纯运动学仿真里,直接用参考轮速驱动 diff_drive_step,得到的轨迹和参考轨迹几乎重合,因为模型本身没有动力学延迟。很多仿真脚本就是这么做的,验收完全够。第二,omega 的符号要和前文的反解公式保持一致。如果你用反向约定,得到的 8 字会左右镜像。
3.4 完整主循环:把参考轮速喂给运动学模型并画轨迹
把前面所有部分串起来,就是一个最小可运行的主程序:
% main_figure8_simulation.m clear; close all; clc; % 参数 r = 0.05; L = 0.30; v_des = 0.3; dt = 0.01; % 生成参考轨迹与轮速(省略中间计算,见上文) % 假设已经得到 x_ref, y_ref, theta_ref, wr_ref, wl_ref % 初始化 pose = [x_ref(1); y_ref(1); theta_ref(1)]; x_sim = zeros(1, length(wr_ref)); y_sim = zeros(1, length(wr_ref)); % 主循环:用参考轮速驱动运动学模型 for k = 1:length(wr_ref) x_sim(k) = pose(1); y_sim(k) = pose(2); pose = diff_drive_step(pose, wr_ref(k), wl_ref(k), dt, r, L); end % 画图 plot(x_ref, y_ref, '--k', 'LineWidth', 1.2); hold on; plot(x_sim, y_sim, 'r', 'LineWidth', 1.5); legend('参考8字', '仿真轨迹'); axis equal; grid on; xlabel('x / m'); ylabel('y / m');逻辑说明:主循环先把当前位姿存入数组,再调用运动学函数走一步。仿真结束后把参考轨迹和仿真轨迹画在一起,用轴等比例 axis equal 避免图像被拉伸成扁 8 字。如果初始位姿与参考轨迹起点不一致,会看到八成形状相同但整体错位的两条曲线,这就是常见的起点对齐问题。
参数说明:pose 初始值从参考轨迹的第一个点取,而不是固定写 [0;0;0]。因为 8 字轨迹的起点不在原点,而在 (a, 0) 附近,用原点起跑会让整个 8 字偏移。这一行的正确性直接影响最后的轨迹对比图。
4. 把 MATLAB 脚本搬进 Simulink:双轮差速走8字的最小框图与运行要点
4.1 为什么用 Simulink:能看到信号流,也方便接控制器
脚本仿真的优势是代码直观、参数好改,但如果你要给老师或同事演示“两个轮速如何变成一条 8 字轨迹”,Simulink 框图比命令行更直观。框图里能看到积分器的流向,清楚显示 v、omega 从哪来,位姿怎么累积。另一个理由是,Simulink 模型可以较平滑地往里面加 PID 控制器、编码器反馈或者硬件在环接口。所以做这类仿真附件时,我一般会同时提供脚本版和 Simulink 版:脚本版用于批量参数扫描,Simulink 版用于汇报和演示。
Simulink 模型不需要做得很复杂。核心只有三个部分:轮速输入源、运动学子系统、输出可视化。左右轮速既可以用 Constant 模块写死,也可以用 From Workspace 模块从工作区导入参考轮速序列。我推荐后者,因为这样 Simulink 里跑的轮速和脚本版完全一致,对比结果才有说服力。
4.2 最小框图:MATLAB Function 块内写运动学更新
手动搭模型的步骤大致是:新建一个空模型,拖入两个 From Workspace 或 Constant 模块,再接一个 MATLAB Function 块,输出位姿到后续显示。如果不用 MATLAB Function,也可以直接用积分器、增益和三角函数模块搭,但连接线会比较多。
为了便于检查,我把 MATLAB Function 块里的代码给出来:
function pose = fcn(wr, wl) % 双轮差速运动学,Simulink 离散执行 % wr, wl 来自工作区参考轮速 r = 0.05; L = 0.3; dt = 0.01; % persistent 变量保存上一时刻位姿 persistent pose_prev if isempty(pose_prev) pose_prev = [1.0; 0; pi/2]; % 必须与参考轨迹起点对齐 end % 运动学更新 v = r * (wr + wl) / 2; omega = r * (wr - wl) / L; pose = pose_prev + dt * [v * cos(pose_prev(3)); v * sin(pose_prev(3)); omega]; pose_prev = pose; end逻辑说明:MATLAB Function 块在 Simulink 每个步长执行一次。persistent 变量 pose_prev 保存上一时刻的位姿,作用相当于一个积分器。如果你更习惯纯 Simulink 风格,可以把 persistent 替换成三个 Integrator 模块,输入分别是 vcosθ、vsinθ、omega。两者的数值结果完全一样。
参数说明:dt 必须和 Simulink 求解器的固定步长一致。如果 Simulink 里设的是变步长,这个函数里的 dt 就会与仿真实际步长不一致,时间轴全乱。建议在模型配置里把 Solver 设为固定步长 discrete 或 ode4,步长填 0.01。
4.3 从工作区导入参考轮速:From Workspace 的两种数据格式
Simulink 引用工作区变量,最常用的是 From Workspace 模块。它要求输入数据不是普通向量,而是结构体或两列矩阵。如果直接把 wr_ref 这个向量填进去,模型会报“Invalid workspace variable”错误。
正确做法是先把数据包装成结构体:
% 准备 Simulink 输入结构体 wr_sim.time = (0:length(wr_ref)-1)' * dt; wr_sim.signals.values = wr_ref(:); wr_sim.signals.dimensions = 1; wl_sim.time = (0:length(wl_ref)-1)' * dt; wl_sim.signals.values = wl_ref(:); wl_sim.signals.dimensions = 1;然后在 From Workspace 模块的 Data 参数里填 wr_sim 和 wl_sim。注意时间列必须从 0 开始,长度要和 wr_ref 的行数一致。很多人在这里报错,都是因为把向量直接填进去,或者时间列从 1 开始。这个细节属于典型的“看着简单,一跑就翻车”。
4.4 Simulink 模型配置清单:固定步长、零代数环、输出回工作区
下面这张表是我在 Simulink 里跑走 8 字仿真时固定检查的参数,直接照抄可以减少非常多的玄学问题。
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| 求解器类型 | 固定步长 | 避免变步长与离散运动学冲突 |
| 求解器 | ode4 / discrete | ode4 精度高,discrete 最契合离散模型 |
| 固定步长 | 0.01 | 与参考轮速采样间隔一致 |
| 仿真时间 | (num_steps-1)*dt | 确保读完整个轮速序列 |
| 输出 | 勾选输出到工作区 | 仿真结束后把位姿矩阵导出绘图 |
仿真时间设置成 num_steps 减一再乘以 dt,是因为轮速序列长度为 num_steps,每步从 0 到 dt,最后一步落在 (num_steps-1)*dt。如果你把仿真时间设得比轮速序列长,模型会在序列结束后保持最后一个值不变,轨迹末端出现一段奇怪的直线。我见过好几个同学被这个尾巴坑到,还以为模型不稳定。
5. 双轮差速走8字仿真避坑:五个让轨迹翻车的细节
5.1 8字交叉点附近轮速突跳:曲率大但速度没降
现象:参考轮速曲线在 8 字中心附近出现很尖的峰值,甚至其中一个轮子转速突然变号,仿真轨迹在交叉点附近出现锯齿。
原因:伯努利双纽线在原点附近曲率接近极大,而线速度保持恒定,角速度等于 v 乘以 kappa,所以左右轮速差会非常大。如果采样点不足,数值微分算出来的 kappa 还会抖动,进一步放大轮速毛刺。
解决:把期望线速度 v_des 从 0.3 降到 0.1,角速度峰值会按比例下降;同时把 t_dense 的采样数从 4001 提到 10001,让曲率计算更平滑。如果还不行,可以在计算完 kappa 后加一次平滑滤波,比如 kappa_smooth = smooth(kappa, 21, 'rlowess')。注意不要过度平滑,否则 8 字会被磨圆。
5.2 轮速饱和导致8字缩小:真实电机跟不上参考序列
现象:把仿真得到的轮速序列发给实际小车,8 字越走越小,甚至原地打转;但在 MATLAB 仿真里轨迹完全正常。
原因:真实电机有最大转速限制,参考轮速一旦超过限制,实际轮速被截断在饱和值。差速驱动的逻辑是,一旦一个轮子饱和,左右轮速差就变小,转向不足,8 字就被压扁。
解决:仿真阶段就加上饱和模块。脚本里可以用 min 和 max 截断轮速,Simulink 里用 Saturation 模块包住 wr_ref 和 wl_ref。然后统计饱和时间段占总时长比例,如果超过 10%,说明你的 v_des 和轨迹尺寸 a 不匹配,应该降低 v_des 或加大 a,而不是换更贵的电机。这个经验同样适用于以后做真实机器人。
5.3 参考轨迹起点没对齐:仿真从原点出发,8字却在别处
现象:仿真的 8 字和参考 8 字形状完全一样,但整体位置偏移,看起来像两条平行曲线。
原因:很多人的参考轨迹从 (a, 0) 出发,而 pose0 却设成 [0; 0; 0]。差速运动学模型没有绝对位置记忆,起点错位就会整体错位。
解决:让 pose0 等于参考轨迹的第一个点。如果是从 gen_figure8_trajectory 生成的轨迹,直接写 pose0 = [x_ref(1); y_ref(1); theta_ref(1)],绝对不要手算。这个“用数据本身初始化”的习惯,可以避免一大类对齐错误。我把这一条放在每次仿真的第一检查项。
5.4 欧拉积分误差累积:走完一个8字终点不闭合
现象:仿真结束时最后一点离起点有明显空隙,8 字看起来没合拢,误差可能到厘米级。
原因:欧拉法是一阶数值积分,误差与步长成正比。8 字总弧长几米,4000 步累加下来误差就很明显。如果 dt 又取得偏大,比如 0.05 s,误差会更大。
解决:先把 dt 降到 0.002 s,观察闭合误差是否明显改善;或者直接把积分换成 RK4。我常用的验收标准是:闭合误差小于 8 字特征尺寸的 1%,比如 a=1 m 时闭合误差小于 1 cm。如果 RK4 都压不到这个量级,就要检查参考轮速生成过程是否有方向或单位错误。
5.5 theta角度跳变导致画图异常:unwrap和wrapToPi不要用反
现象:轨迹图正常,但 theta 曲线在 ±π 之间反复跳变,计算参考轮速偏差时出现 2π 左右的阶跃毛刺。
原因:atan2 返回角度范围是 [-π, π],theta 一路累计会超过 2π。生成参考角度时没有 unwrap,或者计算误差时没有 wrapToPi,两个步骤混用。
解决:生成 theta_ref 时用 unwrap(atan2(dy, dx)),把角度展开成连续递增或递减的曲线;计算角度偏差时用 wrapToPi(target - current),把差值卷回 [-π, π]。unwrap 用在参考生成,wrapToPi 用在误差计算,这个顺序不能反。你如果看到 theta 曲线满是锯齿,十有八九就是这两者用混了。
6. 验证走8字仿真有没有做对:检查轮速曲线、闭合误差和导出动画
仿真跑完不等于做完。我把验证分成三步:先看轨迹重合度,再看轮速曲线,最后导出动画。轨迹重合度可以直接计算仿真轨迹与参考轨迹的最大偏差。闭合误差看终点和起点的距离。这两个数在脚本里一行就能算:
% 计算轨迹偏差与闭合误差 dev_max = max(sqrt((x_sim - x_ref).^2 + (y_sim - y_ref).^2)); close_err = sqrt((x_sim(end) - x_sim(1)).^2 + (y_sim(end) - y_sim(1)).^2); fprintf('最大轨迹偏差: %.4f m\n', dev_max); fprintf('闭合误差: %.4f m\n', close_err);参数说明:dev_max 反映仿真轨迹与参考轨迹的空间偏离,如果开环仿真能达到厘米级以下,说明运动学模型和轮速反解没问题。close_err 是终点到起点的距离,主要受数值积分误差影响。对 a=1 m 的 8 字,close_err 小于 1 cm 我认为可以直接交作业。
再检查左右轮速曲线。把 wr_ref 和 wl_ref 画在同一个图上,你会发现两轮速曲线是相互追赶的:直线段处两轮速接近,转弯处相差拉大。如果两条曲线完全重合,说明轮速差没起作用,8 字画不出来;如果相差过大,则可能进入饱和区。
最后导出动画。MATLAB 的 VideoWriter 可以把仿真过程保存成视频,放进附件里比静态图更有说服力:
% 导出仿真动画 v = VideoWriter('figure8.avi'); open(v); fig = figure; for k = 1:50:length(x_sim) plot(x_ref, y_ref, '--k'); hold on; plot(x_sim(k), y_sim(k), 'ro', 'MarkerSize', 8); axis equal; grid on; xlim([-1.5 1.5]); ylim([-1.5 1.5]); drawnow; writeVideo(v, getframe(fig)); hold off; end close(v);参数说明:循环里每隔 50 步保存一帧,这样视频文件不会太大。每帧先画参考轨迹作为底图,再画当前仿真位置的红点,视觉上能清楚看到小车沿 8 字移动。getframe 截取的是当前 figure 内容,所以每帧都要保持轴范围一致,否则画面会跳动。
我做这类仿真时还有一个习惯:先跑一版不加饱和的模型,确认运动学是对的,再逐步加饱和、加控制器。每一步只改一个变量,出了问题马上知道是哪一步引入的。这个习惯帮我省掉了大量调试时间。希望帮到你。
本文还有配套的精品资源,点击获取