资讯动态

OpenMV+STM32双MCU物料搬运机器人:视觉与串口通信实战解析

发布时间:2026/9/9 1:49:29 来源:尧图企业网站定制
简介这套代码包基于OpenMV与STM32F103C8T6提供完整的物料搬运机器人实现方案面向参加工程训练比赛或嵌入式系统学习的学生解决机器视觉识别、物料搬运控制等实际问题。资源共287个文件以C语言源码为主包含.c/.h源文件、.o编译中间文件、.hex固件、Keil工程配置、.map链接映射及启动文件等压缩后仅6.62MB目录结构清晰便于检索。内容覆盖OpenMV图像采集与物料定位、STM32外设驱动、串口通信、巡线行走及抓取释放逻辑同时也体现图像预处理、特征提取、目标检测和实时任务调度思路由于硬件抽象程度较高代码具备良好移植性可扩展到其他STM32型号方便学习者参照其分层设计快速二次开发。目前已有1638人浏览/下载是嵌入式视觉与运动控制结合的较好参考工程。 这个压缩包名字一看就是典型的“视觉识别 运动控制”双MCU方案在智能车竞赛、高职技能大赛和本科毕设里非常常见。很多朋友拿到代码后第一反应是烧录试试结果OpenMV画面出来了、STM32也能亮灯但物料就是抓不起来或者识别到了却传不过去。这篇我就把这套系统的完整实现思路、双芯片之间的通信协议、代码逻辑和联调坑点都拆开讲清楚让你不只会“跑通代码”更能自己改、自己调、自己排错。1. 项目整体架构一台搬运机器人为什么需要“两个大脑”1.1 OpenMV的角色眼睛和初级判断OpenMV本质上是一块自带摄像头、跑MicroPython的嵌入式视觉开发板。它在这个项目里干的活简单说就三件事找到物料、判断物料、把位置告诉给STM32。以最常见的物料分拣场景为例传送带上过来红色、蓝色、绿色三种小方块OpenMV通过find_blobs()函数在帧图像里搜索色块然后输出色块的中心坐标(cx, cy)、色块面积area和颜色编号code。这些数据本身不产生任何物理动作但它是整个系统的“感知源头”。这里要注意OpenMV不是万能的它擅长的是“基于颜色/形状的规则识别”而不是深度学习。有人非要拿它跑YOLO跑起来帧率只有个位数完全没有实用价值。在搬运机器人这个场景里规则的色块识别 边缘检测就足够用了没必要上高不可攀的模型。1.2 STM32F103C8T6的角色手脚和调度大脑STM32F103C8T6是一颗基于ARM Cortex-M3内核的入门级MCU主频72MHzFlash 64KBRAM 20KB。很多人第一眼看这个配置觉得不过尔尔但在物料搬运机器人这个场景里它承担了真正的“干体力活”部分解析OpenMV通过串口发来的坐标数据和颜色指令控制直流减速电机的启停、正反转和PWM调速控制舵机或机械臂气缸的夹取、放下动作根据当前运动状态切换工作模式寻找→抓取→搬运→放下→复位。这个芯片的优势在于外设接口齐全USART、TIM、GPIO、DMA都有、生态资料丰富正点原子、野火的例程满天飞、价格便宜最小系统板十几块就能拿下。对于课程设计和竞赛项目用它做执行控制是性价比极高的方案。1.3 为什么是OpenMV STM32而不是树莓派 Arduino有人会问既然OpenMV本身就有一颗MCU为什么还要加STM32直接用OpenMV的IO口驱动电机不行吗可以但很危险。OpenMV的IO驱动能力很弱而且它的主芯片要跑图像算法CPU占用率已经很高了如果再去处理电机PWM、编码器计数这些实时性要求极高的任务很容易出现“图像卡顿、电机失控”的情况。视觉和运动控制拆分到两片芯片上是工业控制里非常经典的“感知与执行分离”架构。至于树莓派 Arduino的组合性能确实更强但功耗高、体积大、启动慢而且树莓派加摄像头加散热片的成本直接翻好几倍。在“成本敏感、环境相对固定”的搬运机器人场景OpenMV STM32是经过无数竞赛验证的稳定方案。2. 硬件选型物料搬运机器人的四大部分2.1 视觉部分OpenMV Cam选型OpenMV产品线里最常用的是OpenMV M7、M4和H7 Plus。我在这个项目里更推荐OpenMV H7 Plus原因是它的处理器性能更好处理QVGA320x240分辨率的画面时能稳定跑到30~50帧而且H7 Plus有专用的WiFi扩展接口后续如果你想做远程调试会方便很多。预算有限用M7也没问题只要能忍受帧率低一点、色块识别偶尔掉帧。选购OpenMV时务必注意排线接口建议搭配一块LCD扩展屏进行现场调试——不然你根本看不见摄像头到底在看什么这会在后面联调时让你抓狂。2.2 主控部分STM32F103C8T6最小系统板STM32F103C8T6最小系统板是淘宝里的“蓝色药丸”板板载8MHz晶振、USB转串口芯片、复位电路和LED指示灯。买的时候注意两点一是尽量选带ST-Link下载接口的版本用ST-Link下载比串口ISP下载稳定得多二是如果要做比赛建议买两片板子备用这芯片引脚很密热插拔时容易烧。2.3 执行机构电机、驱动模块与舵机物料搬运机器人的执行机构通常分两类类型典型器件控制方式用途移动底盘四路直流减速电机TT马达或JGA25L298N或TB6612模块PWM调速控制机器人前后左右移动抓取机构SG90或MG996R舵机或小型气动手指舵机PWM角度控制气动需要继电器控制电磁阀夹取、升降物料直驱电机的驱动模块我强烈建议用TB6612FNG而不是L298N。同样输出电流下TB6612的导通压降小、发热低、体积小。L298N那个板子又大又热走线还乱在竞赛狭小的底板上非常不好布局。当然了如果你手上正好只有L298N也能用电机不转时先检查ENA/ENB跳线和地线共地问题就行。2.4 电源设计这是很多新手翻车的地方一套系统里同时存在三种电压需求OpenMV工作电压3.3V输入建议5V稳压电流要求500mA以上STM32最小系统板通过USB或5V引脚供电电机驱动模块需要外接6~12V动力电源舵机需要5~6V稳定电源电流峰值可达1A以上不能用板载稳压芯片供电。我见过太多人共用一个5V电源导致系统复位、图像雪花点乱飘的案例。这里提供一个安全的供电方案7.4V锂电池两节18650串联给电机驱动模块单独供电再通过降压模块降到5V给OpenMV、STM32和舵机分别供电且每一路前都要加一个100uF左右的电解电容滤波。动力地和逻辑地在电源输入端单点连接避免地环路引入干扰。3. 通信协议OpenMV与STM32之间的“行话规范”3.1 传输内容坐标、颜色标识与指令帧OpenMV向STM32发送的数据本质上是一个结构化的数据包。手动定义裸数据时最容易出的问题就是两边的字节序、数据长度、帧格式不一致。所以我们会设计一个固定格式的帧协议让双方都按这个“行话”说话。一个简单可用的帧格式如下帧头(2字节) | 数据长度(1字节) | 数据区(长度不固定) | 校验和(1字节) | 帧尾(2字节) 0xAA 0x55 | len | cx_high cx_low cy_high cy_low color | sum | 0x0D 0x0A帧头0xAA 0x55用于提供给接收端一个起始对齐的标记避免半路解析错乱数据长度len表示数据区字节数坐标数据占2字节高字节在前低字节在后校验和sum数据区所有字节累加后取低8位。发送端计算接收端重新计算后比对不相符就丢弃整帧可以过滤掉大部分串口干扰产生的乱帧帧尾0x0D 0x0A这是回车换行符方便在串口助手里观察调试。3.2 为什么帧协议比裸发数据更可靠有人图省事直接用print(cx, cy)或uart.write(str(cx) , str(cy))往串口里扔数据STM32端再用scanf解析。这种方案在短距离、无干扰的桌面上偶尔能跑通但一旦电机启动电源波动引起串口电平毛刺就很容易丢字节、错位导致STM32解析出一个巨大的异常坐标机器人就直接冲出去了。所以帧头 校验和这一套不是可选项是必要性设计。哪怕是课程设计也建议养成这个习惯因为你在实验室遇到的问题一定会比在赛场上的故障更多。3.3 STM32端接收解析有限状态机STM32端的串口中断接收程序推荐使用“有限状态机”的思路。将接收过程拆分成几个状态// 状态枚举 typedef enum { FRAME_STATE_WAIT_HEAD1, // 等待帧头第1字节 0xAA FRAME_STATE_WAIT_HEAD2, // 等待帧头第2字节 0x55 FRAME_STATE_WAIT_LEN, // 等待数据长度 FRAME_STATE_DATA, // 接收数据区 FRAME_STATE_CHECK, // 等待校验和 FRAME_STATE_WAIT_TAIL1, // 等待帧尾0x0D FRAME_STATE_WAIT_TAIL2 // 等待帧尾0x0A } FrameState;在串口接收中断里每收到一个字节就把当前状态机推进一步。这种写法的好处是不受一次中断接收多少字节的影响哪怕一帧数据被拆成几次中断发过来也能正确拼装。如果某一状态收到的字节不符合预期直接回到FRAME_STATE_WAIT_HEAD1丢弃前面的数据重新对齐。4. OpenMV端代码核心逻辑拆解4.1 颜色阈值标定不是改代码是改参数很多新手直接把示例里的thresholds参数抄过来用发现自己的物料怎么都识别不到就开始怀疑摄像头坏了。其实OpenMV的find_blobs()用的阈值是基于LAB颜色空间的给定的是(L_lo, L_hi, A_lo, A_hi, B_lo, B_hi)六元组不同光照条件下同一个红色的L、A、B值差异很大。推荐的做法是先在OpenMV IDE里打开“帧缓冲区”用工具 → 阈值编辑器打开阈值调整窗口一张一张地拖动滑条直到画面里的物料被完整且仅被物料区域覆盖然后点击“将阈值保存至剪贴板”粘贴到代码里。举一个我调过的物料示例# 红色物料在实验室LED光源下的阈值示例 red_threshold (10, 100, 30, 80, 30, 80) # 蓝色物料阈值示例 blue_threshold (20, 80, -10, 20, -30, -10)需要注意阈值不是固定的你要在机器人运行的典型环境光照下标定而不是在强灯光桌面下标定。比赛前一天如果换了场地一定要重新标定一次不要抱有侥幸心理。4.2 识别与坐标输出帧率就是生命核心代码可以用以下骨架实现import sensor, image, time from pyb import UART # 初始化摄像头 sensor.reset() sensor.set_pixformat(sensor.RGB565) # 色块识别用RGB565就够了不要用GRAYSCALE sensor.set_framesize(sensor.QVGA) # 320x240帧率与分辨率权衡 sensor.skip_frames(50) sensor.set_auto_whitebal(False) # 关闭白平衡避免颜色漂移 uart UART(3, 115200, timeout_char1000) red_threshold (10, 100, 30, 80, 30, 80) def find_target(): img sensor.snapshot() blobs img.find_blobs([red_threshold], pixels_threshold200, area_threshold200) if blobs: largest_blob max(blobs, keylambda b: b.area()) if largest_blob.area() 800: # 面积太小可能是噪声点 cx largest_blob.cx() cy largest_blob.cy() return cx, cy, 1 return None while True: result find_target() if result: cx, cy, color_code result # 数据格式帧头|帧头|长度|坐标高|坐标低|坐标高|坐标低|颜色|校验和|尾|尾 data bytearray([0xAA, 0x55, 0x06, (cx8)0xFF, cx0xFF, (cy8)0xFF, cy0xFF, color_code, 0x00, 0x0D, 0x0A]) # 累加校验 sum 0 for i in range(3, 9): # 数据区 sum data[i] data[8] sum 0xFF uart.write(data)这里有两个容易被忽视的细节sensor.set_auto_whitebal(False)如果不关闭白平衡摄像头会随着画面变化自动调整颜色参数导致同样一个红色物料在画面左边是红色、到画面右边可能就偏黄了阈值完全失效。面积过滤find_blobs()会把反光点、地面纹理都当成色块处理所以要设置pixels_threshold和area_threshold并且取最大面积的色块——这比直接取列表第一个色块更稳。4.3 串口发送的关键波特率匹配和缓冲OpenMV端用uart.write()直接发送字节数组STM32端用相同波特率接收。项目里统一用115200是比较稳妥的选择太低会拖慢控制周期太高在长线传输时误码率上升。连接时注意OpenMV的UART3对应的是P4(TX)和P5(RX)要和STM32的串口交叉连接OpenMV的TX接STM32的RXOpenMV的RX接STM32的TX且两块板子必须共地。5. STM32端代码核心逻辑拆解5.1 串口接收DMA 空闲中断还是逐字节解析STM32F103C8T6有3个USART建议用USART1接OpenMV。接收方式有两种主流方案方案A使用HAL库的HAL_UART_Receive_IT()逐字节接收每收到一个字节触发一次中断在中断里推进状态机。优点是代码好写、逻辑明了缺点是中断频繁在72MHz主频下压力不大但和DMA方案比CPU占用率稍高。方案B使用DMA 空闲中断IDLE配置DMA接收不定长数据当串口空闲时触发IDLE中断一次性从DMA缓冲区中取出整帧数据。这个方案效率更高但代码复杂度高不少。对于这个项目的实际数据量OpenMV每秒发送几十帧每帧10个字节方案A完全够用。为了稳定性和可调试性我推荐先用方案A跑通逻辑之后再视情况优化到DMA。5.2 状态机设计从收到字节到执行动作STM32端主循环的伪码如下while (1) { if (frame_complete_flag) { frame_complete_flag 0; // 数据已在 rx_buffer 中已经过校验 uint8_t color_id rx_buffer[6]; uint16_t obj_x (rx_buffer[3] 8) | rx_buffer[4]; uint16_t obj_y (rx_buffer[5] 8) | rx_buffer[6]; // 执行决策 switch (current_state) { case STATE_IDLE: // 上位机/手动启动信号 break; case STATE_FIND: // 根据 obj_x 与图像中心的偏差计算左右轮差速 int16_t error obj_x - IMAGE_CENTER_X; // 简单地用比例控制error越大转向越猛 motor_left_speed BASE_SPEED error * 0.1; motor_right_speed BASE_SPEED - error * 0.1; break; case STATE_GRASP: // 到达物料上方后控制舵机夹取 servoAngle(GRASP_ANGLE_CLOSE); current_state STATE_MOVE_TO_PLACE; break; // ... 其他状态 } } }这里的核心思想是不要在主循环里阻塞等待。你可以用一个全局标志位frame_complete_flag串口接收完一帧完整数据后置1主循环检测到之后处理数据。这样电机PWM输出才能保持平滑不断流。5.3 电机和舵机的底层控制STM32F103C8T6的定时器资源比较丰富有4个通用定时器和一个高级定时器。我用TIM2和TIM3分别做左右电机的PWM输出原因是这两个定时器都有4个通道可以很方便地输出4路PWM并且能配置到不同的GPIO引脚上。常见的GPIO分配方案// 电机PWMTIM3_CH1 - PA6左电机PWM // TIM3_CH2 - PA7右电机PWM // 电机方向PB0 - IN1, PB1 - IN2左电机 // PB10 - IN3, PB11 - IN4右电机 // 舵机PWMTIM2_CH1 - PA0舵机控制需要50Hz周期20ms的PWM信号脉宽在0.5ms~2.5ms之间对应0°~180°。其实就是给定时器配置重装载值为19999即0.5us × 20000配合72MHz产生20ms周期然后调节比较值来改变脉宽。示例代码__HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, 1500); // 1.5ms脉宽舵机居中6. 联调过程与避坑指南6.1 典型问题1OpenMV有画面但STM32收不到数据排查顺序先查接线OpenMV的P4(TX)是否接到STM32的PA10(RX)OpenMV的P5(RX)是否接到STM32的PA9(TX)两个板子的GND是否连在一起这是最高频的问题。再用串口助手隔离故障把OpenMV的TX单独接USB转TTL串口模块打开电脑串口助手看是否收到数据。如果这里都收不到说明是OpenMV发送侧的问题检查波特率和代码。最后查STM32中断如果电脑能收到但STM32收不到在STM32串口中断函数里设一个断点或翻转一个LED看中断有没有进、是不是进了一次就再不进了。6.2 典型问题2串口收到的数据总是乱码或错帧乱码大概率是波特率不匹配或供电不稳。OpenMV代码里UART(3, 115200)STM32初始化里也配置为115200两边一致还是乱码就看电源。尤其是电机启动瞬间电流冲击会把3.3V拉出很大纹波串口就会误码。对策是给STM32和OpenMV加100uF电解电容 0.1uF陶瓷电容并联滤波并尽量让电机动力线和信号线分开走不要在板上交叉。错帧问题则是状态机实现不对。我这里就踩过坑一开始实现时在STATE_WAIT_HEAD2状态下收到一个错误的字节就直接把状态清零然后期望接收新的一帧。但如果错的那个字节恰好是0x55状态机就会卡在一个伪对齐的状态一直等到收到0xAA才恢复。后来改成“错误字节不立即丢弃而是把它当作新的帧头第1字节去匹配”问题就消失了。6.3 典型问题3图像识别到了物料但机器人把它当成噪声忽略pixels_threshold和area_threshold这两个参数直接把色块过滤掉了。调试的时候把阈值调到很小比如50如果这时能识别到再逐步增大找到“能稳定滤除背景噪声”的最小值。如果调来调去都不行大概率是物料颜色和环境背景颜色太接近这时候不要死磕阈值换个颜色的物料或者加个背景板才是正解。6.4 典型问题4搬运动作不稳定夹取时物料掉落舵机的夹持力度调节不是靠代码狂转角度而是靠机械结构。SG90这种9g舵机夹持重物本来就很吃力建议要么换成MG996R金属齿轮舵机要么在机械夹爪上用橡皮筋/硅胶垫增加摩擦力。流程上要保证“到位后延时200ms再夹取”让机器人停稳后再动作不要一边走一边夹。最后再分享一点扩展方向如果你想让这个项目不只是停留在课设层面可以按这个顺序往下升级第一步在OpenMV端加二维码或Apriltag识别让物料自带“身份证”实现指定目标搬运第二步把单机控制的STM32换成CAN总线或者RS485总线多台机器人组网联动第三步引入上位机如Qt或Python通过串口/WiFi查看机器人的运行状态和识别画面。每一次升级本质上都是在你已经搭建好的“视觉控制”骨架上做功能增量代码架构完全不用推翻重来。我当初做完这台搬运机器人之后最大的体会是这个项目真正的价值不是那几个零件而是让你理解了两套MCU系统之间如何通过串口协议高效配合——这个能力在后续接触机械臂、AGV、自动化产线时会一直派上用场。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价