STM32六足机器人实战:运动学逆解与步态控制实现
2026/9/16 14:20:17 网站建设 项目流程

简介:基于STM32的六足机器人完整工程资料,面向嵌入式开发者、机器人爱好者及高校相关课程设计人群,解决从微控制器选型、电机驱动到步态控制与系统联调的全流程实现问题,适合有一定单片机基础并希望深入机器人运动控制的读者。压缩包共856个文件,包含C/H源码、Keil工程文件、PCB与原理图设计文件、Hex固件、APK遥控端及PDF说明等,覆盖软硬件设计全链路,整体约46.48MB,目前已有1103人学习下载。内容包含STM32F4系列主控的硬件原理图与PCB工程、PWM电机驱动与传感器接口设计;固件采用模块化结构,集成FreeRTOS任务调度与PID控制算法,并提供直行、转弯、爬坡等多步态代码,可快速移植到同类多足平台。同时附带无线通信、调试配置与线上APK等说明,便于理解整机搭建与排错,是一份可直接参考的六足机器人实战资料。

1. 六足机器人为何要锁定 STM32 而不是树莓派

六足机器人在嵌入式项目里是一个分水岭:它比小车多出“步态”这个维度,又比机械臂多出“多腿支撑与摆动交替”的时序问题。18 个舵机要在一个控制周期内全部收到新目标角度,三角步态下每条腿落地的瞬间还要完成坐标解算,这时候如果主控系统还在被操作系统调度打断,机身就会出现肉眼可见的抽动。选择 STM32 的核心理由不是算力,而是定时器外设的确定性——PWM 波形由硬件生成,CPU 只负责在每个步态周期内计算角度,任务边界非常清晰。本文从 F103/F407 这类常见芯片出发,把六足机器人的硬件分配、运动学逆解、HAL 固件框架和调参顺序串成一套可以照着落地的方案,适合已经具备基本嵌入式开发经验、想第一次把六足真正立起来走路的工程师。

2. 六足机器人硬件骨架:18 自由度布局、舵机选型与电源预算

2.1 每条腿三个关节,六条腿怎么分配自由度

标准六足布局是每条腿三个转动关节,分别对应髋关节、股关节和胫关节。髋关节轴线垂直机身平面,负责整条腿在水平面上的内外摆动;股关节和胫关节轴线平行,共同决定足端在竖直平面内的位置。每条腿 3 个自由度,六条腿一共 18 个自由度,对应 18 路 PWM 输出。这个自由度分配也是大多数开源六足项目的默认配置,好处是逆解计算可以拆成“平面投影 + 余弦定理”两步,在 STM32 上做浮点运算也不会太吃力。

腿部尺寸比例会影响步态的极限范围。髋关节到股关节之间的固定段通常取 4~6 cm,这段距离决定了足端能否收到身体正下方,距离过短时足端会明显外撇,转向步态会变得笨拙。股节和胫节长度比较常见的比例是 1:1.2 到 1:1.5,比如股节 7 cm、胫节 9 cm。比例太接近,膝关节屈伸角度会很极端,做三角步态时相邻两条腿容易互相干涉;比例拉太长,整机重心被抬高,侧向行走时翻倒力矩会变大。装机前最好先在纸上画出腿的极限位置,确认整个运动范围内股节与胫节不会碰到机身底板。

2.2 舵机选型与扭矩估算

舵机选型主要看扭矩和响应速度。机身重量假设为 1.5 kg,三角步态下任意时刻只有三条腿着地,单条腿至少要承担 0.5 kg 的静载荷,考虑加速冲击和舵机传动损耗,股关节的峰值扭矩要按 2~3 倍安全系数估算。入门方案常用 MG996R 或同类数字舵机,标称堵转扭矩在 9.4 kg·cm 左右,对一台 1.5 kg 以下的小型六足足够用,再往上走可以用液压?不,更贵的舵机如 GDW DS5160 或 Invioid 系列——应避免特定折扣。用一两张参考表:

