从0到1带你打电赛·小车电控篇(五):给小车装眼睛和平衡感——灰度、电磁、编码器与 IMU

上一篇《电机驱动与电源地基》解决的是”小车的腿能不能听话地动起来”。可问题来了:腿动得再稳,要是脑子不知道”我现在偏左了还是偏右了””我跑多快””车头朝哪个方向”,那也只是一辆瞎跑的车。

这一篇我们就来给小车装上四样”感官”——灰度、电磁、编码器、IMU。它们的共同任务只有一个:给后面的控制环(PID)送上一份又干净又准确的反馈信号。打个比方,PID 是司机,感知层就是司机的眼睛、耳朵和平衡感。眼睛进了沙子,再老练的司机也得撞墙。所以这一篇虽然不讲控制算法,却是整车成败的地基里非常硬核的一块。

我们分四块讲:循迹的两条路线(灰度 / 电磁)、测速度的编码器、测姿态的 IMU。每一块都给你能照抄的算法和代码,最后说一句”路上怎么在它们之间切换”。

一、循迹路线一:灰度——小车最常用的”看路”方式

灰度循迹是绝大多数电赛/智能车小车的第一选择:在车头底部横排一组朝下的光电/灰度传感器,地面是白底黑线,传感器看到黑线就报告”我这儿压线了”。把所有传感器的信息汇总,就能算出”黑线现在偏车身中线多远”。这个偏差,就是喂给转向 PID 的核心输入。

数字量阵列:加权位置法(行业标准算法)

最经典、被全网开源项目反复抄的算法,叫加权位置法,来自 Pololu 的 QTR 传感器库 readLine 算法。它的思路特别好懂:

🧩 比喻:同学按学号站一排报到

把 8 个传感器想象成班里 8 个同学,按学号(0~7)站成一排。谁看到黑线谁”举手”,举手的力度(传感器读数)当权重。我们把”学号 × 力度”全加起来,再除以”总力度”,得到的就是一个平均学号——黑线现在大概压在第几号同学附近。比起只数”有没有人举手”,这能算出黑线卡在两个同学中间的位置,精细得多。

写成公式就是:

$$\text{position} = \frac{\sum_{i} (i \times 1000) \times value_i}{\sum_{i} value_i}$$

其中 $i$ 是传感器索引。对 8 路传感器,这个值的范围是 $0 \sim 7000$,正中心就是 $(8-1)\times500 = 3500$。这个数随黑线左右移动单调变化,直接拿来当 PID 输入:设定值 setpoint = 3500,误差 error = position − 3500。

下面是改写自 QTR 库的核心代码,数字量、模拟量阵列都能用:

// N 路灰度阵列加权位置法,返回 0~(N-1)*1000,中心 (N-1)*500
// value[i]: 第 i 路校准后读数(压线=高值),已做归一化
uint16_t readLinePosition(uint16_t *value, uint8_t N) {
    uint32_t avg = 0;   // 加权和 Σ(i*1000*value)
    uint32_t sum = 0;   // 权重和 Σ(value)
    static uint16_t lastPos = 0;
    bool onLine = false;
    for (uint8_t i = 0; i < N; i++) {
        uint16_t v = value[i];
        if (v > 200) onLine = true;        // 判定压线
        if (v > 50) {                       // 噪声门限,>50 才计入加权
            avg += (uint32_t)v * (i * 1000);
            sum += v;
        }
    }
    if (!onLine) {                          // 全丢线:朝上次方向找线(下面细讲)
        if (lastPos < (uint32_t)(N - 1) * 1000 / 2) return 0;
        else return (N - 1) * 1000;
    }
    lastPos = avg / sum;
    return lastPos;                         // PID 输入:error = lastPos - (N-1)*500
}

注意里面两个门限:读数 >50 才计入加权和(滤掉噪声),>200 才算”真的压线了”。这是 QTR 库的经验值,能让位置计算更干净。

数字量 vs 模拟量:要不要那么精细?

刚才代码里的 value[i] 可以是两种来源:

类型 怎么读 优点 缺点
数字量 每路经比较器输出 0/1(压线/没压) 简单、直接读 IO、抗环境光稍好、运算量小 分辨率低,偏差呈阶梯状
模拟量 直接读 ADC 原值 分辨率高,能算出黑线在两路中间的”亚像素”位置 运算量大,更怕环境光和反光

