简介:本资源是一套完整的基于STM32的六轴机械臂智能分拣系统实现方案,面向本科毕业设计、自动化/机器人方向课程设计及期末大作业需求,解决多色物块识别与精准抓取分放的核心控制问题。压缩包含841个文件,总大小23.11MB,主体为516个C源文件与183个H头文件构成的MDK工程(基于STM32F103C8T6+HAL库),辅以27个OpenMV Python脚本实现颜色识别逻辑,另有编译中间文件、链接脚本、调试配置及文档说明等,结构清晰、模块解耦,便于移植适配不同6轴机械臂平台。已有114人学习下载,资源提供完整可运行代码、详细硬件连接说明、舵机驱动参数配置及系统联调要点,涵盖从OpenMV图像采集、HSV阈值标定、坐标映射到STM32运动学解算与PWM舵机控制的全链路实现,是少有的兼顾嵌入式控制与机器视觉的实战型教学项目。 我这个项目做了挺长时间,市面上不少机械臂教学套件都是给厂家自己的上位机在用,调参数、改逻辑全被绑定死,想接自己的传感器和视觉模块非常费劲。所以干脆从底层自己搞一套控制方案,用STM32做主机算轨迹、发脉宽,用OpenMV做视觉定位和颜色分类,两者之间就通过串口通信,把结果变成动作。做完之后发现这套组合的性价比和可玩性非常高,整套源码和文档我也整理出来了,下面把整个项目从硬件选型到联调排错的过程全部拆开讲清楚,希望对打算做机械臂控制、视觉识别或者只是想把手头舵机玩明白的朋友有帮助。
1. 项目全景:目标、架构与方案选型逻辑
1.1 这套系统最终实现了什么
先说功能——给六轴机械臂装上一只"眼睛",让它能从一堆混杂的物块中间识别出不同颜色的目标,按预设位置分门别类地放到指定区域。物块是三种颜色(红、绿、蓝),OpenMV摄像头拍到物块后,STM32控制机械臂启动运动序列:从初始位置出发,到取料点抓取,抬升过渡,再到对应颜色的放料点松开,最后回到初始位置等待下一个物块。
整个流程展开是这样的:
- OpenMV连续采集图像,检测画面中是否出现目标颜色色块。
- 检测到之后,把物块的类别(颜色)和位置坐标通过串口发送给STM32。
- STM32收到完整且有校验的数据帧后,解析出颜色ID和坐标。
- 根据颜色ID映射到放料区域坐标,调用逆运动学解算函数,生成每一路舵机需要转到的角度目标值。
- 逐关节执行角度插值过渡,控制舵机平滑运动,完成"抓取-搬运-释放"的整个流程。
- 完成后回到初始位,流程重新开始。
这个流程听上去不复杂,但实际做起来,各个环节之间的时序配合、通信可靠性、机械臂运动平稳性都会冒出一堆问题,后面我会逐个展开。
1.2 为什么选STM32加OpenMV而不是单芯片方案
有人可能会问:OpenMV本身就是一个带MCU的视觉模块,能不能让它直接把机械臂控制也干了?答案是可以,但不推荐。原因有三点:
第一,机械臂控制需要多个定时器同时输出不同占空比的PWM波,还需要持续处理中断和状态机,OpenMV主控的实时性不如STM32的HAL库加硬件定时器那样得心应手。
第二,六轴机械臂的运动学解算如果用高语言写没问题,但如果要一步步调试每路舵机的角度、速度、加减速曲线,用STM32直接对着寄存器级和标准库操作更顺手。OpenMV端我只需要关心"看见什么",不关心"什么时候发角度",各司其职。
第三,从扩展性考虑,STM32作为主控,以后可以在不改变视觉逻辑的前提下,把OpenMV换成其他传感器模块(比如超声波、激光测距),或者给机械臂加力传感器、夹爪舵机,都只是在STM32端加代码,不需要再把所有数据包格式重新协调一遍。
架构采用主从分离还有一个好处,就是两个模块可以分开调试。OpenMV单独用IDE跑识别脚本,STM32则可以通过串口调试助手直接模拟视觉输入,观察机械臂动作是否正确。联调时如果出了问题,双方都能快速定位是自己这一侧还是通信的问题。
1.3 整体数据流与状态机设计
六轴机械臂的工作流程本质上是一个状态机,我在STM32端定义了这几个状态:
- S_IDLE(待机):机械臂在初始位,等待串口数据。
- S_VIS_RECEIVED(视觉接收):已经收到颜色ID和坐标,准备开始动作序列。
- S_MOVE_TO_PICK(移动到取料位):从初始点到取料点的过渡轨迹。
- S_GRAB(夹取):到达取料点后,夹爪舵机闭合的过渡。
- S_MOVE_TO_PLACE(移动到放料位):夹取到物块后抬升、移动到目标放料点。
- S_RELEASE(释放):夹爪张开,物块放下。
- S_MOVE_HOME(回初始):回到初始位,状态回S_IDLE。
状态机的核心价值在于,把复杂的时序拆成了单步逻辑。比如"移动到取料位"这个状态里,只需要做逆运动学解算、生成各关节的角度序列,不需要关心视觉识别和抓取动作是不是已经完成;真正跳到下一步,等到"是否到达目标角度"的条件满足后再触发。
为了保证视觉信息不丢失,我还在STM32中维护了一个小的数据缓冲区。OpenMV发送数据最快可能几十毫秒一帧,但STM32处理一个完整机械臂动作需要两三秒。这样高频发送没有意义,我在协议上做了设计——OpenMV识别到物块后只持续发送几次,发送完就停,避免长时间占用串口总线,让机械臂在动作执行期间不会反复收到新任务而打断流程。
2. 硬件选型:从机械臂本体到控制板、电源的匹配原则
2.1 六轴机械臂的结构与舵机选择
先明确一点:市面上标"六轴机械臂"的套件很多,但舵机类型、臂长、负载差别很大。我用的是一台小型桌面级六轴机械臂,整体臂展在30厘米左右,每关节都是一个塑料舵机加金属齿轮组构成,末端是两指夹爪。这种套件的优点是结构紧凑、价格可控、维护简单,缺点是负载很小,只能夹取轻的物块(比如3厘米见方、重量在20克以内的木块或塑料块),不适合做重负载抓取。
对舵机的选择,我建议重点关注两个参数:扭矩和工作电压。六轴机械臂每个关节承担的力矩不同,靠近基座的关节(腰部、大臂)需要大扭矩舵机,末端关节(手腕旋转、夹爪)用小舵机即可。我的套件里,一轴到四轴用的都是扭矩在20千克力·厘米以上的舵机,五轴和六轴用9克级别的微型舵机。实际运行中,如果发现某个关节在搬运过程中有抖动或者堵转发热,大概率就是扭矩不够或者电压不足。
关于舵机信号,我强烈建议选数字舵机,不要选模拟舵机。数字舵机的PWM频率可以工作在333Hz甚至更高,响应快,抖动小;模拟舵机在50Hz下摇臂来回摆,很容易让机械臂产生共振。同一套机械臂,数字舵机的控制效果会明显稳定。
2.2 STM32型号怎么定:其实F103够用
很多人在选STM32时容易陷入"参数焦虑",觉得型号越高越好。就拿我们这个项目来说,核心需求就是串口接收、解析数据、开多个定时器输出PWM、跑一个逆运动学解算,这些都是最基本的。我用的是STM32F103C8T6,主频72MHz,64KB Flash,20KB RAM,跑完这些绰绰有余。
唯一要提前考虑的是定时器通道数。六轴机械臂加一个夹爪舵机,总共需要7路PWM输出。F103C8T6的TIM1、TIM2、TIM3、TIM4各有4个通道,挑几个定时器组合完全够用。如果你计划以后加更多执行器(比如两个夹爪、一个旋转台),建议选F103RCT6或者F407系列,反正从F103C8T6升级过去,代码几乎不用大改。
2.3 OpenMV型号与镜头的选择细节
OpenMV系列我建议直接上OpenMV H7,它的处理器性能比M7强不少,在做图像处理时帧率更高,阈值查找和色块识别也会更流畅。当然,如果你手头只有M7,跑单色块识别也够用,但多颜色识别加坐标计算会吃力一些。
镜头选标准广角镜头即可,视场角大,能覆盖更大的工作台。需要注意的是,镜头和物块之间的距离、工作台光照条件直接决定了识别效果。我的工作台是30cm乘40cm,OpenMV倒挂在取料点上方大约40厘米高的支架上,物块在画面中大约占60到100像素见方,识别和坐标计算都足够稳定。
2.4 电源设计:这个问题直接决定机械臂能不能稳定工作
这里必须着重强调——舵机驱动电路和主控电路一定要隔离供电。舵机启动瞬间电流很大,尤其是六轴机械臂多个舵机同时动作时,瞬时电流能到2到3安培甚至更高。如果直接用同一个稳压芯片给STM32和舵机供电,电压跌落会导致STM32复位,严重时直接烧掉控制板。
我的做法是:
- 舵机供电用7.4V 2S锂电池,经过UBEC(可调降压模块)降压到6V,给舵机供电。
- 控制板供电独立用一块5V USB线从充电宝或USB口取电,专门给STM32和OpenMV供电。
- 两套供电只在GND上共地,这样串口电平才能保持一致。
如果你手头没有单独的电源,也可以用大电流降压模块,比如LM2596或者降压型DC-DC,选能持续输出3A以上的型号。但无论如何,千万不要用开发板自带的LDO去带动多个舵机,那是必炸的坑。
3. OpenMV颜色识别:从颜色空间、阈值到串口协议
3.1 为什么用LAB色彩空间做颜色判断
OpenMV官方给的例程大部分是基于RGB色彩空间做颜色阈值,但RGB在自然光照下极不稳定:同一种颜色,阳光直射和阴影里完全不一样,白色物块上的高光反射直接让RGB值跑飞。我实际测试下来,改用LAB色彩空间之后,准确率提升非常明显。
LAB色彩空间把亮度信息放在L通道,颜色信息放在a通道(红绿)和b通道(黄蓝)。这样在做颜色区分时,可以把L通道的影响降得比较低,只针对a、b通道设阈值。例如:
- 红色:L一般不用太严格,a通道较高(例如20到100),b通道适中(约120到220)。
- 绿色:a通道较低(例如0到60),b通道也偏低(约80到160)。
- 蓝色:b通道明显低于绿色,且a通道接近中间值。
实际调试时,用OpenMV IDE里的"阈值编辑器"工具,打开素材图片,拖Lab滑块,观察哪个颜色区域的RGB图像被选中,就能快速找到合适的阈值区间。如果环境光会变化,建议用auto_whitebal设为False,这样白平衡不会自动漂移,颜色阈值相对稳定。
3.2 识别流程:预处理、色块提取、干扰过滤
图像处理流程我拆成四步:
sensor.snapshot()采集一帧图像。find_blobs(thresholds)查找所有在目标颜色范围内的色块,返回每个色块的外接矩形、面积和重心坐标。- 对每个色块做过滤:面积大于一定像素(比如
blob.area() > 500),宽高比合理(不能是一个细长条),而且重心坐标在图像有效区域内。 - 如果有多个同色色块,取面积最大的那个作为最终目标;不同颜色之间,以优先级最高(比如红色优先于蓝色)或者最近原则决定先处理哪一个。
这四步看上去简单,但每一步都有细节。比如在过滤的时候,我遇到一个情况:工作台上一块黑色的垫布,在某些光照下被识别成近似蓝色的色块。原因是LAB中b通道在黑色区域噪声很大。解决方法是加上亮度约束:blob.mean() > 30只保留平均亮度大于一定值的色块。这一条比单纯调阈值效果还好。
3.3 坐标计算与串口发送协议
OpenMV识别到物块后,返回的是图像坐标系中的像素坐标(cx, cy),范围大约在0到320、0到240之间。这个坐标不能直接给机械臂用,因为机械臂的取料点是三维空间坐标。我这里做了一种简化处理:让物块只出现在一个平面工作台面上,OpenMV固定高度悬挂,那么像素坐标和台面物理坐标之间就构成了一个固定的单应性映射。
可以通过三个已知摆放位置的标定点来求映射矩阵,我在OpenMV脚本里预先存了三个像素坐标和对应的物理坐标,用OpenMV的find_homography或手动解一个仿射变换(六参数模型)都能算出来。公式大概是:
- 物理x = a0 + a1 * 像素x + a2 * 像素y
- 物理y = b0 + b1 * 像素x + b2 * 像素y
已知三对坐标,就可以解出a0、a1、a2、b0、b1、b2。有了仿射变换参数,每次识别出的像素坐标就能转换为台面上物理坐标(单位毫米),然后用这两个值加上固定的Z高度,作为机械臂末端的取料目标点。这个方案的精度大约在10毫米以内,对抓取3厘米大小的物块来说够用了。
串口通信协议我定义成定长帧,每帧6个字节:
- 帧头1:0xAA
- 帧头2:0x55
- 数据类型:0x01表示颜色识别结果
- 颜色ID:1红、2绿、3蓝、0表示未识别
- 物理坐标X:单字节(映射范围0到255,方便直接拼包)
- 校验:前5字节累加和低8位
每次识别成功,OpenMV连续发送这个帧3次,每次间隔30毫秒,然后在未来2秒内不再重复发送。STM32端只要在一帧时间内收到完整数据,校验通过就认为有效。这种设计大大降低了串口丢包带来的偶发问题。
颜色阈值区间的维护也是一项重要工作。OpenMV跑几天之后,环境光可能有细微变化,我习惯把阈值保存成文件,启动时加载。如果发现某天识别率下降,用IDE重新跑一次阈值编辑器,把三组区间更新后重新保存,不用改主逻辑代码。这些细节在源码里都有体现。
4. STM32控制层:串口解析、运动学解算与PWM输出
4.1 串口DMA接收:不丢字节的可靠通信
STM32端接收OpenMV数据,我用的是串口空闲中断加DMA的方式。简单说,就是让DMA把串口收到的数据自动搬到一个环形缓冲区,当串口总线空闲(即一帧数据传输完毕)时触发空闲中断,然后在中断里解析缓冲区数据。
对比传统的中断逐字节接收,DMA接收有个明显优势:不管OpenMV发送多少数据,CPU不用每个字节都停一下,而是完全由硬件搬运,解析的时候一次性处理。这样即使OpenMV偶尔连续发多帧,也不会漏字节。
具体配置要点:
- 串口初始化配置:波特率115200、8位数据、无校验、1位停止。
- DMA配置:使用DMA1通道5(USART2_RX),模式为循环模式,数据宽度为字节。
- 接收缓冲区:定义长度64字节的一维数组。
- 空闲中断:使能
USART_IT_IDLE,在中断回调里读取USARTx->SR和DR清除状态,然后处理缓冲区。
解析函数在收到0xAA 0x55帧头后,按协议提取颜色ID和坐标,做累加和校验。如果校验不通过,直接把缓冲区清空,不执行后续动作,避免错误数据导致机械臂乱动。
4.2 逆运动学:六轴解算的简化实现
六轴机械臂的逆运动学在纸面上是一大堆矩阵变换,但实际项目中我们通常只关心末端执行器(夹爪)要到达的位置和姿态。我采用的方案是:六轴结构中前三个关节主要决定末端位置,后三个关节决定姿态。因为我们的任务是抓取一个放在平面上的物块,夹爪的姿态基本固定(竖直向下夹取),所以可以直接用解析法求解前三轴的角度,后三轴保持固定角度,最后用夹爪舵机控制开合。
以我的机械臂构型为例(这是很常见的六轴关节结构):
- 关节1(腰部):控制机械臂左右回转。末端X坐标和Y坐标的反正切就是关节1角度,atan2(y, x)。
- 关节2(大臂):控制上臂俯仰。根据末端Z坐标和关节1解算后的半径R,结合大臂和小臂长度,用余弦定理求夹角。
- 关节3(小臂):根据大臂角度和末端位置,算出小臂相对水平面的角度。
两连杆机构的角度求解非常经典:
设大臂长度L1,小臂长度L2,末端在平面内的投影距离为r,Z方向高度为z,则末端到基座的距离为:
- R = sqrt(x^2 + y^2)
- D = (R^2 + z^2 - L1^2 - L2^2) / (2 * L1 * L2)
- 关节3角度(小臂) = acos(D) 的补角
- 关节2角度(大臂) = atan2(z, R) - atan2(L2 * sin(关节3角度), L1 + L2 * cos(关节3角度))
三个角度都出来了,再加上固定的后三轴角度,就得到六轴舵机的目标角度。具体角度值到PWM脉宽的转换公式是:
- 脉宽 = 500 + (角度 / 180.0) * 2000(单位微秒)
也就是说,0度对应0.5毫秒脉宽,180度对应2.5毫秒脉宽。每个舵机的中位(90度)对应1.5毫秒。
但这里有个很重要的细节——机械臂的连杆长度和关节零点偏移必须校准。我一开始直接用厂家标称长度解算,结果末端位置偏离实际位置好几厘米。后来我在源码里留了一个"机械臂参数校准函数",通过手动调整关节零点偏移量和连杆长度,让机械臂实际到达位置和理论坐标尽量吻合。这是整个项目里最花时间的一步,但校准好了就一劳永逸。
4.3 多路PWM输出与运动平滑
PWM输出用STM32的定时器,我选用TIM1和TIM2的组合,TIM1输出通道1到4,TIM2输出通道1到3,共7路。定时器配置成PWM模式1,预分频器设置为72-1,计数周期设置为20000-1,这样PWM频率就是50Hz,脉宽控制范围0到20000对应0到20000微秒。假设舵机中位1.5毫秒,就设置比较寄存器为1500,脉宽为1500微秒。
需要注意的是,不同舵机的PWM频率上限不同。数字舵机虽然能工作在333Hz,但50Hz也能正常工作,差别只是响应速度。为了保证主控逻辑简单和兼容性,我用50Hz。
运动平滑是另一个关键点。直接让舵机从当前角度跳到目标角度,机械臂会猛地甩动,夹爪里的物块可能被甩飞,关节齿轮也容易打齿。我用了简单的梯形速度规划:将目标角度和当前角度之差分为多个小步,每一步间隔10到20毫秒,每步递增角度,形成一个加速-匀速-减速的位移曲线。STM32端用一个简单的增量式PID控制角度逼近速度:
- 误差 = 目标角度 - 当前角度
- 速度增量 = Kp * 误差 + Ki * 误差积分 + Kd * 误差变化率
- 当前角度 = 当前角度 + 限幅后的速度增量 * 时间步长
实际测试中,Kp取0.3,Ki取0.0001,Kd取0.05,效果已经很平滑。如果机械臂抖动,多半是Kp太大或者步长太短,适当降低Kp和步长即可。
4.4 夹爪控制:夹取力度的经验值
夹爪舵机控制有几个经验值可以直接用:
- 待机状态:夹爪张开,保持半开状态,避免物块意外掉落。
- 抓取状态:夹爪舵机先快速闭合到接近物块的大小,再缓慢增加力度,到达设定阈值后停止,避免夹坏物块或舵机堵转过热。
- 空载状态:夹爪闭合力度可以减小,减少舵机功耗,延长寿命。
我通过一个简单的电流检测或者角度反馈来实现力度控制——如果舵机在闭合过程中到达指定脉宽后,电流突然增大,说明已经被物块卡住,就停止继续加大力度。在Stm32源码中我留了一个gripper_force_session()函数,开发者可以自己调整夹取力度参数。
5. 联调排错:从识别乱跳到机械臂抖动的完整排查链路
5.1 问题一:OpenMV识别率低,红色和橙色分不清
这是联调时最让人头大的问题。刚开始装好,我发现OpenMV经常把橙色的物块误判成红色,或者把红色物块在暗光下当成棕色直接忽略。
排查过程:
- 首先排除物块本身问题。我拿标准红色色卡和橙色色卡在相同光照下分别拍图,发现LAB的a通道红色和橙色差别确实不大,单纯调阈值区间很难区分。
- 于是我在算法层面做了改进:不仅依靠颜色阈值,还加上了"色相一致性"判断。红色物块在LAB空间中,b通道通常接近零,而橙色物块的b通道明显偏正。这样通过a和b两个通道的组合条件,就能把红色和橙色区分开。
- 另外,我还发现OpenMV的自动白平衡导致颜色漂移。在
image初始化时关闭自动白平衡和自动增益,用固定参数,颜色识别稳定了很多。
最终,颜色识别准确率从大概70%提升到了95%以上。剩下的5%误判,是因为环境光线的极端变化,这套项目目前的处理方式是如果颜色置信度低于阈值,就输出"未识别",机械臂不执行动作,等待下一帧。
5.2 问题二:机械臂运动中抖得厉害,甚至舵机咔咔响
机械臂抖动这个问题几乎一定会遇到。最开始我还以为是舵机本身质量问题,后来测试发现关掉机械臂电源、只用手掰动关节时舵机没有明显卡顿,问题出在控制逻辑上。
排查过程:
- 第一步,检查PWM频率。如果频率选的太低(比如40Hz),数字舵机的响应速度跟不上,一帧一帧地跳,导致抖动。把频率提升到标准数字舵机支持的333Hz,现象有所缓解。
- 第二步,检查舵机供电电压。用万用表测舵机供电端,发现舵机同时动作时电压降到5.4V,明显偏低。换了一个能稳定输出6V、峰值5A的UBEC后,抖动基本消失。
- 第三步,检查运动插值算法。由于我一开始采用的大角度跳变被拆成若干小步,但每步的间隔和角度增量是均匀的,造成机械臂先加速后急停,产生冲击。改成梯形速度规划,每步的角度增量按先加后减的方式分配后,整个运动柔和了很多。
5.3 问题三:串口数据丢帧,机械臂偶尔不响应
这个问题的排查有点玄学。OpenMV发送数据正常,STM32的串口也配置正常,但偶尔一次完整动作执行完后,机械臂没有回到待机状态,而是卡死。
排查过程:
- 用逻辑分析仪抓串口波形,发现OpenMV确实连续发了3帧数据,但STM32只完整解析出了1帧。
- 检查STM32串口中断,发现用的是"逐字节接收+帧结束判断"的方式,导致在高负载状态下串口中断容易被堵住,丢字节。
- 改成DMA接收+空闲中断后,丢帧率降为零。另外,在协议中增加帧校验,即使偶尔有错帧也会被直接丢弃,不会产生错误动作。
这里有一个重要的经验:能不用中断逐字节接收的,就别用。DMA加空闲中断是STM32串口接收的标准姿势,几乎所有网络通信和传感器数据接收场景都适用。
5.4 问题四:机械臂动作顺序偶发错误,把物块放到了错误位置
这个问题的根源不是机械臂本身,而是"状态同步"的问题。原本的设计是OpenMV识别到物块就发数据,STM32在动作执行过程中如果收到下一个物块的数据,会直接改变当前状态机的目标位置,导致机械臂还没放完手里的物块,就被新任务打断。
解决办法是给状态机加了一个"忙标志位":
- STM32在进入S_MOVE_TO_PICK状态时,置
busy_flag = 1。 - OpenMV发送数据时,STM32先检查
busy_flag,如果是1,就先缓存起来,等当前动作执行完毕、回到S_IDLE后再处理缓存的任务。 - 这样不仅解决了误动作问题,还自然实现了"排队处理"功能,之后就算工作台上有多个不同颜色的物块,机械臂也能依次处理完。
从串口协议的设计上说,这也说明一个问题:不是所有数据都要立刻响应,有时需要引入"节拍"和"缓冲"的概念。
6. 源码工程结构与二次开发建议
6.1 OpenMV端脚本的文件组织
OpenMV端我拆成了三个文件:
main.py:主循环,调用识别函数和通信函数。color_thresholds.py:保存三组颜色阈值区间和识别参数。homography.py:仿射变换标定参数,标定点坐标和转换函数。
主循环的逻辑大概是:
import sensor, image, time from color_thresholds import COLOR_THRESHOLDS from homography import pixel_to_physical sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time=2000) sensor.set_auto_whitebal(False) sensor.set_auto_gain(False) while True: img = sensor.snapshot() for color_id, threshold in COLOR_THRESHOLDS.items(): blobs = img.find_blobs([threshold], pixels_threshold=500, area_threshold=500) if blobs: blob = max(blobs, key=lambda b: b.area()) phys_x, phys_y = pixel_to_physical(blob.cx(), blob.cy()) send_serial_frame(color_id, phys_x, phys_y) time.sleep_ms(200) time.sleep_ms(50)关键优化点:
find_blobs的参数pixels_threshold和area_threshold不能设太大,否则小物块识别不到;也不能设太小,否则噪声色块会被算进去。我一般设500到1000。merge=True参数可以把相邻的同一颜色色块合并成一个,适合物块被部分遮挡的情况。
6.2 STM32端代码结构
STM32端按照模块划分,采用HAL库和标准库结合的方式编写,文件结构如下:
main.c:初始化硬件、状态机主循环。serial.c:串口DMA接收、数据帧解析、数据缓冲。arm_kinematics.c:逆运动学解算、机械臂参数设置。servo_pwm.c:PWM初始化、舵机角度控制、梯形速度规划。task_state.c:状态机逻辑,定义各状态之间的切换和处理。
主循环大致是:
while (1) { if (parse_serial_frame(&color_id, &pos_x, &pos_y)) { arm_task_set_target(color_id, pos_x, pos_y); } arm_task_tick(); // 根据状态机执行当前动作 delay(10); }开发者拿到源码后,需要根据自己的机械臂尺寸修改三个地方:
arm_kinematics.c中的连杆长度L1、L2。servo_pwm.c中各路舵机的PWM脉宽范围(0度、180度对应值)。task_state.c中的放料区坐标(用你自己的工作台尺寸标定)。
6.3 如何适配你自己的机械臂
不同厂商的机械臂结构参数差异很大,但适配方法基本一致:
- 先用尺子量出机械臂的大臂长度、小臂长度和肩高。
- 让机械臂回到一个已知姿态(比如所有舵机90度),记下此时末端位置。
- 逐关节手动微调零点偏移,让机械臂的末端能到达你工作台上几个标定点的物理坐标。这个过程需要反复试,一般十几分钟到半小时。
- 把参数写进源码,编译烧录,验证基座坐标和末端坐标是否对应。
如果你的机械臂不是传统的六轴串联结构(比如有平行连杆结构),逆运动学解算公式可能需要改一下。不过大多数桌面级六轴机械臂都是标准串联构型,改参数就能直接跑通。
6.4 从单色块到多颜色、传送带、自动循环的扩展思路
当前项目已经实现了三色(红、绿、蓝)物块的分类分放,如果想做更复杂的分拣应用,可以考虑这几个方向:
- 增加更多颜色识别:在
color_thresholds.py里再加几组阈值,STM32端在状态机里增加对应的放料区映射即可。 - 增加物块掉落检测:在夹爪上装一个红外对射或霍尔传感器,判断夹爪是否真的抓到了物块。如果没抓到,机械臂返回初始位重试,而不是直接去放料点空放。
- 引入传送带:OpenMV负责检测传送带上经过的物块,测出物块到达取料点的时间,STM32控制机械臂在固定的取料窗口执行抓取。这个需要处理更精确的时间同步。
- 如果物块是堆叠的,可以加入深度摄像头或激光测距,确定物块的Z坐标,不再假设物块都在同一平面上,这样机械臂的抓取会更灵活。
源码在GitHub上做了整理,包含OpenMV端脚本、STM32工程、硬件接线图、标定说明文档以及拍摄的演示视频链接。文档里把每个模块的接口、参数含义、烧录步骤都写清楚了,即使你之前没玩过OpenMV或者对STM32不熟,按文档走一遍也能把项目跑起来。
我做这个项目最大的感受就是:机械臂控制加视觉识别,真正的门槛不在于某一个环节有多难,而在于把多个模块可靠地协同起来。串口协议、状态机、运动学这些单独拎出来都是大学课程里的老知识,但放到一个真实系统里,每一步都有细节需要打磨。希望这篇拆解和源码能帮你少走一些弯路。
本文还有配套的精品资源,点击获取