简介:面向嵌入式开发者与机器人爱好者,这是一份基于STM32F103C8T6的六轴机械臂工程示例,重点演示目标位置抓取功能的软件实现,适合学习STM32外设控制、机械臂运动学或正在寻找可移植参考代码的读者。压缩包共851个文件、约23.2MB,主体为515个C源文件与194个头文件,同时包含Keil工程配置(uvprojx/uvoptx)、编译产物(hex/axf)、CubeMX的ioc引脚配置、Python辅助脚本,以及线性插值等DSP算法相关源码,便于在真实板卡上烧录验证,也能帮助理解轨迹规划与插补计算环节。项目文件分层较清晰,从电机驱动、传感器读取到上层抓取逻辑均有涉及,可据此梳理六轴机械臂的整机控制流程。内容预览中还可见arm_common_tables、arm_dct4_init等CMSIS-DSP文件,说明工程集成了ARM官方数学库,对需要开展机械臂运动控制或伺服调参的开发者有参考价值。目前已有377人浏览学习,适合STM32机械臂相关课题的入门与二次开发。 我不止一次被人问到:STM32F103C8T6这种“小蓝板”,到底能不能驱动六轴机械臂做目标位置抓取?我的回答是,能,而且如果只是配合舵机来做离线/半自动抓取,这颗Cortex-M3芯片的性价比高到离谱。这篇文章就是把我自己做的一个完整项目拆开来讲,从stm32c8t6原理图设计、六轴机械臂DH参数建模,到目标位置抓取的代码实例,全部摊开说清楚。适合手里有F103C8T6最小板、想入门机械臂控制,或者已经能点灯但不知道怎么把舵机和坐标运算串起来的读者。
1. 项目概述与整体方案设计
1.1 为什么是STM32F103C8T6,而不是ESP32或Arduino
先说结论:STM32F103C8T6做六轴舵机机械臂的控制核心,完全够用,而且比Arduino更稳,比ESP32更适合学习底层原理。
很多人习惯用Arduino UNO做机械臂,图它库多、写起来简单。但真到了六轴场景,Arduino UNO的AVR核心在处理运动学运算时明显吃力,尤其是逆解运算里大量三角函数和矩阵运算,精度和速度都打折扣。而STM32F103C8T6主频72MHz,带硬件乘法器和32位数据处理能力,算4x4齐次变换矩阵的几个乘法只需要微秒级,跑几百Hz的控制循环毫无压力。
有人会问,那为什么不直接用ESP32,还能带WiFi做无线控制。我的考量是这样:如果目标只是“把机械臂跑起来并实现指定坐标抓取”,那么STM32的裸机开发流程反而更清晰——PWM定时器、串口DMA、GPIO中断,每一步都能直观理解,不受RTOS和网络协议栈干扰。用ESP32当然可以做远程控制,那属于另一个项目方向。做六轴机械臂,先把核心运动学吃透,比什么都重要。
1.2 六轴机械臂的整机架构
这个项目的机械结构,我选的是常见的铝合金六轴机械臂套件,关节分布是:底座旋转轴(J1)、大臂俯仰(J2)、小臂俯仰(J3)、腕部旋转(J4)、腕部俯仰(J5)、末端法兰旋转(J6)。驱动方式全部采用舵机,型号上J1用大扭矩MG996R(55kg·cm级别的甚至更好),J2/J3也至少用MG996R级别,J4/J5/J6可以用SG90或者MG90S级别的小舵机。为什么这么分配?因为越靠近基座的关节承受的力矩越大,J1到J3需要对抗整个臂的重量和负载,力矩不够会发抖、丢步,甚至烧舵机。
控制系统核心就一块STM32F103C8T6最小系统板,配上舵机驱动板(或者自己用三极管/ULN2003搭的PWM驱动电路)。整机供电用12V转5V的稳压模块,舵机直接从5V供电取电,STM32板子可以共用5V,但要注意舵机启动瞬间电流可能冲到2A以上,所以电源余量至少4A起步,否则一通电就会系统复位。
整体工作流程这样:上位机串口下发目标位置的X/Y/Z坐标和末端姿态角度 → STM32读入后做逆运动学解算,得到六个关节的目标角度 → 定时器产生PWM信号驱动舵机逐关节转动 → 末端夹爪到位后,根据IO反馈或延时控制夹爪闭合,完成抓取。
2. 硬件设计:stm32c8t6原理图与关键电路
2.1 核心最小系统的原理图要点
STM32F103C8T6虽然很多模块板都做好了,但如果你要自己画板,或者排查硬件问题,有几个原理图节点要特别注意:
- 供电部分:VDD接3.3V,VDDA接3.3V并串一个磁珠或10Ω电阻做滤波,这是ADC稳定工作的前提。VSS和VSSA接地。
- 复位电路:NRST引脚接10kΩ上拉电阻到3.3V,并联一个100nF电容到地,形成典型的RC复位电路。如果复位引脚悬空,芯片上电偶尔会进不了正常状态。
- 启动模式:BOOT0和BOOT1必须通过10kΩ电阻下拉到地,这样芯片从Flash启动。很多新手画板时忘了给BOOT0加下拉电阻,导致程序下载后必须手动触碰引脚才能从Flash运行,非常坑。
- 晶振电路:8MHz主晶振两端接两个20pF负载电容,电容另一端接地。如果是用内部RC振荡器(HSI)可以省掉外部晶振,但做机械臂这种对时序精度有要求的项目,建议用外部晶振,串口波特率会更准。
- 下载接口:SWD接口只需SWDIO、SWCLK、GND三根线,加上3.3V就是四根。SWD比JTAG省IO,调试六轴机械臂时IO本来就紧张,建议直接引SWD。
如果你用的是一块钱一块的蓝色Pill板,其实这些都不用管,板厂已经把最小系统做好了。但只要你外接舵机驱动、电源模块和通信接口,就必须在自己的布板上把原理图链路画清楚。
2.2 舵机PWM驱动电路怎么做
六轴加一个夹爪,共需要至少7路PWM信号。STM32F103C8T6的TIM1、TIM2、TIM3、TIM4加起来有比较多的PWM通道,通常我这样分配:
| 外设 | 通道 | 用途 |
|---|---|---|
| TIM1 | CH1~CH4 | J1~J4舵机 |
| TIM2 | CH1~CH3 | J5、J6、夹爪 |
| USART1 | TX/RX | 上位机通信 |
| PA0~PA2 | GPIO输入 | 夹爪到位限位开关 |
舵机的信号线直接接IO口吗?不行。虽然舵机信号线理论上只需要3.3V的高电平就能识别,但工程上最好在信号线上串联一个几百欧姆的电阻,再并联一个10kΩ下拉电阻到地。原因有二:第一,舵机信号线长的时候会引入噪声,串电阻可以抑制振铃;第二,舵机内部的电路板是独立的,如果舵机损坏,串接电阻能保护MCU引脚不被反灌电压打坏。我实际调试时遇到过舵机内部短路,直接把一个IO口烧了,就是因为没加保护,后来所有信号线都串联了330Ω电阻,再没出过问题。
电源部分必须是星型布线:12V输入 → 稳压模块 → 5V母线 → 舵机电源线和MCU电源线分别从母线引出。千万不能让舵机的电流回路经过MCU的地线,否则舵机转动时地线抖动,MCU的ADC和串口通信会被干扰得乱七八糟。我自己第一次试的时候,舵机一转到某个角度,串口就会收到乱码,就是因为共地回路太长。
3. 运动学建模:六轴机械臂DH参数推导
3.1 DH参数表怎么建立
要说清楚“目标位置抓取”,核心就是坐标系的转换。六轴机械臂的每一个关节都是一个旋转自由度,要搞明白末端执行器在世界坐标系下的位置,就需要建立每个关节的坐标系,然后一步步变换过去。DH参数(Denavit-Hartenberg)就是描述相邻关节坐标系之间相对关系的四个参数:关节角θ、连杆偏移d、连杆长度a、连杆扭角α。
具体到我用的这款六轴机械臂,我测出来的DH参数表大致是这样(不同机械臂的结构尺寸不一样,需要实际测量):
| 关节 | θ(变量) | d (mm) | a (mm) | α |
|---|---|---|---|---|
| J1 | θ1 | 120 | 0 | -90° |
| J2 | θ2 | 0 | 150 | 0° |
| J3 | θ3 | 0 | 120 | 0° |
| J4 | θ4 | 80 | 0 | -90° |
| J5 | θ5 | 0 | 0 | 90° |
| J6 | θ6 | 60 | 0 | 0° |
这里a值就是相邻两个关节轴之间的连杆长度,也就是大臂和小臂的实际长度。d值是沿着关节轴方向的偏移量,底座高度和腕部到法兰的距离都体现在d里。α值的正负号很关键,它决定了坐标变换矩阵里旋转分量的方向。
在建立DH表的时候最容易忽略的是“零点位置定义”。我调试时发现,很多舵机安装完成后,软件里设置的0°和机械结构的0°并不对应。比如J2大臂,理论上应该垂直向上是0°,但舵机装上去后可能开机就是30°。所以DH表里的θ变量最好加上一个安装偏置角,即软件角度 = 目标角度 + 舵机安装偏置。这个偏置在调试时用水平尺或角度尺标定,别偷懒。
3.2 正运动学:从角度到坐标
正运动学就是已知六个关节角,求末端执行器的位置和姿态。标准做法是把每个关节的齐次变换矩阵相乘:
T_06 = T_01 * T_12 * T_23 * T_34 * T_45 * T_56
每个T矩阵的通用形式是:
| cosθ -sinθ*cosα sinθ*sinα a*cosθ | | sinθ cosθ*cosα -cosθ*sinα a*sinθ | | 0 sinα cosα d | | 0 0 0 1 |代码里我不建议用二维数组硬算,而是先用辅助函数解耦:
// 计算单个DH变换矩阵 // 输入:关节角theta_rad, 偏移d, 杆长a, 扭角alpha_rad // 输出:4x4旋转平移矩阵mat[4][4] void dh_matrix(float theta_rad, float d, float a, float alpha_rad, float mat[4][4]) { float ct = cosf(theta_rad); float st = sinf(theta_rad); float ca = cosf(alpha_rad); float sa = sinf(alpha_rad); mat[0][0] = ct; mat[0][1] = -st * ca; mat[0][2] = st * sa; mat[0][3] = a * ct; mat[1][0] = st; mat[1][1] = ct * ca; mat[1][2] = -ct * sa; mat[1][3] = a * st; mat[2][0] = 0.0f; mat[2][1] = sa; mat[2][2] = ca; mat[2][3] = d; mat[3][0] = 0.0f; mat[3][1] = 0.0f; mat[3][2] = 0.0f; mat[3][3] = 1.0f; }然后把六个矩阵依次相乘,取最后结果的第4列前三个元素,就是末端在世界坐标系下的X、Y、Z坐标。这里有个数值细节:矩阵乘法最好用双精度还是单精度?我的经验是,在STM32F103C8T6上用float就够,因为机械臂本身的机械误差和舵机精度(大概1°~2°)远大于单精度浮点的舍入误差。但是如果做定点数优化,精度反而不够,所以我强烈建议直接开FPU(STM32F103没有硬件FPU,但因为F103C8T6是Cortex-M3,浮点运算靠软件库),用编译器的数学库就行,不必为精度焦虑。
3.3 逆运动学:从坐标反推关节角
目标位置抓取最难的一步,是给你一个X、Y、Z坐标,让机械臂自动算出六个关节各该转多少度。这个问题叫逆解,方法很多:几何法、代数法、迭代法。对于六轴机械臂,我推荐先用**代数法(解析法)**理解关节间解耦逻辑,再在代码里用几何法简化前三个关节的计算。
在解析法中有一个关键概念:腕部解耦。对于大多数六轴机械臂,J4、J5、J6三个轴的交点都在同一个点(称为腕部中心点)。这样末端位置只与J1、J2、J3相关,而J4、J5、J6只影响末端姿态,不影响位置。因此逆解步骤可以拆分为:
- 根据目标末端位置和姿态,计算腕部中心坐标。
- 腕部中心只由前三个关节决定,先用几何法解出θ1、θ2、θ3。
- 根据目标姿态和前三个角度,解出θ4、θ5、θ6。
实际代码里,我通常会先做“简化为Pitch轴运动平面”的几何解算。假设J1旋转后,机械臂在一个垂直平面内工作,那问题就变成了一个二连杆(大臂加小臂)加一个俯仰角的问题。
// 逆解函数:输入目标坐标x,y,z,输出六个关节角度 // 返回0成功,-1表示目标不可达 int inverse_kinematics(float x, float y, float z, float angles_deg[6]) { // 1. 计算J1旋转角:atan2(y, x) float theta1 = atan2f(y, x); // 2. 转换到机械臂平面坐标系 float r = sqrtf(x * x + y * y); // 水平投影距离 float s = z - BASE_HEIGHT; // 去除底座高度后的垂直高度 // 3. 计算J2、J3:二连杆逆解(余弦定理) float L1 = ARM_UPPER_LENGTH; // 大臂长度 float L2 = ARM_LOWER_LENGTH; // 小臂长度 float link_dist = sqrtf(r * r + s * s); if (link_dist > L1 + L2 || link_dist < fabsf(L1 - L2)) { return -1; // 超出可达范围 } float cos_alpha = (L1 * L1 + link_dist * link_dist - L2 * L2) / (2.0f * L1 * link_dist); float alpha = acosf(cos_alpha); // 大臂与连线夹角 float beta = atan2f(s, r); // 连线与水平面的夹角 float theta2 = beta + alpha; // 大臂角(需要根据实际安装方向调整符号) float cos_gamma = (L1 * L1 + L2 * L2 - link_dist * link_dist) / (2.0f * L1 * L2); float gamma = acosf(cos_gamma); float theta3 = gamma - (float)M_PI; // 小臂相对大臂的角度 // 4. 姿态角解算:根据期望的腕部俯仰角决定θ4/θ5/θ6 // 这里默认末端TCP垂直于工作平面,简化处理 float theta4 = 0.0f; float theta5 = -(theta2 + theta3); // 腕部俯仰补偿,使末端保持固定姿态 float theta6 = 0.0f; // 5. 角度转换为度并加上偏置 angles_deg[0] = theta1 * 180.0f / (float)M_PI; angles_deg[1] = theta2 * 180.0f / (float)M_PI; angles_deg[2] = theta3 * 180.0f / (float)M_PI; angles_deg[3] = theta4 * 180.0f / (float)M_PI; angles_deg[4] = theta5 * 180.0f / (float)M_PI; angles_deg[5] = theta6 * 180.0f / (float)M_PI; return 0; }这个代码是高度简化的,但已经能完成核心目标:给定一个平面内的坐标,解出六根轴角度。实际项目中你还需要考虑多种姿态解的选择(比如肘上肘下解)、奇异点规避、关节限位判断。我在电控上增加了一个逻辑:逆解算出来的每一个角度,在发送到PWM之前都必须过一遍限位表,任何一个关节超出机械限位,直接放弃当前目标,返回错误,避免舵机堵转烧毁。
3.4 轨迹插补为什么也要写在STM32里
坐标到位后如果直接硬跳,六个舵机会同时猛转,轻则抖动,重则烧舵机、晃断结构件。所以目标位置抓取的完整流程中,必须有轨迹插补这一步。
最简单的做法是线性插值:把起点到终点的路径分成N个中间点,每个控制周期(比如20ms)输出一个中间点对应的六个角度,舵机平滑转动。
// 线性插值函数:从当前角度数组cur[],插值到目标角度数组tar[] // 插值步数step越大越平滑,但执行时间越长 void interpolate_angles(float cur[6], float tar[6], int step) { float delta[6]; for (int i = 0; i < 6; i++) { delta[i] = (tar[i] - cur[i]) / step; } for (int s = 1; s <= step; s++) { float tmp[6]; for (int i = 0; i < 6; i++) { tmp[i] = cur[i] + delta[i] * s; } set_servo_angles(tmp); // 六路PWM同时更新 delay_ms(20); } }这个函数在STM32上跑起来,关键是“六路PWM同时更新”。用HAL库的话,对应调用__HAL_TIM_SET_COMPARE同时设置六个通道,或者更底层地直接操作TIMx->CCR1~CCR4寄存器,保证一个控制周期内六路信号同步更新,否则舵机会出现滞后错位,末端轨迹会歪。
4. 目标位置抓取的代码实例
4.1 主程序框架:状态机写法
机械臂代码不建议写成一坨顺序执行的流水账,最好用状态机来管理整个抓取流程。因为机械臂会收到各种各样的指令,可能正在转动时又来一个急停命令,如果没有状态机,逻辑会乱成一锅粥。
我的主状态机大致这样:
IDLE(空闲) → MOVE_TO_PREPARE(移动到预备点) → MOVE_TO_TARGET(移动到目标点) → GRASP(闭合夹爪) → MOVE_TO_PLACE(移动到放置点) → RELEASE(释放夹爪) → 回到IDLE每个状态用枚举变量管理,主循环不断轮询状态和外部命令。STM32F103C8T6的72MHz跑这个循环绰绰有余,不需要RTOS,裸机就够了。核心价值是:状态机把“坐标运算”和“动作执行”解耦,即使逆解运算偶尔卡个几毫秒,也不影响舵机PWM的实时输出。
4.2 PWM初始化与角度映射
舵机的控制原理很简单:20ms周期内,高电平脉宽1ms对应0°,2ms对应180°(不同舵机略有差异)。STM32的定时器PWM输出就是干的这个活。
以TIM1的CH1为例,初始化关键代码如下:
void servo_pwm_init(void) { // 开启TIM1和GPIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef gpio; gpio.GPIO_Pin = GPIO_Pin_8; // TIM1_CH1默认映射到PA8 gpio.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio); // 定时器配置:72MHz分频后得到1MHz计数频率,周期20ms TIM_TimeBaseInitTypeDef tim; TIM_TimeBaseStructInit(&tim); tim.TIM_Prescaler = 72 - 1; // 72MHz / 72 = 1MHz,计数周期1us tim.TIM_Period = 20000 - 1; // 20ms周期,即20ms刷新一次 TIM_TimeBaseInit(TIM1, &tim); // PWM通道配置 TIM_OCInitTypeDef oc; TIM_OCStructInit(&oc); oc.TIM_OCMode = TIM_OCMode_PWM1; oc.TIM_OutputState = TIM_OutputState_Enable; oc.TIM_Pulse = 1500; // 初始脉宽1.5ms,舵机中位 TIM_OC1Init(TIM1, &oc); TIM_Cmd(TIM1, ENABLE); TIM_CtrlPWMOutputs(TIM1, ENABLE); // 高级定时器必须开主输出 }这里有两个容易踩的坑。第一个,TIM1是高级定时器,PWM配置完成后必须调用TIM_CtrlPWMOutputs(TIM1, ENABLE),否则PA8上没有波形输出,很多人查了半天发现寄存器都没问题,就是忘了这一步。第二个,PSC分频值72-1是因为STM32的时钟树72MHz分频后要得到1MHz的计数时钟,这样每计一个数就是1微秒,脉冲宽度直接以微秒为单位计算,非常直观。角度转换就很简单了:
// 角度转脉宽:0°对应500us,180°对应2500us,线性映射 uint32_t angle_to_pulse(float angle_deg) { return (uint32_t)(500 + angle_deg * 2000.0f / 180.0f); }考虑到实际舵机的线性度并没有理论那么完美,我建议在手爪抓取的关键点位做一次校准,确定实际脉宽和真实角度的对应关系,修正比例系数。
4.3 夹爪抓取逻辑与限位反馈
目标位置抓取的“抓”字,不是简单地把夹爪闭合,它涉及到位控制和力控制。用舵机做夹爪时,我们不能直接控制夹持力,只能控制角度。所以我一般会给夹爪加到位检测:两个IO口接微动开关或光电传感器,检测夹爪闭合是否碰到物体。
代码逻辑是这样:
// 执行抓取动作 void grasp_action(void) { // 先张开夹爪到最大位置 set_servo_angle(GRIPPER_CHANNEL, GRIPPER_OPEN_ANGLE); delay_ms(300); // 缓慢闭合 for (float ang = GRIPPER_OPEN_ANGLE; ang >= GRIPPER_CLOSE_ANGLE; ang -= 1.0f) { set_servo_angle(GRIPPER_CHANNEL, ang); delay_ms(15); // 如果检测到夹爪夹到物体,立即停止 if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) // 限位触发为低电平 { break; } } }这一步的细节很重要。如果没有到位检测,当物体尺寸小于预期时,夹爪会直接闭到底,舵机堵转发热,用不了几次就烧了。有了限位开关之后,舵机夹持力控制在刚好碰到物体的位置,寿命长得多。
4.4 串口协议与上位机命令设计
配合上位机调度,串口协议要尽量简单可靠。我的协议格式是:
帧头(0xAA 0x55) + 命令字(1字节) + 数据长度(1字节) + 数据(N字节) + 校验和(1字节)常用的命令字:
#define CMD_MOVE_LINEAR 0x01 // 线性轨迹运动到坐标 #define CMD_MOVE_JOINT 0x02 // 关节角直接运动 #define CMD_GRASP 0x03 // 执行抓取 #define CMD_RELEASE 0x04 // 执行释放 #define CMD_GET_POSE 0x05 // 查询当前末端坐标数据部分直接用float类型的X/Y/Z坐标,上位机只要按照四字节小端格式发就行。接收用串口中断+缓冲区的方式,不要在中断里做逆解,而是把整帧数据解析完成后置一个标志位,主循环里再统一处理。这样即使上位机连续发数据,也不会因为运算超时而丢帧。
5. 调试记录与常见问题排查
5.1 舵机抖动、复位、烧IO的常见排查
我把自己踩过的坑和网友问得最多的问题整理成一个速查表:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 上电后STM32反复复位 | 舵机启动瞬间电流拉低5V电压 | 换大功率5V电源(4A以上),在舵机电源端加大电容 |
| 舵机到位后嗡嗡响/抖动 | PWM周期不稳定,或脉宽恰好在中点附近 | 检查定时器分频是否配置正确,用示波器看PWM波形;给舵机加死区设置 |
| 串口收到乱码 | 共地问题/波特率不准 | 用外部晶振;MCU与USB转TTL共地;把波特率降到9600测试 |
| 逆解结果角度跳变 | DH参数符号定义错误或安装偏置未标定 | 单关节逐个测量实际角度与软件角度差 |
| 六轴动作但末端位置不准 | 机械回差/舵机负载下垂 | J2/J3换更大扭矩舵机;在多圈运动中记录累计误差 |
| IO口/MCU烧毁 | 舵机堵转反灌电流 | 信号线串联电阻;电源用地隔离 |
这里最容易被轻视的是“舵机堵转”。当某个关节因为机械限位或者逆解错误被命令转到物理极限之外,舵机会持续堵转,电流飙升、发热,然后通过信号线把毛刺传回MCU。轻则复位,重则烧IO。所以我在每个关节角度输出前都做限位检查,并且在舵机电源和MCU电源之间加了一个低阻值的PTC自恢复保险丝,电流异常时自动断开舵机电源,MCU保持正常运行,还能在串口打印故障信息。
5.2 目标位置不可达怎么处理
目标位置抓取时最容易遇到的问题:上位机给了一个坐标,但逆解返回-1,机械臂完全没反应。这时候很多人的第一反应是代码bug,但实际上大多数情况是“目标点不在机械臂的可达空间内”。
可达空间受机械臂DH参数中的a、d值和关节限位共同决定。我之前用一版DH参数时,J2的限位是0°~90°,导致机械臂只能向前上方伸,后方和下方区域的坐标永远不可达。这不是逆解的错误,是物理约束。
处理策略是,在串口返回中增加一个状态码:0xEE表示目标不可达,上位机可以根据返回值在界面上提示用户调整目标点位置。同时把当前可达空间的半径计算出来,做成表格反馈给上位机,这样软件层面能自动筛选合法坐标。
5.3 调优心得:从“能跑”到“好用”
整个项目做完,我最大的体会是,六轴机械臂的难点不在“驱动舵机”,而在“让末端准确到达目标”。要做到这一点,少不了几步调试:
第一是角度零偏标定。每个舵机安装后,先用简单的测试程序逐个归零,用角度尺量出实际偏差,写进一个数组里。之后所有逆解角度输出前都加上这组偏置。这一步可以解决大部分位置偏到姥姥家的问题。
第二是夹爪中心点(TCP)标定。DH表里的末端坐标系原点在哪,决定了下发坐标的含义。我在调试时用一个尖头笔装在夹爪上,手动控制机械臂去点桌面上的几个已知坐标点,反过来修正DH参数里的d值(腕部偏移)。用三五个点做最小二乘拟合,精度能提高一个量级。
第三是轨迹速度的控制。舵机机械臂不像伺服电机,没有闭环反馈,所以运动速度必须保守。我一般让末端最大线速度限制在20~30mm/s,太快了不仅末端轨迹会明显偏移,急停时还会因为惯性导致整机晃动。这个速度对桌面级的抓取演示完全够用。
最后再分享一个小技巧:调试时一定不要直接从“上位机发坐标”开始。先用串口助手手动下发关节角命令,让机械臂逐个关节动起来,确认每个轴方向正确、限位可靠;再切换到正运动学模式,输入一组已知角度,确认计算的末端坐标和真实位置吻合;最后才跑逆解和完整抓取流程。这个顺序看着多花了时间,实际上能帮你少排查无数个脑溢血级别的bug。
本文还有配套的精品资源,点击获取