
第18届全国大学生智能汽车竞赛四轮车控制策略深度解析从舵机PID到速度闭环说出来你可能不信我在第18届全国大学生智能汽车竞赛四轮组备赛时最崩溃的不是摄像头图像处理也不是元素识别反而是两个看起来“不起眼”的小闭环舵机转向和速度控制。调了一个月车才从“原地抽搐”进化到“稳定跑完全程”。现在回头看四轮车组几乎所有玄学问题最后都能归结到控制策略和参数整定上。这篇文章就把那段时间踩过的坑、试过的方法、最终沉淀下来的方案掰开揉碎讲清楚从舵机PID到速度闭环再到两者怎么配合跑出稳定圈速。不管你是第一次接触智能车竞赛还是已经跑到国赛一轮游想找个突破口这篇都能给你一套可以直接抄作业的思路。1. 先理解四轮车的控制任务转向与速度的博弈1.1 赛道元素与控制需求拆解第18届竞赛的四轮车组赛道可以简化成几类元素的组合直道、弯道半径不同、十字路口、环岛、坡道部分赛区有。每类元素对控制系统的“诉求”差异非常大。直道要求提速果断、方向稳定最怕的是车在直道上画龙弯道要求转向响应快且精准出弯后能迅速回正加速最怕的是转向不足推头或者转向过度甩尾十字和环岛则要求控制策略具备“元素感知”能力不能靠单纯PID硬扛需要状态机配合速度规划。这就决定了四轮车的控制架构一定不是孤立的“一个舵机一个电机”而是转向环、速度环、元素状态机三者紧密耦合的协同系统。1.2 控制架构选型分离控制为什么是主流在智能车组里电机和舵机共用一套主控常见STM32F103/F407/K60但实际上它们在逻辑上应该分开处理。转向控制是一个典型的“快速响应”环路赛道图像的误差每帧都在变化摄像头帧率50fps左右舵机需要在一个控制周期内常设5ms~20ms输出合适的方向信号响应延迟直接决定过弯极限。速度控制则相对“慢热”电机机械惯性大编码器测速频率通常在100Hz~1kHz而且赛道坡度、电池电压波动都会干扰速度这要求速度环具备较强的抗扰动能力和积分补偿能力。我的做法是把两个环独立开来调试先单独调好转向让车能走中线再单独调速度让车能稳定到目标速度最后通过元素状态机和查表策略把两者组合起来。这比一开始就搞复杂的模型预测控制要稳妥得多毕竟竞赛车队的核心命题永远是“稳定完赛”优先。2. 转向控制舵机不是单纯调个PID那么简单2.1 舵机选型与PWM驱动要点第18届四轮组大部分队伍用的是数字舵机常见的有SD5、S3010、飞特等。数字舵机和模拟舵机的最大区别在于响应速度和控制死区竞赛车建议优先选响应速度快、回中精度高的数字舵机。舵机本质上是位置伺服器输入是PWM脉宽输出是舵盘角度。常规舵机脉宽范围在0.5ms~2.5ms对应0~180度但竞赛车使用的转向舵机通常会限制在机械极限内比如0~90度。这里有一个容易忽略的点PWM频率。模拟舵机习惯50Hz周期20ms但很多数字舵机支持更高频率比如300Hz。频率越高舵机响应越快但功耗也更高且对供电要求更苛刻。我实测过同一个舵机在50Hz和200Hz下的阶跃响应后者到达目标角度的时间能压缩约30%。前提是舵机供电必须稳得住否则高频率下会明显发热甚至抖动。2.2 舵机角度控制的PD核心舵机控制有两种思路一种是直接开环给PWM图像偏差大就转多偏差小就转少一种是闭环PID。竞赛中绝大多数队伍用的是后者准确说是“PD”控制。为什么不用I因为舵机是位置伺服系统本身有积分属性外部加入积分项容易导致振荡。再加上赛道图像误差是快速变化的动态输入积分项在这个场景下弊大于利。我实际使用的舵机PD控制代码结构如下/* 摄像头/电磁误差输入范围[-100, 100] */ int16_t image_error; static int16_t last_error; float set_pwm, p_term, d_term; p_term steer_kp * image_error; d_term steer_kd * (image_error - last_error); last_error image_error; set_pwm steer_mid p_term d_term; if (set_pwm steer_max) set_pwm steer_max; if (set_pwm steer_min) set_pwm steer_min; TIM_SetCompare1(TIM1, set_pwm);这个代码看起来很简单但参数整定和细节处理才是关键。先说中值校准。舵机中值set_pwm不是随便给的而是机械调零后测出来的。做法是先把舵机接到车上手动给不同PWM让前轮摆到完全正中记录这时的PWM值。每次机械结构拆装之后都要重新校准否则直道会偏。再说限幅。舵机PWM限幅不能只考虑舵盘极限还要考虑转向拉杆的机械限位。如果不设软件限幅PID输出过大把舵机打到物理极限会听到“咔咔”声长期这样会扫齿甚至憋坏舵机。2.3 微分项的方向与滤波很多新手在调PD的时候会忽略D项方向。舵机PD的D项作用是预测误差变化趋势抑制振荡。但如果D项过大会出现一个典型问题进弯初期误差刚建立D项为负会抑制舵机转向导致入弯响应变慢。我的经验是D项系数不要贪大只要能消除直道高频抖动即可。另外摄像头图像的误差信号本身噪声较大直接差分会把噪声放大。建议先对误差做一阶低通滤波再做微分相当于串联一个低通滤波器。/* 一阶低通滤波alpha取0.3~0.6 */ filtered_error alpha * image_error (1 - alpha) * last_filtered_error;滤波会带来相位延迟所以alpha不能太小否则转向滞后严重弯道会明显外抛。2.4 舵机PD的参数整定方法舵机PD的参数整定有一套相对高效的流程比纯试错节省大量时间。第一步只给P项从很小的kp开始让车能走出基本中线。如果出现直道抖动说明kp偏大减小kp或增加D项。第二步加入D项观察直道稳定性目标是把直道画龙消除掉。第三步提高车速后重新整定因为速度越快相同图像误差需要的转向响应越强。这里分享一个独家经验不要只用“感觉”调参一定要用上位机录波形。我用VOFA协议把图像误差、滤波后误差、PWM输出、实际速度同时打印出来配合摄像头录制的视频回放能非常精准地定位问题是出现在“控制器没输出”还是“输出但车没响应”。3. 速度闭环编码器、电机驱动与增量式PID3.1 测速原理与单位换算速度闭环的前提是准确的测速。四轮组常用编码器有两种安装方式一种是电机尾部同轴安装另一种是车轮轴外部安装。无论哪种本质都是通过单位时间内的脉冲数来推算速度。我用的编码器是512线增量式光电编码器比霍尔式精度高很多配合电机减速比1:30整车轮胎直径64mm。那么单位脉冲对应的行驶距离为一圈脉冲数 编码器线数 x 4倍频 x 减速比 512 x 4 x 30 61440 每脉冲对应距离 pi x 轮胎直径 / 一圈脉冲数 3.14159 x 64mm / 61440 0.00327mm如果控制周期是10ms那么目标速度1m/s对应的计数是1m / 0.00327mm ≈ 305810脉冲/s 一个控制周期内 3058脉冲这个值就是速度环反馈量的“目标值”。竞赛中通常把速度表示成“每控制周期脉冲数”不用换算成m/s因为闭环控制只需要一致性和比例关系。这里有个细节定时器采集编码器时要确认是正交解码让计数器在AB相跳变时同时加减计数这样方向反转也能正确测速。3.2 增量式PID的实现与代码速度环的主流实现方式是增量式PID输出是“本次控制量相对上次的增量”而不是绝对占空比。增量式PID的离散形式可以写成delta_u kp * (e_k - e_k1) ki * e_k kd * (e_k - 2*e_k1 e_k2)C代码实现typedef struct { float kp; float ki; float kd; float e_k1; float e_k2; float integral_limit; float output_limit; } SpeedPID; float SpeedPID_Update(SpeedPID* pid, float target, float current) { float e_k target - current; float delta_u; static float u; delta_u pid-kp * (e_k - pid-e_k1) pid-ki * e_k pid-kd * (e_k - 2.0f * pid-e_k1 pid-e_k2); u delta_u; if (u pid-output_limit) u pid-output_limit; if (u -pid-output_limit) u -pid-output_limit; pid-e_k2 pid-e_k1; pid-e_k1 e_k; return u; }增量式的好处主要有几点一是输出平滑即使参考速度突变占空比变化也是渐进的不会猛冲。 二是天然抗积分饱和因为历史积分效果体现在当前输出u里限幅后不会有无限累积的问题。 三是从手动模式切换方便切换到闭环时只需要从当前占空比累加增量即可。3.3 速度环为什么用PI而不是PID速度环我强烈建议只用PI不加D。原因很实际电机是带惯性和一阶滞后特性的执行机构速度反馈信号经过编码器采集后本身带有脉冲量化噪声。D项会对这个噪声进行放大结果就是电机“嗡嗡”震颤车身抖动轮到速度反而波动更大。D项真正有用的场景是温度控制、电磁阀位置控制这类大滞后系统。在电机速度环里PI已经能提供足够的动态性能。关于I项参数的经验值是先把kp调到车能跟住目标速度且无明显振荡然后从很小的ki开始加看到实际速度能无静差收敛到目标值即可。ki太大会引起低频振荡典型表现是车速一会儿快一会儿慢周期约零点几秒。3.4 电机驱动与死区补偿速度环的输出最终作用在MOS驱动或驱动芯片的PWM占空比上常见的是BTN7971、DRV8701或MOS全桥。这里有个非常关键但容易被忽视的点死区电压。在占空比很低时小于某个阈值电机扭矩不足以克服摩擦车不会动。这个阈值因电机和驱动电路而异一般在5%~15%之间。如果不做死区补偿闭环低俗时会出现“给了PWM但没加速”的盲区速度环会积分越来越高等到占空比突破死区时又突然加速造成明显的“闯动感”。我在代码里做的是当目标速度从零起步时先直接给一个固定的启动占空比约25%强制冲出死区然后立刻切回闭环。实测这个“kick-off”策略能让起步速度稳定很多。4. 联动策略弯道降速与直道提速的实现4.1 转向与速度的耦合策略很多队伍把转向和速度完全独立用固定目标速度跑全场。如果车速放得很保守确实能跑完但圈速上不去。真正跑得快的车一定是根据当前道路情况实时调整目标速度的。我的方案是基于图像误差和赛道元素识别动态生成目标速度if (识别到直道) target_speed 直道目标速度 (比如3500脉冲/周期) if (识别到弯道) target_speed 弯道基础速度 弯道曲率补偿 if (识别到环岛) target_speed 环岛速度 (比如1800脉冲/周期)弯道曲率补偿可以用一个简单函数误差绝对值越大说明当前弯越急目标速度线性降低。float cur_radius_factor 1.0f - fabs(image_error) / 100.0f * 0.5f; target_speed base_curve_speed * cur_radius_factor;这里的关键是降速要提前加速要平滑。我通常在弯道入口前几十毫秒就根据误差趋势预测降速而不是等误差已经很大才反应。实现方法是计算误差的导数如果误差快速变大说明前方是急弯提前降低目标速度。4.2 状态机与元素识别的作用四轮车的稳定性光靠PID是跑不赢环岛的。环岛入环、岛内循迹、出环这三个阶段控制策略完全不同。我的状态机分为直道状态、弯道状态、环岛入环状态、环岛岛内状态、环岛出环状态、十字状态。每个状态有独立的舵机PD参数和速度PI目标。直道状态转向阻尼稍大保证稳定弯道状态转向增益稍高保证响应环岛岛内状态使用固定转向角度加微调速度设定较低保证顺利绕环。状态切换的“粘滞回差”是一个重要细节。比如环岛识别不能一满足条件就切换否则图像噪声会引起状态频繁跳变。我在每个条件里都加入了持续帧数要求比如连续10帧满足才切换这比单纯阈值靠谱得多。4.3 级联控制思想在智能车中的应用这两年“级联PID”在竞赛圈特别火其实智能车四轮组很多并不需要严格的双闭环级联因为舵机和电机是两个独立的执行机构。但速度环和转向环确实存在通过车身动力学耦合的现象转向会带来离心力改变轮胎姿态影响速度。最典型的场景是高速进弯时如果转向和降速不能同步车会出现明显反打或者甩尾。我的处理是在状态机和速度策略层面做“松耦合”状态机知道当前处于什么元素速度策略根据元素和误差动态调整目标速度。这比传统的双闭环级联例如电流环-速度环-位置环更贴合竞赛车的实际工况毕竟我们没有过高的执行器动态要求。5. 调试方法论从纸面参数到赛道飞驰5.1 参数整定的四步法新手最容易犯的错是一上来就同时调多个参数改一版跑一次调了一下午毫无进展。我总结了四步法可以有效压缩调参时间。第一步调舵机中值和机械结构。把车架空看前轮是否能精准回中打方向时是否有卡顿。机械不顺控制调得再好都没用。第二步单独调转向PD。把速度设置到30%左右调整PD让车能稳定走中线。重点记录两组参数稳定参数和临界振荡参数。第三步单独调速度PI。架起车让电机悬空给定阶跃目标速度看响应曲线是否快速收敛且无超调。注意悬空和落地工况差别很大落地后要重新微调。第四步联动联调。在赛道上跑逐步提高目标速度观察弯道和直道的表现针对性调整状态机参数、目标速度和PD参数之间的匹配。5.2 上位机与波形工具的正确用法现在智能车圈的标配组合是VOFA配合串口蓝牙模块无线传输数据。我调试时的数据发送格式如下/* 先组帧 */ uint8_t data[18]; memcpy(data, image_error, 2); memcpy(data2, filtered_error, 2); memcpy(data4, set_pwm, 2); memcpy(data6, target_speed, 2); memcpy(data8, current_speed, 2); /* 然后串口DMA发送波特率115200 */注意蓝牙模块要选透传模式尽量减少发送频率如10ms一次否则会影响主程序控制周期。有了波形数据后判断问题会清晰很多。比如车在弯道外抛波形上能看到“舵机PWM已经打满但实际转向还是不够”这时候问题不在控制而在机械限位或速度过快。波形能把“控制问题”和“执行问题”区分开这是靠肉眼盯车看不出来的。5.3 数据回放用历史数据定位麿人bug有一种经典bug车每次跑到同一个路段就异常但现场看不出原因。如果没有记录功能就只能靠猜。我在车上挂了一块SD卡以10ms周期记录所有关键变量跑完后直接把CSV文件导出来分析。这类问题通常在数据里一目了然比如某段误差突变导致D项输出尖峰或者编码器采样丢步导致速度环误判。举个例子有一次车总在直道末端突然向右猛打方向看回放发现是摄像头在强烈逆光下图像二值化出错误差瞬间跳到满值。后来在图像层加曝光补偿解决控制层没动一行代码。6. 常见问题与排障实录6.1 舵机抖动、嗡嗡响现象直道上舵机高频抖动能听到明显的嗡嗡声。排查顺序供电电压是否稳定舵机瞬间电流抽干电源。转向拉杆是否有虚位用手转动前轮是否有卡顿。PD参数中D项是否过大导致高频振荡。舵机PWM频率是否设置不当数字舵机需要匹配对应频率。我的经验八成情况是供电问题IMU独立供电加上舵机单独一路能解决绝大部门抖动。其次是D项太大降低D项后抖动立即消失。6.2 起步猛冲与急加速顿挫现象车启动时突然窜出去或者加速过程一顿一顿。根源通常是速度环参数不匹配。起步猛冲很多时候是kick-off启动占空比过大或者PI输出限幅设置太高。急加速顿挫则可能是死区补偿触发的占空比跳变太大。我的处理把启动占空比降到刚好克服静摩擦之后闭环节累计的积分项会自然把速度顶上去。同时把速度环的输出限幅设置得缓和一些比如每周期占空比变化不超过满幅的2%。6.3 直道画龙与弯道外抛这两个问题经常一起出现本质上是转向增益和车速匹配不好。直道画龙PD的D项可以特别好地压住但要检查滤波是否到位图像噪声太大导致误差差分值乱跳。弯道外抛第一反应不是加P而是降速。车速过快时物理极限决定了再大的舵机转角也无法完成转向。正确流程是先降低弯道目标速度确认能稳定入弯然后再尝试微调PD。6.4 积分饱和与参数突变虽然增量式PID天然减弱积分饱和但在目标速度大幅变化时还是可能出现响应过慢。我的做法是给输出限幅设了“前馈补偿”状态机切换目标速度时直接给一个基础占空比偏移比如进入环岛时占空比减去当前速度PID输出的20%让速度快速跟随目标。参数突变主要指从调试档位切到比赛档位时所有参数都变了导致车完全失控。我强烈建议比赛档位的参数必须是从调试档位逐步微调而来而不是“感觉上差不多”直接套用。最好的办法是用宏定义把所有参数集中管理每次微调只改一个并记录在参数表里。写在最后这套控制策略从第18届四轮车组的实际调试中沉淀出来后来我把它迁移到第19届、第20届的备赛中依然有效。核心思想就一句话控制不是玄学是建立在机械、传感器、算法三者协同之上的工程问题。我个人体会最深的一点是与其追求复杂的融合算法不如先把基础的PD转向和PI速度调通透。车能稳定走线、速度能稳得住圈速自然不会差。当你把这两个基础闭环玩明白了后面再上更高级的状态估计、路径规划都只是水到渠成的事。最后再分享一个小技巧每次调参前先在参数表里记录当前赛道环境比如轮胎磨损、电池电压、赛道背景光照。因为这些外界条件动一点参数就可能要跟着微调。比赛日的“玄学神车”往往就是这么来的——不是代码变了是环境变了而好的参数表能让你在十分钟内完成适应。祝各位在赛道上跑出理想的圈速。