
从我带车队第一年的经历看大学生电动方程式里的所谓“算法”最容易劝退新队员。你一进队学长丢给你一堆名词扭矩控制、FOC、SOC估算、卡尔曼滤波再看一眼比赛规则里关于ASMS和急停的要求直接懵掉。但真正把一台车从“能跑”调到“跑得快”算法团队的工作并没有那么玄乎它更像是在一堆实时约束下做取舍。这篇先讲整体框架和第一块能落地的东西适合刚接手算法、或者正在纠结“到底从哪开始写代码”的队友。很多人以为搞赛车算法等于天天调PID或者训练神经网络这其实是最大的误会。电动方程式赛车的算法核心首先是安全逻辑和整车状态管理其次是扭矩分配和能量策略再往下才是各种控制算法和估计器。上来就写滤波器和PID大概率会在实车调试时被一套急停逻辑打得措手不及。所以第一篇先把这个架构捋清楚顺便把你能在Simulink或者C代码里立刻动手的部分讲透。1. 先搞清楚“算法”在赛车里到底管哪几件事1.1 从整车视角看算法家族谱我们把一台大学生电动方程式赛车打散凡是涉及“读信号、做判断、输出指令”的地方全是算法团队的活。按功能可以分四大块。第一块是整车控制逻辑也就是VCU上层。它接收加速踏板、制动踏板、挡位、电池管理系统BMS状态、电机控制器状态输出的是驾驶员扭矩需求、故障等级、上/下电指令。这一层必须跑得极稳它决定了车会不会在某一瞬间莫名失去动力或者更可怕地突然窜出去。第二块是状态估算。车辆当前实际速度是多少路面有没有坡度电池还有多少可用功率电机温度会不会在下一圈过热。这些物理量不能全靠传感器直读很多传感器要么太贵要么装不下得用软件估。这里会用到滤波、最小二乘、状态观测器甚至在新规则下需要估算轮胎滑移率。第三块是控制算法。典型的如驱动防滑控制TCS通过比较前后轮速差判断是否打滑再对扭矩做限制还有制动能量回收时的扭矩协调既要回收到能量又不能影响制动脚感。未来做高级功能比如横摆稳定性控制也会落在这块。第四块是故障诊断与安全监控。比赛规则里要求很多硬件层面的安全机制但软件同样要判断传感器是否失效、通信是否超时、高压是否异常。这一层不需要太花哨的算法但必须是代码里最严谨的部分。这四块不是孤立的它们在一个实时任务里互相配合。你可以粗浅理解成状态估算把“现在怎么样”算清楚控制算法决定“接下来怎么办”整车控制把决策转成“给电机多少电流”安全监控随时准备打断这一切。1.2 第一代架构最容易踩的坑很多车队第一版算法代码会写成一个大循环加一堆if-else传感器读数来了就处理按键按了就切状态。逻辑集中在一起调试时确实方便但人一多就乱了。我们第一年就吃过这个亏。控制组两个人同时改一份代码一个人加故障处理一个人加扭矩斜率限制合并的时候对不上结果实车测试时一踩加速踏板电机先抖了一下才发力。后来查了一下午发现是故障标志位被误置位扭矩限制逻辑被错误触发。从那以后我们强制分层每一层只用固定的接口通信。1.3 我们最终选定的分层骨架推荐给所有刚开始做电动方程式算法的车队直接采用五层结构。第一层是硬件抽象层负责把所有传感器信号转成统一的工程单位比如把ADC值转成百分比踏板开度把CAN报文转成转速值。第二层是状态估算层输入处理后的传感器数据输出估计后的车速、加速度、剩余功率等。第三层是决策控制层根据驾驶员的输入和估算结果计算目标扭矩包含各种限制和保护逻辑。第四层是执行输出层把扭矩值换算成电机控制器的请求报文同时生成给其他控制器的心跳报文。第五层是诊断与标定层负责故障码存储、参数在线修改、数据记录。这个分层不一定是最优的但它有一个好处每个新队员只要负责其中一层不用一开始就理解整车全部逻辑。而且实车出问题时能快速定位是传感器层的数据不对还是估算层发疯还是决策层逻辑错了直接看接口数据就行。2. 整车控制的核心扭矩策略与状态机2.1 驾驶员意图如何一步步变成扭矩整车控制里最基础的函数就是“请求扭矩计算”。它接收加速踏板开度、制动踏板是否被踩下、当前允许的最大扭矩输出最终给电机控制器的目标扭矩。关键点是扭矩不能直接等于“加速踏板百分比乘以最大扭矩”。理由很简单如果车在坡道上起步踏板开度10%对应的扭矩可能不足以克服重力车子会溜坡而高速时如果踏板开度还是10%可能会带来驾驶者不期望的冲击感。所以我们需要一条扭矩MAP横轴是加速踏板开度纵轴是车速中间的数据是扭矩百分比再用表格插值得到基础扭矩。这个MAP不是拍脑袋定的。第一种方法是参考燃油车定油门MAP的思路踏板开度对应节气门开度第二种方法是先用台架测出电机的外特性曲线即不同转速下最大可用扭矩再把踏板开度和扭矩百分比对应起来。对于第一版算法直接用如下公式就已经能跑baseTorque pedalMap(pedalPercent, vehicleSpeed) * maxTorqueAtCurrentRPM其中pedalMap返回0到1之间的系数。低速时为了让起步更平顺通常把低踏板开度的扭矩放大比例调低避免一踩就窜。2.2 状态机为什么要硬实时切换整车状态机看起来简单无非是“启动、就绪、行车、故障、下电”几个状态但状态切换的条件和优先级必须非常明确。我见过很多车队写成“如果电压大于XX且制动未踩且踏板为0则进入READY”结果在行车过程中电压波动导致误退出DRIVE直接断开扭矩。实际工程中状态切换要满足两个原则。第一个原则是“进入条件要严退出条件要宽”。比如进入READY需要启动按钮按下、急停未触发、BMS允许放电、电机控制器无故障全部满足才切换但退出READY只需要急停被拍下或者系统检测到严重故障。这样设计的目的是避免系统在临界状态反复横跳。第二个原则是“状态切换必须在一个确定的任务周期内完成”。我们使用的是1ms主循环所有状态切换都在这个循环里执行不允许在中断服务函数里直接改全局状态。这样保证任何时刻系统都处于已知状态调试时看状态字就知道车在干什么。状态机的代码写法可以用switch-case也可以用查表法。第一版老实写switch-case最合适逻辑清晰也不容易出错。后面如果需要更复杂的事件响应再上事件驱动状态机。2.3 扭矩干预逻辑安全兜底的那一层在基础扭矩算完之后真正的工程重点才开始。如果你直接把基础扭矩发给电机控制器危险系数会很高。因为驾驶员可能在高附着路面一脚踩到底这时候扭矩没问题但如果是在湿地或者弯心峰值扭矩会直接让后轮打滑车子瞬间甩尾。因此扭矩输出前要经过多层限制。第一层是扭矩斜率限制器防止驾驶员猛踩踏板时扭矩变化率过大引起冲击。典型设置是扭矩上升速率不超过300Nm/s扭矩下降速率更快一些比如500Nm/s模拟燃油车的节气门响应。第二层是防滑限制如果驱动轮转速明显高于从动轮说明在打滑此时要主动削减扭矩削得激进的可以直接乘一个0到1之间的系数。第三层是功率限制根据电池SOC和温度查表得到当前允许的最大功率再换算成扭矩上限。这一层逻辑写起来不难难的是标定各个参数。以扭矩斜率为例太大车子会顿挫太小起步时感觉车没劲。我们第一版的标定方法是直接在空旷场地反复做全油门起步把上升速率从大往小调等主观感受不“突”了再定下来。3. 状态估算模块车速、坡度、SOC怎么“算”出来3.1 车速不能只读轮速传感器学生车队最喜欢直接用轮速算车速但因为轮胎有滑移率轮速乘以轮胎半径根本不准。加速时轮速会比实际车速高一点制动时又比实际车速低。而且轮速传感器本身有分辨率限制低速时可能一个周期才更新一个脉冲数值跳变非常严重。所以在第一版算法里我们建议至少做两个简化处理。第一个是滤波平滑对轮速信号做一阶低通。第二个是“仲裁”同时采集两个后轮轮速如果四驱则是四个轮子取最小值作为参考车速。原因是在驱动工况下驱动轮存在滑转最低的那个轮子的速度更接近车辆实际速度当然这个规则在滑移率高到离谱时需要特殊处理否则会得到过一个很离谱的估计值。如果你有非驱动轮轮速优先用非驱动轮轮速除以滚动半径作为车速。这是最简单也是最稳的方案后面再升级成基于加速度传感器和轮速融合的坡度估计。3.2 为什么滤波比PID入门更重要很多学控制的新队员上来就问“什么时候教PID”但在赛车算法里滤波和信号处理才是日常做得最多的事。油门踏板信号来自电位器/霍尔传感器在车上会受电磁干扰直接读出来的值会抖动轮速信号是脉冲计数低速时低到没法直接用。没有一块干净的信号层后面任何控制算法都白搭。我的建议是新队员在写第一个控制算法之前先亲手实现三种滤波器滑动平均滤波、一阶低通滤波、限幅滤波。只要能解释清楚每一种的优缺点和适用场景再往前走。3.3 用滑动平均和一阶低通滤波起步滑动平均滤波最简单取最近N个采样值的平均值作为输出。优点是对周期性噪声效果好缺点是有延迟且占用内存。代码基本长这样class MovingAverage { float buffer[50]; int index 0; int count 0; float sum 0; public: float update(float value) { if (count 50) { buffer[count] value; sum value; } else { sum - buffer[index]; buffer[index] value; sum value; index (index 1) % 50; } return sum / count; } };一阶低通滤波更常用因为它只存一个历史值内存占用极小表达式也直观float lowPassFilter(float value, float lastValue, float alpha) { return alpha * value (1.0f - alpha) * lastValue; }alpha越小滤波越平滑但延迟越大。alpha和截止频率之间有关系需要在采样周期已知的情况下换算。比如采样周期Ts1ms想滤掉100Hz以上的高频噪声可以取alpha约等于Ts * 2 * PI * fc / (1 Ts * 2 * PI * fc)大约0.386。实际标定时可以现场试车静止时看数据显示的波动程度调到波动不太明显且踩踏板时响应跟手即可。注意滤波不是越多越好。每个滤波都会引入相位延迟多个滤波器串联后延迟叠加会导致实车手感变“肉”。我们在转向和制动相关信号上尽量少滤波或不滤波只有对噪声特别敏感的传感器比如踏板、电流才加一阶低通。4. PID控制在赛车里的应用场景与调参方法4.1 PID在算法栈里的位置PID是车队里被过度神话的一个词。实际上在整车主控里PID直接用的场景并不多。常见的应用是定速巡航的油门控制、制动能量回收时的制动压力闭环以及一些温度控制场景。更核心的电机电流环、转速环通常在电机控制器内部就完成了不需要我们在VCU里写。所以第一篇算法文章里讲PID重点不是推导公式而是让你知道它怎么被集成到整车状态机里。比如定速巡航驾驶员设一个目标车速估算模块给出实际车速PID输出扭矩增量叠加到基础扭矩上。4.2 手动调参的四个步骤PID调参网上教程一大堆但车队里最管用的还是“先P后I再D”的土办法。先把I和D设为0只加比例项让车在一个安全速度区间跑逐步增大Kp直到车速出现小幅振荡然后退回来一点大概往回退30%到50%。接着加积分项先设一个很小的Ki比如0.01看稳态误差能否消除。如果车速出现周期性振荡说明Ki偏大减小Ki。最后加微分项Kd主要用于抑制超调但车上信号噪声大Kd过大会把噪声放大所以没用过特别大的值。这套方法在我们车队实际调定速巡航时第一次上手大概用了一个下午。真正的时间不是花在调参上而是花在数据采集上。必须把车速、目标车速、PID输出、电机扭矩请求全部通过CAN记录下来回去离线画曲线不然你在车上凭感觉调参调一整天也说不清到底改了什么。4.3 初始参数表给一个第一版就能用的初始范围表具体数值要结合电机峰值扭矩和整车质量调整。控制场景Kp初始范围Ki初始范围Kd初始范围备注定速巡航2.0 - 5.0 (Nm/ (m/s))0.01 - 0.050或极短微分时间先关D制动压力闭环0.5 - 2.00.005 - 0.020响应快但别振荡温度控制风扇10 - 500.1 - 0.50大惯性对象如果你觉得手动调参太累后面可以引入粒子群算法做离线的参数自整定但那至少是第二阶段的事。第一版老老实实用工程经验参数够用且安全。5. 数据闭环从纯写代码到能实车调试的关键一步5.1 数据记录是算法的“眼睛”没有数据记录就谈不上算法开发。实车测试时你只靠感觉去判断“车好像动力变弱了”根本不知道是电池功率限制还是防滑介入太猛。所以算法代码里必须留一块专门的数据记录模块。最低要求是记录这些通道时间戳、整车状态、加速踏板百分比、制动开关状态、请求扭矩、实际扭矩、电机转速、车速、电池SOC、电池最大允许功率、故障码。第一版用SD卡写CSV文件都行后面再换CANalyzer或者开源的上位机方案。关键是记录频率要跟控制周期一致最好每1ms或者每5ms记录一次且文件里带精确时间戳方便回放时对齐。5.2 一个能跑的上位机显示方案很多车队卡在“数据攒了一堆看着一堆CSV却不知道发生了什么”。最简单的方法是先用Python的matplotlib离线画图虽然土但足够分析99%的问题。等后面需要实时看数据再上一套网页版或者Qt上位机。我给一个离线分析的典型代码片段import pandas as pd import matplotlib.pyplot as plt data pd.read_csv(log_20240815_1000.csv) fig, axes plt.subplots(3, 1, figsize(12, 8)) axes[0].plot(data[time_ms], data[vehicle_speed_kmh]) axes[0].set_ylabel(Speed (km/h)) axes[1].plot(data[time_ms], data[torque_request], labelrequest) axes[1].plot(data[time_ms], data[torque_actual], labelactual) axes[1].set_ylabel(Torque (Nm)) axes[1].legend() axes[2].plot(data[time_ms], data[soc_percent]) axes[2].set_ylabel(SOC (%)) plt.show()这个脚本不到二十行但能帮你瞬间看出扭矩请求和实际响应之间差了多少是不是存在限功率防滑介入是不是太频繁。5.3 从仿真到台架再到实车的三级跳算法开发切忌直接上车。标准流程是先在纯离线环境里用录制好的数据跑一遍代码确认不会崩溃、逻辑正确。然后上电机台架让VCU真实发送CAN报文给电机控制器验证通信协议和扭矩响应。最后才上车做低速测试。台架环节最容易被忽略但价值最大。你可以在台架上故意模拟故障比如拔掉急停线、发送一个超范围的扭矩请求看VCU是否正确响应。这些测试在实车上做既危险又耗时。我们在台架上把整套下电逻辑测了无数遍真上车时反而很顺利。6. 新手常见问题与排查技巧实录6.1 踩下加速踏板没反应排查思路从信号源头开始。先看上位机/日志里踏板百分比是否变化如果一直是0查传感器供电和CAN报文如果踏板值正常再看状态机是否在DRIVE如果状态机正常再看请求扭矩是否大于0如果请求扭矩为0再看是不是扭矩限制全开比如电池功率限制为0或者故障激活。这一条链路走下来九成问题都能定位。6.2 电机有功率但车不走这种问题一般出现在扭矩请求正常但电机没转的情况。优先检查电机控制器是否收到目标扭矩报文CAN报文ID和DBC文件对不对心跳是否正常。另外注意电机控制器的使能信号、急停回路、ASMS状态这些硬件信号经常和CAN通信是独立的软件能发请求但不代表硬件允许放电。6.3 数据曲线毛刺特别多记录数据在回放时出现大量尖峰通常不是整车真的在抖而是信号处理链路有问题。检查传感器的采样率设置、CAN总线的位定时配置、是否有终端电阻冲突、以及整车高压部件对传感器线束的干扰。我们在实车中就发现电机控制器大功率输出瞬间踏板传感器信号会出现周期性毛刺最后用磁环和双绞屏蔽线解决。6.4 状态机跟抽风一样自动跳到大故障这种问题一般就是状态机“进入条件太松”或“退出条件太严”导致的误判。比如电池SOC小于5%就进故障态但电池压差在急加速瞬间又触发了一闪而过的故障状态机直接切到了不可恢复的故障态。解决思路是把故障分类瞬时故障设定去抖时间比如连续100ms都在阈值外才真正算故障对不可恢复故障才允许立即进入故障态锁存。7. 第一阶段的里程碑建议写到这里第一篇的内容也差不多该收尾了。我想强调的是大学生电动方程式算法开发不是比谁的代码看起来“高深”而是比谁能在有限时间里把系统跑稳。第一阶