把一个六自由度机械臂从散件组装成能自动跑动作,第一段是结构,第二段才是灵魂。很多人在“六自由度机械臂示教操作、轨迹记录与复现”这类标题面前会觉得高深,其实拆开看,它要完成的事并不复杂:用手把机械臂拖到一个姿势,单片机记住这一路上的每个关节角度,点一下按钮,机械臂再按同样的顺序把这些角度演一遍。真正上手做一次,问题才会暴露出来:有的机械臂根本拖不动,有的角度信号跳来跳去,有的记录时还能看,回放时却歪得离谱。
这些坑,和芯片算力关系不大。用 stm32f103 做六轴示教,芯片能力完全够用。这个阶段的难点不在这里。
先说清楚我的判断:这个项目本质不是“做一个能动的机械臂”,也不是“训练一个会学习的机器人”,而是搭建一套“带时间标签的六维关节角度记录与回放系统”。你记录的不是动作画面,而是一组随时刻变化的关节角度序列。把这件事理解透了,后面调参、排错、扩展功能才不会走弯路。
1. 六轴示教,先搞清楚你究竟在采集什么数据
示教操作听起来很“工业”,实际拆开就是三件事:获取关节角度、保存关节角度、输出关节角度。一个六自由度机械臂,一次采样要得到六个角度值。拖动的过程可以看成一条连续的曲线,单片机要做的是在这条曲线上密集打点,每个点都记录“这一刻是第几毫秒,六个关节分别转到多少度”。
回放则是反向过程:按相同的时间顺序,把这一串点重新喂给六个关节的执行器。
1.1 示教内容不是视频,而是“时间 + 六个角度”的数据流
如果只把示教理解成“拍一段视频然后播放”,很容易误以为需要摄像头、图像识别、人工智能这些高级部件。真正低成本 DIY 机械臂的示教,多数是直接记录关节角度。
我们需要的数据结构,核心是一个轨迹帧。每一帧大概包含:
- 时间戳:这段轨迹从开始到现在的相对时间;
- 六个关节角度值:分别对应底座、肩、肘、前臂、腕等运动关节。
拖动时,程序以固定间隔读取六个信号源;记录结束后,这段轨迹本质上就是一组数组或链表。回放时,按照时间戳逆推每个时刻的期望角度,再把角度发送给电机执行器。
这里最容易被忽略的是“固定间隔”四个字。很多人用主循环直接采样,但主循环里如果同时处理按键、串口打印、电机刷新,循环时间就会忽快忽慢。表面上看每个角度都记录到了,回放却会发现动作时快时慢。
所以不要急着写业务逻辑,先把时基固定住。
1.2 为什么关节空间直接记录,比逆运动学更适合这个阶段
网上搜索六轴机械臂,很容易被 dh 参数、轨迹规划、逆运动学 ros 开发这些词带走。它们不是没有用,而是在这个“拖动示教 + 轨迹复现”项目里,优先级可以往后放。
原因是:拖动手臂时,你要记录的是物理关节角度,而不是笛卡尔空间坐标。整个系统并不需要“根据末端位置反算六个关节角”这个过程。你从传感器读到的角度,已经是机械臂实际所在的角度,记住它就好。
逆运动学、DH 参数真正派得上用场,是在你把动作放到 gazebo、mujoco 这类仿真环境里验证时。如果你希望在电脑里先看一遍动作是否符合预期,再决定要不要让实体机械臂执行,那才需要建立运动学模型。第二个阶段可以先不做这个,做出来以后再补也来得及。
这个区分很重要:它决定了你第一版固件的复杂度可以压得很低,也更容易跑通。先把真实硬件上的“采集、存储、回放”做好,再去碰仿真和算法,才不容易被一堆抽象概念卡住。
2. 组装前先确认三件事:能不能拖动、能不能读角、供电稳不稳
六自由度机械臂的机械结构装完,不等于可以开始写示教代码。在实际动手写程序之前,要先确认三个物理层面的问题。这三个问题如果没确认好,程序写得再漂亮也白搭。
2.1 电机扭矩和减速比决定“手感”,不是所有电机都能手动示教
常见的桌面级机械臂,关节处用的是舵机或者直流减速电机。舵机内部有减速齿轮,减速比越大,输出扭矩越大,但反向驱动也越困难。
所谓反向驱动,就是你不给电机通电、或者让电机处于松力状态时,用手能不能轻松转动输出轴。示教操作要求在断电或释放力矩的状态下,人手能把关节拖到预期位置,同时角度传感器还能跟着转动并输出正确数值。
如果使用的是普通大扭矩舵机,锁住时手基本掰不动,强行掰容易扫齿,对示教来说体验很差。如果使用的是总线舵机或者带力矩输出控制的电机,可以通过指令让电机处于无力矩模式,这时候关节可以自由活动,拖动示教就顺理成章。
这里没必要一开始就算复杂的“机械臂电机扭矩计算”公式。真正影响体验的是两点:
- 减速比和输出扭矩是否适合这个尺寸的机械臂;
- 是否支持释放力矩或反向驱动。
如果是套件材料包里已经配好的舵机,先别拆,只做一件事:把舵机输出轴单独接上关节,不给控制信号,手动转动关节,感受一下阻力大小。如果阻力大到不能平滑转动,就需要考虑换装支持力矩释放的舵机,或者在结构上加入离合器。
2.2 角度采样前,先把传感器的尺子标定好
要记录角度,硬件上必须有角度传感器。常见低成本方案有几种:
- 舵机内部电位器:很多模拟舵机内部本身就有位置反馈电位器,但未必引出信号,所以不一定能直接读;
- 关节处单独安装电位器:结构简单,但安装位置要求与关节同轴,否则机械臂动作时传感器读数会和真实关节角不一致;
- 磁编码器:精度更高,但成本也高,且需要贴磁铁,安装要求更高;
- 带角度反馈的总线舵机:例如串行总线舵机,可以直接通过串口读取当前角度,使用体验好很多。
选完传感器后,不要急于做高精度标定。先做一个最简单的两点标定:让关节转到机械限位的一端,记录传感器读数;再让关节转到另一端,记录读数。为了保证标定可靠,先用一个已知角度,比如用直角尺或量角器,确认限位处真的是 0 度和某个预设角度,而不是凭感觉。
注意:很多 DIY 机械臂的角度偏差不是算法导致的,而是电位器安装时没有和关节轴对齐。传感器主轴每偏一度,最终显示的关节角度就会带一个固定误差,而且误差随转动方向非线性变化。
2.3 供电、控制线和电流上限,是故障率最高的地方
六自由度机械臂工作起来,六个电机同时动作,瞬间电流会比单个电机大不少。很多人用一块开发板直接给六个舵机供电,结果就是动作时电压跌落、单片机复位、角度乱跳。
至少要有两路电源思路:
- 单片机逻辑供电一路;
- 电机驱动供电一路。
两路电源的地线要共地,但功率电流不要走单片机板载稳压器。舵机或电机驱动部分的电源线尽量短,线径要能承受峰值电流。
还有一个很常见但容易忽略的问题:stm32f103 的 GPIO 多为 3.3V 逻辑,如果控制板上的舵机控制板是 5V 逻辑,需要先确认电平兼容,不要长期把 5V 信号直接灌进芯片引脚。很多套件在正常使用中没问题,但在插拔线、上电瞬间却容易烧 IO,往往就是电平转换和电源时序没处理好。
3. 轨迹记录的最小实现:把一次拖动变成可回放的数据
物理框架和传感器确认之后,程序上的最小实现其实很精简。你可以先不接任何按键,只用串口触发,也可以先做一个按钮。先跑通记录和回放的基本链路,情绪价值会立刻上来,然后再去完善功能。
3.1 用结构体保存轨迹帧
F103 的 RAM 不大,如果只是保存几条几十秒的轨迹,完全可以放在内存数组里。假设 10ms 采样一帧,一秒钟 100 帧,20 秒轨迹约 2000 帧。每帧保存时间戳、6 个角度值,即使每个角度用 float 或 int16_t,占用空间也不算夸张。示例结构体可以这样设计:
typedef struct { uint32_t timestamp; // 相对起始时间的毫秒数 int16_t joint[6]; // 6个关节角度,单位为0.1度,适合int16存储 } track_frame_t; #define MAX_FRAME_COUNT 3000 track_frame_t track[MAX_FRAME_COUNT]; uint16_t frame_count = 0;角度用 int16_t 且按 0.1 度为单位,是很多低成本方案里的常见做法。它比 float 更省 RAM,也方便观察原始值。如果传感器输出的是模拟电压对应的 ADC 值,可以先把 ADC 值换算成角度,再在这个结构体里保存,不要让后续逻辑依赖 ADC 量程。
3.2 实时记录时的主循环:采样、时间、保存边界
最简单的记录流程是一个状态机,常见状态包括:
- IDLE:等待开始记录;
- RECORD:循环读取角度,写入轨迹数组;
- STOP:停止记录,等待回放;
- PLAY:按时间回放轨迹。
状态切换可以用按键,也可以用串口命令。记录时最关键的是保证采样周期尽量稳定。建议使用定时器中断或定时器标志位,每到一个固定时间片,才触发一次采样,而不是在主循环里用 delay 空等。
在 stm32f103 上,不需要上 FreeRTOS 也能完成这个任务。程序本质上是“一个时间片状态机 + 一个存储队列”。引入操作系统反而会让初版调试更难。
示例逻辑大致是:
uint8_t sample_flag = 0; // 假设定时器每10ms产生一次中断 void TIM_IRQHandler(void) { if (TIM_GetITStatus(TIMx, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIMx, TIM_IT_Update); sample_flag = 1; } } while (1) { if (state == STATE_RECORD && sample_flag) { sample_flag = 0; if (frame_count < MAX_FRAME_COUNT) { track[frame_count].timestamp = elapsed_ms; // 累计毫秒 read_joint_angles(&track[frame_count].joint[0]); frame_count++; elapsed_ms += SAMPLING_INTERVAL_MS; } else { state = STATE_FULL; } } }这只是示意,不是完整工程。要注意的地方是:不要让角度读取函数内部有长时间阻塞,比如等待串口数据、延时等待传感器稳定等。一旦阻塞,采样间隔就会抖动。
3.3 存储和输出:先写串口调试,再放到内部 Flash/SD
第一版不一定需要掉电保存。先把轨迹通过串口发送到电脑,格式简单一些,例如第一列为毫秒时间戳,后面六列为角度值,用逗号或制表符分隔。这样不但能看到轨迹数据,也能用绘图工具画出六个关节的角度曲线,快速判断有没有明显跳变。
如果要让机械臂断电后还能记住动作,可以将轨迹写入单片机内部的 Flash 模拟 EEPROM 区域,或者外接 SPI Flash、SD 卡。注意 F103 的 Flash 有擦写寿命,不能每次执行都整片擦写。常见做法是把轨迹区规划成固定扇区,以扇区为单位写入,或者只存少量常用轨迹在内部,大量数据放外部存储。
这一步有两个必要性:
- 串口导出数据,可以用于排查传感器异常;
- 固化存储,才能让“示教一次、以后重复使用”成立。
如果每回一次电都需要重新拖一遍,那这套系统还算不上完整工具。
4. 回放不是简单地把角度逐个写出去
记录完成后,回放才是真正暴露问题的环节。很多人发现:明明记录时动作流畅,回放时关节一顿一顿,甚至最后位姿和记录时对不上。原因通常不是芯片速度不够,而是回放过程少做了几个关键处理。
4.1 为什么不插值会抖动
如果记录间隔是 10ms,直接每 10ms 把上一帧角度赋给舵机,其实已经接近可行。但真正回放时会遇到一个问题:解析到的时间戳不是严格等间隔,或者舵机执行角度变化需要时间,直接跳变会让关节承受很大的角速度突变,听起来就是咔哒咔哒的顿挫。
尤其在记录时如果中间有停顿、犹豫,或者采样被其他任务打断,相邻几帧的角度差会忽大忽小。解决方法是回放时做插值处理。
对于这种教育级机械臂,线性插值通常已经够用。也就是在两个已知轨迹帧之间,按当前回放时间算出比例,再逐个关节做角度插值:
float ratio = (current_time - frame_a.timestamp) / (float)(frame_b.timestamp - frame_a.timestamp); for (int i = 0; i < 6; i++) { target_angle[i] = frame_a.joint[i] + ratio * (frame_b.joint[i] - frame_a.joint[i]); }线性插值适合起点终点确定、速度要求不高的场景。如果动作比较复杂,可以换成梯形速度规划,让关节在启动和停止时更平缓。但不要一上来就上高阶曲线,先用线性插值跑通,再判断是否有必要平滑。
4.2 从记录到回放:起始判断、时间基准和增量匹配
回放时另一个常见问题是“从错误的起点开始”。如果机械臂当前姿态和记录轨迹的第一帧不一致,程序直接跳到第一帧,机械臂会突然猛抽一下,甚至撞到限位。
规范的流程是:
- 开始回放前,先让机械臂回到记录动作的起始姿态;
- 判断六个关节是否都接近记录首帧的角度;
- 满足一定误差范围后再开始回放;
- 回放开始后,按照相对时间戳逐渐推进。
回到起始姿态时,也不建议六个关节同时高速转过去。应该先计算每个关节需要转多少度,设定一个统一或按关节限制的速度,依次或同步移动到起始点。
如果轨迹数据里的时间戳是“从 0 开始的相对时间”,回放时的时钟也要从 0 开始计时,不要直接用当前系统时间。用定时器计数,让回放速度可调。常见的“速度倍率”,本质只是把相对时间乘一个系数,例如 1.0 倍速用原始时间戳,0.5 倍速用原始时间戳乘 2,表示更慢。
4.3 复现异常时的排查顺序
回放如果出现问题,不要先怀疑代码逻辑,更不要立刻去改运动学算法。按下面顺序排查,通常能快速定位:
- 先看原始轨迹数据:把记录时的串口输出保存下来,画出角度曲线。如果曲线本身有跳变,问题在传感器或采样;
- 再看回放目标值:在程序里把回放时每个时刻计算出的目标角度通过串口输出,确认插值结果是否正确;
- 再查实际执行:观察舵机或电机的实际角度是否到达目标角度。如果响应慢,增大每一步的时间间隔,或降低速度倍率;
- 最后查时间抖动:如果记录时用了阻塞调用,回放时可能出现时间错位,导致动作越来越快或越来越慢;
- 检查 PWM 配置:如果控制的是 PWM 舵机,确认识别周期和占空比范围有没有覆盖完整角度范围。有些配置中用定时器输出 PWM 时,占空比到不了 100%,舵机末端会差几度,回放结果自然不完全一致。
注意:不要只用“看起来差不多”来判断回放是否正确。给每个关节装不装编码器是一回事,调试时至少要用串口打印某个关节的目标角度和实际反馈角度,看两者误差趋势,而不是只看动作是否丝滑。
5. 拖动示教方式不止一种,低成本方案先跑通再升级
做这个项目时,还会遇到一个更实际的问题:机械臂到底应该怎么“拖”?不同舵机方案会带来完全不同的操作体验。这直接影响你要不要换硬件。
5.1 普通舵机、总线舵机、带电流反馈的执行器,差在哪里
普通 PWM 舵机,比如常见的 SG90、MG996R,特点是便宜、简单,但通常没有角速度控制和力矩释放功能。要让这种舵机实现拖动示教,常见套路是“断电后拖动,舵机内部电位器仍随输出轴转动,再重新上电读取角度”。听起来可行,实际结构上往往做不到:舵机在不通电时输出轴可以被手转动,但内部的电位器是否随之转动,需要看减速齿轮和电位器之间的连接方式;很多舵机断电后转动不顺滑,强行扭还有扫齿风险。
总线舵机在这类项目里越来越受欢迎。它通过串口协议控制角度和读取当前角度,有些还支持扭矩开关。打开扭矩释放模式后,输出轴可以反向转动,同时还能通过读取位置反馈得到角度。这让示教实现简单很多:你只要周期性地向舵机发送读取角度命令,并记录下来即可。缺点是成本高,而且串行总线在六个舵机同时回传时,通信周期要做合理规划,不能让一帧数据等待太久。
再往上走,就是带电流检测、力矩控制的执行器,例如一些开源机械臂项目里使用的直流无刷电机加编码器方案。它们可以做恒力拖动示教,力反馈更细腻,但难度也上了一个量级。对于刚接触这个方向的人来说,不建议一开始就上这种方案。先把总线舵机或电位器方案跑通,已经能覆盖大部分教学和毕设需求。
5.2 如何把电位器或磁编码器正确“绑”在关节上
如果你选择在关节处外接电位器,结构安装是成败关键。先把一个基本原则记下来:电位器的转轴必须和关节旋转轴尽量同轴,转动范围不能超过电位器的电气行程。
在三个地方最容易出问题:
- 电位器没有固定在机械结构上,随着关节转动一起旋转,线被拧断;
- 电位器转了超过 300 度,超过碳膜行程,输出不再单调;
- 角度方向接反,导致关节往正方向转,读数反而减小。
推荐的做法是先在关节上临时安装电位器,手动从限位的一端转到另一端,用串口打印 ADC 数值,确认数值是单调递增还是递减。确认之后再决定代码里要不要做反向变换。
零点标定也需要重视。组装完成后,机械臂的零点未必和电位器机械零位一致。不要试图通过硬装去凑零点,应该在程序里保存一个“零点偏移值”。每次上电时,先让机械臂回到一个已知的机械基准姿态,比如都转到限位区,然后读取各关节角度,记为偏移,后续采集时减去这个偏移即可。
5.3 记录时要保护机械臂,不要硬碰结构极限
拖动示教时,人很容易只关注“我想让末端走到哪里”,而忽略六个关节是否快到了机械限位。尤其是六轴机械臂,中间关节反转,末端姿态可能越走越拧。如果示教过程中真撞到机械限位,轻则舵机堵转发热,重则齿轮扫齿或结构断裂。
代码里至少要做三件事:
- 每个关节设置软限位,角度目标超出允许范围时禁止执行;
- 记录时如果连续多帧角度值不变,而电机仍在输出大电流,可以认为关节被堵住,自动停止记录;
- 回放前检查整段轨迹是否都在安全角度范围内,不要等跑到一半才去判断。
提醒:很多材料包采用 3D 打印结构件,机械强度有限。示教时不要对末端用力过猛,扭矩冲击很容易让打印件在螺丝孔附近开裂。宁可多花点时间慢慢拖,也不要试图模拟高动态动作。
6. 从“能复现一次”到“可教学、可调试、可迭代”的小机器
当你能拖一段动作、让机械臂自己复现出来,这个项目已经完成了一半。继续往下走,不是急着加更多花哨功能,而是把这段经验沉淀成可维护的系统。
6.1 这个方案适合谁,不适合谁
先划清边界。这个方案适合的人:
- 刚开始接触单片机控制和机械臂、想亲手搭一套完整系统的学生或爱好者;
- 要做课程设计、毕业设计,需要一套可视化演示效果的场景;
- 想理解“采样、存储、回放”这一基本数据流的人。
不适合的场景:
- 工业级的高精度装配任务,它的重复精度通常达不到要求;
- 需要根据实时视觉或传感器反馈调整轨迹的场景;
- 对动作精度有严格指标的批量生产环节。
不要期待一套 F103 拖动示教系统能直接变成工业机械臂。工业示教器的价值在于精确标定、安全逻辑、误差补偿和长期稳定性,这些内容在这个项目里不会完整出现,也不需要在这个阶段出现。
6.2 下一步工程化建议:动作命名、离线验证、参数化
如果你想让这套系统更接近“可长期使用”的工具,下面几个方向值得逐步补上:
- 给动作编号和命名。不要只保存一条轨迹,把轨迹存成多个文件或扇区,通过按键或串口选择一个动作执行。
- 增加串口指令协议。例如
REC开始记录、STOP停止、PLAY 1 0.8表示播放 1 号动作,速度倍率 0.8。 - 在电脑端把轨迹导出成 CSV,然后绘制曲线。如果发现某个关节角度变化太陡,就能提前发现,不用反复在实体机械臂上试错。
- 如果打算做运动学分析,这时候再补 DH 参数。从结构装配图或 3D 模型里测量各个关节距离,建立坐标系。有了 DH 参数,才谈得上把轨迹放到 gazebo、mujoco 这类仿真环境里跑。
- 掉电保存。把这套记录逻辑从内存数组改成内部 Flash 或外置存储,动作才能真正沉淀下来。
真正常用的进阶配置,不是一次全上,而是一个一个加。每加一项都要确认它不会破坏时序。比如串口传输大量数据,如果采用轮询方式会阻塞主循环,导致采样间隔不稳定,那就改 DMA 或用缓存区拼接数据,让主循环不被长时间卡住。
6.3 最后建议:先把动作精度表达清楚,再谈提高精度
很多初学者在机械臂示教项目里最容易犯的错,是看到回放没有完全贴合原来的动作,就急着调算法或换更高精度的舵机。但更合理的第一步,是把“误差是多少”量化出来。
让机械臂回到某个起点,点击开始记录,匀速走一段路径,再手动回到起点。对比记录的首帧和结束帧,通常能看出零点偏差和回程误差。再记录一条来回重复的摆臂轨迹,让机械臂连续回放,观察每条轨迹的电机关节角差多少。
只有当你能用数字说出“这次回放和上次回放角度差了多少”,后续的 PID 参数整定、舵机性能升级、加编码器闭环才有明确方向。如果一开始就追求“看起来一模一样”,你很难知道到底是控制器问题、电机问题还是机械间隙问题。
示教操作和轨迹复现,真正吸引人的地方不在于让机械臂学会了一个动作,而在于它把一次含糊的手部动作,翻译成了一组精确、可保存、可修改的数据。当你意识到这一点,stm32f103 便不再只是驱动舵机的开发板,而是一台小小的“动作编译器”。先跑通最小闭环,再慢慢把动作变成可命名的指令、可导出的数据、可复现的轨迹,这条路会比盯着算法术语走要踏实得多。