💡 比喻

数字量像只问”踩到线了吗?是/否”;模拟量像问”你离线多近?打个 0~100 分”。后者能算出更细的偏差,但也更容易被路灯、反光骗到。

模拟量有个必做的预处理:归一化。每一路传感器单独记下自己在纯黑、纯白时的最大最小值,运行时把读数折算到 0~100。这能消除各路传感器的个体差异,换场地也只要重测一遍。

// 标定阶段:扫黑白记录每路 min[i]/max[i]
// 运行阶段:归一化到 0~100
uint8_t normalize(uint16_t raw, uint16_t mn, uint16_t mx) {
    if (raw <= mn) return 0;
    if (raw >= mx) return 100;
    return (uint8_t)((uint32_t)(raw - mn) * 100 / (mx - mn));
}

工程四件套:”实验室能跑、赛场翻车”的分水岭

灰度循迹真正拉开差距的,不是那个加权公式,而是下面这四件事。

① 黑白阈值,一定要现场标定。 别用上次的、别用实验室的。赛场的光照、地面材质、纸张反光都和你家不一样,固定阈值一换场就误判。

⚠️ 2022 年那届的"翻车现场"

那年好几支队伍的小车在赛场识别/循迹翻车——实验室明明能跑,一到赛场就不行。根因几乎都是同一个:赛场环境和实验室差太多,参数没现场重标。(来源)

基础做法:上电后让阵列扫过真实赛道的纯黑线和纯白底,采集每路 min/max,阈值取中值。

进阶一点,可以上 OTSU 大津法做动态阈值,免去手动标定:

// 非线性映射,增强黑线区分度(减小传感器个体差异)
adc[a] = pow((adc[a] / 10.0), 3) / 15000;
// OTSU:遍历灰度级找"最大类间方差"对应的灰度作阈值
// 类间方差 = p0 * p1 * (m0 - m1)^2   (p0/p1 两类概率,m0/m1 两类均值)
// 阈值限幅 100 <= threshold <= 800
output[i] = (input[i] <= threshold) ? 1 : 0;     // 二值化

有作者用 OTSU + 三次方非线性映射,在 9 片不同材质、不同光照、带污渍褶皱的场地上测试全部正常,还省掉了上电黑白标定这一步(来源)。

💡 比喻:标定就像考试前定及格线

不同试卷(场地)难度不同,你得先看看这次黑题白题大概多少分,把及格线划在中间,而不是死守上次的 60 分。OTSU 就是自动帮你找”最能把黑、白分成两堆”的那条线。

② 全丢线,绝不能把 error 清零。 这是新手最容易踩的命案。

🔥 全丢线清零 = 直冲出弯

丢线大多发生在急弯切出赛道的那一瞬间。此时最后那个偏差方向,恰恰就是你该回去找线的方向。如果把 error 归零,车就以为”前方笔直”,一头冲出去。

正确做法(就是上面代码里那段):丢线时记住上一周期的偏差极性,朝原来弯道的方向打死方向去重新抓线。再补一条超时保护:长时间持续丢线就减速甚至停车倒找,别让车原地打转。

🧩 比喻

好比你过弯时眼睛突然闭了一下,正确做法是”刚才在往左拐就继续往左找路”,而不是”看不见了就直着冲”——直冲必然冲出赛道。

③ 十字/直角路口,必须特判。 经过十字时多路传感器会同时压线,加权位置会突然跳变、把偏差算飞,车就猛打一下方向。处理办法:8 路里有 ≥3 路同时压线就判为路口,进入”路口模式”——这时候不用常规偏差了,而是靠状态机让车直行/盲走固定一段(配合编码器或惯导计程)穿过去,过完再恢复巡线。一位省赛一等奖选手就是这么干的,并强调”换赛道、换车都得重标重调,没有万能参数”(来源)。

④ 安装高度与前瞻,是个三方妥协。 前瞻 = 传感器有效检测点离车头多远。

  • 前瞻 → 高速直道更稳,能提前预判弯道,但急弯容易提前切弯、切到弯内;
  • 前瞻 → 急弯跟得紧,但高速时容易发抖。