舵机类型标称扭矩工作电压控制信号适用机身
MG90S1.8 kg·cm4.8V20ms 周期,500-2500us 脉冲桌面级迷你六足
MG996R9.4 kg·cm4.8-6.0V20ms 周期,500-2500us 脉冲1.5 kg 以内入门六足
30kg 级别金属舵机30 kg·cm6.0-7.4V数字信号,支持更高频率3 kg 以上载重实验平台

注意市面上舵机参数虚标很常见,国产 MG996R 实际扭矩往往到不了标称值。协议层面建议在步态周期里留出 20% 的扭矩余量,否则电池电压一掉,腿部会在摆动相末端软掉,出现拖地。

2.3 电源预算、舵机供电与 MCU 供电必须分离

18 个舵机同时动作时,瞬时电流会非常难看。以 MG996R 为例,单舵机堵转电流约 1.2A,即使正常摆动只按 0.3A 平均估算,瞬时尖峰也可能到 3A 以上。如果把 STM32 和舵机共用同一路电源,PWM 引脚上的干扰会直接传导到复位电路,表现为机身一动主控就重启。常见做法是双电源方案:舵机用 2S 锂电池供电,经过 BEC 或降压模块稳定到 6V;STM32 板单独用另一路 5V 电源,地线在电池负极单点汇合。若手头只有一个电池,则至少要加一枚容量大于 1000uF 的低 ESR 电解电容跨接在舵机电源输入端,并用 DC-DC 隔离模拟区电容。

3. 运动学逆解:从足端坐标到 18 路角度,推导与 C 实现

3.1 先算髋关节角:水平面上的投影问题

每条腿的逆解可以分两步走。第一步看水平面,把足端目标点投影到机身平面上,投影点与髋关节轴心的连线相对机身中轴线形成的夹角,就是髋关节角度。用atan2f直接求解即可:

float coxa_angle = atan2f(y, x);

其中x是足端在机体坐标系中沿机身轴方向的距离,y是沿腿部安装轴向外方向的距离。这里atan2fatanf更安全,因为它不需要调用方处理 x=0 的四象限问题,返回范围正好覆盖舵机从 -90 度到 90 度的常见行程。

算出髋关节角后,把目标点变换到“髋关节旋转后的平面”中,原先的三维问题就降维成了二维:新的水平距离为sqrt(x*x + y*y),再减去髋关节物理长度,剩下的就是股关节和胫关节要在竖直平面内覆盖的距离。这个小技巧让后面的三角函数推导只发生在一个平面内,不容易出错。

3.2 股关节与胫关节:余弦定理求解平面三角形

降维之后的问题可以归纳为:已知股节长度femur、胫节长度tibia,以及从股关节轴心到足端的距离在竖直平面内的投影R和垂直高度z,求解两个关节角。从股关节到足端的空间直线距离D为:

D = sqrt(R*R + z*z)

在股节、胫节和虚拟连线D构成的三角形中,三条边长度都已知,直接用余弦定理求膝关节角:

tibia_angle = PI - acos((femur^2 + tibia^2 - D^2) / (2*femur*tibia))

股关节角则由两个角度叠加:从竖直方向到虚拟连线的夹角,以及虚拟连线和股节之间的夹角。前一个用atan2f(R, -z),后一个再用一次余弦定理。这里是完整的 C 函数:

