简介:围绕STM32实现电机按指定距离精准行驶的完整工程,面向已具备单片机基础、希望打通编码器测速与PID闭环控制的学习者与嵌入式开发者。工程以STM32F4系列HAL库为底层,将编码器脉冲采集、位置偏差计算、PID运算与PWM占空比输出串联成闭环链路,可用于小车定距行驶、丝杠定位等场景。压缩包共236个文件,其中h头文件121个、c源文件107个,主要为HAL外设驱动与用户控制逻辑;另含Keil工程文件(uvprojx/uvoptx)、启动汇编文件、批处理脚本与可直接烧录的hex,包体约1.65MB,目录完整、开箱可编译。目前已有1023人学习。读者可从中获得编码器接口与中断计数写法、Kp/Ki/Kd整定与采样周期设置思路、定时器PWM调速实现,以及超调、振荡、静差等常见问题的排查线索。
1. 从"走够 2 米"这个需求说起:开环定时为什么必然跑偏
让一台小车不多不少正好走完 2 米,是 stm32 控制行驶指定距离这类题目里最容易被低估的部分。多数人的第一版方案是:给电机一个固定占空比,延时 3 秒,停车。这套做法在实验室新电池、空载、瓷砖地面上能跑到 2.05 米,看起来"成了",然后换成木地板或者电池掉到 7.2V,同一个 3 秒就变成 1.72 米到 2.38 米的随机分布。距离要求的本质不是"控制时间",而是"控制位移",必须引入反馈,也就是编码器加 PID 闭环。
这套方案面向三类人:正在做单片机课程设计、需要交一台能定点停车小车的学生;要做毕业设计、需要在答辩现场演示误差指标的人;以及工作中要快速搭一台样机机构、验证运动控制逻辑的工程师。读完之后你应该能自己算清楚脉冲当量、定下定时器和外设的资源分配、把串级 PID 写进中断、并且拿一张误差表说服验收老师。
2. 需求拆解与技术选型:编码器、驱动与两张 PID 控制环
2.1 把"走 2 米"翻译成脉冲数和误差带
方案能不能落地,第一步看的是指标是否可量化。"行驶指定距离"这句话里其实藏了三个参数:目标距离(比如 2000mm)、允许误差(工程上通常取满量程的 1% 左右,即 ±20mm)、重复精度(同一目标连跑五次,最大值与最小值之差)。这三项定不下来,后面 PID 调得再好也是玄学。
把距离换算成编码器脉冲,是整个方案的地基。以常见的带霍尔编码器的直流减速电机为例:编码器本体 11 线,减速比 1:30,AB 两相在定时器编码器接口模式下按四倍频计数,那么输出轴转一圈的计数是 11×4×30 = 1320 个脉冲。轮径 65mm,周长约 204.2mm,于是每个脉冲对应的位移是 204.2 / 1320 ≈ 0.155mm。2 米对应的脉冲数就是 2000 / 0.155 ≈ 12903。这个数字必须自己算一遍并写进代码的宏定义里,不能"大概估一个",否则误差带根本压不住。
| 参数 | 取值 | 说明 |
|---|---|---|
| 编码器线数 | 11 线 | 电机尾部霍尔编码器 |
| 减速比 | 1:30 | 输出轴转速约为电机轴 1/30 |
| 倍频方式 | 四倍频 | TIM 编码器模式 TI12 |
| 轮径 | 65mm | 实测而非标称,磨损后需重标 |
| 脉冲当量 | ≈0.155mm/pulse | 周长 ÷ 每圈计数 |
| 2m 对应脉冲 | ≈12903 | 目标值写入位置环 |
提示:脉冲当量不要用理论值一锤定音。装好轮子、加载整车重量之后,用手推车走 1 米,读编码器累计值,反算真实当量,这一步叫里程标定,能把 3%~5% 的系统误差直接消掉。
2.2 主控与外设选型:定时器和 IO 怎么分配
主控选 STM32F103 系列是这类题目性价比最高的选择:主频 72MHz,定时器资源足够,标准库或 HAL 库资料都多。关键不是选哪颗芯片,而是外设资源怎么切分才不打架,这一步翻车的人特别多。
我的分配习惯是这样:TIM3 和 TIM4 工作在编码器接口模式,分别接左右轮的 AB 相,只做计数不做中断;TIM1 的 CH1、CH2 输出两路 20kHz 的 PWM 给电机驱动;TIM2 做 10ms 的固定周期中断,作为整个控制系统的时基。电机驱动用常见的双 H 桥模块,注意它需要三路信号:两路方向加一路 PWM,方向引脚切换时要有死区,否则换向瞬间电流冲击会把驱动打保护。
PWM 频率选 20kHz 是有原因的:低于 8kHz 电机会发出明显啸叫,而且电流纹波大、低速爬行严重;高于 25kHz 又会让驱动模块的开关损耗上升、发热明显。20kHz 刚好在人耳听觉上限附近,实测低速平稳性也最好。占空比分辨率方面,72MHz 经预分频到 72kHz 计数、周期设为 3600,分辨率就是 1/3600,对低速微调足够用。
/* TIM1_CH1/CH2 输出 20kHz PWM,供左右轮调速 */ static void Motor_PWM_Init(void) { GPIO_InitTypeDef gpio = {0}; TIM_TimeBaseInitTypeDef tb = {0}; TIM_OCInitTypeDef oc = {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1, ENABLE); gpio.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; /* PA8->CH1, PA9->CH2 */ gpio.GPIO_Mode = GPIO_Mode_AF_PP; /* 复用推挽输出 */ gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio); tb.TIM_Period = 3599; /* 72MHz/3600 = 20kHz */ tb.TIM_Prescaler = 0; tb.TIM_ClockDivision = TIM_CKD_DIV1; tb.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, &tb); oc.TIM_OCMode = TIM_OCMode_PWM1; oc.TIM_OutputState = TIM_OutputState_Enable; oc.TIM_Pulse = 0; /* 上电占空比为 0,防窜车 */ oc.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM1, &oc); TIM_OC2Init(TIM1, &oc); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_CtrlPWMOutputs(TIM1, ENABLE); /* 高级定时器必须开主输出 */ TIM_Cmd(TIM1, ENABLE); }这段代码里有两个容易被忽略的点:一是TIM_CtrlPWMOutputs,TIM1 属于高级定时器,不调用这一句引脚上永远没有波形,很多人卡在这里以为是硬件坏了;二是上电时TIM_Pulse必须置 0,否则复位瞬间电机会全速冲出去,把摆在桌边的车直接送下地。方向控制则用两个普通 GPIO,配合一个Motor_Set(left, right)函数封装,正数正转、负数反转、0 刹车,上层控制代码只跟这个函数打交道。
2.3 控制律选型:单环、串级与位置式增量式的取舍
只用速度环能不能走准 2 米?能,但很难。速度环的稳态误差会让小车"一直差一点",靠时间累积出来的距离误差随路况漂移。所以正确的结构是串级:外环是位置环(输入目标脉冲数,输出目标速度),内环是速度环(输入目标速度,输出 PWM 占空比)。外环负责"还差多少",内环负责"现在跑多快",两层职责分开,参数也好整定。
具体到实现形式,位置环用位置式 PID,因为它需要保留积分项来消除稳态距离误差;速度环用增量式 PID 更合适,增量式输出的是占空比的"变化量",天然带抗积分饱和特性,换向和突加负载时不容易产生大超调。这个组合是我试过最省调参时间的一套。
还有一点值得提前说:微分项要做"微分先行",也就是对测量值微分而不是对误差微分。目标距离是阶跃信号,直接对误差微分会得到一个巨大的尖峰,输出瞬间打满,小车会窜一下。改成对反馈量微分,冲击就没了。这个改动一行代码,效果差别巨大。
3. 总体架构设计:三层闭环与五个模块的职责划分
3.1 分层结构:决策层、控制层、执行层
把代码按"谁负责什么"切开,是这套方案能不能改、能不能讲清楚的关键。我一般分成三层。执行层最贴近硬件,包含电机 PWM 输出、方向控制、编码器读数、按键与显示,这一层不含任何控制逻辑,只做"给定值到物理量"的转换。控制层是核心,跑 PID 运算,包含位置环、速度环、限速曲线和停车判据。决策层管整个流程,是一个状态机:什么时候启动、什么时候减速、什么时候判定到位、异常时怎么停。
五模块的划分对应下来就是:encoder(测距)、motor(驱动)、pid(算法)、motion(状态机与轨迹)、debug(串口数据上报)。模块之间只通过明确的接口函数通信,比如motion只能调用Motor_Set(),绝不能直接写TIM_SetCompare1()。这条纪律看着啰嗦,但当你要在答辩前一夜改控制参数时,它会救你。
整个数据流是单向的:编码器 → 里程计算 → 位置环 → 目标速度 → 速度环 → PWM → 电机 → 编码器。闭环里任何一处出现符号错误,表现出来都是"车往后倒"或者"越跑越快",所以第一次联调务必把轮子架空,用手转轮子看反馈符号对不对。
3.2 任务时序:10ms 速度环与 20ms 位置环怎么排
时序设计不合理,是"参数怎么调都抖"的常见根源。我的做法是:TIM2 每 10ms 进一次中断,在中断里读取编码器增量、计算当前速度、跑速度环 PID、更新 PWM;位置环不放在这个中断里,而是在主循环里靠一个计数器每两次速度环执行一次,也就是 20ms 一次。
为什么位置环要慢?因为位置环的输出是目标速度,它的变化本来就该平滑;跑太快会和速度环抢节奏,形成两个环互相追着振荡。速度环 10ms 对应 100Hz 的带宽,对一台几十瓦的小车足够了。另外,中断里只做算术,绝不放串口打印和浮点除法以外的东西,printf塞进 10ms 中断是标准的翻车操作,一条 115200 的串口日志能占掉好几毫秒。
/* TIM2 中断:10ms 控制节拍 */ void TIM2_IRQHandler(void) { static uint8_t div = 0; if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); /* 1. 采样:本周期脉冲增量 -> 速度(mm/s) */ g_enc_delta = Encoder_ReadDelta(); g_distance += g_enc_delta * PULSE_TO_MM; /* 累计里程 */ g_speed = g_enc_delta * PULSE_TO_MM / 0.01f; /* mm/s */ /* 2. 位置环:20ms 跑一次,输出目标速度 */ if (++div >= 2) { div = 0; if (g_state == RUN) g_target_speed = Position_Loop(g_target_dist - g_distance); } /* 3. 速度环:每 10ms 跑一次,输出 PWM */ float pwm = Speed_Loop(g_target_speed - g_speed); Motor_Set(pwm, pwm); /* 差速模型下左右同值 */ /* 4. 到位判定 */ Motion_CheckArrive(); } }这段代码体现了三个设计决定:里程累计用浮点毫米而不是脉冲数,方便上层直接和"2000mm"比较;位置环的节拍用软件分频实现,不额外占用定时器;Motor_Set接收的是 -1000~1000 的归一化值而不是原始比较寄存器值,把硬件细节彻底挡在控制层之外。参数上,PULSE_TO_MM就是前面算出的 0.155,改成实测标定值即可。
3.3 状态机:待机、加速、匀速、减速、到位、制动
没有状态机的小车,行为是"能跑但不可解释";有状态机之后,每一个时刻它在干什么都能一句话说清。我定义六个状态:IDLE待机(PWM 为 0,里程清零)、ACCEL加速(目标速度斜坡上升到巡航速度)、CRUISE匀速(保持巡航)、DECEL减速(进入减速区,按曲线降速)、BRAKE制动(短时反向 PWM 抵消惯性)、ARRIVED到位(锁定,等待下一次触发)。
状态迁移的判据要写死:从ACCEL到CRUISE看当前速度是否达到巡航速度的 95%;从CRUISE到DECEL看剩余距离是否小于减速阈值;从DECEL到BRAKE看剩余距离是否小于停车阈值且速度低于某个门限;BRAKE持续固定时间(比如 80ms)后进ARRIVED。这些阈值全是可调参数,集中放在一个config.h里,答辩现场要改也就是改几个宏。
加分项是加一个"斜坡启动":目标速度不是从 0 直接跳到 400mm/s,而是每个控制周期增加固定步长,比如每 10ms 涨 20mm/s,这样 0.2 秒后到巡航速度,起步不打滑。打滑是距离误差最主要的来源之一,尤其在光滑地面,斜坡启动能把这个误差压掉一大半。
4. 关键模块实现:编码器测距、串级 PID 与定点停车
4.1 编码器四倍频与里程标定
编码器接法定成败。STM32 的定时器有专门的编码器接口模式,把 TIMx 配置成TIM_EncoderMode_TI12,AB 两相都参与计数,硬件自动完成四倍频和方向判别,CPU 只需要定期读计数器的差值。这里有个细节:计数器是 16 位的,跑长距离会溢出,但用补码相减(int16_t)(now - last)可以自动处理回绕,不需要手动判溢出。
#define PULSE_PER_REV 1320.0f /* 11线 × 4倍频 × 减速比30 */ #define WHEEL_MM 65.0f /* 实测轮径 */ #define PULSE_TO_MM (3.1415926f * WHEEL_MM / PULSE_PER_REV) /* ≈0.1547 */ /* 读取本周期脉冲增量,带符号,10ms 调用一次 */ int16_t Encoder_ReadDelta(void) { static uint16_t last = 0; uint16_t now = TIM_GetCounter(TIM3); int16_t delta = (int16_t)(now - last); /* 16位减法自动处理溢出 */ last = now; return delta; }标定的做法必须写进方案里,否则误差永远是"说不清"。流程是:把车放在起点,车轮正对,用直尺量出 1000mm 的直线;软件里把PULSE_TO_MM临时改成 1.0,让里程直接显示累计脉冲;推车走完 1 米,记下累计脉冲数 N;真实当量 = 1000 / N。这个值会比理论值偏大一点(因为轮子受压后有效半径变小),通常差 2%~4%,改回去之后系统误差立刻收敛。标定要重复三次取平均,单次偶然误差不小。
注意:左右两个轮子要分开标定,各用各的当量。加工误差和装配公差会让两个轮子的当量差 1% 以上,走 2 米就是 2cm 的偏差,正好压在误差带边缘。
4.2 串级 PID 的实现细节:积分分离与微分先行
两个环的代码结构相近,但参数和形态不同。位置环用位置式,输出是目标速度且必须限幅;速度环用增量式,输出是 PWM 增量。下面这段是核心实现,重点看三处:积分分离、微分先行、输出限幅。
typedef struct { float kp, ki, kd; float i_acc; /* 积分累加 */ float last_err; /* 上次误差,增量式用 */ float last_meas; /* 上次测量值,微分先行用 */ float out_max; /* 输出限幅 */ } PID_t; /* 位置式 PID:外环位置环,输入剩余距离(mm),输出目标速度(mm/s) */ float PID_Position(PID_t *p, float err, float dt) { /* 积分分离:误差大时先不积分,防止起步阶段积分饱和 */ if (fabsf(err) < 200.0f) p->i_acc += p->ki * err * dt; p->i_acc = Constrain(p->i_acc, -p->out_max, p->out_max); float d = -(p->last_meas - 0.0f) / dt; /* 微分先行,此处仅示意 */ p->last_meas = err; float out = p->kp * err + p->i_acc + p->kd * d; return Constrain(out, -p->out_max, p->out_max); } /* 增量式 PID:内环速度环,输入速度误差(mm/s),输出 PWM 变化量 */ float PID_Increment(PID_t *p, float err, float dt) { float p_term = p->kp * (err - p->last_err); float i_term = p->ki * err * dt; float d_term = p->kd * (err - 2.0f * p->last_err + p->last_err2) / dt; p->last_err2 = p->last_err; p->last_err = err; return p_term + i_term + d_term; /* 返回的是增量,需外部累加 */ }参数含义需要说清楚:位置环的out_max就是巡航速度上限,比如 400(mm/s),它同时充当限幅和积分上限,防止"还差很远就积分爆掉"。速度环的kp影响响应快慢,ki消除稳态速度差,kd在小车上通常给 0,因为编码器速度信号本身噪声就大,加微分会把噪声放大成高频抖动。我一般的整定顺序是:先把位置环参数设成纯比例(ki=kd=0),把速度环调稳;然后加位置环的 ki 消静差;最后看情况补一点 kd。
积分分离的阈值 200mm 是经验值:起步时剩余距离 2000mm,误差远超阈值,积分不累积,避免一开始就顶到饱和;进入 200mm 以内开始积分,正好用来吃掉最后的静差。这个设计能让停车精度从 ±30mm 提升到 ±10mm 量级。
4.3 减速曲线与停车判据:定点停车靠的不是刹车片
很多人以为定点停车就是"到了就断电",结果车靠惯性冲出去十几厘米。正确的做法是提前减速,让小车以低速接近目标点。减速区用一条基于运动学的限速曲线:剩余距离 s 与允许速度 v 的关系是 v ≤ √(2·a·s),其中 a 是可用减速度(实测取值,一般在 800~1500 mm/s²)。每个控制周期算一次这个允许速度,和目标速度取小,作为速度环的给定。
这条曲线的意义在于:剩余距离越短,允许速度越低,小车自然减速,不会出现"急刹"和"冲过头"。实现只需要一行sqrtf,但效果比任何经验阈值都稳。
停车判据不能只看距离,还要看速度,两个条件同时满足才算到位:
/* 到位判定:距离与速度双条件,配合短时制动 */ void Motion_CheckArrive(void) { if (g_state != DECEL) return; float remain = g_target_dist - g_distance; /* 剩余距离(mm) */ if (fabsf(remain) < ARRIVE_DIST_MM && /* 距离门限,例如 3mm */ fabsf(g_speed) < ARRIVE_SPEED_MMPS) /* 速度门限,例如 20mm/s */ { g_state = BRAKE; Motor_Set(-200, -200); /* 短时反向制动 */ g_brake_tick = 0; } }ARRIVE_DIST_MM建议设成误差带的 1/5 左右,比如要求 ±20mm,就设 3~5mm;ARRIVE_SPEED_MMPS设成巡航速度的 5%。制动阶段用一个计数器计满 8 个控制周期(80ms)就切断输出,因为反向制动时间长了会让车倒回去,反而引入新的误差。这个 80ms 是实测调出来的,不同电机和整车重量差别很大,别照抄。
4.4 参数整定与波形调试:把黑匣子变成看得见的曲线
调参最大的效率提升来自可视化。我的做法是在debug模块里拼一个紧凑的数据帧,每 20ms 通过串口发一次,包含目标距离、当前距离、目标速度、当前速度、PWM 五路数据,格式是逗号分隔加换行。上位机用任意支持串口波形的工具打开,就能同时看到位置曲线和速度曲线。
数据帧的字段顺序要固定,并且加一个帧头校验,否则偶发的丢字节会让波形整体错位,看着像系统在剧烈振荡,实际是解析错了,这种冤枉路我走过不止一次。
调参的具体顺序:先架空车轮,只调速度环,给一个阶跃目标速度,看响应有没有超调和稳态误差,kp从 1.0 开始往上加,加到开始振荡就回退 30%;然后落地,位置环kp从 2.0 开始,观察 2 米行程的曲线,如果接近目标时来回摆动,说明位置环太强,降到 1.0 左右;最后加ki,从 0.01 慢慢加,直到停车后的静差收敛到 3mm 以内。
波形上要盯两个特征:一是速度曲线有没有"台阶感",有的话是 PWM 分辨率不够或者采样周期太长;二是减速段是不是平滑下降的弧线,如果出现直线掉到底再反弹,说明减速曲线没生效,还在用固定阈值。这两点看懂了,调参基本就是照着图形改数字,不再是碰运气。
5. 避坑与常见问题排查:从"每次多跑 20cm"到"原地抖"
5.1 现象:每次停车都多跑一段,且距离很稳定
原因通常是里程标定的脉冲当量偏小,或者停车后没有制动、靠惯性滑行。稳定的偏差属于系统误差,恰恰是最好治的。解决方式是重新做一次 1 米标定取三次平均,同时把制动阶段的时长从 80ms 调到 120ms 试一次,看残差往哪个方向走。如果标定之后仍然多跑固定距离,那就是纯粹的机械惯性,用减速曲线把接近速度压到 100mm/s 以下,误差会明显收敛。
5.2 现象:低速时车身前后抖动,串口波形速度值正负跳变
原因是速度环kp过大叠加编码器低速分辨率不足。10ms 采样周期下,速度 50mm/s 时一个周期内的脉冲增量不到 4 个,量化误差直接变成噪声。解决办法有两个:把低速段的 PWM 设一个最小死区补偿(比如起步给 8% 的基础占空比),以及在速度计算上做一阶低通滤波v = 0.7*v_new + 0.3*v_old。不要指望靠调参消除量化噪声,那是物理限制。
5.3 现象:一上电小车就全速冲出去
原因几乎都是 PWM 初始化顺序或者方向引脚默认电平不对。正确顺序是:先初始化方向 GPIO 并全部拉低,再初始化 PWM 且比较值置 0,最后才使能定时器。另外检查Motor_Set里对 0 值的处理,很多实现写成"0 表示不动作",结果上一次的占空比残留下来继续输出。这个坑的代价很高,轻则撞坏轮子,重则烧驱动,第一次联调务必把车架在垫块上。
5.4 现象:中断里加了串口打印之后,距离变得极不稳定
原因很直接:printf是阻塞式输出,115200 波特率下发 40 个字节大约要 3.5ms,占掉了 10ms 中断的三分之一,控制周期被拉长,所有积分项和微分项的时间基准都错了。解决办法是把数据先写进环形缓冲区,在主循环里发;或者干脆把调试输出放在主循环里,通过一个标志位通知。控制中断里只允许做定长浮点运算和 GPIO 操作,这是条硬规矩。
5.5 现象:同一目标连续跑五次,误差能差出一倍
重复性差通常指向三处:电源电压波动、地面摩擦不一致、起步打滑。先量电池电压,满电和亏电状态下同一组 PID 参数表现完全不同,加一级电压补偿或者改用稳压供电能解决一大半;再看起步,把斜坡启动的加速度减小,让轮子有个建立静摩擦的过程;最后检查车轮是否偏心,把车推着走一段,看编码器数值是否线性增长,非线性就说明轮子有问题。逐一排查下来,重复精度一般能稳定在 ±10mm 以内。
6. 验收与答辩:拿一张误差表说话,比讲十分钟原理管用
到了验收环节,老师或者评审最想看的只有两件事:指标达没达到,以及数据可不可信。所以演示的流程要提前排练成固定动作,而不是现场随机操作。
建议按这个顺序走:先把车放在起点,用卷尺在桌面上贴一条胶带做 2 米刻线;上电复位,屏幕或串口显示当前里程为 0;按启动键,车走完自动停;用直尺量车头到刻线的距离并当场记录。这个动作重复五次,把五次结果写进一张表,现场填。
| 序号 | 目标距离 | 实测距离 | 绝对误差 | 是否合格 |
|---|---|---|---|---|
| 1 | 2000mm | 2008mm | +8mm | 合格 |
| 2 | 2000mm | 1994mm | -6mm | 合格 |
| 3 | 2000mm | 2011mm | +11mm | 合格 |
| 4 | 2000mm | 1992mm | -8mm | 合格 |
| 5 | 2000mm | 2006mm | +6mm | 合格 |
这张表的价值远超任何原理讲解。它同时证明了系统误差(五次均值约 +2.2mm)和重复精度(极差 19mm),而这两项正好对应评分标准里的"准确性"和"稳定性"。如果误差偏大,也要敢当场分析:误差主要来自哪一段、下一步打算怎么改,这比藏起来强得多。评审最反感的不是误差大,而是被问"为什么"的时候答不上来。
答辩时被问到的问题高频集中在三处。第一是"为什么用串级而不是单环",答案要落到稳态误差和响应速度的分离上,别背概念,直接说"单环速度控制在 2 米行程上会累积时间误差,位置外环能把剩余距离直接反馈回来"。第二是"PID 参数怎么定的",答题模板是"先内环后外环,先比例后积分,参数由阶跃响应曲线读出来的超调量反推"。第三是"换一个距离还能不能跑",这时候现场改成 1500mm 重跑一次,如果还能停在误差带内,前面所有的讲解就都立住了。
还有一个很容易忽略的软性加分点:把关键参数做成按键可调,或者留下一个串口命令能改目标距离。评审临时提要求的时候,你能在十秒内响应,整个方案的可信度会完全不一样。
最后说个我自己的习惯:每次做这类定点停车的项目,我都会在第一次联调前强制走一遍"架空车轮、手转看反馈、单元测试三个函数"的流程,三分钟的事,能省掉后面无数个怀疑人生的夜晚。距离控制的精度不是调出来的,是标定和流程管出来的,希望帮到你。
本文还有配套的精品资源,点击获取