经验做法:直道用远前瞻,弯道用近前瞻,可以动态切换(灰度阵列靠”用哪几路算偏差”来实现切换,切换点要带迟滞,否则在直弯交界来回切会抖)。

⚠️ 恩智浦智能车光电组的纠结

一份技术报告里,底盘高度反复纠结:太低上坡蹭赛道、过障被顶住;太高重心高、高速过弯侧翻;前瞻调太远又有噪点。最后只能在”不蹭赛道”的前提下尽量压低,再配俯仰角控制前瞻。(来源)

一句话:高度和前瞻都不是越大越好,是重心稳定性、过障能力、识别准确率三者的折中。

关于 TCRT5000 红外光电

入门玩具车上最常见的就是 TCRT5000 这种离散红外反射光电。它是红外发射二极管(波长 950nm) + 光敏三极管,集成了日光阻断滤光片(Vishay 官方手册)。关键参数:

  • 峰值工作距离 2.5mm,有效范围 0.2~15mm,最佳 0.2~6.5mm;
  • 供电 3.3~5V,配 LM393 比较器整形输出数字量。

🔥 TCRT5000 必须贴地,这是它最大的硬约束

离地太远,反射光变弱,会把白地误判成”黑”。所以循迹时模块要贴到 2~8mm(贴近峰值 2.5mm 最好)。调试口诀:没遇黑线时指示灯应长亮,一旦灯灭就是遇到黑线了。 安装高度不一致会导致各路灵敏度不同,要逐路调电位器。

💡 比喻

TCRT5000 就像用手电照地面看反光,离太远光散了,白地黑地都看不清,必须凑很近(几毫米)才分得清黑白。

选型小结:TCRT5000 离散红外便宜、易上手,但单点少、贴地要求严、抗环境光差,适合入门;集成灰度阵列(如 8 路 IIC 数字阵列、模拟量阵列)一致性好、分辨率高、部分带自标定,是电赛/智能车的主流;摄像头方案前瞻最远、能识别十字数字等复杂元素,但算力和调试成本高(留到《K230 视觉与通信协议》一篇展开)。2024/2025 电赛 H 题(自动行驶小车)的主流一等奖方案,就是 8 路灰度 + 惯导(主控 MSPM0G3507),不是电磁,这点后面会再强调。

灰度循迹的调参口诀(转向环)

偏差信号算出来后,喂给转向 PID。这里先给经验,完整的调参流程见《PID 调参实战》一篇。

💡 灰度转向调参铁律

  1. 先把基础速度降到很低(最大的 30~50%),Ki = Kd = 0,只加 Kp 直到能跟线;
  2. 升速,加 Kd 抑制振荡和过弯超调;
  3. Ki 在循迹转向环通常保持 0——转向是”位置跟随”,P 就能消静差,Ki 多余且容易引发振荡和过冲。

有个 Arduino 5 路 PD 循迹的实测起点可以参考:Kp=15、Ki=0、Kd=200、基础速度 200(PWM 0~255),5.14m 的赛道跑约 5 秒(来源)。

还有一条容易被忽视:偏差信号要先低通滤波再算微分。灰度原始读数有抖动,直接微分会被 Kd 放大,车就发抖。经验做法:

adc_err = 0.4 * err + 0.6 * adc_err;   // 一阶低通,新值权重 α≈0.3~0.5

注意滤波别太狠:α 太小会引入滞后,让弯道响应变慢——这是前瞻和滤波之间的又一个权衡。

🔥 灰度循迹必查的几个坑

  • 偏差极性 / 电机加减号接反 → 车不回正反而越跑越偏,直接冲出去。上电第一件事:手推车体,确认 error 符号和 left/right 混合方向匹配。
  • 用实验室/上次的阈值上赛场 → 误判。每次新场地重标或用 OTSU。
  • 十字不特判 → 偏差算飞,急打方向。
  • 偏差不滤波直接微分 → 高速发抖。
  • Ki 盲目加在转向环 → 过弯过冲、画龙。

二、循迹路线二:电磁——蒙眼跟着 20kHz 小调走

