简介:第七届全国大学生工程训练综合能力竞赛物流小车与机器人方向的完整工程包,主要面向参赛学生、指导教师及嵌入式机器人开发者。压缩包内共58个文件,整体约109KB,以C源程序与头文件为主,覆盖设备驱动、LCD显示、步进与直流电机控制、机械臂执行、坐标定位和总体控制逻辑等模块,并附带Keil MDK工程、STM32CubeMX配置(.ioc)、汇编启动文件(.s)、Markdown说明等辅助内容。通过这些源码和配置,读者能在标准IDE中打开、编译,并配合实际小车硬件进行下载调试,快速理解物流小车从感知、决策到执行的控制链路。代码按模块拆分、路径清晰,便于抽取其中的电机控制、传感器处理或机械臂动作等子程序复用到自己的项目中。目前已有197人学习使用,对备战工程训练赛、开展物流小车课程设计或机器人入门实践都颇具参考价值。
1. 国赛物流小车项目,先把 zip 包当成工程交付物来看
第七届全国大学生工程训练综合能力竞赛的智能物流小车赛项,表面拼的是机械结构和视觉算法,真正拉开队伍差距的地方往往在赛前一周:谁能把底盘运动学、抓取机构、视觉识别和赛场状态机整合进一个干净的工程包,谁就能在转场调试里少出问题。这个赛项要求小车在限定场地内完成物料识别、抓取、搬运和入库,看起来是“跑一圈”的流程,实际涉及麦克纳姆轮解算、串级 PID、视觉坐标系标定和任务状态调度,任何一环松了,整车就表现成时好时坏。我见过不少队伍代码散落在桌面,来赛场前临时整包拷贝,结果依赖路径全断,现场半小时都在解压缩包排错。所以这篇直接站在“交付一个能稳定跑完全程的固件工程”的角度,把选型、任务拆解、调参顺序和部署细节讲清楚。
2. 物流小车底盘、驱动与视觉单元的选型逻辑
2.1 麦克纳姆轮 vs 差速轮:底盘运动方案怎么定
物流小车赛项里,底盘运动方案直接决定任务流能不能程序化。常见方案有差速轮、舵轮和麦克纳姆轮三种。差速轮结构简单、控制成熟,但横向移动需要额外转弯动作,在狭窄货架区会浪费时间;舵轮精度高但机械复杂、重量大、学生调试门槛高。麦克纳姆轮的优势是能实现全向平移,配合四轮独立驱动,小车可以正对货架完成取放,不需要调头,姿态保持能力强。
麦克纳姆轮的动力学本质是把每个轮子的辊子与地面的摩擦力分解出前进和侧向两个分量,四个轮子的转速组合出不同的合成速度方向。控制上常用运动学逆解,把目标 vx、vy、omega 换算成四个轮子的转速:
import math R = 0.05 # 轮子半径,单位米 W = 0.25 # 左右轮距,单位米 L = 0.20 # 前后轴距,单位米 half = (W + L) / 2 def inverse_kinematics(vx, vy, omega): # 四个输出依次对应左前、右前、左后、右后轮 speeds = [ ( vx + vy + omega * half) / R, ( vx - vy - omega * half) / R, ( vx - vy + omega * half) / R, ( vx + vy - omega * half) / R, ] return speeds上面这段代码把目标速度向量拆到四个轮子。注意正负号取决于辊子安装方向,实际安装时如果发现车斜着走,把对应轮的符号取反即可。W 和 L 需要用实际车体尺寸替代,量的是轮子与地面接触点的中心距,而不是底盘外沿。轮子的辊子材质同样要纳入考虑,赛场地面一般是光滑亚克力板或喷绘布,硬质 PU 辊子摩擦系数适中,不会因打滑产生积分误差;橡胶辊抓地强但容易粘灰,跑两圈后阻力明显增大,PID 参数会漂移。
2.2 STM32 与编码器电机:控制板选型和关键参数
底盘控制我一般选 STM32F407,原因是物流小车里视觉和运动控制相对独立,视觉放在 OpenMV 上,底盘实时控制必须放在单片机里,保证编码器读取和 PID 输出不被打断。F407 有四个编码器接口和四个高级定时器可以用作 PWM 输出,主频 168 MHz,CubeMX 生成工程模板也快,适合赛项这种多人并行开发的场景。电机选择方面,37GB 或 520 编码器电机是主流,37GB 减速比常见 1:30,扭矩足够推动 3 kg 左右的车体;520 电机速度快但电流大,对电池和驱动板要求高。
编码器分辨率是选型时容易被忽略的点。国赛场地要求小车停在货架前时位置误差一般在 1 cm 以内,轮子直径 10 cm 的话,1 cm 对应大约 3.2° 的轮转角。不同编码器线数对应的单脉冲位移如下:
| 编码器线数 | 轮轴每转脉冲 | 单脉冲位移 | 停车表现 |
|---|---|---|---|
| 200 线 | 6000 | 0.52 mm | 抖动明显,方向相关 |
| 360 线 | 10800 | 0.29 mm | 可接受,偶尔超调 |
| 500 线 | 15000 | 0.21 mm | 稳定,成本略高 |
低于 200 线的编码器会明显感觉到停车位置抖动,而且抖动方向和负载方向相关,很难靠调参消掉。驱动芯片用 DRV8701 加外部 MOS,或者直接买集成双 H 桥模块,持续电流至少按电机堵转电流的 1.5 倍选。供电方面,2S 锂电池加 5V/3A 降压模块给主控和 OpenMV,舵机单独从电池直供,避免舵机启动瞬间把主控电压拉垮。
2.3 OpenMV 视觉单元:LAB 阈值与串口协议
视觉单元负责两件事:识别物料颜色和 AprilTag,以及引导机械臂到抓取点。OpenMV H7 Plus 在这个赛项里很合适,摄像头、图像处理和串口输出集成在一起,RGB 565 输出,识别色块和 Apriltag 都有现成库,不需要跑 Linux,上电 1 秒内就能进入识别状态,对现场抢时间很重要。识别颜色时,关键是 LAB 色彩空间阈值,OpenMV 的 find_blobs 默认在 LAB 空间做阈值匹配。现场调阈值时我会先关掉自动白平衡,再扫一遍色卡:
import sensor, image sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QQVGA) sensor.skip_frames(time=2000) sensor.set_auto_whitebal(False) # 关闭自动白平衡,防止色温变化导致阈值漂移 red_threshold = (60, 80, 15, 80, 5, 60) while True: img = sensor.snapshot() blobs = img.find_blobs([red_threshold], pixels_threshold=50, area_threshold=50) for blob in blobs: img.draw_rectangle(blob.rect(), color=(0, 255, 0)) img.draw_cross(blob.cx(), blob.cy(), color=(0, 255, 0)) print("cx=%d cy=%d area=%d" % (blob.cx(), blob.cy(), blob.area()))这段代码里最容易被忽略的是 set_auto_whitebal(False)。比赛现场的灯光一般是白色 LED 和自然光混合,不同灯位下色温差异很大,开着自动白平衡会让同一个红色物料在不同位置的 LAB 值变化超过 20,阈值完全没法固定。关掉白平衡后,用标准色卡在场地中央重新采一遍阈值,就能保证整个场地的识别一致。OpenMV 与 STM32 之间用 UART 通信,我一般把协议定义成固定帧:帧头、数据长度、识别物体 ID、x 坐标、y 坐标、置信度、校验和,底盘的代码不用关心 OpenMV 内部实现,拿到 x/y 直接进入导航逻辑。
3. 物流小车任务流程拆解与核心控制实现
3.1 取料—转运—入库:任务状态机设计
国赛物流小车赛项的场地逻辑围绕“取料—转运—入库”展开。场地里分布着物料暂存区、装卸区和若干库位,小车需要按顺序识别物料并放到对应库位。整个流程可以拆成五到七个固定状态,状态机是管理这类流程最稳妥的方式,比顺序执行多一个优势:任何一步失败都可以被捕获并做重试或跳过。我用一个枚举加 switch 实现状态机,转移条件收敛在几个输入函数里:
typedef enum { ST_INIT = 0, ST_SCAN_MATERIAL, ST_NAV_TO_SHELF, ST_GRAB, ST_NAV_TO_DROP, ST_RELEASE, ST_RETURN, ST_RECOVERY } TaskState; TaskState current_state; void task_dispatch(void) { switch (current_state) { case ST_SCAN_MATERIAL: if (vision_scan_done) current_state = ST_NAV_TO_SHELF; break; case ST_NAV_TO_SHELF: if (navigation_arrived()) current_state = ST_GRAB; break; // 其余状态同理 } }每个状态都要带超时保护。物流小车赛项现场最怕的不是慢,而是卡在某个状态里不动,比如机械臂没夹到东西但程序以为夹到了。我的做法是给每个状态设一个最大停留时间,超时后自动转入 ST_RECOVERY,把车退回上一个安全点重新尝试。状态机里还应该记录进入时间和当前尝试次数,方便串口日志里定位是在第几次重试之后成功的。
3.2 机械臂与滑台:串级 PID 参数整定顺序
抓取机构常见的是三自由度机械臂(底盘旋转、大臂抬升、夹爪开合)或者丝杆滑台加舵机夹爪。从稳定性角度我更推荐丝杆滑台方案,因为滑台的每个自由度跟运动方向一一对应,不需要解运动学逆解,调试时能直接预估每个伺服要走多少脉冲。伺服控制上,位置环和速度环要分开调,先用串口发固定速度指令,观察速度跟踪情况,调速度环 Kp 和 Ki,原则是让速度响应快但不过冲;开启位置环后再调 Kp,观察停车是否超调。下面是一组适合 37GB 电机的初始参数:
| 控制环 | Kp | Ki | Kd | 说明 |
|---|---|---|---|---|
| 速度环 | 0.08 | 0.02 | 0 | 上升快,稳态波动 ±3 RPM |
| 位置环 | 0.35 | 0.01 | 0.05 | 到位误差 < 4 个编码器脉冲 |
抓取动作本身要求“慢而稳”。我一般把抓取阶段的底盘最大速度限制在 0.1 m/s,加速度限制在 0.05 m/s²。机械臂运动时底盘如果还在微调抖动,夹爪对不准物料中心,后续放置误差会指数放大。这个限制在代码里用速度斜坡实现,而不是直接给目标速度,否则加速瞬间电流冲击会把视觉模块的供电电压拉低,造成 OpenMV 复位。
3.3 像素到车体坐标:单应矩阵标定与误差控制
OpenMV 识别到物料后传回像素坐标,控制板需要换算成车体坐标。我建议在 OpenMV 端直接完成坐标转换并发送车体坐标系下的 x、y,底盘的导航程序就不需要关心图像坐标。这个方案在比赛里最实用,因为 OpenMV 和底盘代码可以由不同队员维护,接口只暴露一个坐标和置信度。坐标换算是基于单应矩阵,在 OpenMV 的视角范围内找三个已知世界坐标的标记点,记录对应像素坐标,用 find_homography 求矩阵 H:
import image # 三个标定点:世界坐标 (x, y) 和 像素坐标 (u, v) world_pts = [(0.0, 0.0), (0.5, 0.0), (0.0, 0.4)] pixel_pts = [(120, 80), (400, 85), (115, 300)] H = image.find_homography(pixel_pts, world_pts) # H 是 3x3 矩阵,后续用 H.inverse() 将像素坐标映射到世界坐标标定时注意不要把标记点集中在一个小区域,否则矩阵病态,离群点误差会被放大。物料如果放在地面上,标定平面和识别平面一致,误差可以控制在 1.5 cm 以内。使用中要定期验证 H 是否失效,最简单的验证是放一个已知坐标的物料在场地随机位置,让 OpenMV 输出换算后的坐标,与实测值对比,偏差超过 3 cm 就重新标定。比赛现场灯光改变、镜头被撞、地板不平整都会导致 H 退化。
3.4 麦克纳姆轮底盘:分段直线与原地转向转速表
底盘导航常见的做法是“先转角度、再走位移”,或者“边走边转”。货架区空间窄,我采用“分段直线 + 原地转向”策略,每段路径只有两种运动:直线前进或原地旋转。这样麦克纳姆轮的解算退化成更简单的形式,前向运动四个轮子同速,旋转四个轮子对角反向,误差集中在终点前的减速上。四个轮子的转速组合如下:
| 运动类型 | 左前 | 右前 | 左后 | 右后 |
|---|---|---|---|---|
| 前进 | +V | +V | +V | +V |
| 后退 | -V | -V | -V | -V |
| 左平移 | -V | +V | +V | -V |
| 右平移 | +V | -V | -V | +V |
| 顺时针转 | +V | -V | +V | -V |
这张表是麦克纳姆轮的经典转速组合。实际跑的时候有一个坑:四个轮子的安装方向如果有一个错误,左平移会变成斜向移动。验证方法是在车体架空时用手动模式发“左平移”指令,如果车体带纵向分量,说明某个轮子的辊子装反了。另一点是转向要以陀螺仪为准而不是累计编码器差值,编码器累计角度误差会随着旋转次数增加而膨胀,陀螺仪零漂在单次转向里可以被忽略。
4. 固件工程组织、zip 打包与赛场部署
4.1 固件工程目录结构与模块职责划分
比赛后期工程代码量通常在 3000 行以上,参与调试的有三四个人,目录结构不清楚,现场改参数组件很容易改完找不到。我习惯的目录结构是底层驱动和上层逻辑严格隔离,上层逻辑全部通过接口调用底层驱动,比如 motor_set_speed(id, rpm)、servo_goto(id, angle)。这样调 PID 时只动模块层和 config 目录,不会误改到串口驱动。
project/ core/ main.c state_machine.c drivers/ motor.c / motor.h encoder.c / encoder.h servo.c / servo.h modules/ pid.c / pid.h navigation.c / navigation.h vision_port.c / vision_port.h config/ config.h pid_params.h track_map.hconfig 目录是整个工程的“现场开关”。场地尺寸、物料摆放位置、速度限制全部放进 config.h,现场改参数只需要重新编译,不用到处翻代码。这个习惯在赛前两个星期开始坚持,能避免很多“我昨天明明改过了”的争论。track_map.h 里存库位坐标,以现场公布的图纸为准,不要沿用往届数据,因为国赛的场地布局每年都有调整。
4.2 用 zip 打包固件:一条命令生成干净源码包
比赛现场最常见的混乱是工程从一台电脑拷到另一台电脑时路径失效。Keil 和 STM32CubeIDE 生成的文件包含绝对路径,换电脑编译时中间文件全部失效。赛前的工程包,只保留源码、构建脚本和依赖说明,不包含 build 或 Debug 目录:
# 在工程根目录执行,生成干净的源码包 zip -r logistics_car_firmware_$(date +%Y%m%d).zip core drivers modules config \ -x "*/build/*" "*/Debug/*" "*/Release/*" "*.o" "*.elf" "*.map"这条命令里去掉了编译产物,只留源码和配置。zip 包单文件、可校验、带时间戳,到了赛场解压后直接打开工程编译,不会缺文件。配合 md5sum 生成校验值,能避免 U 盘拷贝过程中文件损坏导致的诡异编译报错。如果担心转场过程中源码被误改,可以用 zip 加密打包,但密码一定要记录在团队共享文档里,否则赛后复盘时会打不开,这个场景经常发生在多人冲刺阶段。
4.3 从 floor 到 site_b:赛场多版本配置管理
转场阶段最怕前一天调好的参数到第二现场不适用。我一般准备三个版本的配置:config_floor.h、config_site_a.h、config_site_b.h。每次现场调完一组关键参数,立刻把 config.h 复制一份加上现场名。第二轮调试出问题时可以快速回退到之前能跑的版本,不需要从头调。配置内容和对应场景可以这样管理:
| 配置版本 | 速度上限 m/s | 抓取延时 ms | 视觉阈值文件 | 备注 |
|---|---|---|---|---|
| floor | 0.25 | 300 | th_floor.py | 实验室地面 |
| site_a | 0.20 | 500 | th_site_a.py | 第一赛场,光照偏暗 |
| site_b | 0.28 | 350 | th_site_b.py | 第二赛场,有侧光 |
视觉阈值和底盘 PID 分文件保存,它们的相关性弱但都会影响状态机超时。现场环境变化时优先调整阈值,底盘 PID 不动,除非换地面材质。每个版本的 config 里我用宏开关控制启用哪一组,比如#define SITE_ID site_b,而不是手动注释掉其他配置,避免现场改错行。
4.4 解压后编译失败:头文件路径与编码排查
解压 zip 包重新编译时,最常见的错误是找不到头文件或外设库。Keil 工程文件里的头文件路径是绝对路径,换电脑后需要重新指定。我建议在工程目录放一个 build_env.md,写下需要的 IDE 版本、芯片支持包版本和库路径。另一个常见的坑是 zip 包带了中文文件名或者路径里有空格,Windows 解压偶尔会把路径解析错,打包前用ls -lR检查目录结构,确保所有路径都是 ASCII 字符。代码里的中文注释也会因编码不同在换电脑后报错,统一用 UTF-8 编码保存源文件。
提示:解压后先完整编译一次,确认零错误再改参数。不要在没验证编译的工程上直接改赛场逻辑,错误定位成本会翻倍。
5. 赛前调参的一线记录与稳定性验证
5.1 调参顺序:先底盘后取放,先单步后全流程
很多队伍在赛前几天陷入“全流程反复跑,每次都差一点”的循环,原因是没把调参拆成独立步骤。我的顺序:第一步只验证底盘在场地上的定位精度,让小车直线跑两米,记录最终位置偏差,调整位置环 Kp 直到误差小于 1 cm;第二步只验证机械臂抓取,物料在固定点,从同一姿态抓 20 次统计成功率;第三步加上视觉识别,让物料随机摆放,识别后抓取 10 次;最后才合成完整流程。每步数据记录在案,哪一步失败就知道问题在哪个模块。
5.2 录制回放与功耗观测:两个低成本验证手段
验证环节推荐两个手段。录制回放是让小车完整跑一遍,把电机转速、编码器位置、状态机各状态的时间戳全部通过串口写进 SD 卡,跑完后离线逐帧分析,可以看到“到位超时”发生在哪个编码器位置,而不是只看最终失败。功耗观测用 USB 电流计串在电池和主控之间,满电下连续跑十圈,如果某一圈电流明显升高,说明机械结构出现卡滞或轮子被地面杂物缠住。这两个方法在工创赛智能物流小车里特别好用,因为它们不依赖额外仪器,关键是养成每次测试都留日志的习惯,而不是只在出问题时才开日志。
5.3 串口命令行在线调参:省下 20 分钟的现场技巧
最后送一个赛场技巧:把关键延时参数集中到配置表里,用串口指令在线修改,不重新编译。做法是在 UART 中断里加一个命令行解析器,支持set speed 0.25、set grab_delay 500这类指令,这样不打开电脑也能临时调整参数,同时修改记录输出到终端方便归档。解析器本身只需要几十行代码,却能在赛前调试窗口里省下大量编译时间,也避免反复插拔 USB 带来的接口风险。配合前面提到的多版本配置,现场改动可以快速写回对应版本的 config 文件,赛后复盘时每一轮参数调整都有据可查。
本文还有配套的精品资源,点击获取