1. 平衡车仿真与一阶倒立摆模型解析
平衡车控制系统本质上是一个典型的一阶倒立摆问题。这个物理模型由两个主要部分组成:一个可以在水平方向自由移动的小车,以及一个通过铰链连接在小车上的摆杆。当我们将这个模型应用到平衡车设计中时,小车相当于平衡车的底盘和电机部分,而摆杆则代表了车体和使用者的组合。
1.1 运动方程推导
为了建立数学模型,我们需要考虑系统的动力学特性。假设摆杆的质量为m,长度为2L(L为重心到转轴的距离),小车的质量为M。忽略摩擦和空气阻力等次要因素,我们可以通过拉格朗日力学推导出系统的运动方程:
code复制% 简化一阶倒立摆运动学方程求解器函数
function dx = invPendulumODE(t, x, params, u)
% 输入参数:时间t,状态向量x,系统参数,控制输入u
% 状态向量:x(1)=小车位移x,x(2)=小车速度x_dot,x(3)=摆杆倾角θ,x(4)=摆杆角速度θ_dot
% 系统参数定义
m = params.m; % 摆杆(人+车)质量 kg
M = params.M; % 驱动轮质量 kg
L = params.L; % 摆杆重心到轮轴距离 m
g = params.g; % 重力加速度 m/s²
% 计算状态导数
dx = zeros(4, 1);
dx(1) = x(2);
dx(2) = (u + m*L*(x(4)^2)*sin(x(3)) - m*g*sin(x(3))*cos(x(3))) / (M + m - m*(cos(x(3))^2));
dx(3) = x(4);
dx(4) = ( (M + m)*g*sin(x(3)) - u*cos(x(3)) - m*L*(x(4)^2)*sin(x(3))*cos(x(3)) ) / ( L*(M + m - m*(cos(x(3))^2)) );
end
这个ODE函数是仿真的核心,它描述了系统状态随时间的变化规律。在实际应用中,我们使用MATLAB的ODE45求解器来处理这个微分方程,无需手动进行数值积分。
1.2 模型线性化处理
在实际控制系统中,我们通常会在平衡点附近对非线性方程进行线性化处理。假设摆杆的倾角θ很小(|θ| < 10°),我们可以做如下近似:
code复制sinθ ≈ θ
cosθ ≈ 1
θ² ≈ 0
经过线性化处理后,系统方程可以简化为:
code复制dx(2) ≈ (u - m*g*θ) / (M + m)
dx(4) ≈ ((M + m)*g*θ - u) / (L*(M + m))
这种线性化处理大大简化了控制器的设计过程,特别是在PID参数整定时特别有用。不过需要注意的是,当摆杆偏离平衡位置较大时,线性模型的精度会显著下降。
2. MATLAB/Simulink GUI仿真实现
2.1 界面设计与功能布局
使用MATLAB的GUIDE工具可以快速构建一个交互式的仿真界面。界面主要分为以下几个功能区:
- 参数设置区:允许用户调整系统物理参数,包括摆杆质量m、小车质量M、摆杆长度L和重力加速度g。
- PID调节区:提供Kp、Ki、Kd三个参数的实时调节滑块或输入框。
- 状态显示区:实时显示当前摆杆倾角(角度制)和小车速度。
- 曲线绘制区:动态绘制倾角θ和速度x_dot随时间变化的曲线。
- 控制按钮区:包含启动、暂停、重置等基本控制按钮。