有些赛题(尤其智能车竞赛电磁组)不是白底黑线,而是在赛道中心埋一根漆包线,通上 20kHz、约 100mA 的正弦交变电流,产生交变磁场。车上用工字电感去感应这个磁场——离导线越近,感应出的电压越大。

🧩 比喻:蒙眼跟着哼歌的人走

地上那根线一直”哼着”20kHz 的歌,车的两只”耳朵”(左右电感)谁听得响就说明离谁近,靠左右耳的响度差,就能判断车往哪边偏了。

电磁循迹在电赛里用得不算多(主要是智能车竞赛电磁组的常态),但思路很经典,而且 TI MSPM0 芯片在这上面有独特优势,值得了解。

信号链:从 30mV 微弱交流到一个能用的数

单个 10mH 工字电感在离线上方 15~20cm 处感应出的原始信号,只有大约 30mV 的 20kHz 小交流——这么弱、这么高频,单片机根本没法直接读。必须走完整的四级信号链:

电赛小车电控 · 示意图
  1. LC 并联谐振选频:只放大 20kHz,抑制其它频率的干扰。10mH 电感配 6.8nF 电容,谐振频率约 19.3kHz(理论上 20kHz 对应 6.33nF,工程上取就近标称值 6.8nF,刻意略低一点适配寄生电容与公差)。
  2. 运放放大约 100 倍:把 30mV 抬到伏级(STM32 不超 3.3V)。
  3. 二极管检波(常用肖特基 BAT54S):把 20kHz 交流的包络/峰值整流成直流。
  4. RC 低通滤波:平滑后送 ADC。

💡 两个比喻串起来

  • LC 谐振选频像调收音机台:周围一堆杂音(各种磁场),LC 只把 20kHz 这个台调清楚放大,别的拧小声。
  • 检波整流像把抖动的水面读成水位:电感信号是 20kHz 高速抖动的交流(像水波纹),二极管 + 电容只看波纹有多高(包络),变成稳定的”水位”给单片机读。

核心算法:差比和 (L−R)/(L+R)

电感读到值之后,最关键、最该记住的算法就是差比和

$$\text{偏差} = \frac{L – R}{L + R}$$

为什么不直接用 L−R?因为整体场强会漂移——电流波动、不同赛道,会让所有电感的读数整体涨 10%,这时候位置没变,L−R 却跟着变了。而差比和的分子分母同比例缩放,比值不变,所以偏差只反映位置,不受幅值影响。

🧩 比喻:按比例打分,而不是绝对分

全班整体多考了 10 分(信号变强),但你比同桌高出的相对名次没变。除以总分看相对位置,就不会被整体高低带偏——这就是抗幅值漂移。

工程上还会先把每个通道归一化到 0~100(value/max×100),方便统一设阈值、识别环岛/十字等特殊元素。下面是真实电磁组代码里的双重差比和:

// 归一化(每通道 value/max*100)
Left_Adc  = (adc_deal_last[LEFT_1]*100) / adc_max[0];
Right_Adc = (adc_deal_last[RIGHT_1]*100) / adc_max[1];
// 双重差比和求偏差 AD_Bias
AD_Bias = ((Left_Adc - Middle_Adc)*100/(Left_Adc + Middle_Adc))
        - ((Right_Adc - Middle_Adc)*100/(Right_Adc + Middle_Adc));

⚠️ 标定别贪满幅

12 位 ADC(0~4096)实测一般不超 3600。标定时让电感最大值落在量程的 70~90%绝不要调到满幅——满幅会削顶饱和、丢分辨率,还给特殊元素的极值留不出空间。

电感布局:成绩的隐形变量

电感怎么摆,直接影响直道稳不稳、弯道灵不灵:

布局 特点
一字横排 简单、直道稳,但对弯道/特殊元素弱
八字向外 V 两端差异大,对弯道敏感,外侧可取极值识别元素
双横 + 竖直(双 T) 综合直弯,竖直电感对正上方磁场敏感,用于环岛/坡道识别
双 45° 对称性好,但要求左右运放/电感一致性极高,否则跑偏

布局要点:相邻电感间距 ≥2cm,避免互感串扰。

⚠️ 第 16 届电磁越野组的教训

