简介:面向全国大学生智能汽车竞赛折线电磁组参赛队员的完整方案分享,围绕十九届赛道变化,从车模选型、硬件配件清单到模块连接方式均有清晰梳理。资源以1个PDF文件呈现,压缩包大小1.49MB,内容涵盖电磁信号采集与归一化、差比和循迹算法、滤波处理,并针对直角弯道和苯环检测给出了具体判断逻辑;同时提及G车模与F车模的差异、TOF测距避障思路及蓝牙调参方法,适合需要快速搭建整车系统和优化控制策略的中级参赛者。已有5502人学习下载,是一份从底层原理到落地调试都具备参考价值的备赛资料。
1. 折线电磁赛道:直角弯道让循迹电感“失明”的真相
今年十九届电磁组把室内PVC跑道换成了折线形电磁赛道,原来的圆环岛也变成了六边形折线环。看起来只是赛道几何变了,但真正的坑在所有老代码的假设上:直角弯道处电磁线垂直布置,车头带出去的两侧电感会同时失去信号,这时候用1号和4号电感做差比和,偏差会突然变成无效值。换句话说,不是算法调不动,是传感器配置在直角弯道里进入了一个“盲区”。这次分享会把硬件选型、电感信号链路、直角弯道与苯环检测、障碍躲避四段拆开讲,适合正准备从普通循迹升级到折线元素的队伍,也适合想评估自己方案边界的老队员。
2. 车模与传感器选型:F/G车模差速转向下的模块接线与资源分配
2.1 三轮车模的差速转向结构
官方指定F车模和G车模,两者在底盘布局和电机参数上差不多,都使用两个后轮差速转向,前轮是随动的万向轮。这就决定了转向执行机构不是舵机而是差速值,算法上必须把位置偏差映射到左右轮速度差,而不是一个角度。比赛中有人用G车模,主要看中它的电池仓位置更靠近整车重心,过直角弯道时尾部甩动幅度更小;也有队伍坚持用F车模,因为它的底盘悬架软,对场地接缝适应性更好。选型没有绝对优劣,关键是接收发板和中线传感器支架的安装孔位。这里有一个容易忽略的点:两个车模的轮距不完全相同,同样的差速PWM在G车模上的实际转向半径会比F车模小一点,所以开环转向参数不能直接照搬,需要重新标定。
2.2 资源分配与接口对应关系
整套方案的模块连接必须清楚,否则会浪费调试时间。母板是LQ-STC32母板加核心板,外设接口都是固定的。下面这张表是我把配件清单里的模块和母板接口一一对应后整理出来的。
| 模块 | 接口类型 | 连接方式 | 关键参数 |
|---|---|---|---|
| LMV358四路电磁运放 | 模拟电压 | PH2.0-6P转杜邦线,3V3/GND对应 | 10mH电感,6.8nF谐振电容 |
| LQ_SDZ512方向编码器 | 正交解码 | 编码器接口,突出部分对丝印 | 512线/圈 |
| DRV8701双路驱动 | PWM+GPIO | 电机接口,电源严禁反接 | 2组PWM+2组IO |
| ICM20689陀螺仪 | SPI | IMU接口直插 | 角速度,SPI速率可配 |
| VL53 TOF | IIC | 对应IIC接口 | 0~120cm,抗阳光 |
| 沁恒蓝牙主从机 | 串口 | 蓝牙接口 | 透传,用于上位机 |
| 干簧管模块 | GPIO | 普通IO口 | 检测起跑线磁标 |
这张表看着简单,实际接线时有两个高发错误。第一,PH2.0-6P转杜邦线虽然防呆,但有人为了省事直接插到排针上,3.3V和GND一旦错位就会烧运放模块。第二,STC32只支持方向编码器,普通增量编码器接上去读数会乱跳。测试时如果编码器值一直为0或突变,先检查接口插针是不是反了。干簧管模块在配件清单里用于起点检测,可以接在母板的任意GPIO上。它的作用是比赛起跑时磁标触发,给计时系统一个脉冲。有些队伍把干簧管用作圈数计数,这没问题,但不要让它的信号参与电磁循迹逻辑,因为它对金属接近很敏感,在电机驱动板附近容易误触发。
2.3 编码器与电机驱动初始化代码
上电后我会先跑一遍硬件自检,确认PWM和编码器工作正常再进主循环。初始化代码在STC32平台下可以这样写。
// 硬件初始化,主频24MHz void hardware_init(void) { PWM_Init(PWM_CH1, 1000, 0); // PWM频率1kHz,初始占空比0 PWM_Init(PWM_CH2, 1000, 0); GPIO_Init(MOTOR_IN1, GPIO_OUT_PP); GPIO_Init(MOTOR_IN2, GPIO_OUT_PP); Encoder_Init(ENC1, ENC_MODE_QUADRATURE); Encoder_Init(ENC2, ENC_MODE_QUADRATURE); Encoder_Clear(ENC1); Encoder_Clear(ENC2); }这里PWM频率选1kHz而不是更高的原因是DRV8701驱动板在这个频率下死区时间更可控,电机低速时的线性度更好。初始化最后清零编码器,是为了避免上电瞬间的毛刺计数污染状态。如果是第一次接线,建议先用手转动后轮,同时通过蓝牙读编码器计数的正负,确认方向和电机正反转定义一致。
另外,陀螺仪ICM20689走SPI比走IIC快一个数量级,控制周期10ms时读取数据能省下不少时间。OLED显示模块不要和蓝牙模块共用同一个串口,否则会出现数据干扰。比赛时可以保留OLED显示电感值,蓝牙模块拔掉,避免无线中断影响主循环时序。
3. 电感信号链路:LC谐振采集、均值滤波与差比和偏差
3.1 谐振放大电路的作用原理
赛道信号是20kHz、100mA的交流电流,产生的是交变磁场。电感线圈在这个磁场中产生微弱的感应电压,直接接到单片机ADC上根本采不到有效值,必须经过LC谐振选频和运放放大。10mH电感和6.8nF电容并联构成谐振电路,谐振频率大约在19.3kHz,接近20kHz的信号频率,所以对20kHz的磁场信号增益最大,同时对其他频段干扰有衰减作用。LMV358运放将谐振电压放大到ADC的0~3.3V量程。
电感感应电压大小取决于两个因素:一是电感与电磁线的距离,距离越近磁场强度越大;二是电感线圈平面与磁力线的夹角,夹角越接近垂直感应越大。因此四个电感的朝向决定了它们的敏感区域。上面的示意图中1号和4号电感垂直向下,在直道上正好对着电磁线,偏移时它们的变化方向相反,适合做循迹;2号和3号电感斜45°,在直角弯道和苯环入口处能捕捉到侧向磁力线,适合做元素识别。这个分工是后面所有判断逻辑的基础。
3.2 均值滤波与中位值滤波
直接使用ADC采样值会包含高频噪声和毛刺,尤其是电池电压波动和电机换向干扰都会耦合进来。比赛中比较稳妥的做法是先做中位值滤波剔除野值,再做均值滤波平滑曲线。中位值滤波对脉冲干扰非常有效,均值滤波则能降低随机噪声。下面是一个循环队列形式的均值滤波实现,每次调用只更新一个样本,复杂度为O(1)。
#define SAMPLE_TIMES 8 uint16_t adc_buf[SAMPLE_TIMES]; uint8_t buf_index = 0; uint32_t sum = 0; uint16_t lpf_filter(uint16_t new_value) { sum -= adc_buf[buf_index]; // 移出最旧样本 adc_buf[buf_index] = new_value; // 写入新样本 sum += new_value; buf_index = (buf_index + 1) % SAMPLE_TIMES; return (uint16_t)(sum / SAMPLE_TIMES); }这段代码中的adc_buf、buf_index和sum要为每个电感单独分配,不能在多路复用时共用。SAMPLE_TIMES取8时,在10ms控制周期内相当于对最近80ms的信号做平均,既滤除了高频干扰,又不会造成明显的相位延迟。如果想更平滑可以取16,但直角弯道检测需要快速响应,我建议普通循迹用8,元素判断用原始采样值或只做一次中位值滤波。
3.3 归一化与差比和计算
归一化的目的是让电感值不受电池电压和传感器高度差异的影响。每个电感在赛道正上方时的最大值和远离赛道时的最小值需要单独标定。归一化公式把原始ADC值映射到0~100。
float norm_value(uint16_t raw, uint16_t min_val, uint16_t max_val) { if (max_val <= min_val) return 0.0f; float v = (float)(raw - min_val) * 100.0f / (float)(max_val - min_val); if (v < 0.0f) v = 0.0f; if (v > 100.0f) v = 100.0f; return v; }min_val和max_val不是固定的,需要根据每次上电后的电感值自动更新,比赛日光变化可能会导致电感值缓慢漂移。可以在起跑线上做一个自动标定,或者在主循环里用滑动窗口记录最大值和最小值,防止过拟合。
差比和偏差计算使用1号和4号电感的值。当车模在直道居中时,两者相等,偏差为0;发生偏移时,靠近电磁线一侧的电感值增大,另一侧减小。计算公式如下。
float compute_error(float l1, float l4) { float sum = l1 + l4; if (sum < 1.0f) sum = 1.0f; // 防除零,直角弯道时电感可能同时接近0 return (l1 - l4) / sum * 100.0f; }偏差值范围在-100到100之间,但实际直道上一般不会超过±30。如果偏差超过±40,说明车模已经严重偏移,直接用PID会让积分饱和,我会在PID外面加一个软限幅和急停保护。另外,差比和对两侧电感同时减小的场景天然不敏感,比如直角弯道失去信号时偏差为零,这时必须依赖2号和3号电感切换到元素状态,这正是第四章要处理的问题。
3.4 转向环PID与陀螺仪阻尼
电感偏差计算出来后,需要转换为两轮的速度差。常见做法是用一个PD控制器,输出速度差叠加到基础速度上。基础速度由编码器闭环或开环给定,速度差则控制转向。为了防止车模在高速过直角弯时出现甩尾,我通常会在转向环里加一项角速度阻尼,直接用ICM20689读到的z轴角速度乘以阻尼系数,叠加到差速输出上。使用陀螺仪阻尼能有效抑制转角振荡,尤其适合折线赛道频繁换向的场景。
float pid_control(float error, float dt) { static float last_error = 0; float p = error * KP; // 比例项 float d = (error - last_error) / dt * KD; // 微分项 float gyro_term = gyro_z * GYRO_K; // 角速度阻尼项 last_error = error; return p + d + gyro_term; }这里的gyro_z需要和转向方向对齐,否则会放大震荡。调试时先把GYRO_K设为0,调好KP和KD,再逐步增加GYRO_K,直到高速直道和弯道不出现左右摆动。注意陀螺仪数据需要低通滤波,因为SPI读取的原始角速度噪声比较大,简单的一阶滞后滤波就够了。另外,差速输出要限制幅值,比如最大速度差不超过基础速度的30%,否则后轮会出现打滑,反而损失抓地力。
4. 直角弯道与苯环检测:用小电感状态机识别赛道元素
4.1 直角弯道检测:1号和4号同时失明的应用
折线赛道上的直角弯道,在转弯前车模会先驶出赛道外沿,1号和4号电感正对的电磁线消失,它们的ADC值会同时跌到很低。这个特性在直道和普通弯道都不会出现,所以可以作为进入直角元素的触发条件。阈值要根据场地标定,我用的参考值是直道电感值的40%。
#define CORNER_THRESHOLD 1200 if (l1_raw < CORNER_THRESHOLD && l4_raw < CORNER_THRESHOLD) { corner_detected = 1; }这里l1_raw是滤波后的ADC原始值,不是归一化值,因为归一化会把最小值压到0,无法和正常赛道区分。触发后需要通过2号和3号电感判断转向方向,靠近弯道内侧的电感值更大。如果2号大于3号则向右转,反之向左转。判断符号稳定后立即进入开环控制。
4.2 苯环入口检测:电感总值与方向识别
苯环的检测和上一届圆环赛道类似,入口处有多条电磁线密集交叉,四路电感值总和会显著高于其他位置。可以使用sum_all = l1+l2+l3+l4,当总和大于一个高阈值时认为车模正处在苯环入口。因为入口的两根电磁线分别与2号和3号电感接近垂直,所以2号和3号的值都很大,比较两者大小就能确定入口方向。
为了防止误判,我建议在状态机里连续计数,比如连续3个控制周期都满足sum_all > RING_THRESHOLD才进入苯环状态,否则只做普通弯道处理。苯环的处理逻辑和直角弯道不同,苯环入口需要提前减速并保持一段闭环循迹,直到入口方向被确认后再切到开环,因为苯环的弯曲半径比直角弯道大很多,直接开环容易从外圈冲出去。苯环出口判断上,折线环岛出口与入口形状不同,电磁线会再次出现一次高密度交汇。建议记录进入苯环时陀螺仪航向角,当航向角累计变化量超过120°时认为车模已经处于出口区域,切回NORMAL状态。这样比单纯靠电感总和判断更可靠,因为环岛内有一段直道,电感值会短暂接近正常水平。
4.3 状态机切换实现与参数表
把直角弯道、苯环和正常循迹整合成一个有限状态机,能让逻辑清晰且便于调参。状态切换条件和动作见下表。
| 状态 | 进入条件 | 执行动作 | 退出条件 |
|---|---|---|---|
| NORMAL | 默认 | PID循迹+测速 | 检测到直角弯道或苯环 |
| CORNER | l1<1200 && l4<1200 | 开环差速转固定角度 | 左轮编码器累加达目标 |
| RING | sum_all>阈值连续3周期 | 减速并保持循迹 | 陀螺仪航向角变化超120° |
| AVOID | TOF距离<45cm | 开环避障四步 | 编码器相位完成 |
状态机实现的代码骨架如下,这里以直角弯道为例。
switch (element_state) { case STATE_NORMAL: if (l1_raw < CORNER_THRESHOLD && l4_raw < CORNER_THRESHOLD) { element_state = STATE_CORNER; target_encoder = encoder_left + 80; // 目标累加值,需根据车速标定 } break; case STATE_CORNER: set_motor(-20, 40); // 左轮弱反转,右轮正转,向右快速转向 if (encoder_left >= target_encoder) { element_state = STATE_NORMAL; pid_reset(); // 清除PID积分 } break; }set_motor的参数需要注意:在2.3中我们已经定义了PWM方向,这里左轮-20表示反转20%占空比,右轮40%正转。这种一负一正的差速比原地旋转柔和,能避免万向轮卡顿。target_encoder=80是一个参考值,实际要根据车速、轮胎磨损、弯道角度标定。开环转向期间不要跑PID,否则PID的积分值会残留,造成出弯后向一边偏。
状态机退出后要记得pid_reset()清除积分,这是很多队伍在连续直角弯道中翻车的原因。苯环的处理类似,区别是进入RING状态后先按sum_all降速,再通过2/3号差比判断转向方向,最后在出口处切回NORMAL。出口判断可以用陀螺仪航向角累计,累计到120°就认为已通过苯环的折线段。
5. 障碍躲避与开环控制参数整定:从TOF到编码器累加
5.1 VL53测距模块与避障四步流程
障碍物是45×20×20cm的红色长方体,放在电磁线中心。传统红外测距在室外强阳光下容易饱和,所以改用VL53 TOF激光测距模块。它的有效量程0~120cm,通过IIC接口读取,抗阳光能力很强。处理障碍的四步是躲避、绕过、直线行驶、回赛道。
检测到障碍后,如果继续用闭环PID循迹,车模会一头撞上去。所以必须先切到开环控制,并且每一步的切换条件都用编码器累加值而不是定时器。使用编码器累加值能确保相同的位移,不受车速波动影响。
5.2 状态切换代码与参数计算
下面这个骨架展示了四步切换的实现方式。
#define AVOID_PHASE1_ENC 150 #define AVOID_PHASE2_ENC 200 #define AVOID_PHASE3_ENC 100 if (tof_distance < 45 && tof_distance > 0) { avoid_phase = 1; avoid_encoder_target = encoder_left + AVOID_PHASE1_ENC; } switch (avoid_phase) { case 1: // 躲避:向右偏出 set_motor(0, 35); if (encoder_left >= avoid_encoder_target) { avoid_phase = 2; avoid_encoder_target = encoder_left + AVOID_PHASE2_ENC; } break; case 2: // 绕过:向赛道方向回打 set_motor(30, -10); if (encoder_left >= avoid_encoder_target) { avoid_phase = 3; avoid_encoder_target = encoder_left + AVOID_PHASE3_ENC; } break; case 3: // 直线行驶:回正车头 set_motor(30, 30); if (encoder_left >= avoid_encoder_target) { avoid_phase = 0; pid_reset(); } break; }相位1中左轮停、右轮35%正转,车模会向右前方偏出,避开障碍。相位2左轮30%正转、右轮-10%反转,车模向赛道回打方向,走到直线行驶位置。相位3两轮同速直线行驶,直到车模靠近赛道线。这几个常量的标定方法是用蓝牙回传编码器数值,在障碍物前记下encoder_left,完全绕过后再记一次,两次差值就是AVOID_PHASE1_ENC的参考值。
5.3 一个有效的调参顺序与验证技巧
最后分享一个我常用的验证技巧:把直角弯道的目标编码器累加值作为最基本参数,先用慢速40%占空比跑通,再用正常速度跑,对比两次实际转角。由于编码器累加值代表的是轮子转过圈数,同样的累加值在低速和高速下对应的路程一致,但实际转向角会因为轮胎打滑略有差异,所以高速时补偿值通常要加20%。
还有一点,回赛道后不要立刻切回正常循迹,要先检查电感偏差是否恢复到可辨识范围。如果车模离赛道太远,1号和4号电感值都很小,差比和偏差接近0,PID会认为车模已经居中,实际却还歪着。这时可以用陀螺仪航向角辅助,等航向角也归零后再切换。上面的代码已经演示了用avoid_phase=0后执行pid_reset(),这就是为了清掉开环阶段的残留误差。
本文还有配套的精品资源,点击获取