前言
在智能车竞赛中,“看不见的信号”往往是最致命的。很多时候小车冲出赛道,并不是算法写错了,而是由于硬件误差或环境干扰导致的逻辑误判。
本文将从信号采集、滤波算法、归一化处理、核心循迹算法(差比和差)以及硬件优化五个维度,分享我们如何让电磁车从“蛇形游走”做到“贴线疾驰”。
📡 信号的收集
在开始算法之前,首先要了解我们处理的是什么量级的数据。我们的主控 RT1064 开启了 12 位 ADC 采样,这意味着原始数值范围是 0∼40960 \\sim 40960∼4096 。
在正常赛道之上,电感感应到的有效值大约在 1000 ~ 3500 左右。这个量级下,环境中的噪声导致信号跳动,交给 PID 计算的时候小车会产生莫名的抖动。为了获得平滑的信号,我们采用了死区处理和 N 阶去极值平均滤波 。
(1) 采样阶段:多通道同步获取
我们首先在一个控制周期内对 4 路关键传感器进行连续 5 次高速采样,存入缓冲区 :
// 采样示例:在一个周期内连续获取多组数据
for(i = 0; i < 5; i++)
{
filter_buf_L[i] = adc_convert(Left_ADC_Pin); // 左侧横向
filter_buf_LS[i] = adc_convert(Left_Shu_Pin); // 左侧纵向
filter_buf_RS[i] = adc_convert(Right_Shu_Pin); // 右侧纵向
filter_buf_R[i] = adc_convert(Right_ADC_Pin); // 右侧横向
}
(2) 🧹 死区处理:提出微弱噪声
在实际中发现,采集到的几百个单位的数值几乎全是环境中的杂散磁场干扰或电路本身的零漂 。
// 去除微弱信号:
if(filter_buf_L[i] < 240) filter_buf_L[i] = 0;
if(filter_buf_LS[i] < 500) filter_buf_LS[i] = 0;
if(filter_buf_RS[i] < 500) filter_buf_RS[i] = 0;
if(filter_buf_R[i] < 240) filter_buf_R[i] = 0;
💡 注:为何横向电感去除的是小于 240 的信号,而竖向电感去除的是小于 500 的信号?
📝 具体的参数设定逻辑及原因,将在后续文章中详细提及。
(3)算法实现:剔除异常值
普通的均值滤波容易被一个“极大”或“极小”的异常脉冲拉低整体精度。
我们的方案是:去掉一个最高值,去掉一个最低值,剩下的求平均。这样可以极大地增强系统的鲁棒性。
⚠️ 重要提示:必须先进行死区处理再进行滤波计算,否则微弱的干扰信号会被平均到滤波结果中 。
/**
* @brief N阶去极值平均滤波
* @param ADC: 指向采样缓冲区的指针
* @return 过滤后的平滑平均值
*/
float ADC_Del_MaxMin_Average_Filter(float *ADC)
{
uint16 max = 0; // 记录最大值
uint16 min = 0xFFFF; // 记录最小值
uint32 sum = 0; // 累加总和(注意用uint32防止溢出)
float average = 0.0;
int i;
for(i = 0; i < 5; i++)
{
if(max < ADC[i]) max = ADC[i]; // 更新最大值
if(min > ADC[i]) min = ADC[i]; // 更新最小值
sum += ADC[i]; // 累加
}
// 计算平均值
average = (float)(sum – max – min) / (5 – 2);
return average;
}
💡 关于数据类型的关键细节
需要特别强调的是 uint32 sum 的数据类型:
- 🔢 计算背景:
- 12 位 ADC,最大值为 4096。
- 5 次累加最大约为 4096×5=204804096 \\times 5 = 204804096×5=20480。
- ⚠️ 溢出风险:
- 虽然 uint16(最大 65535)目前能存下。
- 但如果你以后增加采样次数(如 16 次以上),uint16 就会发生溢出。
(4)归一化处理:从“绝对值”到“百分比”
归一化的本质是将 12 位 ADC 的原始量程(0∼40950 \\sim 40950∼4095)映射到统一的 0∼1000 \\sim 1000∼100 。
- 逻辑一致性:这样,后续所有的控制逻辑(如差比和差、PID)处理的都是比例关系,而不是不稳定的绝对数值。
- 代码复用性:即便切换了不同性能的电机或舵机,你的控制算法在逻辑层是不需要大规模重写的,还可以照搬到别的工程之中,复用很方便 。
/* 核心代码:归一化映射 */
// 将滤波后的 ADC 值(adc_deal_1)映射到 0-100 范围
left_adc = 100.0 * adc_deal_1[0] / 4096; // 12位 ADC_MAX=4096
leftshu_adc = 100.0 * adc_deal_1[1] / 4096;
rightshu_adc = 100.0 * adc_deal_1[2] / 4096;
right_adc = 100.0 * adc_deal_1[3] / 4096;
(5)避坑与调优心得
取样次数的取值:
在我们的项目中,采样次数取值为 5,通常可以选取 5-7。
- 📈 平滑度 vs 延迟:
取值越大,信号越平滑,但延迟也会随之增加 。 - ⚠️ 风险提示:
如果滤波太重(取值过大),车子在过急弯时感知会产生滞后,导致过弯时舵机“晚打”从而冲出赛道 。
信号的处理
(1)基础版:差比和算法
该算法通过分别计算横向偏差和纵向偏差,然后进行加权求和(这里各占 50% 权重)。
// 横向偏差 = (左横 – 右横) / (左横 + 右横)
// 纵向偏差 = (左纵 – 右纵) / (左纵 + 右纵)
AD_Bias = 10.0 * (
0.5 * (left_adc – right_adc) / (left_adc + right_adc) +
0.5 * (leftshu_adc – rightshu_adc) / (leftshu_adc + rightshu_adc)
);
📐 算法公式化表达
AD_Bias=10×(0.5⋅L−RL+R+0.5⋅Lshu−RshuLshu+Rshu)AD\\_Bias = 10 \\times \\left( 0.5 \\cdot \\frac{L – R}{L + R} + 0.5 \\cdot \\frac{L_{shu} – R_{shu}}{L_{shu} + R_{shu}} \\right)AD_Bias=10×(0.5⋅L+RL−R+0.5⋅Lshu+RshuLshu−Rshu)
核心原理:
- 思想:利用 差比和 (Difference-Ratio-Sum) 的思想,将传感器采集的“绝对数值”转化为“相对比例”。
- 公式拆解:
- L−RL – RL−R (差):反映偏差方向(车头偏左还是偏右)。
- L+RL + RL+R (和):用于归一化,消除因赛道磁场强度不同或电池电压波动导致的信号整体强弱差异。
(2) 进阶版:差比和差算法
此算法的核心在于分母引入阻尼项,解决了传统差比和算法在丢线边缘分母趋近于 0 导致数值发散的问题。
/* 核心参数:
* ALPHA (横向权重)
* BETA (纵向权重)
* OMEGA (阻尼系数)
*/
// 1. 分子:横向与纵向差值的加权和,决定偏差方向
float fenzi = ALPHA *
(left_adc – right_adc) +
BETA * (leftshu_adc – rightshu_adc);
// 2. 分母:在横向之和的基础上,引入纵向差值的绝对值作为“阻尼”补偿
// 使用 fabs() 取绝对值,防止分母过小导致偏差发散
float fenmu = ALPHA *
(left_adc + right_adc) +
OMEGA * fabs(leftshu_adc – rightshu_adc);
// 3. 计算最终偏差值
AD_Bias = 10.0 * (fenzi / fenmu);
📐 算法公式化表达
AD_Bias=10×α⋅(L−R)+β⋅(Lshu−Rshu)α⋅(L+R)+Ω⋅∣Lshu−Rshu∣AD\\_Bias = 10 \\times \\frac{\\alpha \\cdot (L – R) + \\beta \\cdot (L_{shu} – R_{shu})}{\\alpha \\cdot (L + R) + \\Omega \\cdot |L_{shu} – R_{shu}|}AD_Bias=10×α⋅(L+R)+Ω⋅∣Lshu−Rshu∣α⋅(L−R)+β⋅(Lshu−Rshu)
- 分子:
- 主要决定偏差的方向(左转还是右转)。
- 分母:
- 引入 fabs() 绝对值项作为阻尼。
- 核心作用:即使 L+RL+RL+R(横向电感之和)极小,分母依然有纵向差值的绝对值“撑着”,从而保证分母不会变成 0,防止计算结果发散。
(3)⚔️ 两种算法对比
🤝 共同优势
-
🔋 自适应能力强
- 当电池电压发生变化(如从 8.4V 掉到 7.4V),或换了信号源功率不同的赛道时,四个电感数据都会呈比例上升或下降。
- 原理:相当于给每个数据乘上一个比例系数 kkk。由于计算公式中分子分母同时含有该系数(且 k1=k2k_1=k_2k1=k2),系数直接约掉,计算结果保持不变。
- 效果:确保小车不会因为电量不同、场地不同,而出现从“猛虎”变“猫咪”的尴尬情况 。
-
📏 归一化友好
- 它们都非常契合归一化处理后的数据(0-100)。
- 计算出的偏差值 (Error) 范围为 [-10, 10](代码中乘上系数 10,否则为 [-1, 1]),这对于 PID 调参的帮助非常之大。
⚖️ 各自优劣势深度解析
A. 差比和算法 (Basic Version)
| ✅ 优势 | 简单省心。几乎不需要调参,可以调整的只有一个系数,且一般情况下不需要修改 。 |
| ❌ 劣势 | 1. 边缘发散(致命伤):当小车严重偏离处于丢线边缘时,分母(两路电感之和)趋近于 0。此时微弱噪声会导致输出值发散爆表,引发舵机打死或车头左右抽搐。2. 直道抖动:走直线时纵向电感较小,传统权重分配(横 0.7 / 纵 0.3)可能导致直道抖动严重。3. 弯道迟钝:面对大弯道或急弯时,信号感知的灵敏度往往不足。 |
B. 差比和差算法 (Advanced Version)
| ✅ 优势 | 1. 稳定性强:通过在分母中引入阻尼项 fabs(),即便横向信号全无,分母也能被纵向差值的绝对值“撑住”,使偏差输出平滑连续,有效防止结果发散爆表。2.高线性度:通过 ALPHA 与 BETA 的权重平衡,让纵向电感在大弯道时提供额外的增益,赋予系统更高的线性度。 |
| ❌ 劣势 | 调参难度高。需要开发者反复跑赛道来确定三个关键参数(ALPHA, BETA, OMEGA)的最佳比例。甚至在直道、大弯、小弯可能需要不同的比例,测试工作量大 。 |
(4)📉 最后的平滑屏障:一阶低通滤波
在通过“去极值平均滤波”处理完原始 ADC 采样后,我们得到了平滑的输入。但在实际高速行驶中,即便输入是平滑的,由于赛道细微起伏或车体姿态抖动,计算出来的偏差值 AD_Bias 仍可能存在高频细微跳变 。
// 以直道为例:追求极致平滑
// current_AD_Bias 是本次计算的原始值,last_valid_bias 是滤波后的结果
last_valid_bias =
last_valid_bias +
0.8f * (current_AD_Bias – last_valid_bias);
//即last_valid_bias =(1-0.8) last_valid_bias + 0.8f * current_AD_Bias ;
🧮 算法原理
核心公式:
y(n)=(1−α)×y(n−1)+α×x(n)y(n) = (1 – \\alpha) \\times y(n-1) + \\alpha \\times x(n)y(n)=(1−α)×y(n−1)+α×x(n)
举个栗子 🌰:
假设 last_valid_bias = 2,current_AD_Bias = 10,系数 alpha = 0.8:
0.2×2+0.8×10=8.20.2 \\times 2 + 0.8 \\times 10 = 8.20.2×2+0.8×10=8.2
可以看到,数据没有直接跳到 10,而是平滑过渡到了 8.2。
🎛️ Alpha 系数调参指南
我们可以通过调整系数 alpha 来调节性能 :
| 越大 (接近 1) | ⚡ 响应非常快,能迅速跟上信号的变化。 | 过滤高频抖动的效果差,信号容易震荡。 | 赛道非常平整,追求极致的转弯灵敏度。 |
| 越小 (接近 0) | 🌊 输出极其平滑,能过滤掉很强干扰。 | 产生严重的“相位滞后”(即车已经变向了,计算出的偏差值还没反应过来)。 | 赛道起伏大,传感器噪声很强,需要牺牲响应速度换取稳定。 |
(5)💡 实战心得体会
项目初期调试的时候车速比较慢,差比和算法还是很好用的,逻辑简洁好调整。但是随着我们对速度要求提高,算法的缺陷就暴露了。
🚫 高速下的“抽搐”与失控
当我们尝试提速的时候,发现很难控制车体始终保持在赛道的绝对中央。
- 现象:在高速冲入急弯或由于惯性产生小幅度偏移时,差比和算法的劣势非常明显。
- 后果:丢线边缘时,差比和算出巨大的跳变值反馈给舵机,让舵机“抽搐”,导致小车经常在急弯处因打角过度或不足而直接冲出赛道 。
🔄 从“混合使用”到“全线切换”
中期尝试:我们尝试过折中方案——在直道和普通弯道使用差比和差,而在环岛逻辑中保留差比和。
❌ 结果:实测证明,当环岛的入环速度提上去后,基础算法依然无法支撑高动态下的稳定性 。
最终决策:我们决定全赛道统一采用差比和差算法。
这一转变也得益于我们后期调试手段的丰富——通过 TFT180 屏幕、有线及无线 UART 串口实时收集波形数据,原本复杂的 ALPHA、BETA、OMEGA 等参数相对来说好调一点。
成效:
🛡️ 补充内容:丢线保护策略
在信号处理的最后环节,我们需要加入最容易被忽略但最关键的一环——保护机制。毕竟车丢线后到处乱撞的代价太大了:
- 💥 硬件损耗:高速撞击会导致碳纤支架断裂、电机轴弯曲,甚至烧毁昂贵的舵机。
- 📉 参数失效:碰撞会改变电感的前瞻位置和倾斜角度,导致你辛苦调校出的 ADC 基准数据瞬间作废。
🩸 这是真的“血的教训”,一定要保护好你的硬件!
- 💸 意外赔偿:赛道调试现场往往车满为患,失控的小车可能撞到他人的车子或电脑,造成不必要的赔偿损失。
- 🔍 调试辅助:如果车一丢线就主动刹停在原地,你可以立刻观察到它是从哪个位置、以什么姿态冲出去的,而不是去远处的墙角捡车。
// 丢线判定逻辑示例
// 定义一个全局或静态变量记录状态
bool is_lost = false;
// 阈值判定:当四路电感总和极小时(例如 <= 15),视为丢线
if ((left_adc + leftshu_adc + rightshu_adc + right_adc) <= 15)
{
is_lost = true;
}
else
{
is_lost = false;
}
// 执行安全保护
if (is_lost) {
// 1. 立即停止电机输出 (Duty = 0)
// 2. 进阶:执行“主动反向制动”
set_motor_pwm(–200); // 给一个短暂的反向力,迅速把速降下来
// 3. 舵机回正,防止侧翻或堵转
set_servo_pwm(SERVO_MID);
}
🏎️ 实战经验总结:那些代码之外的“制胜关键”
很多同学在调车时会陷入“算法决定一切”的误区,但作为从“小白”走过来的参赛者,我们深刻体会到:工程细节往往比算法逻辑更能决定成败。
(1)🛠️ 硬件决定上限:别让硬件毁了你的算法
我们曾苦苦调代码、调参数,试图消除小车莫名的抖动,最后却发现:电感支架的刚性不足才是罪魁祸首——支架抖得比车还厉害。
算法滤波打不过晃动收集到的复杂数据
💡 核心观点:如果支架晃得比车还厉害,电感捕捉到的磁场变化就会包含大量的物理噪声,非常复杂,这是任何软件算法都难以完美弥补的。
结论:加固支架后,也许原本的代码就已经能让车跑得很稳定了。
“控制变量”是调试的前提
电磁模块的安装位置、前探距离、甚至是倾斜角度,都会直接改变 ADC 的基准读数。
- 固定牢固:一定要保证模块固定牢固。
- 定期检查:碰撞后支架可能会歪,记得手动校正角度,否则你会发现原本调好的参数突然就“失效”了。
保护小细节
- 🛡️ 加固引脚:建议使用热熔胶加固电感引脚,防止在频繁的碰撞和振动中产生虚接或断裂。
(2)📈 阶段化开发:从“跑起来”到“跑得稳”
智能车竞赛是一个层层递进的过程(中期检查 -> 预赛 -> 决赛),我们的策略是:
- 🟢 前期(探索阶段)
- 策略:采用差比和算法。
- 理由:逻辑直观、易于部署,能让你快速看到反馈,建立信心。
- 🔴 后期(实战提速)
- 策略:当车速提升到算法瓶颈时,果断切换为差比和差算法。
- 理由:利用其平滑的线性特征,支撑更激进的速度策略。
(3)📊 数据化调试:从“玄学调参”到“量化分析”
电磁组的灵魂在于对数据的掌控。我们强烈建议尽早建立完善的调试流程:
🛠️ 调试手段进阶
- High Level:甚至可以采用 MATLAB 仿真之类的工具,直接输出波形对比。
📉 数据建模与分析
我们将抓取的原始数据导出到 Excel 中,这不仅方便我们分析直道、弯道、十字等不同赛道元素的特征,还能帮助我们进行“模拟调参”。
⚡ 独家技巧:Excel 模拟调参
在更换“差比和差”算法时,我们的操作流程如下:
- 理论上,进阶算法的输出量级应与原算法大致相等。
⚠️ 注意:软件模拟无法完全覆盖高速行驶下的物理惯性,因此需要在模拟参数的基础上进行实车微调。收集数据时,不仅要收集“正正好”的姿态,还要收集略微偏差甚至极限偏差的状态。只有覆盖了小车偏离赛道时的各种信号反馈,你建立的模型才会足够稳定!
📝 写在最后的话
电磁车的调试过程可能很枯燥,但当你看到小车从“蛇形游走”到“贴线疾驰”的那一刻,所有的付出都是值得的。
希望这些经验能帮正在埋头调代码的你,避开那些物理上的坑。🚀
网硕互联帮助中心





评论前必须登录!
注册