最近整理第 21 届智能车项目资料时,又看了一遍车模跑“走马观碑”路段的运行视频。画面里车模以中低速稳定通过,转向流畅,没有明显抖振,看上去已经“能跑”了。但视频标题里有个关键信息:未加入视觉。
很多同学看到这种视频会下意识觉得,车都能稳定跑过这一段了,视觉识别加不加好像只是锦上添花。这个判断其实不准确。恰恰是这个“能跑但还没加上视觉”的中间版本,才是整个项目里最值得保存、也最有技术含量的一版。它证明了底层机械、驱动、测速、闭环控制这些基础设施是可靠的;而视觉识别要解决的是另一层问题,两者不能互相替代。
这篇文章想把这个工程阶段讲透:为什么“未加入视觉”本身是一个重要的技术里程碑;在这个阶段车模的软硬件架构是怎么搭出来的;闭环控制代码怎么写、怎么调;以及怎么通过运行视频和录制作业,为下一步接入视觉识别留好接口、定好基准。如果你正在做智能车竞赛、机器人小车,或者也在经历“先让车跑起来,再让它看懂路”的开发过程,这篇内容应该能帮你少走不少弯路。
1. 为什么“未加入视觉”是一个值得记录的技术里程碑
先说结论:在没有视觉的情况下,车模能稳定跑完“走马观碑”路段,至少证明了以下四件事是成立的。
第一,动力链路是通的。电池、稳压、电机驱动、PWM 输出、电机,这一整条链路如果任何一环有问题,车是动不起来的,更不要说稳定跑完一整段。第二,速度闭环是有效的。通过编码器测速加 PID 控制,车模能在不同负载和路况下维持目标速度,这是后面做视觉识别时最重要的前提。第三,路径保持是可用的。无论是靠电磁传感器、灰度传感器还是 IMU 融合,车模能沿着赛道线走,没有冲出边界。第四,状态机框架是可扩展的。当前版本即使没有视觉,代码里也要提前预留视觉结果的接入位置,否则后面加功能时只能大改。
不理解这个阶段价值的人,容易犯一个错误:把“能跑”当作“完成”。实际上,竞赛赛题里的视觉识别任务,比如识别路边碑体上的图案或字样,涉及的是图像采集、图像预处理、目标检测、结果输出和与车辆控制状态机的联调。这些工作在没有接入视觉之前,根本无从验证。
但反过来也要清楚:没有视觉的版本,能验证的是“车本身没问题”,不能验证的是“车在识别后还能不能保持正确动作”。识别到目标后要不要减速、要不要变道、要不要停车,这些决策逻辑只有在视觉接入后才能完整测试。所以“未加入视觉”不是终点,而是把系统分成两层后的第一层验收,这个验收越严格,第二层接入时越安全。
从项目管理的角度看,这个阶段也最适合做基线保存。跑一遍、录一段视频、记一组参数,之后的每一次改动都可以和这个基线对比。如果没有这个基线,等视觉代码加进去之后出了问题,你很难判断是控制的问题、图像的问题,还是两者联动的问题。
2. “走马观碑”元素解读:它到底考的是什么
“走马观碑”原本是一个成语,大意是骑马经过石碑时就能快速看清碑文,形容速度快且观察敏锐。智能车竞赛把它用作赛题元素,本质上是在高速运动场景下增加一个“观察-理解-决策”的任务,这比单纯循迹要难一个维度。
放在赛道上,这个元素大致可以理解为:赛道旁边放置一个预先设定好的“碑体”,上面有特定的图案、字样或符号。车模在行驶到该区域时,需要在不完全停车或者仅短暂降速的情况下,通过摄像头识别出碑体上的内容,并根据识别结果做出后续动作。这里的难点不在于“识别”本身,而在于“运动中的识别”。
为什么说运动中识别很难?因为车在运动时,摄像头画面每一帧都在变化,图像会有运动模糊,光照条件随位置变化而改变,碑体在画面中的大小和位置也不稳定。如果车模以较高速度通过,留给识别的有效帧数可能只有几十帧甚至十几帧,算法必须在这些帧里完成检测和判断。这不像离线识别一张图片,可以慢慢调参。
所以“走马观碑”这个元素实际上把系统分成了三个层次:
| 层次 | 任务 | 当前项目状态 | 依赖 |
|---|---|---|---|
| 基础运动层 | 电机驱动、测速、转向、稳定行驶 | 已完成并录制视频验证 | 电池、电机、编码器、主控 |
| 感知层 | 摄像头采集图像,识别碑体图案 | 未加入视觉 | 摄像头、图像算法、算力 |
| 决策联动层 | 根据识别结果调整车速和路径 | 待感知层完成后联调 | 状态机、识别结果接口 |
这个表格里最关键的是第三层。很多团队在第一层做完之后,以为第二层做完就大功告成,结果第三层联调时发现,识别结果出来得太慢,车都已经冲过碑体了。这也是为什么我建议在“未加入视觉”阶段就要把状态机里的视觉接口和降速策略先写好,而不是等摄像头到了再临时补。
3. 这个阶段的车模系统架构
抛开具体型号,无视觉阶段的车模系统架构并不复杂,但每个模块都有明确职责。
主控芯片负责所有逻辑计算,常见的选型是 STM32 系列,也有团队用更高主频的 MCU 或带 AI 算力的开发板。在主控之外,驱动模块接收 PWM 信号并驱动电机,编码器反馈实际转速,IMU 提供姿态和角速度信息,电源模块负责把电池电压稳定到主控和传感器需要的工作电压。这个阶段没有摄像头,所以图像链路是空的。
从数据流来看,整个系统是一个典型的闭环:
设定速度/路径 → 主控计算 → PWM 输出 → 电机驱动 → 车轮转动 ↑ ↓ ← 编码器/IMU 反馈 ← 车身实际状态 ←这个闭环在无视觉阶段是自洽的。主控知道车跑多快、偏了多少,并根据这些信息实时修正输出。摄像机接入后,数据流会变成:图像作为另一路输入,经过识别算法变成“前方是否有目标”或“目标是什么”的高层信息,再注入到主控的状态机里,影响速度设定和转向逻辑。
架构设计上有一个容易被忽略的点:视觉结果不应该直接控制电机,而应该先进入状态机,由状态机决定动作。这样做的好处是,当视觉识别出现错误或超时时,控制层仍然有兜底策略,不会因为一帧误检就让车子猛打方向。
无视觉阶段还要确定通信和调试方式。常见做法是串口输出日志,或者用无线模块把车模内部状态传到上位机。项目视频里能录到稳定的运行画面,通常意味着调试阶段已经把日志系统做好了,否则出了问题很难定位。
4. 环境准备与硬件平台说明
这一节按通用经验整理,不限定具体厂商型号。实际项目中请以你手里的板卡和竞赛规则为准,这里重点是提供一个完整的最小系统清单。
| 模块 | 作用 | 备注 |
|---|---|---|
| 主控板 | 运行控制算法和状态机 | 常见用 STM32 系列,需预留摄像头接口 |
| 电机驱动模块 | 放大 PWM 信号驱动电机 | 注意电流余量和散热 |
| 直流电机加编码器 | 驱动车轮并反馈转速 | 编码器精度直接影响速度闭环效果 |
| IMU 模块 | 提供角速度和加速度 | 用于转向补偿和姿态参考 |
| 稳压电源 | 输出稳定的 5V/3.3V 等电压 | 电池电压变化时仍需稳定 |
| 灰度传感器或电磁传感器 | 检测赛道线 | 视具体赛题选择 |
| 电池 | 整机供电 | 注意电压和放电能力匹配 |
| 摄像头 | 视觉识别输入 | 当前阶段可先不装,但接口要预留 |
软件工具链方面,嵌入式端一般使用 Keil MDK、STM32CubeIDE 或 IAR 进行开发,代码用 C 语言编写;后续如果做视觉,可能还会用到 OpenCV、图像采集工具和上位机调试软件。如果你是团队协作,建议把代码仓库放在 Git 上,每个阶段打一个标签,视频素材单独归档。
环境准备阶段有几个安全提醒,虽然不是技术难点,但出了问题代价很大。电池接线时要先断开供电再操作,防止短路;电机驱动模块在高负载下会发热,第一次上电前最好用低速空转测试验证接线;车模测试要在空旷、地面平整的场地进行,周围不要有易碎物品和人。任何涉及赛道的调试,都要提前确认场地授权和测试时间安排。
5. 无视觉阶段的核心实现:从打通电机到稳定跑完
无视觉阶段的工作可以拆成四个步骤:电机驱动、速度闭环、路径保持、状态机预留。每一步都有独立的验证标准,不要连做三步再一起测试。
5.1 电机驱动:先让轮子“听话”
第一步是让电机能够根据 PWM 占空比转动,并且可以控制方向和转速。方向控制依赖驱动模块的 IN 引脚电平组合,转速控制依赖 PWM 占空比。
这里最常见的坑是 PWM 频率选择。频率太低电机会有明显啸叫,频率太高驱动模块可能响应不过来。一般直流电机驱动,PWM 频率在 10kHz 到 20kHz 之间比较常见,具体看驱动芯片手册。另一个坑是上电瞬间电机意外转动,所以初始化代码必须在启动 PWM 之前把占空比设置为 0,并且把方向引脚设置为确定状态。
验证标准很简单:程序里给一个固定占空比,电机转速应该稳定;切换方向引脚电平,电机反转;此时如果编码器有反馈,读数方向应该跟随变化。这步做不好,后面所有闭环都是空谈。
5.2 编码器测速与速度闭环
编码器把机械转角变成脉冲信号,主控通过定时器计数计算转速。测速精度直接影响速度闭环。速度闭环最常用的是 PID 控制器,增量式或位置式都可以,关键是先调 P,再调 I,最后加少量 D。P 太小响应慢,P 太大容易震荡;I 的作用是消除稳态误差,但过大的 I 会让系统超调甚至振荡。
调试速度闭环时,建议用上位机把目标速度和实际速度画成波形。如果实际速度围绕目标速度来回震荡,先减小 P;如果实际速度和目标速度之间有固定偏差,适当增加 I。调参要有耐心,一次只改一个参数,并记录改动结果。
5.3 路径保持:传感器循迹与 IMU 融合
路径保持的常见方案有两类。一类是灰度传感器阵列循迹,通过检测黑线位置计算偏差,再把偏差映射到转向。另一类是电磁循迹,通过感应导线周围的磁场判断位置。进阶方案是融合 IMU 的角速度数据,在车身偏移时进行补偿。
无视觉版本里,路径保持只需要做到“不冲出赛道”即可,但要注意,这个阶段速度不能设得太高。因为路径保持的可靠性与速度成反比,速度越高,留给修正的时间越短。建议从低速开始,逐步提高,每提高一档都重新观察运行是否稳定。
5.4 预留视觉接口的状态机
即使没有摄像头,控制逻辑也要写成状态机。因为“走马观碑”任务本质上是一系列状态切换:正常行驶、接近碑体、识别、决策、继续行驶。如果主循环里全是if嵌套,后面接视觉时会非常难维护。
状态机要提前定义好各个状态,并预留两个关键变量:视觉结果标志和视觉就绪标志。在没有摄像头时,这两个变量可以被固定为默认值,不影响运行;接入摄像头后,识别线程只需要把结果写进这两个变量,控制状态机就会自动切换状态。
6. 完整示例代码实现
下面以 STM32 HAL 库风格给出几个核心代码片段。注意,这不代表你项目的实际工程,重点是理解每个模块的职责和接口设计。
6.1 电机 PWM 驱动
// motor.c —— 电机 PWM 驱动(STM32 HAL 示例) #include "motor.h" void Motor_Init(void) { // 启动 PWM 输出 HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2); // 初始占空比为 0,防止上电瞬间电机转动 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 0); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, 0); } // speed 范围 -1000 ~ 1000,正值为前进,负值为后退 void Motor_SetSpeed(int16_t speed) { if (speed > 1000) speed = 1000; if (speed < -1000) speed = -1000; if (speed >= 0) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, speed); } else { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, -speed); } }这段代码的关键点有两个:一是限幅,防止传入非法值导致占空比越界;二是电机的方向和占空比分开控制,逻辑清晰,便于后面扩展。实际接线中,GPIOA 的引脚号和你板卡上的驱动模块输入引脚要一一对应。
6.2 增量式 PID 速度闭环
// pid.c —— 增量式 PID,用于速度闭环 #include "pid.h" typedef struct { float kp; float ki; float kd; float integral; float last_error; } Pid_t; void Pid_Init(Pid_t *pid, float kp, float ki, float kd) { pid->kp = kp; pid->ki = ki; pid->kd = kd; pid->integral = 0.0f; pid->last_error = 0.0f; } float Pid_Update(Pid_t *pid, float target, float current) { float error = target - current; float output; // 积分限幅,防止长时间偏差导致积分饱和 pid->integral += error; if (pid->integral > 200.0f) pid->integral = 200.0f; if (pid->integral < -200.0f) pid->integral = -200.0f; output = pid->kp * error + pid->ki * pid->integral + pid->kd * (error - pid->last_error); pid->last_error = error; return output; }PID 代码本身不复杂,复杂的是参数调节。积分限幅这里用了 200 这个经验值,它应该根据你的 PWM 最大输出范围来定。如果 PWM 最大是 1000,积分限幅取最大输出的五分之一左右是比较稳妥的起点。调参时先从 kp 开始逐步增大,出现震荡就回调,再加入 ki 消除稳态误差。
6.3 运行状态机与视觉接口预留
// main_control.c —— 运行状态机,预留视觉识别接口 #include "main_control.h" typedef enum { STAGE_START, // 起步阶段 STAGE_NORMAL, // 正常巡线行驶 STAGE_STELE_APPROACH, // 接近碑体,准备降速 STAGE_STELE_WAIT, // 等待视觉识别结果 STAGE_STELE_PASS, // 识别完成,通过碑体区域 STAGE_STOP // 停车 } RunStage_t; RunStage_t stage = STAGE_START; uint8_t vision_ready = 0; // 视觉模块是否就绪 uint8_t vision_result = 0; // 视觉识别结果:0 未识别到目标,1 识别到目标 // 视觉模块结果回调,接入摄像头后由识别任务调用 void Vision_OnResult(uint8_t result) { vision_result = result; vision_ready = 1; } void Control_Loop(void) { switch (stage) { case STAGE_START: TargetSpeed_Set(0.3f); if (Encoder_GetSpeed() > 0.25f) { stage = STAGE_NORMAL; } break; case STAGE_NORMAL: TargetSpeed_Set(1.2f); // 接近碑体的判断条件,可以是里程累计、按键标记或传感器触发 if (Track_Flag_IsSet(TRACK_FLAG_STELE_NEAR)) { stage = STAGE_STELE_APPROACH; Track_Flag_Clear(TRACK_FLAG_STELE_NEAR); } break; case STAGE_STELE_APPROACH: // 降速,为视觉识别提供稳定的图像采集窗口 TargetSpeed_Set(0.6f); if (vision_ready) { stage = STAGE_STELE_WAIT; } break; case STAGE_STELE_WAIT: // 等待视觉结果,超时则按未识别处理,避免卡死 if (vision_ready && vision_result == 1) { stage = STAGE_STELE_PASS; } else if (Timeout_Check(1000)) { stage = STAGE_STELE_PASS; // 超时降级 } break; case STAGE_STELE_PASS: TargetSpeed_Set(1.0f); break; default: break; } }这段状态机代码是无视觉版本和视觉版本之间的“桥”。当前阶段,Vision_OnResult不会被调用,vision_ready一直为 0,所以状态会从STAGE_STELE_APPROACH一直等到超时,然后按“未识别”继续执行。这个流程和将来接入视觉后的流程完全一致,只是缺少真实的识别结果。这样设计的好处是,视觉代码接入后,主控逻辑不需要大规模改动。
6.4 视觉识别流程骨架(预留参考)
这一小段是给下一步接入视觉做参考的,当前版本并未运行。骨架使用 Python 加 OpenCV 描述,便于快速验证识别流程。
# vision_demo.py —— 后续接入摄像头时的识别流程骨架 import cv2 def send_to_mcu(detected): # 将识别结果发送给主控,串口或网络方式按你的方案实现 pass def main(): cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() if not ret: continue # 1. 裁剪碑体可能出现的区域,减少计算量 roi = frame[240:480, 0:640] # 2. 灰度化 + 二值化,把目标图案从背景中分离 gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 3. 轮廓分析,判断是否出现目标图案 contours, _ = cv2.findContours( binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) detected = False for cnt in contours: area = cv2.contourArea(cnt) if area > 500: detected = True break # 4. 把结果发给主控 send_to_mcu(detected) cv2.imshow("stele", roi) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()这里只演示了最简单的阈值加轮廓方法。真实赛题场景里,可能需要对字符、数字或特定图案做更准确的识别,后续可以用模板匹配、特征匹配或者轻量级神经网络替换第三步。骨架的价值在于固定输入输出接口,识别算法怎么换都可以,但发到主控的结果格式保持不变。
7. 运行验证与录像方法论
“车跑起来了”和“车稳定跑起来了”是两码事。无视觉阶段的验收,建议按照下面几条标准来做。
第一,连续多次运行,至少保证在相同参数下 5 次里有 4 次能完整跑完目标路段。如果只有一两次成功,说明系统处于临界状态,速度快了或电池电压稍微变化就可能失败。第二,记录关键运行数据。通过串口把目标速度、实际速度、转向偏差等数据导出来,存成文件。这些数据能够在录像之外提供更精确的判断依据。第三,录像需要有固定机位和跟随机位。固定机位用来判断车是否跑在赛道线内,跟随机位用来观察车身姿态和电机声音是否正常。视频文件名建议包含日期、参数版本、速度档位,例如20250601_stele_v1.0_speed1.2.mp4。
录制动运行视频时,还有一个容易被忽略的细节:要在不同的电池电量条件下各录一段。电池电压充足时和快没电时,电机响应速度明显不同。如果你只在满电时测试,比赛时剩余电量下降可能出现完全不同的车况。视频素材要备注电量状态,这是很实用的排查信息。
判断成功与否的第一指标是“稳定通过且不触碰赛道边界”,第二指标是“速度波动小”。如果录像里车有明显的前后耸动,说明速度闭环还没调好;如果转向处有剧烈摆动,说明路径保持参数需要调整。这两类问题用肉眼看视频就能发现,不用等运行数据导出。
8. 常见问题与排查思路
无视觉阶段看起来简单,实际踩坑点不少。下面按问题现象整理了一份排查表。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 电机不转 | 驱动模块供电异常或 PWM 未启动 | 检查电池电压、驱动模块电源指示灯 | 重新上电并确认初始化顺序 |
| 电机只能转不能正反转 | IN 引脚逻辑接反或代码写反 | 单步调试方向引脚电平 | 核对驱动模块 IN 引脚定义 |
| 转速有明显抖动 | PID 参数不合适或编码器读数有毛刺 | 打印编码器原始计数和 PID 输出 | 先调 P,再加滤波,最后微调 D |
| 速度偏差始终存在 | 积分项不足或 PWM 死区 | 观察目标速度与实际速度差值 | 加大 ki 或补偿 PWM 死区 |
| 转弯时冲出赛道 | 转向灵敏度过高或速度过快 | 分速度档位录制对比视频 | 降低当前档位速度或调整转向系数 |
| 电池掉电快 | 电机堵转或驱动效率低 | 检查轮胎是否卡滞、转动是否顺畅 | 降低负载并检查电机电流 |
| 跑几次后表现不一致 | 电池电压下降或机械松动 | 记录电量并检查螺丝和轮胎 | 建立“电量-参数”对照记录 |
| 串口日志乱码 | 波特率不匹配或地线未共地 | 确认串口参数并检查接线 | 统一波特率并重新接线 |
排查问题的大原则是:一次只改一个变量。很多团队在电机不转时同时改代码、换电池、动接线,结果问题解决了也不知道是哪个因素起的作用。正确做法是先排除供电,再排除初始化,再检查逻辑,顺着数据流一步步来。
9. 从“未加入视觉”到“接入视觉”的落地路径
无视觉版本的稳定运行只是第一步,接下来真正有挑战的是把视觉识别接进来。很多人以为摄像头一插、代码一烧就能看结果,实际工程上要经历五个阶段。
第一阶段是摄像头选型和图像采集。要确定摄像头分辨率、帧率、接口类型。分辨率不是越高越好,因为图像处理需要时间,高分辨率会拉低帧率,运动场景反而容易糊。第二阶段是图像预处理。灰度化、二值化、去噪、边缘提取,这些操作的目标是把目标图案从复杂背景中分离出来。第三阶段是目标识别。根据赛题要求,可能是识别图案、字符或特定形状,可以选择传统视觉方法,也可以训练轻量级模型,但都要考虑主控算力。第四阶段是识别结果与状态机联调。这一阶段要把视觉代码跑起来,并把vision_ready和vision_result两个变量真正交给主控。第五阶段是整体调优。识别速度、降速策略、超时处理都要在真实赛道上反复验证。
联调阶段最大的风险是时间不同步。视觉识别需要几十毫秒甚至上百毫秒,而车可能已经跑出去很远了。所以无视觉阶段为什么要求状态机里设置“接近碑体后降速”这个状态,就是为了给视觉争取时间。实际项目中,可以在赛道接近碑体的位置用一个传感器触发降速,提前降到识别友好速度,等识别结果回来后再决定下一步动作。
还要考虑识别失败怎么办。视觉算法不可能 100% 正确,所以状态机里必须有超时降级策略。超时未识别,就按一个默认动作处理,而不是让车停在赛道中间。这个策略在无视觉阶段已经写进代码里了,只是当时它只是“未来会用到”的兜底逻辑,接入视觉后它就变成真正的保命逻辑。
如果团队想在开发板上先验证视觉流程,建议先用离线图片测算法,把识别准确率稳定在一个可接受水平后,再放到车上在线测试。在线测试时先在低速下跑,稳定后再逐步提速。不要一开始就冲到高速,否则图像模糊和转向压力会叠在一起,问题极难定位。
10. 最佳实践与工程建议
这个项目的经验放到任何嵌入式小车项目里都值得复用。
参数管理方面,所有 PID 参数、速度档位、转向系数都应该集中放在一个配置文件里,不要散落在各个源文件中。建议维护一张参数记录表,记录日期、参数值、测试结果和运行环境,调参时才有据可查。你甚至可以给每组参数取一个版本号,比如v1.0_speed1.2_kp18,这样录像、日志和代码能一一对应。
代码注释方面,状态机的每个状态都要写清楚进入条件和退出条件,尤其是降级分支。不要觉得“这段代码以后不会变”就不写注释,等视觉接入后,写注释的人可能已经忘了当时的思路。硬件接线方面,驱动模块和电源之间要留保险丝或拨动开关,方便紧急断电。所有接插件建议用不同颜色区分电源正负极,避免接线错误烧毁模块。
调试习惯方面,添加日志打印要克制,不要每毫秒都输出,否则刷屏之后什么都看不清。建议使用分级日志,正常状态不打印,进入关键状态或异常状态时打印一行。日志内容要包含时间戳和状态名,这样能方便地还原现场。
团队协作方面,这个项目建议至少两个人分工,一个人负责运动控制和底层,一个人负责视觉算法。两个人在接口处约定好数据格式,主控端用vision_ready和vision_result两个变量作为协议,视觉端无论用什么算法,最终都输出这两个变量的值。接口越简单,联调成本越低。
安全方面还要再次强调,调试电机和电池接线时一定要断电操作。测试过程中如果闻到焦糊味或听到异常声音,第一时间切断电源,不要强行继续。另外,所有代码在烧录前做一次编译检查,确认没有明显告警,能减少很多不必要的现场调试时间。
11. 总结与下一步
回到项目视频本身。“未加入视觉”的车模运行视频,表面上是项目处于“能跑”阶段的记录,实际上是整个系统分层验收的关键证据。它验证了动力链路、速度闭环、路径保持和状态机框架这四层基础能力,也为视觉识别接入提供了明确的接口和调试基线。下一步的核心工作,是完成摄像头选型、图像算法开发和与现有状态机的联调。
如果你现在也处于类似阶段,建议先不要急着加视觉代码。把当前版本的车速、PID 参数、状态机逻辑都整理成文档,录制一组多电量条件下的运行视频,作为基线保存。然后再开始视觉部分的开发,一次只加一个新模块,每加一个模块都跑一遍原来的测试流程,确保没有破坏已经稳定的部分。
这个项目真正难的不是某一块技术,而是把运动控制、状态管理和感知识别串成一个完整系统的工程能力。先把没有视觉的版本跑扎实,后面每一步都会更从容。建议把文章里提到的接口设计和排查表收藏备用,等你真正开工接入视觉时,应该能少踩几个坑。