恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
方程式赛车算法核心:油门解析、扭矩控制与状态估计实战
首页
资讯中心
/
方程式赛车算法核心:油门解析、扭矩控制与状态估计实战
方程式赛车算法核心:油门解析、扭矩控制与状态估计实战
发布时间:2026/9/5 3:29:28
开头做方程式赛车算法第一课不是PID也不是卡尔曼滤波而是先弄明白一件事你的代码从踩下油门踏板的那一瞬间到电机真正输出扭矩中间经历了多少毫秒。这个数字决定了你的牵引力控制该按什么频率跑、状态估计该用什么滤波器、CAN报文该定多少波特率。我当初接手车队算法组的时候底盘组那边递过来一摞机械图纸电池组那边给了一个电池箱的通信协议电机供应商丢过来一份电机数据手册然后就没人管了。所有东西都得从零开始。这个系列就是打算把我们踩过的坑、验证过能用的方案、以及那些“如果当初有人跟我说就好了”的东西完整记录下来。这个系列适合谁看车队算法组成员、想转嵌入式车辆控制的工程师、以及那些刚拿到电赛或者方程式赛车的项目但不知道从哪下手的朋友。第一篇先把整体架构讲清楚然后把最核心的油门解析和扭矩控制链路完整落地后面的文章再逐个展开状态估计、能量管理、故障处理这些模块。1. 整体设计先分层再填细节1.1 为什么必须分层设计大学生方程式赛车的算法很多人一上来就想写一套“能跑就行”的代码结果比赛前一个月开始疯狂调试逻辑全纠缠在一起改一个参数到处出问题。我这边的建议是无论代码量多小都按分层来。不是装样子是因为这套系统的实时性要求非常明确。你有一个电子油门踏板传感器信号进单片机单片机算出一个目标扭矩百分比发给电机控制器电机控制器再去驱动电机。整条链路如果超过20毫秒人就能感觉到延迟车就会变得难以控制。分层的好处在于每一层都可以单独测试、单独调参、单独出问题单独修。而且车队每年都有老队员毕业、新队员进来层次清晰的话交接成本会低很多。我见过太多“一个人写了全套代码毕业之后没人看得懂”的情况了。具体的层次划分参考决策层根据驾驶员意图和整车状态算出当前应该输出多少扭矩、是否限制功率、是否进入故障保护。估计层把轮速传感器、加速度计、陀螺仪、编码器的原始信号处理成可用的车辆状态量比如车速、滑移率、纵向加速度。执行层把决策层的目标扭矩转换成CAN报文发送到电机控制器和BMS同时执行相应的斜坡限制和滤波。监控层负责看门狗、绝缘检测、BMS报警信号、制动踏板逻辑等安全相关功能。1.2 输入与输出先定义清楚很多项目做不下去是因为输入输出没定义明白就开始写代码。拿我们的车举个具体例子驾驶员输入有三个油门踏板位置、制动踏板位置、方向盘转角主动循迹方案需要用到后面会讲。整车状态输入就多了四个轮速信号、IMU加速度加角速度、BMS上报的SOC和单体电压、电机控制器上报的电机转速和温度。输出相对简单电机扭矩请求百分比、待机/运行指令、故障指示灯控制信号。这里有一个很重要的原则输入信号永远以真实物理量为准能测的不要估能估的不要猜。比如车速如果你只用一个轮子的轮速除以滚动半径来算那这个轮子稍微打滑一点你的车速估计就飘了。后面我会专门讲多信号融合的车速估算方法在这个系列后面展开。2. 核心模块一油门踏板解析与扭矩映射2.1 踏板信号的处理逻辑油门踏板通常是一个霍尔传感器输出一个模拟电压ADC采样后我们得到一个原始值。这个原始值会抖动所以第一步要滤波。我推荐一阶低通滤波代码很简单#define THROTTLE_ALPHA 0.2f static float throttle_filtered 0.0f; float throttle_lowpass_filter(float raw_value) { throttle_filtered throttle_alpha * raw_value (1.0f - throttle_alpha) * throttle_filtered; return throttle_filtered; }这个throttle_alpha取值看你的采样频率。如果你在MCU上以1kHz的频率跑选0.1到0.3比较合适。选大了信号毛刺还是多选小了踩踏板的响应会很肉。为什么要先滤波再做解析标定因为原始信号抖动如果直接做映射输出的扭矩请求也会跟着抖电机就会产生高频振动对机械结构很不友好。2.2 双通道踏板校验方程式赛车规则里面油门踏板通常要求双通道冗余。我们用两个独立的霍尔传感器标定后会得到两个电压值。正常工作时两个通道换算出来的踏板开度之差应该小于某个阈值。这个校验逻辑是安全相关的我建议放在中断里面执行优先级最高。float throttle_fault_check(float opening_1, float opening_2) { float diff fabsf(opening_1 - opening_2); if (diff 5.0f) { // 开度偏差过大进入降级模式 return throttle_degraded_flag 1; } return 0; }一旦偏差过大直接切断扭矩输出会显得太激进但也不能不管。我的方案是偏差超过5%时限制最大扭矩为30%超过10%时直接断动力。这套逻辑我们实车验证过能避免很多低级故障。2.3 扭矩映射表的标定方法踏板开度跟扭矩请求之间的关系不是简单的线性映射。原因在于驾手感线性映射下踏板到中段的时候电机扭矩增长过快新手很难精准控制出弯加速。我采用的是一种可标定曲线一般用三段折线逼近低速段踏板开度0%到20%扭矩增长较缓便于蠕行和低附着路面起步。中速段20%到80%比较激进用于出弯加速。高速段80%到100%扭矩增长率回落避免大扭矩导致后轮打滑。实际标定时我们把扭矩映射表放在一个二维数组里用插值查表的方式计算float torque_lookup_table(float throttle_percent) { float points[5] {0.0f, 10.0f, 30.0f, 60.0f, 100.0f}; float values[5] {0.0f, 15.0f, 45.0f, 75.0f, 100.0f}; // 线性插值 for (int i 1; i 5; i) { if (throttle_percent points[i]) { float ratio (throttle_percent - points[i-1]) / (points[i] - points[i-1]); return values[i-1] ratio * (values[i] - values[i-1]); } } return values[4]; }这套映射表不是一开始就准的需要配合驾驶员的反馈反复迭代。我的习惯是每次练车后让车手给每段踏板特性的主观评分然后调整中间两个断点的坐标位置。经过几轮迭代后车手会明显感觉车更容易控了。3. 核心模块二车速估算与滑移率计算3.1 为什么车速估算这么关键如果你去问一个有经验的车队核心人员整车控制里最难调的是什么大概率不是电机响应而是你根本不知道当前车的真实速度到底是多少。这个量贯穿了很多算法牵引力控制要用能量管理要用自动紧急制动要用甚至线控转向也要用。有人可能会问车上不是有GPS吗GPS在室外开阔环境下精度确实不错但在赛道上有遮挡、树林、建筑物GPS的更新频率又低直接拿来做实时控制是不现实的。所以主流的方案还是基于轮速传感器加IMU进行融合估算。3.2 轮速的基础计算先讲最简单的部分。每个驱动轮上都装了霍尔式轮速传感器车轮一转就输出脉冲。用MCU的定时器输入捕获功能可以直接测量脉冲间隔进而算出轮速。公式是这样的wheel_speed (60.0f / (pulse_per_rev * dt)) * (2πr / 60)我习惯先把脉冲频率算出来再乘上轮胎的周长得到车轮的线速度。这里请特别注意轮胎的有效滚动半径不是静止时的半径车速上来之后会因为离心力略微变大胎压不同也会影响。所以在标定轮速的时候最好在静态条件下测量周长然后加一个随车速变化的补偿系数。3.3 多信号融合估算车速直接用轮速算车速最大的问题就是打滑。加速时后轮转速转速明显高于实际车速制动时可能抱死轮速骤降甚至归零这时如果单片机还以为是车速变慢了算法逻辑会乱掉。标准的工程方案是采用加速度积分 轮速校正的思路。具体做法用IMU的纵向加速度做积分得到一个车速初步估计。用四个轮子的轮速作为观测值根据车辆驱动形式选择最小驱动轮滑移率或前轮两轮速均值作为参考量。两个信号进入一个简单的互补滤波器float speed_estimation(float accel_long, float wheel_speed_ref, float dt) { static float vehicle_speed 0.0f; float coeff 0.05f; // 加速度积分 vehicle_speed accel_long * dt; // 轮速校正 vehicle_speed coeff * (wheel_speed_ref - vehicle_speed); return vehicle_speed; }这个coeff叫校正系数选大了车速跟随轮速太快打滑时估算也会出错选小了加速度积分漂移会大。我的经验是0.03到0.08之间比较合理具体数值取决于IMU的零偏和赛道情况。3.4 滑移率是怎么用的有了车速估算和轮速滑移率就很好算了slip_ratio (wheel_speed - vehicle_speed) / fmax(vehicle_speed, 1.0f)滑移率的正负表示驱动还是制动状态。正滑移率过大说明车轮在打滑牵引力控制系统要介入。这个计算虽然简单但在实车上要跑得稳还需要注意vehicle_speed接近0的时候做保护不要除以接近0的数。4. 核心模块三牵引力控制逻辑与电机扭矩限制4.1 牵引力控制的目标牵引力控制不是让你车子不容易起步而是让驱动轮滑移率保持在轮胎-路面附着系数的峰值附近。轮胎的附着系数跟滑移率的关系大致是这样的从0开始滑移率增加附着系数先上升大概在10%至20%之间达到峰值然后开始下降。如果滑移率再大就进入完全打滑状态。所以牵引力控制算法的本质就是把每个驱动轮的滑移率保持在那个最佳区间附近。4.2 基于滑移率偏差的PI调节我的做法分成两个层次先做轮间差速判断再做整体扭矩削减。轮间差速判断就是看两个后轮的滑移率是否差异过大。弯道中内侧车轮和外侧车轮的载荷分布不一样轮胎附着力也不一样如果后轮开式差速器或者电机的独立驱动会出现单侧轮滑的情况。这时候需要把扭矩向附着更好的一侧转移这个统称为扭矩矢量控制我们后面单独讲。第二个层次就是整体扭矩限制。当两个后轮滑移率都超过目标值时说明车辆整体驱动力过大需要削减总扭矩请求。我实现了一个带积分项的PI控制器但积分项有做抗饱和处理typedef struct { float kp; float ki; float integral; float integral_max; float output_max; } PidController; float pid_update(PidController *pid, float target, float current, float dt) { float error target - current; float output pid-kp * error pid-integral; if (output pid-output_max) { output pid-output_max; } else if (output 0.0f) { output 0.0f; } else { pid-integral pid-ki * error * dt; // 抗积分饱和 if (pid-integral pid-integral_max) { pid-integral pid-integral_max; } else if (pid-integral 0.0f) { pid-integral 0.0f; } } return output; }这个PI控制器的目标值是目标滑移率比如10%输入是当前最大滑移率输出是一个0到1之间的扭矩修正系数。最终发给电机控制器的扭矩请求是actual_torque_request driver_torque_request * traction_control_factor4.3 调参心得先Ki后Kp从小往大调我调试牵引力控制参数的顺序是先把Kp设为0只加Ki观察车辆在低附着路面上能不能收敛到目标滑移率然后再慢慢加Kp改善响应速度最后细调整体卡尔曼滤波的带宽。调参过程中最重要的一件事是看数据。赛车上有CAN记录仪每个通道的扭矩请求、滑移率、轮速、车速都记录下来导出后对比分析。没有数据支撑的调参就是瞎调这是我的血的教训有一年我们拖着调好的牵引力控制上场结果车辆表现很怪后来查数据才发现是滑移率估算里有个符号写反了导致控制方向完全反了。5. 核心模块四制动能量回收与制动力分配5.1 电机制动和液压制动的配合逻辑方程式赛车的电动组通常都有动能回收功能。电机在制动时作为发电机工作把动能转化成电能存回电池。但这里有个问题电机制动的力矩是有限的而且在电池SOC很高或者温度过高的时候BMS会禁止回收。所以必须设计一套逻辑来协调电机制动和液压制动。我的实现思路是制动踏板踩下时先读取制动踏板位移传感器的值。把制动力需求分解为两部分一部分由电机执行另一部分由液压制动执行。电机制动的部分不能超过电机峰值转矩和电池允许充电功率两者之间的较小值。液压制动的部分则由制动卡钳和主缸的关系决定这块通常是机械部分调好的算法只需要做减法。5.2 能量回收的边界条件能量回收不是想回收就能回收的至少要考虑以下几个边界条件SOC上限电池已经接近满电时回收功率必须降为0否则电池过充保护会触发甚至损坏电芯。电池温度低温时电芯内阻大大功率充电会对电池造成损伤高温时则要防止热失控。这两个方向都需要在算法里面做温度相关的限制。车速下限车速很低的时候电机反电动势非常小回收效率很低而且控制精度差我一般设定5km/h以下就退出能量回收。ABS介入状态如果液压ABS已经开始工作能量回收必须立刻退出否则会干扰ABS的制动压力调节。这些边界条件的判断每一个都要放在状态机里管理用一组优先级规则来处理。下面是我常用的制动力分配伪代码供参考float blend_regenerative_brake(float brake_pedal, float soc, float motor_speed, float veh_speed) { float total_brake_torque brake_pedal * max_brake_torque; float regen_allow 1.0f; // SOC限制 if (soc 0.95f) regen_allow 0.0f; else if (soc 0.85f) regen_allow (0.95f - soc) / 0.10f; // 车速限制 if (veh_speed 5.0f) regen_allow 0.0f; // 电机转速限制 if (motor_speed 500.0f) regen_allow 0.0f; // 计算回收扭矩 float regen_torque total_brake_torque * regen_allow; // 限制回收扭矩不超过电机峰值 if (regen_torque max_regen_torque) { regen_torque max_regen_torque; } return regen_torque; }这套逻辑跑下来一个赛季能回收不少能量。尤其是在那种多弯的小赛道每圈能回收的能量大概占总耗电量的10%到15%别小看这一点比赛后半程优势非常明显。6. 开发环境与工具链搭建6.1 主控平台选择STM32还是其他方案大学生方程式赛车算法的主控我强烈建议用STM32系列我们用的是STM32F405和H7系列。原因有几个资料多、库函数成熟、开发板便宜、车队里容易找到有经验的人带。而且CubeMX的图形化初始化配置可以省掉大量底层寄存器配置时间。当时我们遇到一个问题F405的处理性能跑完整个算法循环控制频率1kHz加上CAN收发和ADC采样片上的资源还很充裕但用H7以后计时器分辨率更高控制周期抖动明显变小。所以如果你的车队预算允许直接上H7省心很多。6.2 代码架构与任务调度我强烈推荐使用FreeRTOS哪怕你让主循环跑得再溜没有实时操作系统后期加了功能模块必然乱套。任务划分参考硬实时任务1kHz油门解析、扭矩控制、状态估计更新优先级最高。中等实时任务100HzBMS状态读取、温度监控、故障诊断。低实时任务10Hz数据记录、状态指示灯刷新、遥测数据发送。注意一个坑FreeRTOS的任务调度本身有延迟如果你的控制算法直接跑在任务里并且被其它高优先级任务打断控制周期会不稳定。解决方案是在一个定时器回调里触发软件定时器让它通知高优先级任务运行而不是用任务自身的延时函数。6.3 CAN通信配置与实车调试整车通信全部走CAN总线波特率500kbps标准的扩展帧。这里有几个关键点第一报文ID的分配要有规则。我采用的是按优先级分配故障类报文ID最低保证最高优先级其次是控制类最后是状态监测类。第二每个报文要有超时监测。重要报文超过100ms没收到就置位对应的故障码这个在上电自检和运行时都要执行。第三CAN总线上的负载别塞太满。我见过一些车队什么数据都想往总线上放最后总线负载超过60%丢帧严重。合理的负载应该控制在30%以下。采集到的数据可以先在本地缓存比赛间隙或者维修区再通过WIFI下载没必要全部实时上传。7. 常见问题与排查技巧实录7.1 故障现象电机无响应但代码逻辑看起来没问题这个问题我们车队踩过排查了很久。最后发现是CAN控制报文里有一字节生命信号没更新电机控制器检测到生命信号超时直接拒绝执行任何扭矩指令。解决办法是加一个实时计数器每发送一帧报文就自增电机控制器侧做比对。类似的低端错误往往不是算法复杂而是协议细节没对齐。7.2 故障现象车轮轮速跳变严重导致扭矩抖动轮速传感器的信号断断续续问题出在传感器和齿圈的安装间隙上。霍尔传感器的安装距离是有要求的间隙太大会导致信号幅度不够间隙太小球头结构会摩擦损坏。正确做法是在装车前用示波器查看传感器的输出波形确认高低电平都清晰稳定再上车测轮速。软件上也可以加一道防线对单帧轮速变化率做限制如果一帧之间的车速变化超过物理极限就判定为无效数据用上一帧的值代替。7.3 故障现象低速大扭矩起步时整车抖动剧烈这个现象多半是牵引力控制介入太激进了。起步时滑移率目标值定得太低控制器一检测到略微打滑就大幅削减扭矩扭矩被削掉之后附着力恢复又马上恢复扭矩反复振荡表现出来就是整车前后窜动。调整方向是把目标滑移率适当调高一点比如15%同时在PI控制器输出端加上一个变化率限制。削减扭矩的响应可以快但恢复扭矩的过程要缓这样车手的感觉会平顺得多。7.4 故障现象电池SOC很高但能量回收功率一直被限制这个我们遇到过一开始以为是BMS通讯问题后来排查发现是BMS里要求的最大允许充电功率和单体最高温度两个参数之间的关系我们的算法还没来得及适配直接把回收功率限成了0。所以算法在和BMS通讯时不要只读SOC还要把允许最大充放电功率和温度限制都读过来并且在自己本地做一份镜像防止BMS突然无响应时算法还能按最后时刻的状态做安全兜底。8. 实车调试的准备步骤与安全措施8.1 上电前的检查清单电车调试和油车不一样高压系统的存在意味着任何疏漏都可能造成严重事故。所以上电前必须有检查清单并且要有两个人互相确认高压继电器断开状态确认。绝缘监测仪读数正常。急停开关回路导通测试。制动踏板位置传感器初始值正常。低压控制板供电正常CAN通讯正常。电机控制器没有上报任何故障码。我建议每次上车调试都走一遍这个流程宁愿慢2分钟也不要省这2分钟。8.2 首次上电调试的顺序第一次上电调试的时候不要直接踩油门输出大扭矩先做几个步骤蠕行测试限制最大扭矩输出为2%确认电机能转、方向正确。低速直线测试限制最大车速15km/h在空旷平地上直线行走检查轮速一致性。反向测试确认前进和倒退方向别让算法里的符号搞混这问题很常见。急停测试在低速状态下按下急停开关确认电机立即断电、高压继电器断开。能量回收测试从低速滑行开始逐步加大回收扭矩观察电池SOC和电压变化。每一步通过之后再进行下一步不要跳步。有次我们着急练车直接跳到第5步结果发现方向反了差点造成危险。8.3 数据记录的重要性调试赛车最忌讳凭感觉。我要求所有试车记录都要有时间戳、GPS轨迹、CAN数据、BMS数据、轮速数据。有些数据未必当时用得上但后面出了奇怪的问题翻数据往往能快速定位。我们后来搭了一套简单的遥测系统用WIFI把实时的关键数据传到平板电脑上。虽然不是必须的但它确实能大幅提高调试效率。最少也要用CAN记录仪把数据存下来跑完一圈导出再分析。9. 这一篇的进度和下一篇预告到这一步我们已经完成了赛车算法的主干骨架油门解析、扭矩映射、车速估算、牵引力控制、制动能量回收逻辑并且搭建了完整的开发环境和调试流程。这一套系统跑完你的赛车已经具备了基本的安全运行能力不会一踩油门就打滑失控制动时有能量回收任何故障状态都会安全降级或断开动力。下一步就可以在这个骨架上继续长肉了。我在后面的文章里会重点讲几个模块一是扭矩矢量分配把两个驱动轮之间的扭矩差控制起来让车在进弯和出弯的时候有主动的横摆力矩二是基于IMU和方向盘转角实现的驾驶员意图识别用来做更精细的稳定性控制三是电池热管理策略和SOC估算的细节这部分直接关系到比赛后半程的动力输出能力。还需要提醒一点大学生方程式赛车算法这个项目的核心难点从来不只是某一个算法本身而是传感器信号处理、实时控制、安全保护和整车调参的综合能力。写代码只是其中一环真正让它跑稳跑快靠的是反复测试和对数据的敬畏。最后分享一个我这两年最深的体会宁可在实验室里面把状态机每种情况都推演清楚也不要在赛场上让车手去试错。赛场上试错的代价太大轻则丢掉成绩重则伤人。代码里多做一步安全检查永远比多跑一圈更有价值。