首页 / 资讯中心 / 文章详情

STM32智能小车循迹避障综合实验:状态机与PD控制

STM32智能小车循迹避障综合实验:状态机与PD控制 ★ FEATURED ARTICLE
简介围绕STM32智能小车的黑线循迹与红外避障综合实验这套工程源码面向刚接触嵌入式控制、希望从电机驱动与传感器采集入手理解小车运动逻辑的学习者。代码以定时器配置电机为核心封装前进、刹车、左转、左旋转、右转、右旋转、后退等基础运动函数注释较为详细便于把巡线判断与避障策略拆成可读、可改的模块适合课程实验与入门小车项目对照练习。压缩包共192个文件约6.49MB主体是34个c源文件与35个h头文件覆盖外设驱动和工程组织同时保留Keil工程配置、编译中间文件以及hex烧录文件拿到后可直接对照目录梳理定时器、GPIO与中断相关配置。已有1187人学习说明其在基础巡线避障实验中有一定参考价值读者可据此快速定位运动控制与传感器处理的代码入口并在此基础上调整速度、转向时长与避障阈值。1. 从一块循迹底板说起STM32 智能小车综合实验到底要解决什么很多人拿到 STM32 智能小车套件后做的第一件事是让电机转起来结果轮子一转黑线循迹和红外避障就全乱了——车要么在原地画龙要么一头顶到墙上。这套 ZYSTM32-A1 综合实验的价值不在于把两个实验拼在同一个工程里而在于让黑线循迹和红外避障共用一块主控、一套主循环、一份状态机并且在两种工况切换时不打架。它适合刚点完 LED、想完整做第一个嵌入式小车项目的人课程设计、毕设前期验证、入门竞赛练手都能直接用。读完你应该拿到一套能跑、能调、能讲清楚“为什么这么写”的方案而不是一张只会照抄的接线图。2. 需求拆解与硬件选型三种工况、五路红外和一套电机驱动动手画原理图之前先要把“这个小车要干什么”写死。需求边界不清后面选型就会一路纠结传感器到底买三路还是五路驱动到底用 L298N 还是 TB6612主控用 51 够不够。这一章按功能边界、器件选型、引脚分配的顺序把这些定下来。2.1 功能边界循迹、避障、协同三种工况各自的验收口径我把这套小车的功能切成三个工况每个工况都要有能测的验收口径不然答辩时说不清“做完了”是什么状态。第一种是纯循迹工况场地是一条 1.52 cm 宽的黑色电工胶带直道加弯道最小曲率半径按 30 cm 设计。验收口径是从起点到终点约 5 m 全程不脱线单圈时间稳定在 ±1 s 以内。之所以把线宽限定在 2 cm 以内是因为五路红外传感器的探头间距通常在 1015 mm线太宽会让相邻两路同时压线偏差计算的平均值会变钝弯道就跟不上。第二种是纯避障工况车前 1530 cm 出现障碍物时小车要在 0.3 s 内做出反应单侧有障碍就向另一侧让正前方堵死就先后退再转向。这个距离不是随口定的红外避障模块的有效检测距离通常在 380 cm 之间可调调太远会在弯道被场地边沿误触发调太近则留给转向的时间不够。第三种是协同工况也是这套“综合实验”真正的难点小车正在循迹前方突然出现障碍物要先切到避障动作绕开之后还得自己找回黑线继续跑。很多人的工程在单工况测试时都正常一到协同就露馅——避障结束后车在原地转圈找线或者刚回到线上又被同一个障碍物触发第二次。提示需求边界一次就写清楚“不做什么”比写“做什么”更重要。这套方案不做摄像头视觉、不做 SLAM 建图、不做电机编码器闭环调速纯粹用开关量传感器加 PWM 差速实现。边界定了后面所有取舍都有依据。2.2 主控与传感器选型为什么是 STM32F103 加五路 TCRT5000主控这块市面上常见三条路8 位机、STM32F103 这类 Cortex-M3、以及跑 Linux 的板子。选 8 位机的理由是便宜、资料老但它的问题在于定时器和硬件 PWM 通道太少两路电机 PWM 加上一路 1 ms 系统节拍基本就把资源吃满了再想加串口调试都紧张中断响应也慢。STM32F103C8T6 这类芯片刚好卡在甜点上72 MHz 主频、多个通用定时器、通道足够跑两路互补 PWM 加一路 1 ms 节拍GPIO 数量也够接五路循迹加两路避障。选它的真正理由不是“性能强”而是“外设资源刚好够而且不用抢”。循迹传感器选五路 TCRT5000 反射式模块而不是摄像头。摄像头能拿到更丰富的路面信息但要做图像二值化、边缘提取帧率还受限于串口传输对一个小车项目来说投入产出比不划算。五路红外的信息量是 5 bit虽然粗但实时性极好——GPIO 一读就有结果控制周期可以压到 10 ms。代价是它对地面反光敏感浅色瓷砖上和在深色地毯上同一个模块的输出可能完全相反这一点必须靠电位器现场整定。避障用的也是红外反射式模块检测距离可调。这里有个容易被忽略的差别循迹模块是贴地近距离检测避障模块是朝前十几厘米检测两者虽然都是红外反射原理但发射功率和比较器阈值完全不同不能混用同一型号。至于电机驱动L298N 和 TB6612 都行区别在压降和效率对比项L298NTB6612FNG导通压降约 1.42 V电池电压低时明显掉速约 0.5 V低压差效率与发热发热大长时间跑需要散热片发热小可长时间连续工作逻辑电平5 V 逻辑3.3 V 主控需注意电平匹配兼容 3.3 V 逻辑PWM 频率上限建议 10 kHz 以内可上到 20 kHz 以上适合场景教学演示、短时跑动长时间连续跑、电池供电我一般建议这套实验用 TB6612不是因为贵而是因为电池从 8.4 V 掉到 7 V 的时候L298N 会先掉 2 V电机实际只拿到 5 V速度肉眼可见地变慢而循迹的 PD 参数是按某个速度整定的速度一变参数就全部作废——这种“跑两圈就变慢”的问题排查起来非常磨人。2.3 引脚与外设资源分配表选型定完之后先把引脚表排出来避免写到一半发现某个引脚被复用冲突。下面这张表是这套方案实际用的分配可以直接照着接线功能引脚模式/外设备注循迹 最左PB0GPIO 输入上拉权重 -4循迹 左PB1GPIO 输入上拉权重 -2循迹 中PB2GPIO 输入上拉权重 0循迹 右PB3GPIO 输入上拉默认 JTDO需关 JTAG循迹 最右PB4GPIO 输入上拉默认 NJTRST同上避障 前左PB5GPIO 输入上拉检测到障碍为低避障 前右PB6GPIO 输入上拉检测到障碍为低左电机 PWMPA6TIM3_CH110 kHz左电机 IN1/IN2PA4 / PA5GPIO 推挽输出方向控制右电机 PWMPA7TIM3_CH210 kHz右电机 IN3/IN4PB12 / PB13GPIO 推挽输出方向控制调试串口PA9 / PA10USART1115200打印状态1 ms 节拍TIM2定时中断系统时基启动按键PA0GPIO 输入按下开始跑注意PB3 和 PB4 上电默认被 JTAG 占用不关掉的话读回来永远是固定值表现为“这两路传感器失灵”。初始化里加一句GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);只保留 SWD问题就没了。这个坑几乎每个第一次用 F103 的人都会踩一次。3. 总体架构设计三层结构、两个节拍和一个状态机引脚定完接下来是软件架构。小车项目最容易写成“一个大 while 循环里塞满 if-else”能跑但改一处崩三处。这套方案分成三层加一个状态机好处是每层都能单独测传感器可以只打印不驱动电机电机可以只给定值不看传感器状态机可以拿串口喂假数据验证切换逻辑。3.1 感知层、决策层、执行层的职责划分感知层的职责只有一个把物理量翻译成干净的整数。五路循迹读出来是 5 个 bool经过权重加权平均变成 -4 到 4 的偏差值两路避障读出来是两个 bool但要做 3 次采样的多数表决滤掉瞬时的抖动。感知层绝对不做判断也不调用电机函数——这条纪律很重要否则后面想换传感器就得动半个工程。决策层是整个方案的大脑包含两件事控制律和状态机。控制律输入偏差、输出转向量状态机输入避障标志和丢线标志输出当前应该执行哪种动作。把这两件事分开写是因为它们的调试方法完全不同控制律看的是曲线和参数状态机看的是状态跳转表。执行层负责把决策结果翻译成电机能懂的东西给定 -999 到 999 的带符号占空比映射成 TIM 比较寄存器的值和两个方向引脚的高低电平。执行层还要做限幅因为差速时base turn很容易超过量程。三层的调用频率也不一样。感知层每 10 ms 采一次决策层每 20 ms 算一次执行层是跟随决策层立即输出的。这个频率差不是随便定的10 ms 采样配合 0.3 m/s 的车速每 3 mm 走一次采样五路探头的间距在 10 mm 量级采样间隔远小于探头间距不会漏掉线。3.2 定时器节拍1 ms 基准怎么切出 10 ms 与 20 ms 任务裸机项目里最省事的调度方式是“1 ms 中断打拍子主循环看拍子做事”。TIM2 配置成 1 ms 中断中断里只做一件事g_tick。中断函数里千万不要读传感器、不要算 PID、更不要延时——中断里做的事情越多抖动越大PWM 输出的节拍就越不稳。/* TIM272MHz / 72 1MHzARR 999 → 1ms 中断一次 */ volatile uint32_t g_tick 0; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); g_tick; /* 中断里只做计数其他全交给主循环 */ } } int main(void) { uint32_t t_sample 0, t_ctrl 0; board_init(); /* 时钟、GPIO、PWM、串口、TIM2 */ while (1) { if (g_tick - t_sample 10) { /* 10ms采一次传感器 */ t_sample g_tick; sensor_scan(); /* 更新 g_err、g_ob_l、g_ob_r */ } if (g_tick - t_ctrl 20) { /* 20ms跑一次决策 */ t_ctrl g_tick; car_step(); /* 状态机 控制律 输出 */ } debug_report(); /* 空闲时打印不影响节拍 */ } }这里用g_tick - t_sample 10而不是g_tick % 10 0是为了防止某次任务执行时间过长导致漏拍——减法写法即使错过一个节拍下一次也会立刻补上而取模写法会整整丢掉一轮。参数上10 ms 和 20 ms 是最常用的组合如果场地弯道特别急可以把决策周期压到 10 ms代价是串口打印会挤占时间调试信息要相应减量。3.3 状态机设计循迹态、避障态、搜索态、停车态怎么切状态机是这个方案里最值得写进答辩的部分。四个状态跳转条件写死谁也别抢谁的活当前状态跳转条件下一状态动作循迹左右避障同时触发后退双轮反转 300 ms循迹仅左侧触发右让右转差速持续 400 ms循迹仅右侧触发左让左转差速持续 400 ms循迹偏差值 丢线标志搜索沿上次偏差方向原地找线后退障碍消失且计时结束循迹恢复 PD 循迹左让 / 右让计时结束且障碍消失循迹恢复 PD 循迹搜索重新检测到黑线循迹清空 PD 的历史误差任意按键长按 2 s停车PWM 归零两个设计细节决定了它能不能稳定跑。第一是退出条件必须是“计时结束且障碍消失”只写“障碍消失”会在障碍物边沿反复横跳车头就在那里来回抖。第二是从避障或搜索回到循迹时必须清空 PD 的last_err否则上一状态残留的历史误差会让微分项突然打出一个大输出车会猛地甩一下。4. 关键模块实现偏差计算、比例微分循迹与避障优先级架构讲完落到代码。这一章四段代码对应四个模块每段都能单独烧进去验证先只打印偏差确认五路读数对得上位置再接控制律最后接电机。跳过任何一步后面调参数都是盲调。4.1 五路红外采样与加权偏差计算/* 极性统一TCRT5000 模块 DO 压黑线时输出低电平取反成 1 检测到黑线 */ #define S_LL (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0)) #define S_L (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)) #define S_M (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2)) #define S_R (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3)) #define S_RR (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_4)) #define LINE_LOST 99 static const int16_t kWeight[5] { -4, -2, 0, 2, 4 }; /* 位置权重单位半格 */ /* 返回 -4 ~ 4 的偏差负数表示线偏左返回 LINE_LOST 表示五路全白 */ int16_t line_read_error(void) { uint8_t s[5]; int16_t sum 0, cnt 0; s[0] S_LL; s[1] S_L; s[2] S_M; s[3] S_R; s[4] S_RR; for (uint8_t i 0; i 5; i) { if (s[i]) { sum kWeight[i]; cnt; } } if (cnt 0) return LINE_LOST; return sum / cnt; /* 取平均抑制多路同时压线造成的偏差跳变 */ }第一段代码的核心是“极性收敛”和“取平均”两个动作。极性收敛指的是不管模块输出高有效还是低有效都在宏定义这一层统一成“1 检测到黑线”这样后面所有逻辑只认一种约定换一批模块只要改五个宏。取平均是为了处理一种常见情况车正好压在两条线的交汇处或者线宽超过探头间距两三路同时为 1此时直接把权重相加会得到 -6 这种超出量程的值除法一平均就回到 -2量程始终锁在 -4 到 4 之内。丢线返回 99 是个约定值比返回 0 好——返回 0 会被当成“线在正中间”车会保持直行冲出场地返回 99 就能在状态机里单独分支处理。4.2 循迹控制律为什么用 PD 而不是纯比例typedef struct { float kp; float kd; int16_t last_err; int16_t out_limit; } pd_t; static pd_t g_pd { 12.0f, 30.0f, 0, 320 }; /* 初始参数需按实车整定 */ /* 输入偏差输出转向量带符号已限幅 */ int16_t track_output(int16_t err) { int16_t diff; float out; diff err - g_pd.last_err; g_pd.last_err err; out g_pd.kp * err g_pd.kd * diff; if (out g_pd.out_limit) out g_pd.out_limit; if (out -g_pd.out_limit) out -g_pd.out_limit; return (int16_t)out; } /* 差速执行base 是基础速度turn 是转向修正 */ void track_run(int16_t err) { int16_t base 250; /* 0 ~ 999 */ int16_t turn track_output(err); motor_set(MOTOR_L, base turn); motor_set(MOTOR_R, base - turn); }只用比例项P的问题是明显的偏差一出现就立刻修正修正过头就反向超调车在直道上左右小幅摆动也就是俗称的“画龙”。微分项D看的是偏差的变化量偏差快速变化时提前给一个反向的抑制量等效于给系统加了阻尼。这套参数里kp 12、kd 30是初始值实际整定顺序是先把kd设为 0kp从 5 开始往上加加到车在直道上刚好开始小幅摆动然后把kp退回一档开始加kd加到摆动消失为止。out_limit设成 320 而不是 999是因为它要跟base 250配合——如果限幅太宽一次修正就能让一侧轮子反转车会原地打转。经验上把out_limit控制在base的 1.2 倍左右即单侧轮子最多减到接近 0 但不会反转。4.3 避障优先级与转向策略#define OB_L (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5)) /* 检测到障碍为 1 */ #define OB_R (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6)) typedef enum { ST_TRACK, ST_BACK, ST_AVOID_L, ST_AVOID_R, ST_SEARCH, ST_STOP } state_t; static state_t g_state ST_TRACK; static uint16_t g_state_tick 0; /* 状态内计时单位 20ms */ /* 每 20ms 调用一次返回新状态 */ state_t car_sm_step(uint8_t ob_l, uint8_t ob_r, int16_t err) { g_state_tick; switch (g_state) { case ST_TRACK: if (ob_l ob_r) { g_state_tick 0; return ST_BACK; } if (ob_l) { g_state_tick 0; return ST_AVOID_R; } if (ob_r) { g_state_tick 0; return ST_AVOID_L; } if (err LINE_LOST){ g_state_tick 0; return ST_SEARCH; } return ST_TRACK; case ST_BACK: /* 后退 15 拍 300ms */ if (g_state_tick 15 !ob_l !ob_r) return ST_TRACK; return ST_BACK; case ST_AVOID_R: /* 右让 20 拍 400ms */ if (g_state_tick 20 !ob_l) { g_pd.last_err 0; return ST_TRACK; } return ST_AVOID_R; case ST_AVOID_L: if (g_state_tick 20 !ob_r) { g_pd.last_err 0; return ST_TRACK; } return ST_AVOID_L; case ST_SEARCH: /* 最多找 1.5s找不到就停 */ if (err ! LINE_LOST) { g_pd.last_err 0; return ST_TRACK; } if (g_state_tick 75) return ST_STOP; return ST_SEARCH; default: return ST_STOP; } }这段状态机里有三个刻意写进去的保护。第一每个避障状态的退出都要求“计时到 障碍消失”两个条件同时成立缺一个都会在障碍物边缘反复切换。第二ST_AVOID_R的退出只判断!ob_l不判断!ob_r因为向右让的时候右侧传感器可能会扫到新的障碍如果两个都要求消失车会卡死在让行状态。第三搜索状态有超时上限1.5 s 还找不到线就停车避免车在原地无限转圈烧电机——这个上限比“一直找下去”更实用至少在测试时你能看出它停在了哪里。4.4 PWM 调速与电机方向控制#define MOTOR_L 0 #define MOTOR_R 1 void motor_pwm_init(void) { GPIO_InitTypeDef gpio; TIM_TimeBaseInitTypeDef tb; TIM_OCInitTypeDef oc; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); gpio.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; /* TIM3_CH1 / CH2 */ gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); /* 72MHz / 72 1MHz 计数ARR 99 → PWM 频率 10kHz */ tb.TIM_Prescaler 71; tb.TIM_Period 99; tb.TIM_CounterMode TIM_CounterMode_Up; tb.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, tb); oc.TIM_OCMode TIM_OCMode_PWM1; oc.TIM_OutputState TIM_OutputState_Enable; oc.TIM_OCPolarity TIM_OCPolarity_High; oc.TIM_Pulse 0; TIM_OC1Init(TIM3, oc); TIM_OC2Init(TIM3, oc); TIM_Cmd(TIM3, ENABLE); } /* duty: -999 ~ 999负数为反转 */ void motor_set(uint8_t ch, int16_t duty) { if (duty 999) duty 999; if (duty -999) duty -999; if (ch MOTOR_L) { GPIO_WriteBit(GPIOA, GPIO_Pin_4, duty 0 ? Bit_RESET : Bit_SET); GPIO_WriteBit(GPIOA, GPIO_Pin_5, duty 0 ? Bit_SET : Bit_RESET); TIM_SetCompare1(TIM3, (uint16_t)(duty 0 ? duty : -duty)); } else { GPIO_WriteBit(GPIOB, GPIO_Pin_12, duty 0 ? Bit_RESET : Bit_SET); GPIO_WriteBit(GPIOB, GPIO_Pin_13, duty 0 ? Bit_SET : Bit_RESET); TIM_SetCompare2(TIM3, (uint16_t)(duty 0 ? duty : -duty)); } }PWM 频率取 10 kHz 有两个理由一是高于人耳可听范围的下沿电机不会发出尖锐的啸叫声二是 TB6612 这类 MOS 驱动在 10 kHz 时的开关损耗可以接受长时间跑不会烫手。如果换成 L298N频率不建议超过 10 kHz否则三极管来不及完全导通效率会更低。motor_set里把“方向引脚”和“占空比”这两件事绑在一个函数里是为了避免出现“改了占空比忘了改方向”导致电机反着转的问题。传入负占空比时方向引脚翻转、比较寄存器取绝对值逻辑上单调接线反了也只需要在调用处加个负号。5. 避坑与排查这套小车上最容易翻车的五个点前面四章讲的是“应该怎么写”这一章讲的是“实际会怎么坏”。这五个问题在我自己搭和帮别人调的过程中反复出现每一个都有明确的现象、成因和解法照着对号入座能省掉大量时间。5.1 一上电就往一边猛冲或者读出来的传感器永远不变现象车通电后两个轮子朝相反方向转或者串口打印出来的五路循迹值永远是1 1 1 1 1。原因前者是电机方向引脚的电平组合和预期相反两个轮子一个正转一个反转后者几乎都是 PB3、PB4 被 JTAG 占用。F103 复位后 PB3 默认是 JTDO、PB4 默认是 NJTRST配置成普通 GPIO 输入也没用读回来的值固定。解决方向问题不要靠改接线在motor_set里把对应通道的 IN1/IN2 互换即可接线保持整齐便于排查。JTAG 问题在 GPIO 初始化之前加一句GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);只保留 SWD 下载口烧录不受影响。5.2 直道跑得很稳一进弯道就冲出去现象场地直道上小车走得笔直进入半径 30 cm 的弯道后车头贴着外侧冲出去纠正不回来。原因三种可能按概率排序是控制周期太长、kp偏小、差速饱和限幅太窄。控制周期 20 ms 时车速 0.4 m/s 每拍走 8 mm弯道上的偏差累积得很快等算出来已经晚了kp太小则修正力度不够out_limit太小则想修正也修不动。解决先把决策周期压到 10 ms 试一次如果明显改善就是周期问题。否则把out_limit从 320 放到 400再看弯道表现最后才动kp。顺序很重要——先放限幅再调增益不然你会以为kp不够一路加大结果直道上开始画龙。5.3 避障模块在深色地面上一直误报现象场地换到深色地胶或者黑色桌面上避障模块不管前面有没有东西都输出“有障碍”小车一直在后退。原因红外反射式模块靠接收反射光强判断距离深色表面吸光接收端收到的光很弱比较器就判定为“没有反射 → 有障碍”。这不是模块坏了是原理决定的。解决一是现场重新调电位器把阈值调到实际能用的位置二是给模块加个遮光筒用黑色热缩管剪一段套在探头外面挡住环境光干扰三是软件上做多数表决连续 3 次采样有 2 次以上为 1 才算真障碍。第三条最省事代价是响应延迟一个采样周期10 ms 完全能接受。5.4 电机一转单片机就复位现象空载时串口打印正常一旦给电机加占空比单片机随机复位串口输出乱码或者程序跑飞。原因电机启动瞬间电流是堵转电流的量级电池内阻上产生压降主控的供电被拉低到复位阈值以下。另外电机是感性负载换向时会产生反向尖峰通过电源和地线串回主控。解决硬件上电机电源和主控电源分开走线只在电池端单点共地电机两端并 0.1 μF 电容加一个续流二极管主控电源入口加 100 μF 电解电容和 0.1 μF 陶瓷电容。软件上启动时让占空比斜坡上升每 20 ms 加 50而不是直接从 0 跳到 250冲击电流能小一半。这套小车上电就复位的问题九成是这两条之一没做到。5.5 避障结束回不了线在原地转圈现象小车成功绕开障碍物但之后一直在原地左右转找不到黑线最后超时停车。原因绕障动作把车带离黑线太远五路探头全部落在白区返回LINE_LOST而搜索逻辑只是原地转转向方向和线的实际位置相反。另外g_pd.last_err没有清零微分项一上来就是一个大值。解决搜索时不要原地转改成“沿着绕障的反方向小半径画弧”也就是右让之后向左找左让之后向右找这个方向和绕开时车头偏转的方向相反命中率最高。同时把搜索速度降到基础的 60%慢一点更容易被探头扫到。回到线之后第一件事是清空last_err让 PD 从零开始。6. 验收与答辩用一套可复现的测试把车“跑通”讲清楚功能做完不等于项目做完验收测试要能重复、能记录、能拿给不懂的人看。我一般按下面这张表跑一遍每项测三次取最差的那次因为答辩现场发挥失常是常态按最差成绩准备才稳。序号测试项测试方法合格线1直道循迹3 m 直线胶带全速通过3 次全部不脱线2弯道循迹R30 cm 连续弯跑 5 圈脱线次数 ≤ 13单侧避障车前左侧 20 cm 放障碍物0.3 s 内开始右让绕过后 2 s 内回到线上4正前避障车前正中 20 cm 放障碍物先后退再转向不撞不卡死5协同弯道中段放障碍物完成绕障并继续循迹全程不超时停车6掉电重启连续上电 20 次20 次均正常启动无复位异常测试时把串口打印打开每 100 ms 输出一行err / ob_l / ob_r / state / duty跑完之后把日志导出来画一条偏差随时间的曲线。这张曲线在答辩时比任何口头描述都有说服力——评委一眼能看出来你的系统在弯道上是“提前修正”还是“撞上去才修”这两种的控制水平完全不同。答辩讲述我建议按“工况 → 取舍 → 一次翻车”的顺序走不要从技术栈开始讲。先说清楚这套车分了哪三种工况、每种工况的验收口径是什么这能证明你想过需求再说你做了什么取舍比如为什么用五路红外不用摄像头、为什么用 TB6612 不用 L298N、为什么控制周期是 20 ms 而不是 5 ms每个取舍都要落到具体的数字上最后讲一次你踩过的坑和怎么定位的比如“车一进弯就冲出去我一开始以为是 kp 不够加了两轮之后直道开始画龙才回头去查控制周期发现 20 ms 下每拍走 8 mm改成 10 ms 就好了”。这个故事里包含了现象、错误假设、纠正过程比背概念更能说明你真的动手做过。从那以后我每次做这种带传感器的车类项目都会在写控制逻辑之前先把“只打印不驱动”的版本跑通让串口把五路原始值、两路避障值、偏差值一起打出来人工推着车沿场地走一遍确认每一路的跳变位置和物理位置对得上。这一步花不了二十分钟但能挡掉后面八成莫名其妙的“算法不收敛”——毕竟传感器读错了再好的 PD 参数也是白搭。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站