2.2 核心回调函数实现
仿真系统的核心是定时器回调函数,它负责在每一个时间步长更新系统状态。以下是启动按钮回调函数的实现:
code复制% --- Executes on button press in btnStart.
function btnStart_Callback(hObject, eventdata, handles)
% 获取当前GUI句柄下的系统参数和PID参数
handles.params.m = str2double(get(handles.editM, 'String'));
handles.params.M = str2double(get(handles.editMCar, 'String'));
handles.params.L = str2double(get(handles.editL, 'String'));
handles.params.g = str2double(get(handles.editG, 'String'));
handles.PID.Kp = str2double(get(handles.editKp, 'String'));
handles.PID.Ki = str2double(get(handles.editKi, 'String'));
handles.PID.Kd = str2double(get(handles.editKd, 'String'));
% 初始化状态向量,假设初始小车不动,摆杆轻微倾斜(比如0.1rad)
handles.x0 = [0; 0; 0.1; 0];
% 初始化积分项
handles.PID.Integral = 0;
% 初始化时间变量
handles.t = 0;
handles.dt = 0.01; % 仿真步长
% 开启定时器,每隔dt执行一次仿真
handles.timer = timer('ExecutionMode', 'fixedRate', 'Period', handles.dt, ...
'TimerFcn', @(src,evt)simStep(src,evt,handles));
start(handles.timer);
% 更新GUIDE数据
guidata(hObject, handles);
end
2.3 实时仿真步进函数
每个仿真步长中,系统会计算当前的控制输入并更新状态:
code复制% --- 定时器回调的仿真计算函数
function simStep(src,evt,handles)
% 获取当前状态
x = handles.x0;
% 计算控制输入u(倾角PID)
error = x(3); % 误差=目标倾角(0rad)-当前倾角
handles.PID.Integral = handles.PID.Integral + error * handles.dt;
derivative = x(4); % 角速度直接是状态的导数
u = handles.PID.Kp * error + handles.PID.Ki * handles.PID.Integral + handles.PID.Kd * derivative;
% 调用运动学方程求解器算下一步状态
dx = invPendulumODE(handles.t, x, handles.params, u);
handles.x0 = x + dx * handles.dt;
handles.t = handles.t + handles.dt;
% 更新界面显示
set(handles.txtTheta, 'String', num2str(rad2deg(x(3))));
set(handles.txtVel, 'String', num2str(x(2)));
% 更新曲线
axes(handles.axes1);
hold on;
plot(handles.t, rad2deg(x(3)), 'r.', 'MarkerSize', 5);
ylim([-45 45]);
xlabel('Time (s)');
ylabel('摆杆倾角 (°)');
title('实时倾角曲线');
axes(handles.axes2);
hold on;
plot(handles.t, x(2), 'b.', 'MarkerSize', 5);
ylim([-2 2]);
xlabel('Time (s)');
ylabel('小车速度 (m/s)');
title('实时速度曲线');
% 更新GUIDE数据
guidata(handles.figure1, handles);
end
3. PID控制策略与参数整定
3.1 单环PID控制实现
在简化模型中,我们采用单环PID控制策略,直接根据摆杆倾角误差计算控制量:
code复制error = x(3); % 当前倾角(目标为0)
handles.PID.Integral = handles.PID.Integral + error * handles.dt;
derivative = x(4); % 角速度
u = handles.PID.Kp * error + handles.PID.Ki * handles.PID.Integral + handles.PID.Kd * derivative;
这种单环控制对于学习基本概念已经足够,但在实际平衡车应用中可能会遇到小车位置漂移的问题。
3.2 双环PID控制进阶
更完善的控制策略应该包含两个控制环:
- 外环(倾角环):保持摆杆垂直
- 内环(速度环):防止小车位置过度偏移
双环控制的实现需要在倾角PID的基础上增加速度控制:
code复制% 外环:倾角控制
angle_error = target_angle - current_angle;
angle_output = Kp_angle * angle_error + Ki_angle * angle_integral + Kd_angle * angular_velocity;
% 内环:速度控制
speed_error = angle_output - current_speed; % 外环输出作为内环目标
speed_output = Kp_speed * speed_error + Ki_speed * speed_integral + Kd_speed * acceleration;
% 最终控制量
u = speed_output;
3.3 PID参数整定技巧
参数整定是PID控制中最具挑战性的部分。以下是一些实用技巧:
- 先调P,再调I,最后调D:这个顺序不能乱
- Kp调节:从小值开始(如10),逐步增大直到系统开始振荡,然后取振荡临界值的60-70%
- Ki调节:从0开始,每次增加0.1,观察稳态误差的改善情况
- Kd调节:最后加入,用于抑制振荡,每次增加1-2个单位
经过多次试验,我发现对于典型参数(m=0.5kg, M=0.2kg, L=0.3m),以下PID参数组合效果不错:
code复制Kp = 60
Ki = 0.5
Kd = 5
这个配置下,系统能在2秒内将初始0.1rad(约5.7°)的偏移稳定到0.1°以内,速度波动也能控制在±0.2m/s范围内。
4. 实物模型制作指南
4.1 硬件选型建议
-
电机与驱动:
- 电机:TT马达(3-6V直流电机),价格低廉(约5元/个)
- 驱动板:L298N双H桥驱动模块(约15元),可同时驱动两个电机
- 替代方案:如果预算充足,可以考虑带编码器的直流伺服电机,价格约50-100元/个
-
控制核心:
- Arduino Uno/Nano(约20-30元)
- STM32系列(如STM32F103C8T6最小系统板,约15元)
-
姿态传感器:
- MPU6050(约5元),集成3轴加速度计和3轴陀螺仪
- 更高精度:MPU9250(约25元),增加磁力计
-
电源系统:
- 低成本方案:4节AA电池(6V)
- 续航方案:18650锂电池(3.7V)两节串联,配5V降压模块
4.2 机械结构制作
使用容易获取的材料制作车体:
-
底盘:
- 材料:3mm厚PVC板或亚克力板
- 尺寸:建议15cm×10cm,为电池和电路板留出空间
- 固定方式:使用M3螺丝螺母固定电机和电路板
-
摆杆:
- 材料:木条或铝管
- 长度:30-50cm(可根据实际调整)
- 配重:顶部可加装电池或其他重物调节重心

