资讯详情

STM32舵机控制机械臂:从PWM到数据手套与VR映射实战

📅 2026/10/15 15:37:10 | 华诺云谱 👁 阅读
STM32舵机控制机械臂:从PWM到数据手套与VR映射实战
简介这份资源面向嵌入式开发初学者与机器人爱好者提供一套以STM32微控制器驱动舵机、并通过数据手套实现手势操控机械臂的完整工程。项目将嵌入式控制、舵机PWM角度调节与传感器人机交互结合可用于课程设计、毕业设计或机器人入门实践。压缩包共206个文件约4.88MB以C源码与头文件为主体包含STM32外设驱动、定时器PWM输出、串口通信等实现并附带Keil工程配置、编译中间文件与可执行输出方便直接打开编译与二次修改。目前已有6568人学习下载说明其在同类开源项目中具备一定参考价值。读者可从中获取STM32工程搭建思路、舵机控制代码框架、数据手套信号解析与系统集成方法并借助完整目录结构快速定位关键模块理解从手势采集到机械臂动作执行的完整链路。1. 从零搭一套 STM32 舵机控制机械臂为什么我劝你先搞懂 PWM 而不是急着写逆解很多人第一次接触机械臂脑子里想的是六轴逆运动学、ROS、MoveIt结果板子焊好、舵机一通电机械臂直接抽搐着撞向限位。问题不在算法在于连最底层的 PWM 都没配对。STM32 舵机控制机械臂这个方向本质是用定时器输出多路 50Hz 的 PWM 波每路占空比对应一个关节角度再往上叠加人机交互电位器、数据手套、上位机和虚拟现实映射。它适合两类人一类是电子/自动化方向的学生想拿它当嵌入式外设综合练习另一类是想做遥操作或 VR 联动 Demo 的开发者需要一个低成本、可复现的执行端。这套东西不复杂但坑集中在定时器通道分配、舵机供电和角度标定三处下面按我实际拆装的顺序讲。2. 舵机控制原理与 STM32 定时器选型50Hz 从哪来、通道怎么分2.1 舵机为什么吃 50Hz 的 PWM常见舵机SG90、MG996R 这类内部是一个小控制板加电位器反馈它期望外部给它一个周期 20ms 的方波高电平持续时间在 0.5ms 到 2.5ms 之间对应 0° 到 180°。换算一下20ms 周期就是 50Hz0.5ms 高电平对应 2.5% 占空比2.5ms 对应 12.5%。有些舵机标称 1ms~2ms 对应 0~180°实际用起来会有偏差这就是后面要标定的原因。STM32 输出这种波形靠定时器的 PWM 模式。以常见的 STM32F103C8T6 为例它有 TIM1~TIM4 四个通用/高级定时器每个定时器有 4 个独立通道。也就是说一颗 F103 最多能同时输出 16 路 PWM驱动一台六轴机械臂绰绰有余。选型时优先用 TIM2、TIM3、TIM4因为 TIM1 是高级定时器配置刹车和死区时容易出玄学问题新手先绕开。2.2 定时器参数怎么算预分频和自动重装PWM 频率由定时器时钟、预分频系数PSC和自动重装值ARR共同决定PWM频率 定时器时钟 / ((PSC 1) * (ARR 1))F103 主频 72MHzTIM2/3/4 挂在 APB1 上但定时器时钟是 72MHz。要得到 50Hz72,000,000 / ((PSC1)*(ARR1)) 50 (PSC1)*(ARR1) 1,440,000取 PSC71则 (ARR1)20000ARR19999。这样计数器每 20ms 溢出一次正好一个舵机周期。分辨率是 20000 个计数对应 20ms即 1 个计数 1us0.5ms 就是 5002.5ms 就是 2500。这个分辨率对舵机够用因为舵机本身死区就有几微秒。2.3 用 CubeMX 配一路 PWM 的完整步骤我一般用 CubeMX 生成初始化代码省得手写寄存器。步骤如下选好芯片型号在 Pinout 里把某个 GPIO 设为 TIMx_CHy比如 PA0 设为 TIM2_CH1。左侧 Timers 里点开 TIM2Clock Source 选 Internal Clock。Channel1 选 PWM Generation CH1。Parameter Settings 里Prescaler 71Counter Period 19999Counter Mode UpPulse 先填 1500对应中位 1.5ms。生成代码在 main 里调用HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1);。生成的初始化代码里关键几行是这样// tim.c 中 MX_TIM2_Init 片段 htim2.Instance TIM2; htim2.Init.Prescaler 71; // 72MHz/(711)1MHz 计数频率 htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 19999; // 1MHz/20000 50Hz htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(htim2); sConfigOC.OCMode TIM_OCMODE_PWM1; sConfigOC.Pulse 1500; // 初始 1.5ms舵机中位 sConfigOC.OCPolarity TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(htim2, sConfigOC, TIM_CHANNEL_1);逻辑说明Prescaler 把 72MHz 降到 1MHz即每个计数 1usPeriod 设 19999 让计数器数 20000 次溢出周期 20msPulse 是捕获比较值决定高电平持续多少个计数也就是多少微秒。改角度就是改 Pulse。参数说明Pulse 范围理论 500~2500实际因为舵机差异我一般限制在 600~2400避免顶到机械限位烧舵机。如果你用的是 270° 舵机这个范围要重新标。2.4 多路 PWM 的通道分配策略一台六轴机械臂需要 6 路 PWM。用 TIM2 的 CH1~CH4 加 TIM3 的 CH1~CH2 就够。分配时注意两点一是同一定时器的四个通道共用 PSC 和 ARR所以频率必须一致这对舵机正好合适二是避开 JTAG 占用的引脚PA13/PA14/PA15/PB3/PB4否则调试口和 PWM 打架。我吃过这个亏PA15 配了 PWM 结果下载器连不上查了半天。提示如果通道不够可以用 TIM 的互补输出或者换引脚重映射但重映射要开 AFIO 时钟CubeMX 里勾一下就行。3. 人机交互与数据手套映射把手指弯曲变成关节角度3.1 交互方案选型电位器、数据手套还是上位机机械臂的输入方式决定整套系统的复杂度。常见三种方案成本精度适用场景电位器直连 ADC极低中单臂手动示教、教学演示数据手套弯曲传感器中中低遥操作、VR 手势映射上位机/VR 串口下发中高高虚拟现实联动、轨迹录制数据手套的核心是柔性弯曲传感器手指弯曲时电阻变化通过分压电路进 STM32 的 ADC。它的问题是每根手指的阻值范围不一致且随温度漂移所以必须做逐通道标定。虚拟现实那头一般是在 PC 或一体机里跑一个仿真模型把关节角通过串口或无线模块发给 STM32。3.2 ADC 采集弯曲传感器并映射到角度先看采集。假设弯曲传感器和 10k 电阻分压接到 PA1ADC1_IN1// 读取一次 ADC 并转成 0~180 角度 uint16_t adc_raw 0; float angle 0; HAL_ADC_Start(hadc1); if (HAL_ADC_PollForConversion(hadc1, 10) HAL_OK) { adc_raw HAL_ADC_GetValue(hadc1); // 12位0~4095 } HAL_ADC_Stop(hadc1); // 标定值伸直时 raw_min弯曲到底时 raw_max #define RAW_MIN 800 #define RAW_MAX 3200 angle (float)(adc_raw - RAW_MIN) / (RAW_MAX - RAW_MIN) * 180.0f; if (angle 0) angle 0; if (angle 180) angle 180;逻辑说明ADC 是 12 位读到 0~4095。RAW_MIN 和 RAW_MAX 是实测标定值不是理论值必须自己测。映射后做限幅防止超范围。参数说明RAW_MIN/RAW_MAX 的测法——串口打印原始值手指完全伸直记一个数完全弯曲记一个数多测几次取平均。如果传感器抖动大加一个滑动平均滤波// 8 次滑动平均抑制传感器抖动 #define FILTER_N 8 uint16_t filter_buf[FILTER_N] {0}; uint8_t filter_idx 0; uint16_t filter_adc(uint16_t new_val) { filter_buf[filter_idx] new_val; filter_idx (filter_idx 1) % FILTER_N; uint32_t sum 0; for (int i 0; i FILTER_N; i) sum filter_buf[i]; return sum / FILTER_N; }3.3 角度到 PWM 脉宽的转换与死区处理拿到 0~180 的角度后要转成定时器的 Pulse 值。前面算过 1 计数 1us所以// 角度转 PWM 比较值脉宽 500~2500us uint16_t angle_to_pulse(float angle) { if (angle 0) angle 0; if (angle 180) angle 180; return (uint16_t)(500 angle / 180.0f * 2000); } // 应用到某个通道 __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, angle_to_pulse(angle));逻辑说明__HAL_TIM_SET_COMPARE是宏直接改 CCR 寄存器比重新调用 HAL 库函数快适合在控制循环里高频调用。参数说明500 和 2000 这两个数对应 0.5ms 和 2.5ms 的跨度。如果你的舵机是 1ms~2ms 规格改成 1000 angle/180*1000。死区处理指的是机械臂相邻关节不能同时转到极限否则会自碰撞。我一般软件里给每个关节设一个软限位比如关节 2 限制在 15°~165°超出就截断。3.4 虚拟现实端的串口协议设计VR 或上位机下发数据最省事的是定长帧。我常用这种格式帧头(0xAA 0x55) 6个关节角度(各1字节, 0~180) 校验和(1字节)STM32 端用串口空闲中断加 DMA 接收解析时先找帧头再校验和。这样即使丢字节也能重新同步。数据手套如果是有线的直接走 ADC如果是无线的就在手套端加个 MCU 打包发送协议同上。虚拟现实场景里PC 端把仿真模型的关节角实时发下来STM32 只负责执行不做逆解这样分工最清晰也最容易调试。4. 供电、共地与舵机抖动三个最容易翻车的地方4.1 现象舵机一上电就抖或者动一下就复位原因舵机堵转电流能到 1A 以上六轴同时动瞬间电流好几安。如果和 STM32 共用一路 5V比如 USB 供电电压被拉低MCU 直接欠压复位。解决舵机电源和 MCU 电源分开。舵机用独立的 5V/5A 开关电源MCU 用另一路 5V 或 USB。两路电源的地必须共地否则 PWM 信号没有参考电平舵机不受控。我一般用一个大电容1000uF并在舵机电源输入端吸收瞬态电流。4.2 现象PWM 配好了但舵机不动或者只往一个方向转原因一是 PWM 没真正启动只调了HAL_TIM_PWM_ConfigChannel但没调HAL_TIM_PWM_Start二是引脚被复用功能占用比如默认的 JTAG 引脚三是 Pulse 值算错比如把 ARR 当成 20000 而不是 19999导致周期差一点有些舵机就不认。解决先用示波器或逻辑分析仪看引脚有没有波形。没有波形就查启动函数和引脚配置有波形但舵机不动查 Pulse 范围和供电。没有仪器的话接一个 LED 加电阻到 PWM 脚看亮度变化也能粗判。4.3 现象多个舵机同时动时某些通道角度乱跳原因同一定时器的多个通道共用 ARR如果你在中断里频繁改 ARR 去调频率会互相干扰。另外如果在主循环里逐个HAL_Delay更新通道刷新率太低看起来就是乱跳。解决频率固定 50Hz 不动只改 CCR。更新时用一个循环一次性写完六个通道不要加延时。控制周期保持在 20ms 以内最好 10ms 更新一次。4.4 现象数据手套映射的角度和实际手指弯曲对不上原因弯曲传感器的阻值和弯曲角度不是线性关系且每个传感器个体差异大。用统一 RAW_MIN/RAW_MAX 必然对不上。解决逐通道标定并且做分段线性化。简单做法是测三个点伸直、半弯、全弯用两段直线拟合。要求高的话上查表法把 0~4095 分成 16 段每段对应一个角度存成数组。注意标定值要存在 Flash 里否则每次断电重来。用 STM32 的 Flash 模拟 EEPROM 或者外挂一片 24C02 都行。5. 进阶用定时器中断做轨迹插值让机械臂动得不那么愣前面讲的都是「指哪打哪」输入角度直接映射到 PWM机械臂动作很生硬关节是跳变的。要让动作顺滑得做插值。我的习惯是在 TIM6 或 TIM7 上开一个 10ms 的定时器中断在中断里做角度插值逐步逼近目标角度。// 每个关节的当前角度和目标角度 float cur_angle[6] {90, 90, 90, 90, 90, 90}; float tgt_angle[6] {90, 90, 90, 90, 90, 90}; #define STEP 1.5f // 每 10ms 最多变化 1.5 度 // TIM6 中断回调10ms 触发一次 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM6) { for (int i 0; i 6; i) { float diff tgt_angle[i] - cur_angle[i]; if (diff STEP) cur_angle[i] STEP; else if (diff -STEP) cur_angle[i] - STEP; else cur_angle[i] tgt_angle[i]; } // 一次性更新六路 PWM __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, angle_to_pulse(cur_angle[0])); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_2, angle_to_pulse(cur_angle[1])); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_3, angle_to_pulse(cur_angle[2])); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_4, angle_to_pulse(cur_angle[3])); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, angle_to_pulse(cur_angle[4])); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_2, angle_to_pulse(cur_angle[5])); } }逻辑说明主循环只负责更新tgt_angle中断负责以固定步长逼近。STEP 越大动作越快但越顿越小越顺滑但响应慢。1.5°/10ms 相当于 150°/s对大多数舵机是合适的速度。参数说明TIM6 的配置和 PWM 定时器不同它不需要输出引脚只要开更新中断。CubeMX 里 Activated 勾上Prescaler 设 7199Period 设 99得到 10ms 中断。注意中断里不要放耗时操作__HAL_TIM_SET_COMPARE是寄存器操作很快安全。验证方法把tgt_angle从 90 改到 180用手机慢动作拍机械臂看是不是匀速转过去而不是瞬间跳。如果还是跳检查中断有没有真正进可以在中断里翻转一个 GPIO 用示波器看。还有一个技巧是给每个关节设不同的 STEP底座关节负载大STEP 给小一点末端关节轻可以快一点。这样整体动作更协调。我一开始六个关节用同一个 STEP结果底座还没转到位末端已经甩过去了看着像抽筋。从那以后我每次调机械臂都强制先把每个关节单独跑一遍限位和速度再联调。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