木地板测试时编码器脉冲不稳,根因竟是机械连杆张力过大 + 电机振动没约束——软件再调参也救不回,这是硬件缺陷。该队还放弃了 45° 布局,因为硬件对称性不足、左右运放灵敏度不一致导致左右不对称。(来源)

一句话:机械固定、左右对称性、硬件一致性是地基。先把电感装牢、左右配对一致,再谈调参。

MSPM0 的独门优势:片上运放

前面那条信号链里,”运放放大”通常要在板外焊 LM358/OPA4377 之类的运放。但 TI MSPM0 芯片内部就自带运放——比如 MSPM0G3507 含 2 个 OPA,MSPM0L13xx 含 2 个零漂斩波运放 + 1 个 GPAMP,可编程增益(PGA)1~32 倍(单级不够可两级 cascade),输出能直连片上高速 12 位 ADC。

🧩 比喻

MSPM0 的片上运放就像芯片自带放大镜:本来要在板外焊运放当放大镜放大微弱信号,现在 MCU 内部就有 2 个高质量(零漂、抗噪)放大镜,信号不出芯片就放大完直接给 ADC——省料、省地方,还更干净。

这意味着电磁前端的选频信号放大可以做进芯片里,省掉外置运放,减 BOM、减焊接、减板上噪声。零漂斩波运放尤其适合这种微弱信号(低失调、低 1/f 噪声)。配置用 TI 的 SysConfig 图形化完成,官方有 opa_signal_chain_to_adc 等例程。不过要注意:MSPM0 跑纯电磁的完整开源还比较少(多为灰度),建议自己验证片内运放增益够不够,不够就级联或再外置一级。

❗ 别把 2024 电赛 H 题当电磁准备

H 题”自动行驶小车”限定用 MSPM0(常见 G3507),主流一等奖方案是 8 路灰度 + MPU6050 + 位置式 PID,不是电磁循迹。 电磁 + 差比和 + 工字电感主要是智能汽车竞赛电磁组的玩法,别搞混了准备方向。

电磁的几个高频坑

🔥 危险

  • 电机 PWM 频率别落在 20kHz 附近——开关噪声会被 LC 选频电路一并放大,污染信号。取 13~19kHz 错开,或给模拟前端独立 LDO 供电 + 屏蔽 + 物理远离电机。
  • 不做归一化直接用 L−R → 信号涨落时偏差漂移,不同赛道表现不一(差比和就是解决这个)。
  • 相邻电感太近(<2cm) → 互感串扰。
  • 舵机环加大 I → 积分饱和、弯道滞后画龙;微分项不滤波会放大 ADC 噪声抖舵。

💡 排查电磁链路:用示波器自下而上

很多”算法跑不动”其实是前端没出波形(电感虚焊/谐振没对/运放没起振)。正确顺序是:先确认信号源和示波器正常,把电感贴近电磁线量 10mH 引脚看有没有 20kHz 正弦,再逐级往后量放大、检波的输出。先怀疑硬件波形,别一上来怀疑代码。 (来源)

三、编码器:知道自己跑多快

循迹解决了”往哪走”,编码器解决”跑多快”。增量编码器输出 A、B 两路正交方波(相位差 90°),转得越快、脉冲越密;正转反转,A、B 的超前关系相反。

首选读法:定时器编码器模式

STM32 的定时器有个硬件”编码器模式”,强烈推荐用它:硬件自动加减计数、自动判方向,CPU 几乎零开销。

在 CubeMX 里把 TIMx 的 Encoder Mode 设成 “TI1 and TI2”(即 4 倍频,Encoder Mode 3),Prescaler = 0,ARR 设 65535(16 位)或 0xFFFFFFFF(32 位定时器,强烈推荐用 32 位的 TIM2/TIM5 防溢出)。

🧩 比喻:4 倍频就像数火车数得更细

只数车头(2 倍频 = A、B 各数一边)不如车头车尾都数(4 倍频 = A、B 的上升沿 + 下降沿都数)。同样一列火车,你数出 4 倍的格子,测得更精细。

读速度的代码非常简洁——每个固定控制周期(常用 5ms 或 10ms)读一次计数差值:

