ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32六轴机械臂示教操作与轨迹复现教程

STM32六轴机械臂示教操作与轨迹复现教程 做六轴机械臂项目做到中期以后大家问得最多的往往不是“怎么让舵机转起来”而是“怎样让它按照我想要的轨迹动起来”。如果要靠运动学逆解去编写一段手腕翻转、抓取、回位的组合动作对数学和代码能力的要求都不低但如果采用示教轨迹复现的方式整个过程就变成“人拖动小机械臂做一遍单片机记下来再让执行臂重放”工程门槛会一下子低很多。这一期教程围绕六自由度机械臂的示教操作与轨迹复现展开控制核心使用 STM32F103 单片机。文章会先讲清楚整体架构和硬件接线再给出可落地的 PWM 舵机驱动、ADC 角度采集、轨迹记录和回放状态机设计思路最后补充常见故障排查和工程注意事项。无论你是在做课程设计、毕业设计还是自己组一套六轴机械臂玩这套思路都可以直接迁移。1. 为什么六轴机械臂要做“示教操作 轨迹复现”很多刚开始接触六轴机械臂的同学第一反应是让机械臂完成“从 A 点抓取放到 B 点”这样的任务。要做到这一点常规思路是给机械臂建立 DH 参数模型。标定六个关节的零位和运动范围。写出正运动学把关节角度换算成末端位姿。再写逆运动学把目标位姿换算成六个关节角。规划轨迹生成每个时刻的关节位置。最后让控制器逐点执行。这套流程当然是最接近工业机器人开发的但放在 DIY 六轴机械臂上问题也很明显结构件加工误差、舵机安装偏差、减速箱回差都会让理论模型和实际运动对不上。如果每一段动作都想靠代码去“算”出来调试周期会非常长。示教操作则绕开了复杂的运动学推导。它的核心思想是由人直接拖动一套与执行机械臂结构相似的小机械臂各关节的角度传感器实时检测关节位置主控把传感器数据转换为舵机 PWM 信号驱动另一条机械臂同步跟随。“拖动小机械臂”是人工示教比较常用的一种做法。好处有三个不需要逆解操作人员无需关心关节角度只要用手把教臂摆成目标姿态即可。动作直观教臂转多少执行臂就按预设比例转多少效果所见即所得。可以快速产生轨迹把示教过程中每个时刻的六个关节角度按固定时间间隔保存下来就能形成一条完整轨迹需要复现时单片机按同样的时间间隔把角度点回放出来即可。因此本文要做的事情并不复杂读取教臂角度、实时驱动执行臂、定时保存轨迹点、按记录顺序回放。这正好是 STM32F103 这类资源不多、但控制能力足够的 MCU 所擅长的任务。2. 整体方案与工作流程2.1 系统由哪几部分组成整套系统可以划分为四个部分模块作用典型器件示教小机械臂人手拖动提供六个关节的目标角度六轴串联结构各关节安装角度传感器角度采集电路把机械角度变成 MCU 可读的电信号旋转电位器或 AS5600 等磁编码器主控板读取角度、处理按键、记录和回放轨迹STM32F103C8T6 最小系统板执行机械臂按主控给出的 PWM 信号运动六个 PWM 数字舵机 结构件这里要说明一下很多同学会问为什么不让执行臂直接被手拖动对于普通舵机驱动的机械臂舵机内部有减速齿轮组通电后反向拖动很容易导致舵机堵转、发热甚至烧毁。因此更安全的 DIY 示教方案是采用“主从式”结构人拖动的是无动力或轻负载的教臂执行臂只在复现阶段承担运动任务。如果想让执行臂本身支持直接拖动示教需要特殊设计的电机抱闸释放机构和绝对值角度反馈复杂度会高很多。2.2 数据流向用一句话概括系统流程人手拖动教臂 - 六个关节电位器产生模拟电压 - STM32F103 的 ADC 采样 - 转换为舵机脉宽 - 输出 PWM 驱动执行臂 - 同时按时间间隔保存当前角度 - 按键切换后按保存顺序回放在实时跟随模式下STM32F103 每 20ms 左右读取一次六路 ADC 值并把这组值映射成 PWM 脉宽输出。这个循环不断重复看起来就是执行臂跟随教臂同步运动。在记录模式下单片机除了实时跟随还会把每一次的 PWM 脉宽保存到轨迹数组中。停止记录后数组里保存的就是这段时间的完整动作序列。在回放模式下单片机从轨迹数组第一个点开始每隔固定时间间隔输出一组 PWM 脉宽直到全部回放结束。3. 本期硬件组装与接线要点3.1 材料清单第二期既然要接入示教与复现功能硬件上需要增加“教臂”部分。由于材料包的螺丝、铝件尺寸不一样下面只给出通用清单类别说明执行机械臂结构底座、腰部、大臂、小臂、腕部等六轴结构件教臂结构件与执行臂关节数量一致的小型串联臂可以用 3D 打印也可以用型材搭建角度传感器每个教臂关节一个旋转电位器共 6 个舵机每个执行关节一个 PWM 舵机共 6 个主控板STM32F103C8T6 最小系统板或类似兼容板电源舵机独立电源例如 2S 锂电池加稳压模块5V 主控供电按键与指示灯用于切换“记录 / 回放 / 停止”接线材料杜邦线或者排线、铜柱、扎带等如果你手里的执行臂是“总线舵机”版本则舵机接口不再是普通 PWM通信协议不同本文示例代码需要对应替换以下内容以最适合入门学习的 PWM 舵机为例。3.2 示教臂角度传感器装配对于 DIY 主从示教旋转电位器是最容易获取的角度传感器。装配时有几点需要注意电位器要和关节旋转轴同心否则转动时会憋住。每个关节最好有机械限位避免电位器超过有效电气转角。电位器两端分别接 3.3V 和 GND中间抽头接 STM32 的 ADC 引脚。如果某个关节方向反了可以在软件里做反向映射不一定要重新改结构。在功能上教臂并不需要很大的力矩关节转动越顺滑越好。如果结构件转动不顺畅首先检查轴承或轴套的安装同轴度。3.3 执行臂舵机与供电六自由度机械臂的六个舵机在启动和换向瞬间电流较大最忌讳的是用 STM32 最小系统板的 USB 口直接给舵机供电。更合理的供电方式是电池或电源适配器 - 稳压/降压模块 - 舵机电源正负极 - 舵机信号线接 STM32 PWM 引脚还需要特别注意舵机电源地、STM32 系统板地必须共地。很多“舵机不转、舵机乱抖、单片机复位”的问题本质都是没有共地。舵机的信号线是高电平脉冲信号STM32 的 PWM 引脚输出就可以驱动。接线时不需要把所有舵机电源并到主控板上正确的电源走线是直接从电源模块分出来再让主控板单独取电。3.4 引脚规划与避免 IO 冲突STM32F103C8T6 的引脚资源有限。如果六个舵机 PWM 和六路 ADC 都要占用引脚直接随便接很容易冲突。一个比较直观的分配方案如下功能引脚对应外设舵机 0~3 PWMPA0、PA1、PA2、PA3TIM2_CH1~CH4舵机 4、5 PWMPA8、PA9TIM1_CH1、TIM1_CH2教臂 ADC 通道 0~5PA4、PA5、PA6、PA7、PB0、PB1ADC1_IN4~IN9这样安排后PWM 引脚和 ADC 引脚不重叠六个舵机和六路电位器都能接入。如果你手里的 PCB 已经定义好其他引脚以你手上的原理图为准重点是把引脚配置集中到宏定义或配置文件中方便后续调整。4. STM32F103 核心驱动代码解析4.1 舵机 PWM 控制原理常见的模拟或数字舵机控制信号是周期约 20ms 的脉冲。脉冲宽度不同舵机输出的角度不同脉宽典型角度0.5ms0° 附近1.5ms中间位置2.5ms最大角度附近不同厂家舵机的脉宽范围有差异有的实际范围是 0.5ms~2.5ms有的只有 1ms~2ms。建议先查舵机数据手册然后在代码里把范围定义成宏方便标定。在 STM32CubeMX 中将定时器时钟配置成 72MHz预分频器设为 71则计数器频率为72MHz / 72 1MHz也就是计数器每计数一次为 1 微秒。如果把自动重载值 ARR 设为 19999则 PWM 周期为(19999 1) * 1us 20000us 20ms这正好符合舵机的 20ms 周期要求。此时设置 CCR 比较值CCR 值脉宽500500us15001500us25002500us因此代码里不需要写很复杂的数学公式直接“往 CCR 寄存器写入微秒值”即可。4.2 六路舵机 PWM 输出代码假设你在 STM32CubeMX 中已经生成了工程定时器句柄分别是htim1和htim2。可以写一个统一的舵机设置接口// servo.c #include main.h #include servo.h #define PULSE_MIN_US 500 #define PULSE_MAX_US 2500 void Servo_SetPulse(uint8_t idx, uint16_t pulse_us) { if (pulse_us PULSE_MIN_US) pulse_us PULSE_MIN_US; if (pulse_us PULSE_MAX_US) pulse_us PULSE_MAX_US; switch (idx) { case 0: __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, pulse_us); break; case 1: __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_2, pulse_us); break; case 2: __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_3, pulse_us); break; case 3: __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_4, pulse_us); break; case 4: __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, pulse_us); break; case 5: __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_2, pulse_us); break; default: break; } } void Servo_InitAll(void) { HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_2); HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_3); HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_4); /* TIM1 是高级定时器需要使能主输出 */ __HAL_TIM_MOE_ENABLE(htim1); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_2); for (uint8_t i 0; i 6; i) { Servo_SetPulse(i, 1500); } }代码里的“1500”表示舵机先回到 1.5ms 脉宽中位。实际项目中如果机械臂结构安装时零位并不在舵机中位这里需要按你的机械零位修改。4.3 六路 ADC 角度读取如果教臂使用电位器ST 的 ADC1 可以依次扫描多个通道。为了减少抖动我习惯对每个通道连续读几次然后取平均。下面是一个轻量实现// teach_adc.c #include main.h #include teach_adc.h static uint16_t adc_channel_map[6] { ADC_CHANNEL_4, // PA4 ADC_CHANNEL_5, // PA5 ADC_CHANNEL_6, // PA6 ADC_CHANNEL_7, // PA7 ADC_CHANNEL_8, // PB0 ADC_CHANNEL_9 // PB1 }; uint16_t ReadOneChannel(uint16_t channel) { ADC_ChannelConfTypeDef sConfig {0}; uint16_t value 0; sConfig.Channel channel; sConfig.Rank 1; sConfig.SamplingTime ADC_SAMPLETIME_239CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); value HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); return value; } void TeachArm_ReadAll(uint16_t *raw) { uint32_t sum 0; for (uint8_t ch 0; ch 6; ch) { sum 0; for (uint8_t sample 0; sample 5; sample) { sum ReadOneChannel(adc_channel_map[ch]); } raw[ch] sum / 5; } }这里之所以每读一个通道都执行HAL_ADC_Stop()是为了让下一次切换通道时不要沿用上一次的配置。在 6 个电位器、采样率要求不高的场景下这种写法简单可靠。5. 示教、记录与回放软件设计5.1 状态机设计机械臂示教不只是一个“循环读角度”的过程还需要考虑“什么时候开始记录”“什么时候停止”“什么时候回到轨迹首点”“什么时候开始回放”。使用状态机会清晰很多。可以定义这样几种模式模式含义MODE_FOLLOW实时跟随教臂运动但不记录MODE_RECORD实时跟随同时按周期保存轨迹点MODE_BACK_TO_START从当前姿态平滑回到轨迹首点MODE_PLAYBACK逐点回放轨迹按钮的功能可以这样设计按键 1进入记录。如果已经在记录则停止记录并进入“待机”或“跟随”模式。按键 2开始回放。如果采集到的轨迹点数为 0则按下无效。按键 3急停或复位所有舵机输出中位脉宽。采用这种划分后主循环的逻辑会很清楚。5.2 轨迹数据结构使用 STM32F103C8T6 时RAM 只有约 20KB。如果六个关节都保存为uint16_t每个轨迹点需要6 * 2 12 字节如果保存 800 个点总容量为 9600 字节约占 RAM 的一半。示例中先按这个规模设计// trajectory.h #define SERVO_NUM 6 #define MAX_TRAJ_POINT 800 typedef struct { uint16_t pulse[SERVO_NUM]; } TrajPoint; extern TrajPoint g_traj[MAX_TRAJ_POINT]; extern volatile uint16_t g_traj_len;当然800 个点通常不够保存几分钟的长轨迹。如果希望断电保存或保存更长时间可以外接 SPI Flash、SD 卡或者用串口把数据发送到上位机保存。这里先把 RAM 方案跑通再考虑扩展存储。5.3 实时跟随与轨迹记录下面这段代码是核心循环逻辑// main.c 中的循环片段 void MainLoop_Tick(void) { static uint32_t last_record_tick 0; static uint32_t last_follow_tick 0; uint32_t now HAL_GetTick(); if (g_mode MODE_FOLLOW || g_mode MODE_RECORD) { if (now - last_follow_tick 20) { uint16_t raw[6]; TeachArm_ReadAll(raw); for (uint8_t i 0; i 6; i) { g_current_pulse[i] MapRawToPulse(i, raw[i]); Servo_SetPulse(i, g_current_pulse[i]); } last_follow_tick now; } } if (g_mode MODE_RECORD) { if (now - last_record_tick
返回列表