#include <math.h> typedef struct { float coxa_len; /* 髋关节固定段长度, 单位 m */ float femur_len; /* 股节长度 */ float tibia_len; /* 胫节长度 */ } LegGeometry; typedef struct { float coxa; /* 髋关节角度, 单位 rad */ float femur; /* 股关节角度 */ float tibia; /* 胫关节角度 */ } LegAngle; /* 足端坐标: x 向前, y 向外, z 向下为负 */ int leg_ik(const LegGeometry *geo, float x, float y, float z, LegAngle *angle) { float L0 = sqrtf(x*x + y*y); float R = L0 - geo->coxa_len; if (R < 0.01f) return -1; /* 目标太靠近身体, 无法触达 */ float D = sqrtf(R*R + z*z); if (D > geo->femur_len + geo->tibia_len - 0.002f) return -2; /* 目标超出机械臂展 */ if (D < fabsf(geo->femur_len - geo->tibia_len) + 0.002f) return -3; /* 目标过近, 机构无法折叠到该点 */ float phi = atan2f(R, -z); float beta = acosf((R*R + z*z + geo->femur_len*geo->femur_len - geo->tibia_len*geo->tibia_len) / (2.0f * D * geo->femur_len)); float gamma = acosf((geo->femur_len*geo->femur_len + geo->tibia_len*geo->tibia_len - D*D) / (2.0f * geo->femur_len * geo->tibia_len)); angle->coxa = atan2f(y, x); angle->femur = phi - beta; angle->tibia = 3.14159265f - gamma; return 0; }

函数返回非 0 值表示目标点超出关节可及范围,调用方应丢弃该目标。特别注意z的方向约定:我把正方向定义为向上,六足站立时足端在机身下方,所以z传入负值。atan2f(R, -z)里的负号不能省,否则股关节角度符号会翻转,走路时腿会朝反方向蹬。

3.3 机体坐标系转换:步幅和转向怎么叠加

每条腿的足端目标不只在腿部坐标系中计算,还要加上机身位姿变化。实际项目中我习惯定义两个坐标系:机体坐标系固定于机身中心,世界坐标系固定于地面,机身可平移和绕 Y 轴旋转(转向)。步态算法生成的是世界坐标系下的足端位置,装入腿之前要先变换回机体坐标系。

一个常见的简化处理是:忽略机身横滚与俯仰,只对偏航角做二维旋转,同时把机身平移量直接叠加到足端坐标上。对第 i 条腿,足端在机体坐标系下的位置可以写成:

world_x = body_forward + leg_local_x[i] * cos(yaw) - leg_local_y[i] * sin(yaw); world_y = body_side + leg_local_x[i] * sin(yaw) + leg_local_y[i] * cos(yaw);

其中body_forward是步态周期内机身的位移量,body_side用于侧移步态,yaw是转向角。这样腿部逆解函数本身只关心足端相对髋关节的位置,步态的平移、旋转全部在调用前完成,代码边界清晰,后续加 IMU 姿态反馈也只需要改动装配体坐标变换这一层。

4. STM32 HAL 库固件:PWM 输出、步态状态机与姿态补偿

4.1 用定时器分配 18 路 PWM 的最小配置

18 路舵机信号在 STM32 上需要两个或三个定时器协同工作。F103 系列的 TIM1、TIM2、TIM3、TIM4 都带有多个捕获比较通道,以 TIM2 和 TIM3 为例,各出 4 路 PWM,再加上 TIM4 的两路,就凑齐 18 路。如果使用 F407,还可以启用 TIM1 的互补通道,但驱动舵机其实不需要互补输出,常规通道就够。

HAL 库配置时注意几个边界条件:预分频系数和自动重装载值必须保证 PWM 周期为 20ms。以 72 MHz 主频的 STM32F103 为例,预分频设为72-1,使计数器时钟为 1MHz,自动重装载值设为20000-1,输出频率就是 1MHz / 20000 = 50Hz,这正是模拟舵机最常见的刷新频率。下面是初始化一路通道的最小代码:

TIM_HandleTypeDef htim2; TIM_OC_InitTypeDef sConfigOC = {0}; __HAL_RCC_TIM2_CLK_ENABLE(); htim2.Instance = TIM2; htim2.Init.Prescaler = 72 - 1; /* 72MHz / 72 = 1MHz */ htim2.Init.Period = 20000 - 1; /* 1MHz / 20000 = 50Hz */ htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(&htim2); sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 1500; /* 初始脉冲宽度 1500us */ sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