// CubeMX: TIMx -> Encoder Mode = "TI1 and TI2"(4倍频), Prescaler=0, ARR=65535/0xFFFFFFFF
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);   // 启动编码器接口

// 每个固定控制周期(5/10ms)调用一次,返回"单位周期脉冲数"作速度反馈
int16_t Encoder_GetSpeed(void) {
    static int16_t last = 0;
    int16_t cur   = (int16_t)__HAL_TIM_GET_COUNTER(&htim2); // 读计数
    int16_t delta = cur - last;   // signed 差值自动吃掉 65535/0 回绕(单周期增量<32767)
    last = cur;
    return delta;                 // 正/负即正反转,数值即速度反馈
}

关键心法:单位周期脉冲数,直接当速度用

很多新手卡在”怎么把脉冲换算成 m/s”。其实不用换算——那个 delta(单位周期内的脉冲数)本身就能直接当速度反馈喂进 PID。

💡 比喻:数电线杆

不用换算成公里每小时,就像你每 10 秒数一次窗外过了多少根电线杆,杆数本身就代表快慢——数越多车越快,直接拿这个数去踩油门/松油门就行。

如果报告里需要物理速度,再换算也不迟:

$$\text{RPM} = \frac{\Delta CNT \times (1000 / \text{period\_ms})}{\text{PPR} \times 4 \times \text{减速比}}$$

线速度再乘轮周长即可。采样周期是个权衡:太短,ΔCNT 太小,量化噪声大;太长,响应慢。智能车常用 5ms 或 10ms(弱一点的电机推荐 10ms)。

⚠️ 16 位定时器溢出,靠 signed 差值法自动解决

CNT 到 65535 会回绕到 0。代码里 int16_t delta = cur - last; 这一句,利用了无符号回绕的特性,即便跨越 65535/0 边界也能给出正确的带符号增量——只要单周期增量不超过 ±32767。 长距离里程累加要用 int32/int64。

还有一种读法是用 EXTI 外部中断在 A/B 边沿软件计数(M 法数脉冲 / T 法测间隔),更灵活,但占 CPU、高速易丢脉冲。结论:有硬件编码器接口时优先用定时器模式,EXTI 只在引脚/定时器不够时备选。

🔥 编码器必查

  • A/B 接反或电机正负接反 → 反馈方向和控制方向相反,闭环变正反馈,越控越偏。先开环验证:手转轮子正转,读数应增大。
  • dt 不固定(用 HAL_Delay 或主循环计时) → 速度 = Δ脉冲/dt 和积分项全失真,PID 怎么调都不稳。编码器采样和 PID 控制周期,要用同一个硬件定时中断严格定时。 中断里只采集 + 置标志,别做浮点/打印,计算放主循环。

四、IMU:给小车装上平衡感和方向感

IMU(常用 MPU6050)是六轴传感器:三轴加速度计 + 三轴陀螺仪。它能告诉小车两件事——姿态(Pitch/Roll,平衡车直立要用)和航向(Yaw,走直线锁方向要用)。但这两件事的难度天差地别。

姿态融合:互补滤波(必会)

加速度计和陀螺仪各有各的毛病,得互补着用:

  • 加速度计:能算出绝对的倾角,但噪声大、抖;
  • 陀螺仪:积分出来的角度很平滑,但会慢慢漂移。

🧩 比喻:开会时该多听谁

加速度计像个诚实但话痨手抖的人(说的方向对,但一直抖);陀螺仪像个嗓门稳但会慢慢跑题的人(短期稳,但越说越偏)。互补滤波就是:开会时多听那个稳的人(权重 0.98),偶尔用诚实的人把话题拉回正轨(0.02),综合出又稳又准的答案。

互补滤波公式:

$$\text{angle} = \alpha \times (\text{angle} + \text{gyro} \times dt) + (1-\alpha) \times \text{acc\_angle}$$

$\alpha$ 取 0.95~0.98(陀螺权重高)。加速度算角:$\text{pitch} = \text{atan2}(a_y, \sqrt{a_x^2 + a_z^2})$。代码:

