简介基于STM32F103Cortex-M3内核的蜘蛛机器人控制程序源码工程面向嵌入式学习者与机器人爱好者完整实现多足仿生机器人的运动控制。工程由Keil构建涵盖GPIO电机驱动、ADC传感器采集、UART通信及PID运动控制等模块并涉及RTOS任务调度、无线通信、传感器融合、故障诊断与电源管理适合希望掌握机器人底层驱动与嵌入式控制算法的开发者参考。资源包共188个文件、约1.6MB核心为31个.c源文件与34个.h头文件附有.o编译对象、.uv2工程配置、.sct链接脚本等辅助文件便于还原编译流程与项目结构。已有1382人学习下载。通过研读源码可系统梳理外设初始化与中断处理流程掌握多任务实时调度在机器人平台的落地方式并借鉴步进电机控制与故障诊断等算法实现为后续自主开发足式机器人打下基础。1. 一只六足蜘蛛机器人为什么值得为一颗STM32F103写两千行控制代码先把场景摆在前面某开发者照着开源图纸拼了一台六足蜘蛛机器人结构件装好、18 个舵机全部接上上电一瞬间6 条腿毫无章法地乱抖甚至两条腿顶在一起把机身顶翻。折腾一晚上发现不是舵机坏了而是控制逻辑里同一时刻所有腿都在切换状态电机堵转、电流拉垮、PWM 占空比互相打架。这个项目要解决的问题就在这里用一颗 STM32F103 把 18 路舵机驱动、步态规划和遥控链路完整跑通是一份能让六足机器人“站得住、走得稳、能听懂指令”的控制程序源码。适合刚接触多足机器人、想从单一电机控制跨到多自由度运动控制的开发者也适合拿来做课程设计或竞赛底座的嵌入式玩家。上手门槛不算高但里面的坑一点都不少。2. 硬件架构与资源分配STM32F103 的引脚和外设是怎么规划的2.1 六足机器人的硬件拓扑18 路舵机怎么挂到一块板子上这只六足蜘蛛采用最常见的“每条腿 3 个自由度”布局6 条腿一共 18 路舵机。髋关节负责前后摆动大腿关节负责抬腿小腿关节负责够地三个角度配合起来足端才能在一个三维空间里移动到目标点。舵机选型上这份源码按普通 PWM 模拟舵机来写也就是给 50Hz、1~2ms 脉宽的标准信号不是总线舵机。这意味着控制板只需要输出多路 PWM不需要串口总线协议硬件连接非常直观每个舵机信号线接一个 MCU 引脚电源线和地线并联到舵机电源上。电源是整个项目最容易翻车的地方。18 个舵机同时动作时瞬间电流可以到好几安培靠 STM32F103 板子上的 3.3V 稳压器直接供电是完全不现实的。常规做法是两组电源独立供电一组 6V/5V 舵机电源电流能力至少 5A 以上用粗线直接接到舵机电源排针另一组 3.3V 逻辑电源给 MCU、蓝牙模块和传感器供电。两组电源的地线必须共地否则 PWM 信号参考电位不一致舵机会随机乱抖。源码里没有画原理图但控制代码的引脚定义和初始化顺序已经把这种内外设依赖关系写得清清楚楚。2.2 外设资源分配四个定时器输出 PWM一个串口收遥控STM32F103C8T6 有 4 个通用/高级定时器TIM1 有 4 个通道TIM2、TIM3、TIM4 也各有 4 个通道总共 16 路硬件 PWM。但项目需要 18 路舵机缺口 2 路。我看到源码里用了“硬件 PWM 为主 软件模拟 PWM 补齐”的方案这也是这个容量级别 MCU 上最常见的做法。四个定时器选在不同引脚上避免和串口、I2C 调试口冲突。关键外设分配如下表功能外设/引脚说明舵机 PWM 0-3TIM2_CH1~CH4 (PA0~PA3)左前腿、左中腿、左后腿、右前腿的髋关节舵机 PWM 4-7TIM3_CH1~CH4 (PA6, PA7, PB0, PB1)大腿关节 4 路舵机 PWM 8-11TIM4_CH1~CH4 (PB6~PB9)小腿关节 4 路舵机 PWM 12-15TIM1_CH1~CH4 (PA8~PA11)剩余腿部关节模拟 PWM 16-17PB4, PB5定时器中断里用 GPIO 翻转实现遥控串口USART1 (PA9, PA10)接蓝牙透传模块调试串口USART2 (PA2, PA3)打印步态相位和角度注意与 TIM2 通道复用注意PB6、PB7 同时是 I2C1 引脚如果以后要挂 MPU6050 陀螺仪需要和 TIM4 的 PWM 输出做取舍。源码里没有启用 I2C 外设所以不存在冲突但二次开发时要留意。2.3 工程目录结构标准外设库风格模块边界清楚源码工程是标准外设库StdPeriph风格没有用 HAL 库。目录结构比较典型spider_robot/ ├── USER/ │ ├── main.c // 主循环步态刷新、遥控指令处理 │ ├── stm32f10x_it.c // 中断服务函数软件 PWM 翻转在这里 │ └── system_stm32f10x.c ├── HARDWARE/ │ ├── servo.c / servo.h // 舵机角度到 PWM 的映射角度限幅 │ ├── step.c / step.h // 步态规划、相位表、轨迹插值 │ ├── remote.c / remote.h // 串口遥控帧解析 │ └── power.c / power.h // ADC 采集电池电压 ├── SYSTEM/ │ ├── delay.c │ ├── sys.c │ └── usart.c └── 步态参数头文件 gait_cfg.h // 腿长、步长、抬腿高度、周期这个分层思路值得照着学servo 层只关心“把角度写到某个 PWM 通道”完全不关心步态step 层只关心“输出 18 个目标角度”不关心 PWM 寄存器怎么写。两层之间通过一个 float 数组 angles[18] 传递数据。后面想改成 12 条腿或者换成总线舵机只需要替换其中一层不用动另一层。3. 步态规划与逆运动学一条腿从坐标到舵机角度的推导3.1 单腿逆运动学三行几何公式求出三个关节角每一条腿都是一个三连杆结构。髋关节在机身侧面向外伸出大腿再向下伸出小腿。控制目标是“足端踩到空间某个点 (x, y, z)”需要反推出髋、大腿、小腿三个舵机分别转多少度。这就是逆运动学。源码里用几何法求解不涉及矩阵运算在 Cortex-M3 上跑起来很轻量。核心思路分两步先把三维坐标投影到水平面求出髋关节角再在腿部平面内用余弦定理求出大腿和小腿角。核心函数如下static void leg_ik(float x, float y, float z, float *hip_angle, float *knee_angle, float *foot_angle) { float l1 HIP_2_KNEE_LEN; // 大腿长度单位 mm float l2 KNEE_2_FOOT_LEN; // 小腿长度单位 mm float r sqrtf(x * x y * y); // 足端到髋关节的水平距离 float d sqrtf(r * r z * z); // 髋关节到足端的空间距离 // 髋关节角控制整条腿在水平面内的摆向 *hip_angle atan2f(y, x) * 180.0f / PI; // 小腿角大腿与小腿之间的夹角由余弦定理求得 float cos_gamma (l1 * l1 l2 * l2 - d * d) / (2.0f * l1 * l2); *foot_angle acosf(cos_gamma) * 180.0f / PI; // 大腿角大腿相对水平面的角度 float cos_beta (l1 * l1 d * d - l2 * l2) / (2.0f * l1 * d); *knee_angle (atan2f(z, r) - acosf(cos_beta)) * 180.0f / PI; }逻辑上这个函数先把足端坐标投影到水平面atan2 求出髋关节需要转到的水平角然后在腿部平面内把大腿、小腿和髋到足的连线看成一个三角形用余弦定理解出两个内角。参数这里要注意x 是前后方向y 是左右方向z 是离地高度腿长单位统一用毫米。求出的角度是基于“理想坐标系”的实际舵机装上去有安装偏差所以 servo 层会再加上一个 per-leg 的角度偏移量做校准。3.2 三角步态六条腿分两组一组抬、一组撑六足机器人最常用的步态是三角步态这也是源码里默认的 gait 模式。6 条腿按身体两侧交错分成两组A 组是左前、右中、左后腿编号 0、2、4B 组是右前、左中、右后1、3、5。任意时刻只有一组腿抬起向前摆动另一组腿撑在地面上支撑身体并向后蹬形成一个稳定的三角形支撑面。这个动作以周期 T 不断切换机器人就向前走了。源码里用一个简单的相位表管理这个切换过程#define STEP_PERIOD_MS 800 // 一个完整步态周期 #define SWING_PHASE_MS 320 // 摆动相时长占总周期 40% #define SUPPORT_PHASE_MS 480 // 支撑相时长占总周期 60% const uint8_t leg_group[6] {0, 1, 0, 1, 0, 1}; // 0A组1B组 static void step_update(uint32_t tick_ms) { uint8_t phase (tick_ms % STEP_PERIOD_MS) SWING_PHASE_MS ? 0 : 1; for (uint8_t i 0; i 6; i) { if (leg_group[i] phase) { // 本组腿处于摆动相抬腿并前移 leg_swing(i, tick_ms % STEP_PERIOD_MS); } else { // 本组腿处于支撑相落地并向后推 leg_support(i, tick_ms % STEP_PERIOD_MS); } } }逻辑不复杂一个周期 800ms前 320ms 是 A 组摆动、B 组支撑后 480ms 两组交换动作。leg_swing 和 leg_support 会根据当前周期内的相对时间实时计算这条腿足端的目标坐标再调逆运动学转成角度。参数好理解周期越短走得越快但太快会超过舵机跟手速度导致抖动摆动相比例越小支撑时间越长行走越稳但速度变慢。默认 40% 摆动比例是个折中。3.3 足端轨迹插值为什么不能直接让足端点对点移动如果摆动腿直接从上一点线性移到下一点足端在抬起和落地的瞬间速度突变会产生很大的冲击表现就是机身一顿一顿甚至把舵机齿轮打坏。源码里采用了正弦插值抬腿高度按正弦曲线先升后降前进位移也按正弦曲线平滑过渡。这样足端在起止点的速度都为零力学上更柔和。static void leg_swing(uint8_t leg_id, uint32_t phase_time) { float progress (float)phase_time / SWING_PHASE_MS; // 0.0 ~ 1.0 float lift sinf(progress * PI) * STEP_HEIGHT_MM; // 抬腿高度曲线 float x LEG_START_X (LEG_END_X - LEG_START_X) * progress; float y LEG_Y_OFFSET[leg_id]; float z -lift; // 向上抬 // 求逆运动学并写入 angles 数组 leg_ik(x, y, z, angles[leg_id * 3], angles[leg_id * 3 1], angles[leg_id * 3 2]); }这里的 progress 从 0 到 1 线性增长但 lift 用了 sin 曲线高度从 0 升到最高再降回 0。这就模拟了真实的迈步过程提脚、往前伸、落脚。参数里最值得改的是 STEP_HEIGHT_MM抬太高会让重心波动大抬太低会在不平地面上绊倒。推荐从 15mm 开始调然后根据地面情况逐步加到 25mm。步长 LEG_END_X 和 LEG_START_X 的差值决定步幅和周期配合起来就是行走速度。4. 舵机控制链路与遥控从定时器中断到机器人迈出一步4.1 舵机 PWM 信号模型角度和占空比是怎么换算的普通模拟舵机是通过脉宽控制角度的标准范围是 0.5ms 到 2.5ms对应 0° 到 180°周期固定 20ms50Hz。比如 90° 对应的脉宽是 1.5ms。控制程序要做的就是把角度值线性映射到定时器的比较寄存器 CCR 上。这里牵涉到一个容易算错的细节不同品牌的舵机脉宽范围和机械转角范围不一定完全对齐。便宜的舵机常常标称 180°实际脉宽在 0.6ms 到 2.4ms 就到限位了超出范围会发出吱吱堵转声长时间堵转会发烫甚至烧坏。所以源码里单独做了一个 servo 层专门处理角度到脉宽的映射和限幅不让上层步态把越界角度直接灌到寄存器里。#define PWM_PERIOD_US 20000 // 20ms 周期 #define PWM_MIN_US 500 // 0° 对应的脉宽 0.5ms #define PWM_MAX_US 2500 // 180° 对应的脉宽 2.5ms #define SERVO_ANGLE_MIN 0 #define SERVO_ANGLE_MAX 180 static int32_t angle_to_ccr(float angle) { if (angle SERVO_ANGLE_MIN) angle SERVO_ANGLE_MIN; if (angle SERVO_ANGLE_MAX) angle SERVO_ANGLE_MAX; float duty_us PWM_MIN_US (angle / 180.0f) * (PWM_MAX_US - PWM_MIN_US); return (int32_t)(duty_us * 72); // 72MHz 时钟下1us 对应 72 个计数 }这里有个关键设计限幅放在角度值域上而不是脉宽值域上。因为角度限幅可以通过 per-leg 偏移量动态调整比如某条腿机械上只能转到 160°直接把上限改成 160 就行如果限幅放在脉宽上每次换舵机型号都要改映射函数不灵活。返回的 CCR 值直接写入定时器对应通道就完成了舵机角度更新。4.2 定时器 PWM 初始化四路定时器一起配以 TIM2 为例PA0~PA3 四个通道输出 PWM主频 72MHz预分频 71自动重载值 19999得到 50Hz 的 PWM。代码如下static void TIM2_PWM_Init(void) { GPIO_InitTypeDef gpio {0}; TIM_TimeBaseInitTypeDef tim {0}; TIM_OCInitTypeDef oc {0}; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); gpio.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); tim.TIM_Prescaler 72 - 1; // 1MHz 计数频率 tim.TIM_Period 20000 - 1; // 20ms 周期 tim.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, tim); oc.TIM_OCMode TIM_OCMode_PWM1; oc.TIM_OutputState TIM_OutputState_Enable; oc.TIM_Pulse 1500; // 初始 1.5ms舵机回中位 oc.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM2, oc); // OC2 ~ OC4 配置相同省略 TIM_Cmd(TIM2, ENABLE); }预分频 72-1 之后计数频率为 1MHz即一个计数单位正好 1μs这样 TIM_Pulse 直接写脉宽微秒数换算直觉。自动重载值 20000-1 配合 1MHz 计数周期正好 20ms。TIM_Pulse 初始值设为 1500对应 90° 中位这一步很关键上电瞬间所有舵机先回到中位而不是跳到步态角度避免机器人上电就抽搐。4.3 串口遥控解析一帧数据怎么变成前进后退遥控链路用的是蓝牙串口透传模块手机端或另一个蓝牙模块发字节流USART1 中断逐字节接收。通信帧格式在源码里定义得很明确帧头 2 字节 命令字 数据 校验。#define FRAME_HEAD0 0xAA #define FRAME_HEAD1 0x55 typedef struct { uint8_t head0; uint8_t head1; uint8_t cmd; int16_t data; // 命令参数如目标步长 uint8_t checksum; // head0~data 所有字节累加和 } RemoteFrame_t; void remote_parse_byte(uint8_t byte) { static uint8_t buffer[5]; static uint8_t index 0; buffer[index] byte; if (index 2) return; // 帧头不匹配整体左移一字节重新找 if (buffer[0] ! FRAME_HEAD0 || buffer[1] ! FRAME_HEAD1) { buffer[0] buffer[1]; index index - 1; return; } if (index 5) return; uint8_t sum 0; for (uint8_t i 0; i 4; i) sum buffer[i]; if (sum buffer[4]) { remote_handle_cmd(buffer[2], (int16_t)(buffer[3] | 0)); index 0; } else { // 校验失败丢弃整帧 index 0; } }解析逻辑采用逐字节状态机每次中断进来一个字节先积累到缓冲区再检查帧头、长度和校验和。帧头不匹配时没有直接丢弃而是把缓冲区左移一位重新对齐这样即使数据流中间丢了一个字节也不至于错过后面的整帧。checksum 是累加和写起来简单能挡住绝大多数串口干扰。remote_handle_cmd 里根据命令字切换机器人的运动状态前进、后退、左转、右转、停止以及用 data 字段动态调步长和抬腿高度。5. 编译烧录与避坑排查从翻车现场到机器人站起来5.1 开发环境与工程编译MDK 里要确认的三个点工程用的是标准外设库配合 MDK 环境编译。双击工程文件打开后第一次编译大概率会报错主要原因是三点目标芯片型号没选对、宏定义缺失、标准外设库路径没包含全。打开 Target Options 面板在 Device 标签页选 STM32F103C8在 C/C 标签页的 Define 里确认有 STM32F10X_MD 和 USE_STDPERIPH_DRIVER 这两个宏前者告诉库用中等容量芯片的外设定义后者启用标准外设库驱动。编译通过后生成 hex 文件用 ST-Link 或者串口一键下载工具烧进去。烧录前给 STM32F103 上电但不给舵机电源先看 MCU 板上的指示灯和串口打印输出。源码 main 函数里默认上电会通过调试串口打印一段系统信息能收到字符串就说明芯片跑起来了。接下来再给舵机供电观察是否所有舵机都回到中位。这个顺序能避免“舵机乱动导致电流不稳”和“芯片本身就没跑起来”两个问题混在一起排查。5.2 烧录方式与接线自检板子没反应时先查这四步烧录失败是新手最常遇到的第一道坎。STM32F103 支持 SWD 和串口下载两种方式源码工程两种都兼容。用 ST-Link 的话接 SWDIO、SWCLK、GND、3.3V 四根线用串口下载则要把 BOOT0 跳线跳到 1复位后再下载下载完跳回 0。如果提示 No target connected先查四件事USB 线是不是只能充电不能传数据ST-Link 驱动装没装SWD 引脚有没有被其他外设占用以及板子供电是否正常这四步排查能覆盖百分之九十的烧录失败场景。烧录成功但上电乱动的排查顺序也很关键。先断开所有舵机信号线只留 MCU 最小系统确认串口打印正常然后只接一条腿的三个舵机用手托着机身通过串口发指令让这条腿做单腿摆动测试最后再接全部 18 路舵机。每次增加硬件规模后如果出现新问题问题范围就锁定在新接入的那部分而不是全机盲目排查。5.3 避坑记录五条血泪经验现象一上电后所有舵机持续抖动甚至机身共振。 原因舵机电源电流不够或者舵机电源和逻辑电源没共地PWM 信号参考电位漂移。 解决换 5A 以上舵机电源用粗硅胶线重新布舵机电源干线确认逻辑地和舵机地单点共地并在舵机电源两端并一个 470μF 电解电容和 104 瓷片电容。现象二程序里写的左转指令实际机器人往右转。 原因左右两侧腿的舵机安装方向互为镜像角度增量方向相反但步态层用的是同一套角度计算。 解决在 servo 层的 per-leg 偏移表里给左右侧腿加不同的方向系数要么取反角度要么用 180 减角度做镜像调试时单腿慢慢试。现象三步态看起来乱两条相邻腿同时抬起。 原因腿的物理安装顺序和软件里的 leg_id 编号不一致相位表按软件编号分组实际机械腿却属于另一组。 解决在 step_update 里把当前 phase 状态通过调试串口打印到上位机同时观察实际哪条腿在动把编号和相位表修正到一致。这里一定要实测不要想当然按“左前0、右中2”的顺序假设。现象四某个舵机发热特别严重还伴随吱吱异响。 原因逆运动学算出角度超出舵机机械限位舵机被逼到堵转位置持续发力。 解决给这个舵机单独设角度上限同时在 servo_angle_to_ccr 里把动态限幅位置提到逆运动学之前步态参数抬腿高度、步长也要保证足端轨迹不超出运动学可达范围。现象五每次上电瞬间舵机会猛跳一下像抽风一样。 原因上电时 GPIO 还没初始化引脚电平不确定舵机随机收到一个非法脉宽。 解决main 函数最开始把舵机信号线引脚统一设置为低电平等定时器初始化完成、TIM_Pulse 全部写入中位值之后再使能 PWM 输出。从那次以后我每次写多舵机程序都会强制把“输出中位→延时 200ms→进入步态循环”这个时序走一遍。6. 二次开发改造把固定步态改造成参数可调的运动库这套源码默认的步态参数是硬编码在头文件里的步长、抬腿高度、周期都是宏定义。想让它变成真正能二次开发的控制底座第一件事是把这些参数收进一个结构体再给上层提供一套设置接口。typedef struct { float step_length_mm; // 单步步长 float step_height_mm; // 抬腿高度 uint32_t period_ms; // 步态周期 uint8_t gait_mode; // 0 三角步态1 波浪步态 } MotionParams_t; void motion_set_params(MotionParams_t *param); void motion_set_velocity(float linear_speed, float angular_speed);我的建议是增加一个 motion 层对外只暴露“设定速度”和“设定转向角速度”两个接口内部再把速度分解成左右两侧不同的步长。比如转向时左侧腿步长 20mm、右侧腿步长 10mm机器人就会画弧线转弯。这样上层不管是接手机蓝牙、体感手套还是上位机视觉模块都只跟 motion 层打交道不用关心逆运动学和腿编号。改完之后验证分三步走先把所有角度限幅打开用手悬空提着机身做三步空走确认没有腿卡住再放到桌面上用小步长、低高度慢速走最后逐步加大步长和抬腿高度。我自己的教训是改步态参数永远从最保守值起步别一次把速度拉满舵机齿轮崩了是没法后悔的。希望这篇拆解能帮你把这只蜘蛛从“会抖”调到“会走”后续加传感器、做自主循迹就顺理成章了。本文还有配套的精品资源点击获取