修改舵机角度时只需要调用__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pulse),其中pulse的单位是微秒。如果后续换成 333Hz 数字舵机,把Period改为3000-1即可,Pulse 上限也要跟着缩放到 500~2500us 区间内,这个映射表建议放在单独的函数里统一管理,不要散落在步态代码中。

4.2 步态状态机:三角步态与波纹步态的时序参数

步态的本质是给六条腿分配“支撑相”和“摆动相”。支撑相足端着地,相对地面后移推动机身前进;摆动相足端抬起,向前抬到新落地点。三角步态的优势是任意时刻只有三条腿在摆动,另外三条腿形成稳定三角形支撑,对机身静稳定性要求最低。相位分配通常是腿部奇数组(0、2、4)与偶数组(1、3、5)各占半个周期,如下图(文字表示)中的leg_phase[i]数组可以用均匀错开法得到。

我用一个简单状态机管理步态相位,每条腿维护自己的phase计时器:

#define LEG_COUNT 6 typedef struct { float cycle_time; /* 一个完整周期, 单位 s */ float swing_ratio; /* 摆动相占整个周期的比例 */ float step_length; /* 单步步幅, 单位 m */ float step_height; /* 摆动相抬腿高度 */ } GaitParam; float leg_phase[LEG_COUNT]; /* 每条腿当前所处时间点 */ void gait_init(float cycle, float swing_ratio, GaitParam *gp) { gp->cycle_time = cycle; gp->swing_ratio = swing_ratio; for (int i = 0; i < LEG_COUNT; i++) { leg_phase[i] = (float)i / LEG_COUNT * cycle; } } int leg_in_swing(int idx, const GaitParam *gp) { return leg_phase[idx] < gp->cycle_time * gp->swing_ratio; } void gait_tick(float dt, GaitParam *gp) { for (int i = 0; i < LEG_COUNT; i++) { leg_phase[i] += dt; while (leg_phase[i] >= gp->cycle_time) { leg_phase[i] -= gp->cycle_time; } } }

这里摆动相时长由cycle_time * swing_ratio决定。三角步态的相位偏移恰好是两只腿组之间差半个周期,gait_init里均匀分配相位,正好形成“三只抬、三只落”的三角步态。若要实现波纹步态,则把相位差改成cycle_time / 6,每次只抬起一条腿,稳定余量最大但速度最慢。实际调速度时,优先缩短cycle_time而不是增大step_length,因为大步幅会加剧机身重心起伏。

摆动相的足端轨迹还需要一个竖直方向的正弦或梯形插值,否则会出现瞬间加速抖动。我通常对抬腿高度做半正弦曲线:

float swing_z_offset(float progress) { return sinf(progress * 3.14159265f) * gp.step_height; }

progress是摆动相内部的归一化时间,从 0 到 1。这个函数让足端先抬后落,起落速度在两端都趋于零,机身明显更稳。三种步态的本质区别只是相位差和摆动相占比,状态机的骨架可以共用。

4.3 用 MPU6050 做姿态补偿,修正机身倾斜

地面稍有不平,六足机身就会歪向一侧,步态调得再好也会出现“长短腿”。常见做法是在机身中心装一个 MPU6050,读取加速度计数据估算横滚角与俯仰角,再把这部分角度换算成腿部坐标补偿量。加速度计读取函数用 I2C 直接读取原始值即可:

uint8_t mpu60_read_angle(float *roll, float *pitch) { uint8_t buf[6]; if (HAL_I2C_Mem_Read(&hi2c1, 0x68 << 1, 0x3B, 1, buf, 6, 100) != HAL_OK) { return 1; } int16_t ax = (int16_t)((buf[0] << 8) | buf[1]); int16_t ay = (int16_t)((buf[2] << 8) | buf[3]); int16_t az = (int16_t)((buf[4] << 8) | buf[5]); *roll = atan2f((float)ay, (float)az); *pitch = atan2f(-(float)ax, sqrtf((float)ay * ay + (float)az * az)); return 0; }