// 一阶互补滤波:融合加速度(绝对但抖)与陀螺(平滑但漂),求 Pitch/Roll
// alpha=0.98(陀螺权重),dt=控制周期(秒)
float comp_pitch = 0;
void IMU_Update(float ax, float ay, float az, float gy, float dt) {
    float acc_pitch = atan2f(ay, sqrtf(ax*ax + az*az)) * 57.2958f; // 加速度算角(度)
    comp_pitch = 0.98f*(comp_pitch + gy*dt) + 0.02f*acc_pitch;     // 互补融合
}

💡 α 怎么取

α 越大越信任陀螺(更平滑但跟随慢),越小越信任加速度(更跟手但更抖)。小车机械振动大就适当加大 α 滤掉加速度噪声。一般直接取 0.95~0.98 就行。dt 千万记得用秒(0.005),别用 ms 或采样次数,否则融合比例完全错。

进阶可以上一阶卡尔曼滤波(平衡车经典 TKJ 版),效果更平滑,标准参数 Q_angle=0.001、Q_bias=0.003、R_measure=0.03、dt=0.005~0.01。它本质上就是”会自动调权重的互补滤波”——发现加速度计这会儿可信就多听它,发现它在抖就少听它。代价是计算量大。对资源受限的单片机,互补滤波通常已经够用,追求极致平滑再上卡尔曼。

💡 卡尔曼 = 自适应增益的互补滤波

两者本质相似。互补滤波省、收敛快、对小车足够;卡尔曼平滑但费算力。别一上来就堆卡尔曼,先把互补滤波吃透。”什么时候才值得上进阶滤波”,留到《进阶控制算法》一篇细聊。

Yaw(航向):六轴 IMU 的一个大坑

姿态(Pitch/Roll)有加速度计当绝对参考,能纠偏。但 Yaw 不行——水平面内的旋转,加速度计提供不了任何参考(重力始终竖直)。所以 Yaw 只能靠陀螺 Z 轴积分:yaw += gyro_z * dt必然随时间漂移。这是六轴 IMU 的固有局限(想要绝对航向,得上九轴加磁力计,或者用外部基准纠偏)。

🧩 比喻:闭着眼睛数步子估方向

陀螺仪测的是转得多快(角速度),要靠不停累加才知道现在朝哪。就像闭着眼睛靠数步子估自己面朝哪个方向,走久了一点点误差攒起来,最后以为朝北其实朝东北——这就是漂移。

对策有两步。

第一步:开机静止采零偏。 陀螺仪静止时本该读 0,但实际总有个固定的偏心读数。上电后让车绝对静止,采 N 次(常 500~2000,1000 次很常见)陀螺 Z 读数求平均当零偏,运行时每个读数都减掉它。

🧩 比喻

就像体重秤没人站时也显示 0.3kg。你先记住这个 0.3,以后每次读数都减掉它,才是真实角速度。

// 开机静止采陀螺 Z 轴零偏 + 运行时积分 Yaw(相对航向)
float gz_bias = 0, yaw = 0;
void Gyro_CalibZ(void) {            // 上电后车保持静止时调用
    float sum = 0;
    for (int i = 0; i < 1000; i++){ sum += MPU_GetGyroZ(); HAL_Delay(1); }
    gz_bias = sum / 1000.0f;        // 零偏 = 静止读数均值
}
void Yaw_Update(float dt){          // 每个控制周期调用
    float gz = MPU_GetGyroZ() - gz_bias;   // 去零偏
    yaw += gz * dt;                 // 积分得相对航向(短时漂移可忽略)
}

🔥 采零偏时车必须绝对静止

有人手扶、地面震动,都会把运动量误算进零偏,运行时车就持续缓慢自转/跑偏。而且采一次零偏也只能减小、不能消除漂移(温漂、随机游走仍在),长时间运行 Yaw 仍会缓慢漂。

第二步:走直线用”相对航向”,随时重置。 既然绝对 Yaw 会漂,那就不依赖它的绝对值——每段直线起步时把当前 yaw 清零当目标,航向误差 err = 0 − yaw 做 PID,输出叠加到左右轮差速:

// 走直线锁航向:起步时 yaw=0(清零当目标),err = 0 - yaw,差速纠偏
// out = Kp*err - Kd*gz;   pwm_L = base + out;   pwm_R = base - out;

