
在智能车竞赛中两轮直立车摩托组的调试是公认的难点之一其核心在于如何让一个原本不稳定的倒立摆系统稳定、快速地运动。很多同学在初次接触时面对陀螺仪、加速度计的数据和复杂的PID公式常常感到无从下手调参过程更是充满了玄学色彩。本文将系统性地复盘第19届全国大学生智能车竞赛摩托组直立调试的全过程从核心原理、传感器数据处理、串级PID的逐级整定到实际调试中的“坑点”与技巧为你提供一套清晰、可复现的调试方案。无论你是初次接触直立车的新手还是希望优化现有方案的老手都能从中找到实用的思路和代码。1. 直立车控制的核心原理与串级PID架构在深入调试之前我们必须理解两轮直立车为什么能“站”起来。其本质是一个倒立摆系统本身是不稳定的需要通过车轮的持续运动来维持平衡。1.1 倒立摆模型与平衡控制想象一下你用手指顶着一根直立的木棍。当木棍向前倾倒时你的手指需要向前移动来“接住”它反之亦然。直立车的控制逻辑与此完全相同传感器车模上的陀螺仪和加速度计相当于你的眼睛和内耳感知车身的倾角Pitch和角速度。控制器单片机中的PID算法相当于你的大脑根据感知到的“倾倒”趋势计算出需要施加的“纠正力”。执行器电机相当于你的腿驱动车轮产生相应的加速度使车身回到直立状态。这个闭环控制过程需要极高的响应速度通常控制周期在1-10ms任何环节的延迟或误差都可能导致系统发散车倒下。1.2 为什么需要串级PID对于直立车单一的速度环或角度环PID往往难以兼顾响应速度和稳定性。因此串级PIDCascade PID成为了标准解决方案。它将控制任务分层内环速度环快速响应。以陀螺仪测得的车身倾角角速度作为反馈直接控制电机的输出力矩用于抑制车身的快速晃动相当于人的“膝跳反射”。外环角度环精确控制。以融合传感器数据得到的车身倾角作为反馈计算出一个目标角速度给内环用于维持车身的期望角度相当于人的“有意识平衡”。这种结构的好处是外环负责“宏观”定位内环负责“微观”抗扰分工明确参数耦合度低更容易调试。1.3 系统总体控制框图一个典型的直立车串级PID速度控制框图如下此处用文字描述[目标速度] -- (速度PID) -- [目标角度] -- (角度PID) -- [目标角速度] | V 实际车身倾角 -- (传感器融合) -- [MPU6050数据] (角速度PID) -- [电机PWM输出] | V [车轮运动] -- [实际速度反馈]流程解释给定一个目标速度比如0表示静止直立。速度PID根据“目标速度”与“编码器反馈的实际速度”的偏差计算出一个“目标车身倾角”。车要向前跑就需要向前倾。角度PID根据“目标倾角”与“传感器融合得到的实际倾角”的偏差计算出一个“目标角速度”。角速度PID根据“目标角速度”与“陀螺仪测得的实际角速度”的偏差直接计算出最终的电机PWM输出值。电机驱动车轮运动同时编码器反馈速度MPU6050反馈新的角度和角速度形成闭环。重要提示有些方案会将“角速度环”作为内环“角度环”作为外环而另一些方案如上图在角度环外再加一个速度环构成三环串级。本文讨论的是后者即速度环 - 角度环 - 角速度环的结构这也是第19届竞赛中许多优秀队伍采用的方案因为它能更好地实现速度控制。2. 环境准备与硬件平台说明在开始软件调试前确保你的硬件平台和基础代码是可靠的。2.1 硬件平台概览主控MCU通常为STC8/STC16、GD32、STM32F4/F1系列。确保定时器、PWM、编码器接口、IIC/SPI用于IMU等外设配置正确。姿态传感器IMU绝大多数队伍使用MPU6050集成三轴陀螺仪三轴加速度计部分使用MPU9250、ICM20602等。其精度和稳定性是直立的基础。电机与驱动一般采用直流减速电机搭配TB6612、DRV8833、BTN7971等电机驱动芯片。电机性能需一致避免因硬件差异引入控制难度。编码器AB相增量式编码器用于测量车轮实际转速是速度环反馈的关键。精度线数越高速度测量越平滑。电源稳定的电源至关重要。电池电压波动会直接影响电机出力建议在控制算法中引入电压补偿。2.2 软件环境与工程结构开发环境Keil uVision5、IAR、VSCodePlatformIO等。关键代码模块imu.c/.hMPU6050的驱动、数据读取、姿态解算核心。encoder.c/.h编码器计数与速度计算。motor.c/.h电机PWM驱动函数。pid.c/.hPID控制器实现。control.c/.h主控制逻辑集成串级PID调用。main.c主循环调度所有任务。一个建议的工程目录结构如下/Project ├── CMSIS/ 芯片底层库 ├── Drivers/ 外设驱动 ├── User/ │ ├── main.c │ ├── control.c/.h # 核心控制逻辑 │ ├── imu.c/.h # 姿态传感器处理 │ ├── pid.c/.h # PID算法库 │ ├── motor.c/.h # 电机控制 │ ├── encoder.c/.h # 编码器 │ └── filter.c/.h # 滤波器如卡尔曼、互补 └── 其他配置文件2.3 基础驱动检查清单在写控制算法前请务必完成以下检查MPU6050能稳定读取原始数据accel_x,y,z,gyro_x,y,z并通过自检。编码器正反转计数方向正确能通过M法或M/T法计算出准确的速度值单位cm/s 或 m/s。电机PWM输出极性正确给正占空比车轮正转且左右轮转向一致。定时器用于控制周期如5ms的定时器中断配置正确确保控制频率稳定。调试接口准备好串口、蓝牙或无线模块用于将关键数据如角度、角速度、PID输出发送到上位机如匿名上位机、VOFA、SerialPlot进行可视化。这是调试成败的关键3. 传感器数据处理与姿态解算原始数据不能直接用必须经过处理和融合才能得到可靠的姿态信息。3.1 陀螺仪与加速度计的优缺点陀螺仪测量角速度积分可得角度。优点动态响应好短期精度高。缺点存在零漂积分会累积误差时间长了角度会“飘走”。加速度计测量比力在静止或匀速时可通过重力分量反解出倾角。优点长期稳定无累积误差。缺点对振动和运动加速度极其敏感动态响应差。因此我们需要融合二者优点互补滤波。3.2 互补滤波算法实践互补滤波思想简单有效用加速度计的角度修正陀螺仪积分的长期漂移用陀螺仪的角度抑制加速度计的高频噪声。核心代码实现 (imu.c):// 定义全局变量 float Angle_Acc; // 由加速度计计算出的角度单位度 float Angle_Gyro; // 由陀螺仪积分得到的角度 float Angle_Filtered; // 融合后的最终角度 float Gyro_y; // 陀螺仪Y轴角速度假设绕Y轴旋转控制俯仰 // 互补滤波系数通常取0.98-0.995需要调试 #define K 0.98f /** * brief 互补滤波姿态解算函数需在固定周期如5ms内调用 * param acc_y: 加速度计Y轴原始值 * param acc_z: 加速度计Z轴原始值 * param gyro_y: 陀螺仪Y轴原始角速度需乘比例因子转为度/秒 */ void IMU_ComplementaryFilter(short acc_y, short acc_z, float gyro_y_dps) { // 1. 加速度计计算角度atan2单位弧度转为度 // 注意根据传感器安装方向调整acc_y, acc_z的顺序和符号 Angle_Acc atan2(-acc_y, acc_z) * 57.29578f; // 57.29578 180/PI // 2. 陀螺仪积分得到角度增量 // dt为采样周期单位秒例如0.005s (5ms) static float dt 0.005f; Angle_Gyro Angle_Gyro gyro_y_dps * dt; // 3. 互补滤波融合 // 公式融合角度 K * (上一周期融合角度 陀螺仪增量) (1-K) * 加速度计角度 Angle_Filtered K * (Angle_Filtered gyro_y_dps * dt) (1 - K) * Angle_Acc; // 4. 更新角速度值用于角速度环 Gyro_y gyro_y_dps; // 注意单位一致性PID中通常用度/秒 }调试要点系数KK值越大信任陀螺仪越多动态响应好但可能漂移K值越小信任加速度计越多抗静态漂移好但动态响应差。从0.95开始调试。安装方向atan2中的参数(acc_y, acc_z)符号至关重要必须根据你的车模实际安装方向调整。可以通过将车模静止放在水平面上观察Angle_Acc输出是否接近0度来验证。单位统一确保陀螺仪原始值已转换为度/秒dps角度单位为度与PID参数匹配。3.3 进阶方案卡尔曼滤波对于追求更高性能的队伍可以使用卡尔曼滤波。它通过状态空间模型理论上能提供最优估计。但其参数过程噪声Q、测量噪声R较难调试。在智能车应用场景下一个精心调参的互补滤波往往足以满足要求且更易于理解和实现。初学者建议先从互补滤波入手。4. 串级PID控制器逐级整定实战这是调试的核心部分。原则是从内环到外环逐级闭合调好一环再调下一环。我们将按照角速度环 - 角度环 - 速度环的顺序进行。4.1 PID控制器代码实现首先需要一个健壮的PID函数。这里采用位置式PID并加入抗积分饱和和微分先行处理。// pid.h typedef struct { float target; // 目标值 float actual; // 实际值 float err; // 当前误差 float err_last; // 上次误差 float err_integral; // 误差积分 float Kp, Ki, Kd; // PID参数 float integral_limit; // 积分限幅 float output_max; // 输出限幅 float output; // 控制器输出 } PID_TypeDef; void PID_Init(PID_TypeDef *pid, float kp, float ki, float kd, float integral_limit, float output_max); float PID_Calculate(PID_TypeDef *pid, float target, float actual);// pid.c void PID_Init(PID_TypeDef *pid, float kp, float ki, float kd, float integral_limit, float output_max) { pid-Kp kp; pid-Ki ki; pid-Kd kd; pid-integral_limit integral_limit; pid-output_max output_max; pid-err_integral 0; pid-err_last 0; pid-output 0; } float PID_Calculate(PID_TypeDef *pid, float target, float actual) { pid-target target; pid-actual actual; pid-err pid-target - pid-actual; // 积分分离可选误差大时不积分 // if(fabs(pid-err) 50) { // 阈值需调试 pid-err_integral pid-err; // } // 积分抗饱和 if (pid-err_integral pid-integral_limit) pid-err_integral pid-integral_limit; if (pid-err_integral -pid-integral_limit) pid-err_integral -pid-integral_limit; // PID计算 pid-output pid-Kp * pid-err pid-Ki * pid-err_integral pid-Kd * (pid-err - pid-err_last); // 微分项 // 输出限幅 if (pid-output pid-output_max) pid-output pid-output_max; if (pid-output -pid-output_max) pid-output -pid-output_max; pid-err_last pid-err; return pid-output; }4.2 第一步整定内环——角速度环最内环目标让车模能快速响应并抑制自身的抖动。此时先断开角度环和速度环即角度环的目标target_angle固定为0。操作步骤初始化参数将角度环PID的输出限幅output_max设为0使其不工作。角速度环PID的target_gyro设为0。KpKiKd0。手持车模测试用手握住车模使其保持大致直立然后快速小幅度地前后扭动车模模拟扰动。先调Kp逐渐增大角速度环的Kp。你会感觉到电机开始抵抗你的扭动并且有“硬度”。继续增大Kp直到车模在你扭动时产生高频振荡“嗡嗡”声或剧烈抖动。此时Kp略大。回调Kp直到振荡消失车模能迅速抵抗扭动并稳定下来。记录此时的Kp值。再调Kd适当加入Kd从Kp的1/10到1/20开始。Kd能抑制超调和振荡。增大Kd车模抵抗扭动会更“干脆”但过大的Kd会对噪声敏感可能引发高频振动。调到手感“顺滑”且无振荡为止。最后调Ki通常很小或为0角速度环的Ki主要用于消除静差。但由于传感器零漂和电机死区可能需要一点Ki。给一个很小的Ki如0.001观察在长时间手持时电机输出是否会缓慢地朝一个方向变化。如果变化过大说明Ki大了需减小。很多情况下角速度环的Ki可以设为0。调试技巧上位机观察将target_gyro(0),actual_gyro(陀螺仪Y轴),output_pwm发送到上位机。调整参数时观察actual_gyro曲线能否快速跟随target_gyro即快速归零且无超调或振荡。手感调好的角速度环手感上应该是“硬而不振”像握住一个有力的弹簧。4.3 第二步整定中环——角度环目标让车模能自主维持在一个给定的角度。此时闭合角度环但仍断开速度环速度环目标设为0。操作步骤准备确保角速度环已调好。角度环的target_angle初始设为0直立。先调Kp将车模放在地上用手扶住。逐渐增大角度环的Kp。你会发现车模开始有“站”起来的趋势电机出力试图让车身回到0度。继续增大Kp直到松手后车模能自己站起来但可能会在直立位置附近来回摆动大幅低频振荡。这个Kp值就是临界值。回调Kp直到车模能站住摆动幅度很小且收敛。如果Kp太小车模会软弱无力一推就倒Kp太大则会剧烈振荡。再调Kd角度环的Kd非常重要它提供阻尼抑制角度振荡。从0开始增加Kd观察车模站立时的摆动情况。合适的Kd能显著减小摆动幅度让车模快速稳定。Kd太大系统会反应迟钝像“冻住”一样抗扰动能力变差。Ki的处理角度环的Ki通常也设为0。因为角度环的稳态误差即直立时不是绝对0度可以由内环的角速度环来修正。引入Ki容易引起积分饱和导致控制滞后。调试现象与参数关系站不住往一边倒可能是机械中值机械零点不对或角度环Kp太小。大幅低频摇摆像钟摆角度环Kp过大或Kd过小。高频颤抖角速度环Kp过大或Kd不合适也可能是角度环Kd过大。反应迟钝推一下恢复慢角度环Kd可能过大或Kp偏小。4.4 第三步整定外环——速度环目标让直立的车模能稳定在指定速度并且能抵抗外力保持速度。此时整个串级系统全部闭合。操作步骤准备角度环和角速度环已基本调稳车模能原地站稳。理解速度环的作用速度环的输入是“目标速度”和“编码器测得的速度差”输出是“角度环的目标角度偏移量”。想让车向前走速度环就输出一个正的角度偏移让车身前倾车为了保持平衡就会向前加速。速度环的Kp、Ki决定了车模加速/减速的“意愿”强度。先调Kp将目标速度设为一个小值如10 cm/s。逐渐增大速度环的Kp。观察车模开始缓慢移动并试图保持这个速度。Kp太小车模加速慢达到目标速度时间长且容易受外力影响。Kp太大车模会“冲”出去产生速度超调甚至引发整个系统振荡表现为车身前后摇晃着前进。调到车模能平稳启动、匀速运动轻推它能较快恢复目标速度为止。重点调Ki速度环的Ki至关重要它用于消除稳态速度误差。没有Ki车模在坡道或阻力变化时无法保持设定速度。给一个很小的Ki如0.01。观察车模在匀速运行一段时间后是否能精确保持速度。如果车模速度持续缓慢增加或减少爬行说明Ki需要调整。通常需要增大Ki以增强“跟踪力”。警惕积分饱和速度环的积分项最容易饱和。必须设置合理的integral_limit积分限幅通常限制在角度环最大目标偏移量的20%-50%以内。例如角度环最大接受±10度的指令那么速度环输出即角度偏移限幅应在±2到±5度之间。Kd的使用速度环的Kd一般不用因为速度测量噪声较大微分会放大噪声。闭环调试技巧分段调试先调低速0-30 cm/s再调中高速。观察上位机同时观察目标速度、实际速度、角度环目标角度、实际角度四条曲线。理想状态下实际速度应平稳跟踪目标速度角度目标应平滑变化。加入干扰在车模运行时轻轻阻挡它看它能否在解除阻挡后迅速恢复原速。这是检验速度环Ki和整体鲁棒性的好方法。5. 完整控制代码框架示例将上述模块整合一个典型的5ms中断服务程序控制流程如下// control.c // 声明PID结构体 PID_TypeDef pid_speed; // 速度环 PID_TypeDef pid_angle; // 角度环 PID_TypeDef pid_gyro; // 角速度环 // 全局变量 float target_speed 0.0f; // 目标速度 (cm/s) float actual_speed_left, actual_speed_right; // 左右轮速度 float actual_speed; // 平均速度 float target_angle 0.0f; // 角度环目标由速度环输出修正 float actual_angle; // 融合后的车身倾角 float actual_gyro; // 陀螺仪Y轴角速度 float motor_output; // 最终电机输出PWM值 /** * brief 控制任务在5ms定时器中断中调用 */ void Control_Task(void) { // 1. 读取传感器数据 IMU_GetData(accel, gyro); // 读取MPU6050原始数据 actual_speed_left Encoder_GetSpeed(LEFT); // 获取左轮速度 actual_speed_right Encoder_GetSpeed(RIGHT); // 获取右轮速度 actual_speed (actual_speed_left actual_speed_right) / 2.0f; // 平均速度 // 2. 姿态解算 IMU_ComplementaryFilter(accel.y, accel.z, gyro.y); actual_angle Angle_Filtered; // 获取融合后角度 actual_gyro Gyro_y; // 获取角速度 // 3. 串级PID计算 // 外环速度环 PID - 输出为目标角度偏移 float angle_offset PID_Calculate(pid_speed, target_speed, actual_speed); // 角度环的目标值 平衡零点角度 速度环输出的偏移量 // 假设机械平衡零点时实际角度为0.5度则需要补偿 float mechanical_zero 0.5f; // 需实际测量 target_angle mechanical_zero angle_offset; // 中环角度环 PID - 输出为目标角速度 float target_gyro PID_Calculate(pid_angle, target_angle, actual_angle); // 内环角速度环 PID - 输出为最终电机PWM值 motor_output PID_Calculate(pid_gyro, target_gyro, actual_gyro); // 4. 电机输出同时处理转向此处简化为差速 float turn_output 0; // 转向环输出此处省略计算 Motor_SetPwm(LEFT_MOTOR, motor_output turn_output); Motor_SetPwm(RIGHT_MOTOR, motor_output - turn_output); // 5. 数据发送到上位机用于调试 Send_To_UpperComputer(target_speed, actual_speed, target_angle, actual_angle, motor_output); }初始化函数示例void Control_Init(void) { // 初始化PID参数数值仅为示例必须实际调试 // 速度环Kp较小Ki用于消除静差积分限幅防止饱和 PID_Init(pid_speed, 0.5f, 0.05f, 0.0f, 2.0f, 5.0f); // 输出限幅±5度 // 角度环Kp提供恢复力Kd提供阻尼 PID_Init(pid_angle, 25.0f, 0.0f, 0.8f, 10.0f, 100.0f); // 输出限幅±100 dps // 角速度环Kp提供刚度Kd抑制高频 PID_Init(pid_gyro, 3.2f, 0.0f, 0.05f, 1000.0f, 7000.0f); // 输出限幅对应PWM最大值 // 初始化传感器 IMU_Init(); Encoder_Init(); Motor_Init(); }6. 调试中的常见问题与排查思路调试过程不会一帆风顺以下是典型问题及解决方法问题现象可能原因排查思路与解决方案车模完全不动或向一边猛转1. 电机线接反或PWM极性反。2. 编码器方向错误。3. 机械中值零点未校准。4. PID输出限幅为0或过小。1. 单独测试电机正反转。2. 转动轮子看编码器计数增减方向是否正确。3. 将车模静止放在水平面读取actual_angle将此值设为mechanical_zero。4. 检查PID结构体中的output_max是否设置合理。车模剧烈高频抖动“跳舞”1. 角速度环Kp过大。2. 角速度环Kd不合适可能过大或过小。3. 传感器数据噪声大未滤波。4. 控制周期不稳定或过快。1.降低角速度环Kp这是最常见原因。2. 调整角速度环Kd先调Kp再调Kd。3. 对陀螺仪数据进行低通滤波。4. 检查定时器中断配置确保周期精确。车模大幅低频摇摆“钟摆”1. 角度环Kp过大。2. 角度环Kd过小阻尼不足。3. 速度环Kp或Ki过大通过角度环引发振荡。1.降低角度环Kp。2.增加角度环Kd增强阻尼。3. 如果已加入速度环先调小速度环参数确保角度环单独稳定。能站立但一推就倒恢复慢1. 角度环Kp过小。2. 角度环Kd过大过度阻尼。3. 角速度环Kp过小。1. 适当增大角度环Kp。2. 适当减小角度环Kd。3. 检查角速度环是否足够“硬”。能走直线但速度不稳时快时慢1. 速度环Kp过小。2. 速度环Ki过小无法抵抗阻力。3. 编码器速度计算有误或噪声大。4. 电池电压下降未补偿。1. 增大速度环Kp。2. 增大速度环Ki并检查积分限幅是否合理。3. 优化速度计算算法如一阶低通滤波。4. 根据电池电压对PWM输出进行比例补偿。转向不灵敏或过度转向1. 转向环PID参数未调好。2. 左右轮机械差异或电机性能不一致。3. 陀螺仪Z轴数据未用于转向控制。1. 单独调试转向环通常是一个PD控制器以Z轴陀螺仪或摄像头偏差为反馈。2. 对左右轮电机分别进行标定补偿差异。3. 确保使用了Z轴角速度进行微分控制。上位机曲线毛刺多噪声大1. 传感器本身噪声。2. 电源噪声干扰。3. 软件滤波不足。1. 检查MPU6050是否可靠接地和焊接。2. 为模拟电源如MPU6050的VCC增加磁珠或LC滤波。3. 在软件中对原始数据或计算结果进行滑动平均、低通或卡尔曼滤波。7. 最佳实践与工程化建议当车模基本能跑后以下优化能让你的系统更稳定、更鲁棒。7.1 参数固化与自适应参数分场景存储将不同赛道元素直道、弯道、坡道对应的最优PID参数保存在数组或Flash中运行时根据识别到的场景进行切换。离线标定上电时让车模静止2秒自动采集陀螺仪零偏和加速度计零点。在线微调根据电池电压实时微调PWM输出比例电压越低同等PWM占空比对应的实际电压越小。7.2 软件滤波与抗扰速度滤波编码器计算出的瞬时速度噪声大必须滤波。推荐使用一阶低通滤波或滑动平均滤波。// 一阶低通滤波示例 float speed_filtered 0.9f * speed_filtered_last 0.1f * speed_raw;角度安全限制在控制函数中加入角度保护。当actual_angle超过安全范围如±20度时切断电机输出并进入保护状态防止“飞车”损坏硬件。死区处理对于电机死区当计算出的PWM绝对值小于某个阈值如10时直接输出0避免电机“嗡嗡”响却不转。7.3 调试与测试流程单元测试单独测试每个传感器和驱动器。开环测试给固定PWM看电机转动和编码器反馈是否正常。闭环逐级调试严格遵守角速度环 - 角度环 - 速度环 - 转向环的顺序。静态测试手持测试内环地面测试角度环。动态测试先低速后高速先平地后坡道先直道后弯道。压力测试在不同电量、不同路面、施加外力干扰下测试系统稳定性。7.4 机械与硬件检查重心电池等重物尽量放低降低重心。对称性左右轮系电机、轮胎、编码器尽量对称。连接所有接线必须牢固尤其是电机线和编码器线可用热熔胶固定。电源主控板和传感器最好由LDO单独供电与电机驱动电源隔离。直立车的调试是一个系统工程需要耐心和细心。从理解原理开始到传感器处理再到逐级整定PID每一步都至关重要。记住核心口诀内环快外环稳先调P后调D最后I参数从小往大加振荡了就回调。充分利用上位机可视化工具将抽象的数据转化为直观的曲线是快速定位问题的关键。当你调出一辆能稳如泰山、动如脱兔的直立车时所有的努力都是值得的。这份经验不仅是竞赛的利器更是你深入理解自动控制理论的宝贵实践。希望这篇基于第19届智能车竞赛摩托组实战的分享能帮助你少走弯路顺利攻克直立调试难关。如果在实践中遇到新问题欢迎在评论区交流探讨。