注意 MPU6050 需要先配置电源管理寄存器,把器件从睡眠模式唤醒,并设置加速度计量程。姿态解算在六足上不必用完整的四元数卡尔曼滤波,atan2近似就够用——因为机身运动速度远低于碰撞冲击频率,加速度计的低频噪声可以通过简单低通处理。拿到横滚角后,把它乘以腿部到机身中心的 Y 向距离,叠加到2.3节的机体坐标变换里,就能让足端在倾斜地面上自动调整落点,实现姿态保持。采样频率可放到 100Hz 左右,与控制周期错开,避免 I2C 阻塞步态时序。

5. 调参顺序与三处最容易被忽略的坑

5.1 先标定舵机零位:机械装配与代码零点对齐

六足机器人最常见的第一个故障是单腿抬起时其余腿乱抽,根源多半是零位没对齐。舵机出厂时零位通常在中位脉冲 1500us,但机械臂装配角度各不相同,同一块舵机安装在不同腿上时,其“水平伸展”位置对应的脉冲宽度可能相差几十微秒。正式调步态前,先逐个通道发送 1500us 脉冲,用目测或量角器把每条腿的三个关节都掰到机械参考位置,然后记录每个舵机的零位偏差,写进一个数组:

float pwm_offset[LEG_COUNT][3] = { { 1480, 1520, 1495 }, { 1505, 1490, 1510 }, /* 每行对应一条腿的髋、股、胫零位偏移 */ };

这个数组在输出 PWM 前与逆解角度叠加。零位偏差与步幅、步态参数无关,优先调好它,后续所有角度换算才有意义。

5.2 用串口打印与逻辑分析仪验证 18 路 PWM 的实时性

代码跑起来后,靠眼睛判断步态是否正常很困难,尤其是机身只有轻微抖动时。我通常分两步做验证:先从工程里导出实际发送的 18 路脉冲宽度,通过串口以 115200 bps 周期打印,观察相邻控制周期内角度跳变是否超过预设阈值;再用逻辑分析仪同时抓两路 PWM,确认周期稳定在 20.00ms 而不是 19.5ms 这种漂移值。

如果发现周期不稳,原因基本集中在三处:PWM 定时器的预分频和自动重装载没按 1MHz/50Hz 组合配置;中断服务函数里做了耗时的浮点运算导致主循环卡顿;舵机刷新频率与步态控制周期没有间隔对齐。将步态计算放在定时器中断里、PWM 脉宽更新放在主循环中,是最稳妥的分工——前者保证相位推进的确定性,后者避免在中断里调用HAL_TIM_PWM_Start这类重函数。

5.3 给运动学加限幅:防止意外指令损坏舵机与结构

逆解函数返回负数时只是在说“算不出来”,实际舵机并不会因此停在原处。真正危险的场景是目标点刚好在可达域边缘,逆解得出的角度接近 180 度,而 PWM 脉宽映射到舵机硬件极限外,此时舵机堵转发热,齿轮组会直接崩坏。最后的保护措施是角度限幅:

void clamp_angle(float *angle, float min, float max) { if (*angle < min) *angle = min; if (*angle > max) *angle = max; }

把每个关节的允许角度范围做成数组,在leg_ik返回成功后立即调用。限幅值取机械行程的 80%,留下余量给运动中的惯性过冲。验证限幅是否生效的做法是:让步行步幅参数从 10mm 逐步增大到 50mm,同时串口打印每次被限制到的关节索引和限制次数,如果限制次数随步幅线性增长,说明机身结构或足端轨迹设计需要调整,而不是简单调低限幅范围。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询