注意上面那行的 D 项直接用了陀螺角速度 gz(对测量微分,比对误差微分更干净、抗扰)。转 90°:把目标航向加/减 90,闭环自动转到位后再清零,进入下一段直线。再高级一点可以做串级:外环用航向角锁朝向、内环用角速度求跟手——不过对入门小车,上面这套单环 PD 已经够稳。

🧩 比喻

不管罗盘(绝对 Yaw)准不准,起步时直接把现在朝向定为 0°当基准,之后谁偏了就往回掰——就像走路时盯着正前方一个目标走,而不是依赖会跑偏的指南针。

⚠️ 2024 电赛 H 题的航向实战

团队发现单靠 MPU6050 积分航向 + 编码器里程,长距离会累积误差,温漂还让 Yaw 越漂越大。解决:用实时温度算比例系数做动态温漂补偿,把漂移压到 ±1 以内,并用 8 路灰度纠偏兜底。有的队甚至换成维特智能陀螺仪(约 0.05° 精度)替代 MPU6050(约 2°/s 零漂)来提升转向精度。(来源)

一句话:纯惯性导航(IMU + 编码器)长距离必然累积误差,要么加外部基准(灰度/视觉)纠偏,要么温漂补偿 + 定期重置,别指望积分一路准到底。

🔥 IMU 必查

  • 零偏采集没静止 → 运行时持续自转。
  • 以为采一次零偏就永久消漂 → 温漂仍在,必须相对航向 + 重置。
  • dt 单位忘换成秒 → 融合/积分比例全错。
  • 方向环只用绝对 Yaw 单环且不重置 → 漂移被当成真实偏差,车被慢慢带偏。

五、把感官接到一起:分工、串级与切换

四样感官各管一摊,整车里它们这样配合:

电赛小车电控 · 示意图

记住两条分工原则(串级双环的落地代码与调参顺序,见《PID 进阶》和《状态机与整车软件》两篇):

  • 速度环(编码器反馈)用增量式 PID——只输出 ΔPWM、自带积分、无饱和切换冲击,适合连续调速;
  • 方向/航向环(灰度/电磁/IMU 反馈)用位置式 PID(实践中多为 PD)——适合”锁住某个朝向/位置”。

🧩 比喻:开车时手脚分工

方向环是你的手(管方向、外环、慢一点),速度环是你的脚(管油门、内环、快一点)。手脚分工又配合,车才能又快又直。

至于”什么路段用哪个感官”,核心思路是分工 + 兜底

  • 巡线主要靠灰度/电磁给偏差;
  • 直道锁航向用 IMU 的相对航向,防止灰度细微抖动累积成跑偏;
  • 十字/丢线时灰度偏差不可信,切到 IMU + 编码器”盲走”几格穿过去;
  • 一旦灰度长时间丢线,有外部基准(重新抓到的灰度线)就纠偏、没有就靠惯导兜底——绝不让 error 清零直冲。

✅ 这一篇你该带走的

  • 灰度循迹核心 = 加权位置法 + 工程四件套(现场标定 / 全丢线记忆方向 / 十字特判 / 前瞻高度妥协)。
  • 电磁循迹核心 = LC 选频→放大→检波→ADC 信号链 + 差比和 (L−R)/(L+R) 抗幅值漂移;MSPM0 片上运放能省外置前端。
  • 编码器 = 定时器编码器模式 4 倍频,单位周期脉冲数直接当速度,signed 差值吃掉溢出。
  • IMU = 加速度 + 陀螺互补滤波求姿态;Yaw 必漂,靠开机采零偏 + 相对航向 + 随时重置来锁直线。
  • 不管哪种感官,dt 要严格定时、极性先开环验证、参数现场重标并留约 20% 裕度——这是”实验室能跑、赛场也能跑”的分水岭。

感官齐了、反馈干净了,下一步就是真正让控制环动起来——从最基础的”把一个电机的速度调稳”开始,一次搞懂 P、I、D 各自在管什么。我们下一篇 PID 入门见。



本文由 云间辞 原创,发布于 HBZGC 的博客

转载请保留链接: https://cloudlay.cn/nuedc-car-05-sensing/

暂无评论

发送评论 编辑评论


|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