2026/7/30 16:21:40

智能送药小车系统设计:从STM32控制到OpenMV视觉识别的工程实践

智能送药小车系统设计:从STM32控制到OpenMV视觉识别的工程实践 1. 项目概述与核心价值看到“2021年电赛F题智能送药小车国二开源分享”这个标题相信很多参加过电赛或者正在备赛的同学都会心头一热。这不仅仅是一个开源项目更是一份凝结了无数个通宵调试、方案迭代和临场应变经验的“战地笔记”。2021年的F题全称是“智能送药小车系统”是当年本科组中极具代表性的控制类题目它综合考察了机械结构设计、运动控制、视觉识别、通信调度和系统集成等多方面的能力。能拿到国二奖项意味着这套方案在稳定性、完成度和创新性上都已经达到了相当高的水准。如今将整个项目开源其价值远超代码本身它为我们提供了一个绝佳的“解剖样本”让我们能深入理解一个成功的电赛作品是如何从零到一构建又是如何在紧张的比赛周期内应对各种突发状况的。这个开源项目最核心的价值在于“真实性”和“完整性”。它不像某些经过精心修饰的教程只展示最理想化的路径。相反它很可能包含了当时为了赶进度而采用的“土办法”为了提升稳定性而做的冗余设计甚至是比赛现场发现的硬件BUG和软件补丁。对于后来者而言这些“不完美”的细节恰恰是最宝贵的财富。你可以清晰地看到在有限的四天三夜里一支队伍是如何进行任务分解、技术选型、风险权衡和最终整合的。从主控芯片的选型到电机驱动电路的布线从OpenMV识别药房的策略到与安卓上位机通信的协议设计每一个环节都充满了实战的考量。学习这个项目你学到的不是某个孤立的算法而是一套完整的、经过国赛检验的工程化思维和问题解决方法论。2. 核心需求与任务拆解要复现或深入理解这个智能送药小车第一步必须是彻底吃透赛题任务书。2021年F题的核心任务是要求小车在模拟医院病房的场地上自主完成从药房出发到不同病房由数字编号标识送药并最终返回药房的全流程。这个过程看似线性实则暗藏玄机需要我们将总任务拆解为多个可并行或串行开发的子模块。2.1 任务场景与功能需求解析赛题场地通常铺设黑色电工胶带作为引导线构成包含十字、丁字、弯道和环岛等元素的复杂路径。病房位置以数字标签形式放置在路径旁。药房则是一个特定的区域可能带有特殊的视觉标识如二维码或特定图案。小车的基本功能需求可以分解为以下几点循迹导航这是小车的“双腿”必须稳定、快速、抗干扰地沿着黑线行驶并能正确处理所有类型的路口。病房识别这是小车的“眼睛”。需要准确识别路径旁的病房编号通常是0-9的数字并将编号与物理位置对应起来。任务调度与决策这是小车的“大脑”。需要接收上位机如安卓APP下达的送药指令例如“送药到3号病房”然后规划行驶路径去程执行送药动作如通过机械臂或舵机抛投药品并规划返回药房的路径。通信交互小车需要与上位机稳定通信接收指令并上报状态如“已到达3号病房”、“开始送药”、“返回药房”等。机械执行设计一个可靠、简单的送药机构如舵机控制的翻转托盘或推杆能准确将模拟药品可能是小木块或乒乓球投入病房区域。2.2 系统方案选型背后的逻辑一个国二水平的方案在技术选型上必然经过深思熟虑。以下是基于常见电赛实践和该题目特点的合理推演主控芯片MCUSTM32F4系列如F407或F429是极大概率的选择。原因在于第一性能足够。F4系列带有FPU浮点运算单元对于需要做图像处理即便用了专门的视觉模块主控也可能需要处理结果或复杂控制算法如PID调参的场景非常友好。第二资源丰富。具有足够的定时器用于产生PWM控制电机和舵机、串口用于与视觉模块、蓝牙/WIFI模块通信、IO口等。第三生态成熟。资料多库函数完善遇到问题容易找到解决方案这在分秒必争的电赛中至关重要。视觉方案OpenMV是当时的最优解甚至可能是“标配”。OpenMV是一款集成了MicroPython解释器和摄像头的开源机器视觉模块。它的优势在于“开箱即用”内置了颜色识别、模板匹配、二维码识别、AprilTag识别等丰富的视觉算法库。对于识别固定的数字模板或特定的药房标识使用OpenMV的find_template或find_apriltags函数可以极大降低开发难度将团队从复杂的底层图像算法中解放出来专注于应用逻辑。它通过串口与主控STM32通信发送识别结果。循迹方案多路红外对管灰度传感器是主流且可靠的选择。相比于摄像头循迹红外对管方案更简单、响应更快、受环境光影响相对较小。通常会在小车前部布置5-7个红外对管呈一字排开。中间的对管用于精确循迹两侧的对管用于检测路口当两侧传感器同时检测到黑线即为十字或丁字路口。这种方案稳定可靠调试直观。通信方案蓝牙如HC-05/HC-08或WIFI如ESP8266。题目要求与安卓上位机通信这两种方案都很常见。蓝牙配对简单功耗低但传输距离短WIFI传输距离远可组网但配置稍复杂。考虑到比赛场地通常不大且需要稳定连接使用经典蓝牙模块HC-05是很多队伍的选择。通信协议通常自定义一个简单的帧结构例如[帧头][指令类型][数据][校验和][帧尾]。驱动与电源电机驱动常用TB6612FNG或DRV8833这类双路H桥驱动芯片它们比古老的L298N效率高、发热小。电源管理是重中之重必须为单片机、传感器、舵机和驱动电机提供独立且稳定的电压通常会用多个稳压模块如LM2596、AMS1117从总电池如2S或3S锂电池降压得到5V和3.3V。注意开源项目中具体用了哪些型号需要查看其源码和文档。但以上选型代表了在当时技术条件下平衡性能、难度、可靠性和开发速度后的最优解集合。3. 硬件系统设计与核心细节硬件是系统的骨架设计不合理软件再优秀也无法稳定运行。一个国二水平的硬件设计必然在可靠性、可调试性和电磁兼容性EMC上下了功夫。3.1 车体结构与传感器布局车体通常采用现成的亚克力板或碳纤维板小车底盘关键在于传感器和核心模块的布局。红外循迹传感器阵列安装在前轮轴心稍前方的位置离地高度需仔细调节通常1-2厘米以确保对黑色胶带有良好的检测灵敏度同时避免地面反光干扰。传感器排布间距要略小于引导线宽度以确保在任何弯道上至少有一个传感器能检测到线。OpenMV摄像头安装高度和角度需要经过严格标定。太高或太低都会影响识别距离和精度。通常需要向前下方倾斜使其视野能覆盖前方约30-50cm的路面以及路旁的病房标签。一个关键技巧在OpenMV镜头上方加一个遮光罩用黑色电工胶带卷成筒状可以极大减弱顶部灯光造成的眩光显著提升图像质量这是很多新手容易忽略的细节。主控与驱动板应集中布置在车体重心附近降低重心提高稳定性。STM32核心板、电机驱动板、电源模块之间通过排针和杜邦线连接时务必对电源线VCC, GND进行“加固”比如并联焊接或使用更粗的导线避免因接触不良导致系统复位这是比赛中最令人崩溃的故障之一。送药机构机构设计应遵循“简单可靠”原则。常见的有舵机翻转托盘式一个平放的托盘由舵机控制到达病房时舵机转动一定角度将药品倒出。优点是结构简单缺点是药品落点不精确。推杆推出式利用舵机或小型直线推杆将药品从车厢侧面推出。落点相对可控。机械臂夹取式复杂度高除非有很强机械基础否则在电赛紧张周期内风险极大。国二方案很可能采用前两种之一。3.2 电路设计与电源管理稳定的电源是“生命线”。许多赛场上的诡异问题如单片机莫名复位、传感器数据跳动、电机突然无力都源于电源干扰。分级供电与隔离动力电源直接使用电池如7.4V锂电池供给电机驱动模块。电机启停时会产生巨大的电流波动和反向电动势必须与控制系统隔离。控制系统电源通过一个独立的DC-DC降压模块如LM2596将电池电压降至5V。这个5V再供给舵机、OpenMV、蓝牙模块等外设。核心电源再用一个LDO稳压芯片如AMS1117-3.3从5V降压到3.3V专门给STM32单片机和一些3.3V的传感器供电。LDO相比DC-DC纹波更小对单片机更友好。滤波与退耦在每个芯片的电源引脚附近紧贴芯片放置一个0.1uF的陶瓷电容104进行高频滤波同时并联一个10uF的电解或钽电容进行低频储能。这能有效抑制芯片工作时产生的电源噪声。电机干扰抑制在电机的两个引脚之间焊接一个0.1uF的瓷片电容可以吸收电刷产生的火花干扰。在驱动芯片的电源输入处也要加大容值的滤波电容。实操心得我们当时吃了大亏。最初将所有设备都接在同一路5V上一旦舵机动作瞬间电流可达1-2ASTM32的电压就会被拉低导致串口数据乱码甚至重启。后来改为舵机单独一路5V供电仍由同一个LM2596输出但布线分开并加大输入输出电容单片机和其他逻辑器件用另一路经过LC滤波的5V问题彻底解决。这个小改动花了我们大半天调试时间教训深刻。4. 软件架构与核心算法实现软件是系统的灵魂。一个好的软件架构能让调试事半功倍。对于智能送药小车通常采用“前后台”或“多任务协作”的架构。4.1 主程序流程与状态机设计主控STM32的程序通常围绕一个超级循环main loop和定时器中断展开。更清晰的架构会引入状态机State Machine来管理小车的工作流程。// 伪代码示例小车主状态机 typedef enum { STATE_IDLE, // 空闲等待上位机指令 STATE_RECEIVED_TASK, // 已接收任务 STATE_NAV_TO_ROOM, // 导航至目标病房 STATE_ARRIVED_ROOM, // 到达病房区域 STATE_DELIVERING, // 执行送药动作 STATE_NAV_TO_DEPOT, // 导航返回药房 STATE_ARRIVED_DEPOT, // 到达药房 STATE_ERROR // 错误状态 } CarState_t; CarState_t gCarState STATE_IDLE; uint8_t gTargetRoomNum 0; void Main_Loop(void) { switch(gCarState) { case STATE_IDLE: // 检查蓝牙串口是否有新指令 if (UART_ReceivedNewCommand()) { ParseCommand(gTargetRoomNum); // 解析出目标病房号 gCarState STATE_RECEIVED_TASK; } break; case STATE_RECEIVED_TASK: PlanPath(gTargetRoomNum); // 规划路径可能简单到只是设定一个目标标志 gCarState STATE_NAV_TO_ROOM; break; case STATE_NAV_TO_ROOM: // 在这个状态下核心是循迹和路口处理 TraceLine_Handler(); // 循迹控制函数 Cross_Handler(); // 路口处理函数 // 当视觉识别到目标病房号且满足停车条件如到达特定地点时 if (OpenMV_DetectTargetRoom(gTargetRoomNum) ShouldStop()) { gCarState STATE_ARRIVED_ROOM; } break; case STATE_ARRIVED_ROOM: StopCar(); gCarState STATE_DELIVERING; break; case STATE_DELIVERING: ExecuteDelivery(); // 控制舵机完成送药动作 Delay_ms(500); // 等待动作完成 gCarState STATE_NAV_TO_DEPOT; break; // ... 其他状态处理 case STATE_ERROR: StopCar(); // 通过蓝牙向上位机报警 UART_SendErrorMsg(); // 等待复位或远程指令 break; } }状态机的优势在于逻辑清晰易于调试。你可以通过一个全局变量就知道小车当前在干什么出现问题时也能快速定位到哪个状态出了错。4.2 循迹控制算法PID的实战调参循迹的核心是PID控制。红外对管阵列读回的数据需要转化为一个“偏差量”。例如采用5路传感器从左到右编号为1-5。定义当只有中间第3路检测到黑线时偏差为0第2路检测到偏差为-1第1路检测到偏差为-2反之亦然。这样就将位置信息量化了。// 偏差计算示例 int Calculate_Error(void) { int sensor_values[5]; // 0:白线1:黑线 int error 0; // 假设传感器状态为 [0, 1, 1, 0, 0] if (sensor_values[1] 1) error -1; else if (sensor_values[0] 1) error -2; else if (sensor_values[2] 1) error 0; // 中间 else if (sensor_values[3] 1) error 1; else if (sensor_values[4] 1) error 2; else { // 全部检测到白线或黑线可能是脱线或路口需特殊处理 error 100; // 用一个特殊值表示 } return error; }得到偏差error后代入PID公式计算电机PWM输出增量output Kp * error Ki * integral Kd * (error - last_error)PID调参是玄学也是科学比例P决定了对当前偏差的反应速度。P太大小车会在黑线两侧剧烈振荡像喝醉了一样P太小反应迟钝过弯时容易脱线。积分I用来消除静态误差。如果长期有一个固定的小偏差比如小车结构轻微不对称导致总是偏右I项会累积这个偏差并修正。但I太强容易导致“积分饱和”引起超调和震荡。微分D预测未来趋势抑制振荡。当偏差快速减小时D会产生一个反向力防止小车冲过头。D能提升系统稳定性但对噪声敏感如果传感器数据有毛刺D项会放大噪声。调参实战口诀“先P后I再D从小到大慢慢试”。首先将I和D设为0只调P让小车能基本沿着线走即使有振荡。然后慢慢增加D你会发现振荡被有效抑制了过弯更平滑。最后如果小车在直道上存在固定的偏向再引入一个很小的I值。关键技巧将调参参数Kp Ki Kd通过蓝牙实时发送到上位机显示并能在上位机上修改后下发给小车实现“无线调参”这能极大提升调试效率。4.3 视觉识别与通信协议OpenMV端的程序相对独立。它的主要任务是初始化设置摄像头分辨率、帧率加载数字模板图片。循环捕捉图像在main.py的循环中持续抓取图像。图像处理与识别# OpenMV IDE 示例代码片段 import sensor, image, time, pyb from pyb import UART uart UART(3, 115200) # 初始化串口3与STM32通信 # 加载模板图片 (需要在SD卡中提前存放0-9的数字模板图) templates [/0.pgm, /1.pgm, ... , /9.pgm] while(True): img sensor.snapshot() for i, template_path in enumerate(templates): template image.Image(template_path) # 在img中查找template r img.find_template(template, 0.7) # 0.7是相似度阈值 if r: # 找到了匹配的模板 print(Found: %d % i) # 通过串口发送识别结果例如发送字符 0~9 uart.writechar(ord(0) i) # 可以添加矩形框绘制等调试代码 img.draw_rectangle(r) break # 找到就跳出循环 time.sleep_ms(50) # 控制识别频率串口通信将识别到的数字通过串口发送给STM32。协议要简单可靠例如直接发送ASCII字符‘3’代表识别到3号病房。STM32端通过中断或轮询方式接收串口数据并设置一个缓冲区和一个简单的协议解析器。例如约定OpenMV每次发送一个字节的有效数据。STM32收到后将其与当前任务目标病房号比对并判断是否到达了需要停车的“识别区域”通常通过路程估算或特定路标判断。4.4 路口处理与路径决策这是导航逻辑的难点。小车需要根据当前任务和识别到的路口类型决定直行、左转、右转还是掉头。路口类型判断依靠红外传感器阵列。当中间传感器在线且左边和/或右边传感器也检测到线时即为路口。通过记录左右传感器触发顺序和持续时间可以区分十字路口、左丁字口、右丁字口等。决策逻辑需要为小车“绘制”一张简单的地图。例如假设场地是“日”字形路径药房在左下角病房分布在路径节点。可以为每个可能的路口编号并预设小车在不同任务下到达该路口时应执行的动作转左、转右、直行。方法一状态记忆法。小车从起点开始记录经过的每个路口及其转向动作。当需要返回时按相反顺序执行反向动作。这种方法逻辑简单但依赖于初始定位和精确的转角控制。方法二绝对坐标法更优。为场地建立一个粗略的坐标系每个路口和病房都有坐标。小车通过里程计编码器或视觉路标如AprilTag估算自身坐标。这样无论小车在哪里都能通过坐标计算前往目标点的方向。这对于2021年F题这种需要往返多次的任务鲁棒性更强。一个关键实现细节在路口转向时不能简单地让一边轮子正转另一边反转来实现“原地转向”因为这样容易偏离引导线且对轮胎磨损大。更好的做法是让小车继续循迹直到前轮轴心越过路口中心然后根据转向方向让内侧轮子低速、外侧轮子高速以一个弧线轨迹平滑过弯过弯后继续进入循迹模式。这需要精细的电机差速控制。5. 系统调试与问题排查实录电赛作品的开发过程80%的时间可能都在调试和解决问题。以下是基于经验总结的常见问题库和排查思路。5.1 硬件层常见问题问题现象可能原因排查步骤与解决方案单片机频繁无故复位1. 电源干扰电机/舵机动作时发生2. 复位电路电容不良3. 程序跑飞看门狗未喂1. 用示波器观察单片机VCC引脚电压在电机启动时是否有跌落应高于芯片最低工作电压。加强电源滤波电机电源与逻辑电源隔离。2. 检查复位引脚电路确保上拉电阻和电容值正确。3. 检查程序是否在死循环中阻塞太久启用并正确配置独立看门狗IWDG。红外循迹传感器数值跳动不稳1. 传感器离地高度不合适2. 环境光干扰特别是赛场顶灯3. 供电不稳或受到电机干扰1. 调整传感器高度并尝试在传感器发射管和接收管上套上黑色热缩管防止串光。2. 在传感器下方增加局部遮光罩。3. 为传感器供电的5V线路增加磁珠和滤波电容。传感器信号线远离电机驱动线。OpenMV识别距离近或误识别1. 摄像头焦距、角度未调好2. 光照条件变化3. 模板图片质量差或匹配阈值设置不当1. 固定小车通过OpenMV IDE实时画面调整摄像头角度和焦距使目标在预期距离内清晰可见。2. 增加遮光罩或采用自适应阈值算法如image.binary([(gray_threshold)])。3. 重新拍摄在比赛现场光照条件下的模板图片并微调find_template的相似度阈值。蓝牙通信时断时续或数据错误1. 电源干扰导致串口电平不稳2. 蓝牙模块与手机距离过远或有遮挡3. 双方波特率等参数未正确匹配4. 未处理数据粘包问题1. 确保蓝牙模块供电稳定单独LDO供电。2. 缩短距离避免金属遮挡。3. 确认STM32串口与蓝牙模块的波特率、数据位、停止位、校验位完全一致。4. 在通信协议中加入帧头帧尾和校验和并在接收端实现状态机解析避免因数据接收不完整而误解析。5.2 软件与算法层常见问题问题现象可能原因排查步骤与解决方案小车循迹时“画龙”左右振荡PID参数不合适通常是P太大或D太小。重新调参遵循“先P后D”原则。技巧在直道上调试目标是让小车轻微、缓慢地振荡过中线然后被拉回如此反复。这是一种比较理想的状态。过弯时冲出引导线1. 入弯速度太快2. 弯道PID参数与直道相同不适应3. 传感器在弯道上检测不到线1. 在检测到弯道例如持续一段时间偏差绝对值较大时动态降低小车速度设定值。2. 实现两套PID参数直道用一套弯道用另一套更 aggressive 的P或D。3. 检查传感器布局确保在最大弯道时仍有一到两个传感器能检测到线。在路口误判或转向错误1. 路口检测传感器阈值或逻辑有误2. 转向控制时序不对未完全驶过路口中心就转向3. 决策逻辑混乱状态机状态切换错误1. 增加路口检测的“去抖”逻辑例如要求两侧传感器持续检测到黑线超过100ms才判定为路口。2. 优化转向流程进入路口后先继续循迹一小段距离用延时或编码器计数确保车体中心过了路口再执行转向动作。3. 在关键状态切换点通过串口打印当前状态和传感器数据结合上位机监控进行逻辑分析。OpenMV识别到病房号但小车不停车或误停车1. 串口数据接收解析错误2. 停车条件判断逻辑不严谨例如只在某个特定位置才判断视觉结果3. 视觉识别频率过高在病房区域外偶然误识别1. 在STM32端增加串口接收调试输出确认收到的数据与OpenMV发送的一致。2. 采用“多条件联合判断”例如必须同时满足“进入病房识别区域标志”如经过某个特定路口后且“连续3次识别到目标号码”才触发停车。这能有效防止误触发。3. 限制OpenMV的识别区域ROI只对图像中病房标签可能出现的区域进行搜索提升效率降低误报。5.3 系统集成与稳定性提升当各个模块单独测试都正常但整合起来就出问题时问题往往出在“交互”和“时序”上。中断冲突确保不同优先级的中断如定时器中断用于PID计算串口中断用于接收数据不会互相长时间阻塞。在中断服务函数ISR中只做最必要的操作如置标志、存数据复杂的处理放到主循环中。资源竞争如果多个任务如循迹控制、视觉数据处理、通信都需要使用串口或全局变量要做好互斥保护防止数据被意外修改。心跳与超时机制为所有关键流程添加超时判断。例如从发出转向指令开始如果5秒内未完成转向并重新检测到引导线则判定为转向失败进入错误恢复流程如后退一点再尝试而不是傻等。现场适应性调整比赛现场的光照、地面摩擦系数、电池电量都与实验室不同。程序里要预留一些可通过上位机实时调整的参数如PID参数、电机基础速度、视觉识别阈值等。到了现场根据实际情况微调这些参数往往能起到立竿见影的效果。6. 从开源项目到个人备赛的进阶思考拿到一个国二开源项目直接照搬代码和硬件连接图可能能让你的小车动起来但距离你真正掌握并能在新赛题中灵活运用还差得很远。我建议按以下步骤深度挖掘这个开源项目的价值第一步复现与理解。按照开源项目的README尽可能1:1地复现硬件和软件。在这个过程中你会遇到各种预料之外的问题库版本、编译器设置、硬件批次差异解决这些问题的过程就是学习的过程。确保小车能完整跑通赛题基本要求。第二步分析与注释。不要满足于“它能跑”。逐行阅读核心代码特别是状态机、PID控制、路口处理、通信解析这几个部分。为每一段复杂的代码加上你自己的注释用自己的话解释“这里为什么要这么做”。尝试画出系统的软件架构图和数据流图。第三步修改与破坏。这是提升的关键。尝试修改一些参数或逻辑观察小车行为的变化。把PID参数调乱看看小车会怎么跑。修改路口转向的延时看是转早了还是转晚了。模拟一个传感器故障拔掉一个红外对管看你的程序能否容错尝试增加一个功能比如让小车在送药完成后播放一段声音提示。第四步重构与创新。在完全理解原有方案的基础上思考其局限性并尝试用你认为更好的方式去改进。原来的视觉识别用的是模板匹配你是否可以尝试用特征点匹配或者简单的CNN分类如果OpenMV支持来提升抗光照和角度变化的鲁棒性原来的路径决策是硬编码的你是否可以设计一个更通用的、基于地图坐标的导航算法原来的通信协议很简单你是否可以设计一个带重传、确认机制的更可靠的协议通过这四步你吸收的就不再是一个项目的“形”而是其背后的“神”——即面对一个复杂工程问题时如何分析需求、拆解模块、选型设计、调试排错和迭代优化的完整思维框架。这套框架才是你应对未来任何电赛题目乃至更复杂工程项目的核心武器。这个2021年国二开源项目就是一个绝佳的、高水平的训练案例。把它吃透你的备赛之路就成功了一大半。