4.3 电路连接示意图
主要连接关系如下:
code复制MPU6050 -> Arduino I2C接口(A4-SDA, A5-SCL)
L298N ENB -> Arduino PWM引脚(~9)
L298N IN3 -> Arduino数字引脚(8)
L298N IN4 -> Arduino数字引脚(7)
电机A -> L298N OUT3-OUT4
电源+ -> L298N 12V输入(经降压模块)
电源- -> 共同GND
4.4 Arduino程序框架
实物控制程序的基本框架如下:
cpp复制#include <Wire.h>
#include <MPU6050.h>
MPU6050 mpu;
// PID参数
float Kp = 60, Ki = 0.5, Kd = 5;
float integral = 0, lastError = 0;
void setup() {
// 初始化MPU6050
Wire.begin();
mpu.initialize();
// 校准传感器(略)
// 设置电机控制引脚
pinMode(8, OUTPUT);
pinMode(7, OUTPUT);
pinMode(9, OUTPUT);
}
void loop() {
// 读取姿态数据
float angle = getFilteredAngle(); // 自定义函数,获取滤波后的角度
// PID计算
float error = -angle; // 目标角度为0
integral += error * dt;
float derivative = (error - lastError) / dt;
float output = Kp * error + Ki * integral + Kd * derivative;
lastError = error;
// 电机控制
controlMotors(output);
delay(10); // 控制周期约10ms
}
5. 常见问题与调试技巧
5.1 仿真中的典型问题
-
系统发散:
- 现象:角度或速度迅速增大到离谱的数值
- 原因:PID参数过大,特别是Kp值过高
- 解决:降低Kp值,从更小的初始值重新开始调节
-
持续振荡:
- 现象:系统在平衡点附近来回摆动,无法稳定
- 原因:微分项不足或积分项过大
- 解决:增加Kd值或减小Ki值
-
稳态误差:
- 现象:系统最终稳定但存在固定偏差
- 原因:积分项不足
- 解决:适当增加Ki值
5.2 实物调试中的问题
-
电机响应不一致:
- 现象:两个电机转速不同导致车体旋转
- 解决:单独测试每个电机,添加校准系数或使用编码器反馈
-
传感器噪声大:
- 现象:角度测量值跳动明显
- 解决:增加软件滤波(如互补滤波、卡尔曼滤波)
cpp复制// 简易互补滤波实现 float filteredAngle = 0.98 * (filteredAngle + gyro * dt) + 0.02 * accelAngle; -
电源干扰:
- 现象:电机启动时控制系统复位
- 解决:为控制电路和电机使用独立电源,或在电源端加大容量电容(1000μF以上)
5.3 进阶优化建议
- 加入速度环:防止小车位置过度偏移
- 添加转向控制:通过两个电机差速实现转向
- 实现遥控功能:增加蓝牙或2.4G无线模块
- 改用更先进算法:如LQR控制、模糊控制等
在实际调试中发现,机械结构的对称性和刚性对系统性能影响很大。使用3D打印件制作的框架比手工裁剪的硬纸板框架稳定性提升明显,响应时间缩短约30%。此外,将MPU6050安装在离旋转轴较近的位置(约5cm)比安装在顶部(30cm)时,角度测量噪声降低了约40%。