智能车编码器里程计:从硬件连接到软件实现的精准测距指南

📅 2026/7/29 5:14:42
智能车编码器里程计:从硬件连接到软件实现的精准测距指南
1. 项目概述为什么智能车需要精确的“里程计”在智能车竞赛中无论是摄像头循迹、电磁导航还是其他组别让车“知道自己走了多远”都是一个基础且核心的需求。你可能遇到过这样的场景车子在直道上跑得飞快但一到需要精准停车的十字路口或坡道要么提前刹车冲过头要么减速过早停在了半路。这种“距离感”的缺失根源往往在于对实际行驶路程的估算不准。仅仅依赖程序里设定的“延时”或者基于电机PWM占空比的“经验速度”来估算距离在电池电压波动、轮胎打滑、地面摩擦系数变化时误差会急剧放大导致控制策略失效。这就是引入编码器进行实际路程计算的意义所在。它相当于给智能车装上了“高精度里程表”通过直接测量驱动轮的实际旋转圈数来反推车辆行驶的直线距离。这种方法不依赖于电机模型的理想假设抗干扰能力强是实现精准位置控制、速度闭环乃至复杂路径规划的数据基石。对于第十七届及后续各届智能车竞赛的参赛者而言掌握编码器测距是从“能跑”到“跑得准、停得稳”的关键一步。本文将基于常见的增量式光电编码器拆解从硬件连接到软件计算的全流程并分享在实际调车中积累的避坑经验。2. 核心思路与方案选型增量式编码器为何是首选在动手之前我们需要明确核心思路路程等于轮子转过的总弧长。对于一个半径为r的轮子其周长C 2 * π * r。如果编码器测得轮子转了N圈那么理论路程S N * C。但编码器通常不直接输出“圈数”而是输出随着轮子旋转产生的一系列脉冲信号。因此我们的核心任务就是精确统计这些脉冲的数量并将其转换为圈数最后计算路程。2.1 编码器类型选择增量式 vs. 绝对式市面上主要有两种编码器绝对式和增量式。绝对式编码器如热词中提到的AS5047P、MA732输出的是代表唯一位置的角度数字量如SPI或PWM信号。优点是不怕断电上电就知道确切位置。但通常价格较高且对于只需要计算相对位移和速度的智能车来说有些功能过剩。增量式编码器如E6B2-CWZ6C、常见的霍尔编码器电机输出的是两路相位差90度的方波脉冲A相和B相。每旋转一圈会输出固定数量的脉冲这个数量就是编码器的分辨率如500线、1000线。通过统计脉冲数可以知道转过的角度通过判断A、B相的相位关系可以知道转向。对于智能车竞赛增量式编码器是绝对的主流选择。原因很简单成本低廉、接口简单、完全满足测速和测距需求。绝大多数参赛队伍使用的“编码器电机”或外接的编码器模块都属于此类。因此本文将重点围绕增量式编码器的应用展开。2.2 单片机接口方案定时器编码器模式 vs. 外部中断如何读取增量式编码器的脉冲常见有两种软件方法外部中断法将编码器的A相信号接到单片机的某个外部中断引脚。每产生一个脉冲上升沿或下降沿就进入中断服务函数对一个计数器进行加或减操作通过判断此时B相的电平来决定方向。这种方法原理简单但在电机高速旋转时频繁的中断会大量占用CPU资源可能导致其他任务如图像处理卡顿且在高频下可能丢失脉冲。定时器编码器接口模式这是STM32、MSPM0等现代ARM单片机提供的硬件级解决方案。将编码器的A、B相分别接到定时器某个通道的输入引脚上如TIMx_CH1和TIMx_CH2。硬件会自动根据A、B相的边沿和相位关系更新定时器TIMx的计数器CNT值。正转时CNT递增反转时CNT递减。你只需要定期比如每10ms去读取这个CNT值就能知道这段时间内“净”的脉冲变化量。这是最推荐、最可靠的方法它不占用CPU中断资源计数准确几乎所有的智能车竞赛单片机都支持此功能。注意热词中提到了“STM32F103C8T6每个定时器都有编码器功能吗”。答案是STM32F103C8T6的高级定时器TIM1, TIM8和通用定时器TIM2, TIM3, TIM4都支持编码器模式。基本定时器TIM6, TIM7不支持。所以资源是足够的。方案确定我们将采用“增量式编码器 单片机定时器编码器接口模式”的方案。这是经过多届竞赛验证的、稳定且高效的黄金组合。3. 硬件连接与参数校准奠定精确计算的物理基础在写代码之前正确的硬件连接和系统参数测量是确保计算结果可信的前提。3.1 编码器与单片机连接以最常见的STM32F103和正交增量式编码器为例供电确保编码器或编码器电机的VCC和GND与单片机共地并接在稳定的电源上如5V或3.3V视编码器规格而定。电压波动会影响脉冲信号质量。信号线连接将编码器的A相输出线连接到单片机定时器的通道1引脚如TIM2_CH1 - PA0B相连接到通道2引脚如TIM2_CH2 - PA1。具体对应关系需要查阅你所使用单片机的《数据手册》或《引脚定义表》。上拉电阻如果编码器是开源输出即OC门输出则需要在A、B相信号线上各接一个上拉电阻通常4.7kΩ~10kΩ到VCC以确保信号有明确的高电平。很多集成了编码器的电机模块或编码器模块已经内置了上拉电阻无需额外添加。3.2 关键参数测量与计算这里有两个必须亲自测量并校准的参数它们直接决定了路程计算的精度车轮实际半径r(单位米)不要相信理论值用游标卡尺多次测量轮胎外侧直径最好给轮胎装上并充气至比赛常用气压取平均值后除以2得到半径。例如测得直径D 6.50 cm则r D / 2 0.0325 m。编码器线数PulsePerRevolution(PPR)这是编码器旋转一圈所产生的脉冲数。注意这里存在一个极易混淆的倍数关系。物理线数指编码器码盘上刻有的光栅数量。例如一个“500线”的编码器码盘上有500条刻线。单相计数如果只在A相上升沿计数一圈可得到500个脉冲。四倍频计数这是定时器编码器模式的标准工作方式。硬件会在A、B相的每个上升沿和下降沿都进行计数。因此实际每圈产生的可计数脉冲数是物理线数的4倍。即PPR 4 * 物理线数。如何确认最可靠的方法是查看编码器或电机手册。如果手册上写“分辨率 500脉冲/转”通常指的是单相计数。而我们采用四倍频模式所以程序中使用的PPR应该是2000。如果不确定可以写一个简单的测试程序手动将轮子旋转正好一圈读取定时器CNT的变化值这个变化值的绝对值就是你的系统实际的PPR。计算公式推导轮子周长C 2 * π * r每个脉冲对应的轮子行进距离米/脉冲DistancePerPulse C / PPR一段时间内定时器计数器变化值为ΔCNT有符号数正代表前进负代表后退。则该段时间内实际路程米ΔS ΔCNT * DistancePerPulse实操心得r和PPR的微小误差会在长距离累积中放大。务必精确测量。特别是轮胎使用一段时间后磨损半径会变小赛前最好重新校准。4. 软件实现从脉冲计数到路程输出我们以STM32 HAL库为例展示完整的软件实现流程。其他平台如MSPM0、ESP32思路完全一致只是库函数名称不同。4.1 定时器编码器模式初始化// 以TIM2为例编码器接在PA0, PA1 TIM_Encoder_InitTypeDef Encoder_Config {0}; TIM_MasterConfigTypeDef Master_Config {0}; // 1. 开启时钟 __HAL_RCC_TIM2_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); // 2. 配置GPIO为复用上拉模式内部上拉即可通常无需外部电阻 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_0 | GPIO_PIN_1; GPIO_InitStruct.Mode GPIO_MODE_AF_INPUT; // 复用输入 GPIO_InitStruct.Pull GPIO_PULLUP; // 上拉 HAL_GPIO_Init(GPIOA, GPIO_InitStruct); // 3. 配置定时器编码器模式 Encoder_Config.EncoderMode TIM_ENCODERMODE_TI12; // 在TI1和TI2边沿都计数实现4倍频 Encoder_Config.IC1Polarity TIM_ICPOLARITY_RISING; // IC1对应TI1A相 Encoder_Config.IC1Selection TIM_ICSELECTION_DIRECTTI; // 映射到TI1 Encoder_InitStruct.IC1Filter 6; // 输入滤波器可抑制毛刺根据信号质量调整(0-15) Encoder_Config.IC2Polarity TIM_ICPOLARITY_RISING; // IC2对应TI2B相 Encoder_Config.IC2Selection TIM_ICSELECTION_DIRECTTI; Encoder_InitStruct.IC2Filter 6; if (HAL_TIM_Encoder_Init(htim2, Encoder_Config) ! HAL_OK) { Error_Handler(); } // 4. 可选配置定时器溢出值防止长时间运行后CNT溢出归零影响计算 // 对于16位定时器最大值65535。如果PPR很大或转速很快CNT可能溢出。 // 可以将定时器设置为32位如果支持或者使用有符号长整型来处理溢出。 // 此处我们使用一个技巧将定时器周期设为0xFFFF模式为向上计数但启动后不产生更新中断。 // 这样CNT会在0-65535之间循环我们通过处理差值来应对溢出。 htim2.Init.Period 0xFFFF; htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; if (HAL_TIM_Base_Init(htim2) ! HAL_OK) { Error_Handler(); } // 5. 启动编码器接口 HAL_TIM_Encoder_Start(htim2, TIM_CHANNEL_ALL);4.2 路程计算模块实现我们需要一个模块来定期采样CNT值并计算累积路程。这里的关键是处理定时器计数器的溢出对于16位定时器。// encoder_odometer.c #include encoder_odometer.h // 全局变量定义 static int32_t g_encoder_total_ticks 0; // 累积的脉冲计数有符号已处理溢出 static int16_t g_encoder_last_cnt 0; // 上一次读取的CNT值 static float g_distance_per_pulse 0.0f; // 每个脉冲对应的距离米 static float g_total_distance_m 0.0f; // 累积总路程米 // 初始化函数需传入车轮半径(m)和编码器物理线数单相 void Odometer_Init(float wheel_radius_m, uint16_t encoder_line_count) { // 计算四倍频后的每转脉冲数 uint32_t pulse_per_rev (uint32_t)encoder_line_count * 4; // 计算每个脉冲对应的距离 float wheel_circumference 2.0f * 3.1415926535f * wheel_radius_m; g_distance_per_pulse wheel_circumference / (float)pulse_per_rev; // 初始化计数器 g_encoder_last_cnt (int16_t)(__HAL_TIM_GET_COUNTER(htim2)); // 假设htim2是全局的编码器定时器句柄 g_encoder_total_ticks (int32_t)g_encoder_last_cnt; g_total_distance_m 0.0f; } // 更新函数需要被周期性调用例如放在1ms或10ms定时器中断中 void Odometer_Update(void) { int16_t current_cnt (int16_t)(__HAL_TIM_GET_COUNTER(htim2)); int16_t delta_cnt_raw current_cnt - g_encoder_last_cnt; // **核心处理16位定时器溢出** // 因为CNT是16位无符号数0-65535但delta_cnt_raw被当作有符号16位解释。 // 如果真实变化量超过了±32767说明发生了溢出/下溢需要修正。 // 例如上次CNT65000这次CNT1000因为从65535溢出到了0。 // delta_cnt_raw 1000 - 65000 -64000 (在int16_t看来是错误的小正数)。 // 我们需要识别并修正这种跨越65535/0边界的情况。 int32_t delta_cnt_corrected delta_cnt_raw; // 修正正向溢出CNT从接近65535变为一个小的数 if (delta_cnt_raw 32767) { delta_cnt_corrected delta_cnt_raw - 65536; // 等价于 delta_cnt_raw (是一个大正数) - 65536 } // 修正负向下溢CNT从小的数变为接近65535 else if (delta_cnt_raw -32768) { // 实际上int16_t不会小于-32768这里用-30000为例更安全 // 更稳健的判断如果变化量的绝对值异常大30000且方向与预期速度方向相反则可能是下溢 // 一个简单实用的方法是如果变化量绝对值大于一个阈值如60000就认为发生了边界穿越 if (delta_cnt_raw 0 (-delta_cnt_raw) 60000) { // 例如上次CNT1000这次CNT65000从0下溢到65535然后减到65000。 // delta_cnt_raw 65000 - 1000 64000在int16_t看来是负值。 // 我们需要将其修正为 64000 - 65536 -1536? 不对实际是正向走了64000个脉冲。 // 所以逻辑是如果delta_cnt_raw是很大的正数60000它本应是负的大变化修正为 delta - 65536 // 如果delta_cnt_raw是很大的负数-60000它本应是正的大变化修正为 delta 65536 // 但int16_t表示不了64000它会被解释为-1536。所以我们直接判断原始CNT的差值。 } } // 简化方案对于大多数智能车应用速度不会快到让两次采样间脉冲变化超过32767。 // 更通用的方法是使用32位变量来存储CNT的差值。 // 推荐以下更简洁通用的处理方式 { // 将current_cnt和last_cnt转换为32位有符号数直接相减。 // 因为CNT是16位无符号但我们可以将其视为0-65535的循环。 int32_t cur32 current_cnt; int32_t last32 g_encoder_last_cnt; // 假设变化量在 -32768 ~ 32767 之间是合理的超过这个范围意味着发生了环绕。 // 计算原始差值 int32_t diff cur32 - last32; // 如果差值大于32767说明发生了负向环绕如65000-1000实际是1000但计算得-64000 if (diff 32767) { diff - 65536; } // 如果差值小于-32768说明发生了正向环绕如1000-65000实际是64000但计算得64000这里需要小心 // 实际上对于uint16_t: 1000到65000cur-last64000大于32767会被上一条规则修正为-1536这是错误的。 // 因此需要结合速度方向进行更复杂的判断。一个工程上常用的“偷懒”但有效的方法是 // 使用一个32位的扩展计数器在每次更新时记录CNT的“扩展值”。 static int32_t encoder_extend 0; diff cur32 - last32; if (diff -32768) { // 例如 last65000, cur1000, diff-64000 ( -32768) // 这其实是正向转动diff应修正为 diff 65536 1536 diff 65536; } else if (diff 32767) { // 例如 last1000, cur65000, diff64000 ( 32767) // 这其实是反向转动diff应修正为 diff - 65536 -1536 diff - 65536; } delta_cnt_corrected diff; } // 累积总脉冲数 g_encoder_total_ticks delta_cnt_corrected; // 计算本次采样周期内的路程增量并累加 float delta_distance (float)delta_cnt_corrected * g_distance_per_pulse; g_total_distance_m delta_distance; // 更新上一次的CNT值 g_encoder_last_cnt current_cnt; } // 获取接口 float Odometer_GetTotalDistanceM(void) { return g_total_distance_m; } int32_t Odometer_GetTotalTicks(void) { return g_encoder_total_ticks; } float Odometer_GetSpeedMps(float sample_time_s) { // sample_time_s是Odometer_Update的调用周期例如0.01s10ms // 注意这是平均速度。更精确的做法是记录最近几次的delta_cnt_corrected做平滑。 static float last_distance 0.0f; float current_distance g_total_distance_m; float speed (current_distance - last_distance) / sample_time_s; last_distance current_distance; return speed; } // 重置里程计 void Odometer_Reset(void) { g_encoder_total_ticks 0; g_total_distance_m 0.0f; g_encoder_last_cnt (int16_t)(__HAL_TIM_GET_COUNTER(htim2)); }代码关键点解析溢出处理Odometer_Update函数中的溢出处理逻辑是里程计稳定的核心。它通过判断两次CNT读数的差值是否超过16位有符号数的半程±32768来判定是否发生了计数器环绕并进行相应的修正±65536。这是处理16位定时器溢出的标准方法。浮点数与效率路程计算使用了浮点数。对于M3/M4内核的单片机硬件FPU可以高效处理。如果没有FPU如某些M0内核频繁的浮点运算会成为负担。此时可以采用“定点数”运算来优化将g_distance_per_pulse放大若干倍如2^10 1024倍存储为整数计算路程时先使用整数乘加最后再缩小倍数。例如距离 (累计脉冲数 * 每脉冲距离定点数) 10。采样周期Odometer_Update的调用频率决定了路程和速度更新的粒度。通常放在一个稳定的定时器中断中如1ms、5ms或10ms。周期越短速度计算越实时但计算负荷也稍大。10ms是一个常用的折中选择。5. 系统集成与调试让里程计真正为控制服务里程计模块不能孤立工作它需要集成到整个智能车控制系统中并为速度控制环、位置控制环提供反馈。5.1 与速度控制环集成智能车常用的PID速度控制其反馈值就需要来自编码器。// 在速度控制任务中例如每10ms执行一次 void SpeedControlTask(void) { // 1. 更新里程计获取最新脉冲计数 Odometer_Update(); // 2. 计算当前实际速度米/秒 float actual_speed_mps Odometer_GetSpeedMps(0.010f); // 假设10ms调用一次 // 3. 获取目标速度由路径规划或遥控器给出 float target_speed_mps get_target_speed(); // 4. 进行PID计算 float pwm_output PID_Calculate(speed_pid, target_speed_mps, actual_speed_mps); // 5. 输出PWM控制电机 set_motor_pwm(pwm_output); }这里Odometer_GetSpeedMps提供的速度反馈比单纯用电池电压估算或开环控制要准确得多能有效抑制负载变化、坡道带来的速度波动。5.2 用于精准位置控制在需要定点停车如坡道停车、车库停车时里程计是核心传感器。// 假设需要从当前位置前进0.5米后停车 void GoForwardAndStop(float distance_m) { Odometer_Reset(); // 重置里程计从0开始计数 float target_distance distance_m; float current_distance 0.0f; // 使用一个位置式PID或简单的P控制 while (current_distance target_distance) { Odometer_Update(); current_distance Odometer_GetTotalDistanceM(); // 计算剩余距离越接近目标速度设定值越小 float remaining target_distance - current_distance; float target_speed remaining * Kp; // 一个简单的P控制器 // 限制最大和最小速度 target_speed constrain(target_speed, 0.0f, MAX_APPROACH_SPEED); // 调用速度控制环目标速度逐渐趋于0 set_target_speed_for_control_loop(target_speed); // 短延时避免循环过快 HAL_Delay(5); } // 到达目标停车 set_motor_pwm(0); }5.3 现场调试与校准技巧验证脉冲计数将车抬起手动旋转车轮一整圈通过串口打印Odometer_GetTotalTicks()的变化值。这个值应该等于你计算或测量出的PPR四倍频后。如果有偏差检查编码器线数设置和接线。验证距离计算在地面上画一条起点线让车子从静止开始控制其匀速前进一段距离例如1米然后停车。测量实际停止位置与目标位置起点线1米的差距。通过串口打印Odometer_GetTotalDistanceM()的输出。调整wheel_radius_m参数进行校准。例如编码器显示走了1.05米实际走了1.00米说明计算的半径偏大了应等比例减小wheel_radius_m。处理轮胎打滑与空转这是里程计的根本局限。编码器测量的是轮子转过的角度如果轮子打滑原地空转或滑移刹车抱死测量值将与实际路程严重不符。在智能车竞赛中直道加速和弯道急刹容易引发打滑。 mitigation策略包括机械调整确保轮胎抓地力良好差速适当。控制策略避免过猛的加速和刹车采用平滑的速度规划。传感器融合高级对于要求极高的组别可以结合IMU惯性测量单元的数据进行修正。当检测到车轮转速与IMU测得的加速度/角速度不匹配时可以推断出打滑并暂时信任IMU的数据或进入一种保守控制模式。电源噪声干扰电机尤其是碳刷电机运行时会产生强烈的电磁噪声可能干扰编码器信号导致计数错误。确保编码器信号线使用双绞线并远离电机电源线。在软件上可以适当开启定时器输入通道的滤波器如上面初始化代码中的IC1Filter滤除高频毛刺。6. 常见问题与深度排查指南即使按照上述步骤操作在实际调试中仍会遇到各种问题。下面是一个常见问题速查表帮助你快速定位。问题现象可能原因排查步骤与解决方案计数完全不动CNT值不变1. 硬件接线错误A/B相接反、电源未接。2. 单片机定时器未正确配置为编码器模式。3. 编码器损坏。1. 用示波器或逻辑分析仪检查编码器A、B相是否有脉冲输出。如果没有检查编码器供电。2. 检查单片机引脚配置是否正确特别是GPIO是否配置为复用模式。3. 检查HAL_TIM_Encoder_Start是否成功调用。计数方向反了前进时CNT减少A、B相序接反。交换单片机端A、B相的接线或者在软件初始化时交换TIM_ENCODERMODE_TI1和TIM_ENCODERMODE_TI2的设置将EncoderMode从TIM_ENCODERMODE_TI12改为另一种模式或交换IC1和IC2的引脚映射。低速时计数准确高速时计数丢失数值偏小1. 单片机采样频率跟不上。2. 定时器计数器溢出。3. 信号边沿抖动噪声。1. 确保Odometer_Update的调用频率足够高。计算最大转速下每毫秒的脉冲数确保采样周期内脉冲数不会超过处理能力。2. 检查并完善溢出处理逻辑如本文代码所示。考虑使用32位定时器。3. 增加定时器输入滤波器的参数ICxFilter但不宜过大否则会影响高速响应。车子静止时CNT值自己缓慢变化漂移1. 信号线受到严重电磁干扰。2. 编码器本身质量差输出波形有抖动。3. 上拉电阻阻值不合适。1. 优化布线将编码器信号线与电机线、电源线分开必要时使用屏蔽线。2. 更换质量更好的编码器或电机。3. 尝试调整上拉电阻的阻值通常在4.7kΩ-10kΩ或用示波器观察静止时的信号是否干净。计算出的距离总是比实际距离长或短一个固定比例车轮半径r或编码器线数PPR参数设置不准确。执行距离校准让车走一段已知精确距离如2米记录编码器累计脉冲数N。理论脉冲数N_theory 距离 / (2*π*r) * PPR。调整r或PPR使N接近N_theory。通常校准r更方便。在特定速度下计数不稳定时快时慢可能遇到了机械共振点或者PID速度环振荡导致电机转速波动进而影响编码器读数。1. 观察电机实际转速是否平稳。2. 调整速度环PID参数避免振荡。3. 在里程计的速度计算环节加入低通滤波例如current_speed 0.8 * last_speed 0.2 * new_raw_speed平滑输出。进阶排查工具示波器/逻辑分析仪这是最强大的工具。直接观察A、B相信号的波形看是否规整的90度相位差方波有无毛刺、畸变。测量脉冲频率是否与当前转速匹配。串口数据绘图将Odometer_GetSpeedMps()计算出的实时速度通过串口发送到上位机如SerialPlot、匿名上位机、VOFA等绘制速度曲线。可以非常直观地看到速度控制的平稳性、响应性以及是否存在周期性波动或噪声。7. 不同平台与场景的适配要点热词中提到了多种平台MSPM0G3507, ESP32-S3和编码器类型磁编码器AS5047P霍尔编码器这里简要说明关键适配点。MSPM0G3507TI的M0内核单片机。其编码器接口配置与STM32类似也是通过定时器单元实现。需要查阅TI的DriverLib或SDK找到对应的定时器编码器计数功能配置函数。注意其GPIO复用功能和时钟配置的差异。ESP32-S3乐鑫的芯片通常使用Arduino框架或ESP-IDF。在Arduino中有专门的Encoder库如ESP32Encoder库可以方便地使用PCNT脉冲计数外设或软件中断来实现编码器计数。在ESP-IDF中可以直接配置PCNT外设它能硬件计数并处理正交信号性能更好。磁编码器AS5047P/MA732这类是绝对式编码器输出的是数字角度值通过SPI、PWM等。用法完全不同你需要通过SPI读取其角度寄存器然后计算两次读取的角度差值再根据差值和时间计算角速度最后换算成线速度。其优点是无须担心上电初始位置和溢出问题但通信速率和抗干扰能力需要考量。霍尔编码器电机很多智能车用的直流电机内置了霍尔传感器作为简易编码器。其原理与光电增量式编码器类似但通常分辨率很低如每转几个到几十个脉冲。使用时同样可以接入定时器的编码器接口但要注意其输出可能不是标准的正交方波可能需要使用单相计数模式并且由于分辨率低低速测速精度会很差。无论平台和传感器如何变化核心思路不变精确测量轮子的旋转角度变化结合轮子几何参数计算出线位移和线速度。抓住这个本质就能在各种硬件组合中游刃有余。编码器里程计是智能车实现自主、精准运动的基础。它提供的反馈使得闭环控制成为可能从简单的速度稳定到复杂的轨迹跟踪都离不开它。调试里程计的过程也是深入理解嵌入式系统硬件接口、信号处理和控制理论的过程。当你看到小车能够严格按照你设定的距离精准停下时那种对系统拥有精确掌控力的成就感正是智能车竞赛的魅力之一。最后分享一个小心得在最终比赛前务必在不同地面如防滑地毯、光滑木板上重复进行距离校准测试因为摩擦系数会影响轮胎的有效滚动半径轻微的打滑或形变赛前针对比赛场地做最后的参数微调往往能带来意想不到的精度提升。