尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32 HAL库驱动SG90舵机:从PWM原理到多路控制与调试

STM32 HAL库驱动SG90舵机:从PWM原理到多路控制与调试 1. 项目概述从零开始驱动SG90舵机最近在做一个需要精确角度控制的小玩意儿核心就是用一块STM32F103C8T6也就是大家常说的“蓝桥杯”或“最小系统板”那块核心芯片来控制一个SG90舵机。网上资料虽然多但很多是基于标准库的或者讲得比较零散。我这次全程用的是STM32CubeMX配合HAL库来开发感觉这条路对新手和快速原型开发特别友好。这篇文章我就把从原理到代码、从配置到调试的完整过程以及我踩过的几个坑详细记录下来。无论你是刚接触STM32和舵机的学生还是想快速验证某个机械结构可行性的工程师这篇实操笔记应该都能给你一个清晰的参考。SG90舵机是一种位置角度伺服驱动器在机器人关节、摄像头云台、模型控制等领域应用极广。它的控制信号是周期为20ms50Hz、脉宽在0.5ms到2.5ms之间的PWM波。STM32F103C8T6作为一款经典的ARM Cortex-M3内核MCU其丰富的定时器资源天生就是为产生精准PWM信号而准备的。而HAL库作为ST官方主推的硬件抽象层库通过STM32CubeMX进行图形化配置能极大简化初始化流程让我们更专注于应用逻辑。接下来我会拆解整个项目的设计思路、HAL库的PWM配置细节、角度到脉宽的换算、以及如何让舵机动作更平滑。2. 核心硬件与原理深度解析2.1 主角介绍STM32F103C8T6与SG90舵机STM32F103C8T6属于STM32F1系列的“中等容量”产品拥有64KB Flash和20KB SRAM主频最高72MHz。它内置了多达3个通用定时器TIM2, TIM3, TIM4和1个高级定时器TIM1每个定时器都有多个通道可以独立输出PWM驱动几个舵机绰绰有余。我选择它一是因为性价比极高二是因为其生态成熟资料和社区支持都非常好。SG90舵机是一个微型舵机工作电压通常在4.8V~6V但很多情况下5V供电也能正常工作。它有三根线红线VCC电源正极、棕线或黑线GND电源负极、橙线或黄线信号线。其核心控制原理是脉宽调制PWM。舵机内部有一个控制电路它会测量输入信号线的脉冲高电平持续时间即脉宽并将这个脉宽映射到一个特定的输出轴角度。注意务必确保给舵机供电的电源有足够的电流输出能力。单个SG90在空载时工作电流约100-200mA但在堵转轴被卡住无法转动时电流可能瞬间飙升到500mA以上。如果直接用开发板的5V引脚给多个舵机供电很可能导致开发板复位或烧毁USB口。建议使用外接的5V稳压电源如手机充电器加降压模块或专用的舵机驱动板来供电。2.2 PWM控制舵机的原理与关键参数SG90舵机期望的控制信号是一个周期固定为20ms即频率为50Hz的方波。在这个周期内高电平的持续时间脉宽决定了舵机的角度。这是一个线性映射关系0.5ms的脉宽对应舵机0度位置通常是逆时针极限。1.5ms的脉宽对应舵机90度位置中间位置。2.5ms的脉宽对应舵机180度位置顺时针极限。这个关系是线性的因此我们可以推导出公式目标脉宽ms 0.5ms 目标角度 / 180 * 2.0ms。在代码中我们最终需要操作的是定时器的“比较寄存器”值所以必须将这个时间值转换为定时器计数器的计数值。这里就引出了定时器配置的核心参数ARR自动重装载寄存器和PSC预分频器。定时器的计数频率F_cntMCU主频/(PSC 1)。计数器从0计数到ARR然后溢出归零形成一个计数周期。一个PWM周期对应的计数器总值就是ARR 1。我们需要让(ARR 1) / F_cnt 20ms同时控制角度的比较值CCRx满足CCRx / F_cnt 目标脉宽。举个例子如果MCU主频是72MHz我们设置PSC71则分频后计数频率F_cnt 72MHz / (711) 1MHz即计数器每1us加1。为了得到20ms的周期我们需要ARR满足(ARR1) * 1us 20000us所以ARR 19999。此时0.5ms脉宽对应的比较值CCRx 5002.5ms脉宽对应的CCRx 2500。这样我们只需要在代码中设置CCRx在500~2500之间变化就能控制舵机在0~180度之间转动。3. 开发环境搭建与工程配置3.1 工具链准备STM32CubeMX与Keil MDK我使用的开发环境是STM32CubeMX v6.11搭配Keil MDK-ARM v5.37。STM32CubeMX是ST官方出品的图形化配置工具可以直观地配置引脚、时钟、外设等并生成HAL库初始化代码能避免大量底层寄存器操作提升开发效率。Keil则是经典的编译和调试环境。首先在CubeMX中新建一个工程选择MCU型号为STM32F103C8Tx。接下来进行关键配置系统核心SYS在“Debug”下拉菜单中选择“Serial Wire”。这是因为F103C8T6支持SWD调试仅需SWDIO和SWCLK两根线这比传统的JTAG占用引脚更少。如果你使用了ST-Link V2调试器这个配置是必须的。时钟配置RCC在“High Speed Clock (HSE)”选择“Crystal/Ceramic Resonator”。我们的最小系统板外部通常有一个8MHz的晶振需要在这里启用它。然后转到“Clock Configuration”标签页将系统时钟源切换到PLL并调整倍频系数使系统时钟SYSCLK达到72MHz。这是F103系列在标准供电下的最高主频能提供最佳性能。定时器配置TIM这是本项目最核心的一步。假设我们使用TIM3的通道1对应引脚PA6来输出PWM。在左侧引脚图找到PA6将其功能设置为“TIM3_CH1”。然后在左侧外设列表找到TIM3进行模式配置。Clock Source选择“Internal Clock”内部时钟。Channel1选择“PWM Generation CH1”。Parameter Settings标签页Prescaler (PSC - 16 bits value)设置为71。计算逻辑72,000,000 Hz / (711) 1,000,000 Hz (1MHz)。Counter ModeUp向上计数模式。Counter Period (ARR - 16 bits value)设置为19999。计算逻辑PWM周期 (199991) / 1,000,000 0.02秒 20ms。Internal Clock Division (CKD)No Division。auto-reload preloadEnable。这个选项建议启用它允许在下一个更新事件时才将新的ARR值载入可以防止在修改周期时产生毛刺。下方Pulse的值可以先设为1500对应1.5ms90度位置。这个值后续在代码中动态修改。GPIO Settings标签页可以检查PA6的配置默认应为推挽输出Push-Pull输出速度可以设为“High”。工程管理Project Manager设置项目名称和存储路径。在“Toolchain / IDE”中选择“MDK-ARM V5”。在“Code Generator”中我习惯勾选“Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral”这样每个外设的代码会单独成文件结构更清晰。同时务必勾选“Copy all used libraries into the project folder”这样工程可以脱离CubeMX环境独立编译。配置完成后点击“GENERATE CODE”生成工程代码然后用Keil打开生成的工程文件。3.2 HAL库PWM驱动代码剖析CubeMX生成的代码已经完成了定时器和GPIO的初始化。我们需要在用户代码区域添加控制逻辑。主要用到两个HAL库函数HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1);// 启动TIM3通道1的PWM输出__HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, pulse);// 动态设置比较值脉宽第一个函数通常在main函数初始化部分在MX_TIM3_Init()调用之后执行。第二个函数则在我们需要改变舵机角度时调用。一个常见的需求是让舵机在0度和180度之间往复运动。我们可以编写一个简单的函数来实现// 设置舵机角度函数 // htim: 定时器句柄如 htim3 // Channel: 定时器通道如 TIM_CHANNEL_1 // angle: 目标角度范围0~180 void Servo_Set_Angle(TIM_HandleTypeDef *htim, uint32_t Channel, uint8_t angle) { // 将角度线性映射到500~2500的脉冲计数值 // 公式pulse 500 (angle / 180.0) * 2000 // 为避免浮点运算使用整数乘除pulse 500 angle * 2000 / 180 uint16_t pulse 500 (angle * 2000) / 180; __HAL_TIM_SET_COMPARE(htim, Channel, pulse); }然后在主循环中调用它并加上延时就能看到舵机运动了。while (1) { Servo_Set_Angle(htim3, TIM_CHANNEL_1, 0); // 转到0度 HAL_Delay(1000); // 等待1秒 Servo_Set_Angle(htim3, TIM_CHANNEL_1, 180); // 转到180度 HAL_Delay(1000); // 等待1秒 }实操心得直接使用HAL_Delay()进行长时间延时会导致CPU空转效率低下。在实际项目中如果系统还有其他任务如读取传感器、通信应该使用定时器中断或者RTOS的延时函数来替代HAL_Delay()实现非阻塞控制。例如可以设置一个1ms的定时器中断在中断服务函数里维护一个全局计时变量主循环中判断时间是否到达从而控制舵机按特定轨迹运动。4. 进阶控制与性能优化实践4.1 多路舵机控制与资源分配一个定时器通常有4个通道这意味着一个TIMx理论上可以独立控制4个舵机。例如我们可以用TIM3的CH1, CH2, CH3, CH4分别控制四个舵机。在CubeMX中只需将对应引脚如PA6, PA7, PB0, PB1都配置为TIM3的通道输出即可。它们的PWM周期由ARR决定是共享的但比较值CCRx是独立的这正是我们需要的。注意当需要控制多于4个舵机时就需要使用多个定时器。F103C8T6的TIM1、TIM2、TIM3、TIM4都可以用来输出PWM。要特别注意引脚的重映射功能有些通道的默认引脚可能与其他功能冲突需要查看数据手册的“Alternate function mapping”表格。使用CubeMX可以直观地避免引脚冲突。4.2 运动平滑处理与速度控制直接让舵机从一个角度瞬间跳到另一个角度会产生很大的瞬时电流和机械冲击可能损坏齿轮或负载。更优雅的方式是进行平滑插值。最简单的实现是线性插值在起点角度和终点角度之间以固定的时间间隔例如每20ms逐步改变目标角度直到到达终点。// 简单的线性平滑移动函数阻塞式仅作示例 void Servo_Smooth_Move(TIM_HandleTypeDef *htim, uint32_t Channel, uint8_t start_angle, uint8_t end_angle, uint16_t duration_ms) { int16_t step (end_angle start_angle) ? 1 : -1; // 步进方向 uint8_t current_angle start_angle; uint16_t step_delay duration_ms / abs(end_angle - start_angle); // 每度间隔时间 if(step_delay 0) step_delay 1; // 防止除零 while(current_angle ! end_angle) { current_angle step; Servo_Set_Angle(htim, Channel, current_angle); HAL_Delay(step_delay); } }这个函数让舵机以大致恒定的速度从start_angle运动到end_angle总时间为duration_ms。当然这是一个阻塞式函数。在真实应用中你应该基于状态机和非阻塞定时器来重构它避免占用主循环。4.3 供电与噪声处理实战经验舵机尤其是多个舵机同时运动时对电源系统是一个严峻考验。以下是我总结的几点硬件层面的经验电源隔离强烈建议将MCU的逻辑电源3.3V和舵机的动力电源5V/6V分开。可以使用两个独立的稳压模块或者一个电源输入经过两个不同的LDO/DCDC分别供电。地线GND最后在一点共地。大电容缓冲在舵机供电电源的正负极之间并联一个大容量电解电容如470uF~1000uF耐压10V以上和一个小容量陶瓷电容如0.1uF。大电容用于应对舵机启动和堵转时的大电流需求小电容用于滤除高频噪声。这个电容应尽可能靠近舵机的电源引脚焊接。信号线保护虽然舵机信号线电流很小但长导线可能引入干扰。可以在MCU的PWM输出引脚和舵机信号线之间串联一个100~220欧姆的电阻用于限流和阻尼。如果环境干扰严重可以考虑使用光耦隔离信号。软件消抖在给舵机发送新的角度指令后可以适当延时10-20ms等待舵机运动到位、电流稳定后再进行其他精密操作如ADC采样避免电源波动影响其他电路。5. 调试技巧与常见问题排查5.1 基础调试确认PWM信号是否正常当舵机不转动或转动异常时第一步永远是确认MCU是否输出了正确的PWM信号。方法一万用表测量电压将万用表打到直流电压档测量信号线相对于GND的电压。当PWM输出时你应该能测到一个介于0V和3.3V之间的平均电压。舵机在0度脉宽0.5ms时平均电压约为 (0.5/20)*3.3V ≈ 0.08V在180度脉宽2.5ms时平均电压约为 (2.5/20)*3.3V ≈ 0.41V。改变角度代码观察平均电压是否相应变化可以快速判断PWM是否受控。方法二示波器观察波形这是最准确的方法。用示波器探头连接信号线可以看到清晰的方波。检查关键参数周期是否为20ms50Hz高电平脉宽是否在0.5ms~2.5ms之间随代码变化波形是否干净有没有毛刺如果周期不对检查定时器ARR和PSC配置如果脉宽不对检查比较值计算和设置函数。5.2 典型问题与解决方案速查表下表整理了我调试过程中遇到的一些典型问题及解决方法问题现象可能原因排查步骤与解决方案舵机完全不动无反应1. 供电问题电压不足、电流不够、电源接反2. 信号线未连接或接触不良3. PWM未启动输出1. 用万用表测量舵机VCC和GND之间电压确保在4.8V-6V之间。带载测试。2. 检查杜邦线连接确保信号线连接到正确的MCU引脚。3. 检查代码是否调用了HAL_TIM_PWM_Start()。用示波器或万用表法确认信号。舵机抖动或吱吱叫不固定位置1. PWM周期不准不是20ms2. 电源功率不足导致舵机无法到达指定位置3. 机械负载过重舵机扭矩不足1. 用示波器检查PWM周期严格调整为20ms50Hz。2. 检查电源尝试单独给舵机供电或加大电源滤波电容。3. 卸下负载测试如果正常说明需要换更大扭矩的舵机如MG995。舵机角度范围不对如只能转90度脉宽范围设置错误检查代码中角度到脉宽计数值的映射公式。确认0度和180度对应的计数值是否为500和2500在1MHz计数频率下。舵机响应迟钝运动速度慢1. 控制信号更新率太低2. 平滑移动函数的延时过长1. 确保主循环运行速度足够快没有其他耗时任务阻塞。2. 调整平滑移动函数的步进延时减少总运动时间。多个舵机同时运动时系统复位电源瞬间跌落触发MCU的欠压复位BOR这是典型的电源问题。必须将舵机电源与MCU电源隔离并在舵机电源端加大容量储能电容。检查MCU的VDD电压在舵机动作时的波动情况。代码修改后舵机行为异常CubeMX重新生成代码覆盖了用户代码在CubeMX生成的用户代码区/* USER CODE BEGIN xxx */和/* USER CODE END xxx */之间编写代码这部分不会被覆盖。5.3 利用调试器实时监控与修改参数Keil的调试功能非常强大。你可以在调试模式下将pulse变量或angle变量添加到“Watch”窗口进行实时监控。更实用的是你可以在程序运行时直接修改这些变量的值舵机会立即响应。这比反复修改代码、编译、下载要快得多非常适合寻找最优的控制参数比如某个特定动作的角度序列。具体操作进入调试模式暂停程序在“Watch”窗口右键添加要监控的变量。然后可以在“Memory”窗口或直接双击“Watch”窗口中的值进行修改修改后继续运行程序就能观察效果。6. 项目扩展与高级应用思路掌握了基础的单舵机控制后这个项目可以朝很多有趣的方向扩展闭环位置控制SG90是开环控制它只管“转到指令位置”但不知道自己是否真的转到了。可以尝试在舵机输出轴上安装一个电位器或绝对值编码器将实际角度反馈给STM32的ADC实现真正的闭环PID控制提高精度和抗干扰能力。上位机控制与协议通过STM32的串口UART接收来自电脑如串口助手、Python脚本或手机蓝牙的指令。可以定义简单的文本协议例如“#ANG 90\n”表示转到90度实现远程交互控制。构建多自由度机械臂组合3-6个舵机通过精心设计的结构件可以搭建一个多自由度机械臂。核心挑战在于逆运动学求解给定末端位置计算每个关节的角度和轨迹规划。STM32F103C8T6的性能足以进行简单的实时计算。融入传感器反馈结合超声波传感器HC-SR04或红外测距让舵机驱动的云台可以自动跟踪物体或保持与障碍物的距离。结合陀螺仪MPU6050可以实现自平衡或姿态控制。使用DMA释放CPU如果需要控制非常多路如16路舵机且要求精确同步可以考虑使用定时器的DMA功能。将每个通道的比较值CCRx存放在一个数组中通过DMA自动定时搬运到定时器寄存器中无需CPU干预可以产生极其稳定且同步的多路PWM信号。我个人在做一个六足机器人项目时就用了TIM2和TIM3的四个通道结合一个PCA968516路PWM扩展芯片通过I2C控制来驱动总共18个舵机。STM32F103C8T6作为主控负责运动学解算和协调PCA9685负责提供稳定的PWM信号两者分工合作效果非常稳定。这其中的关键就是把本文介绍的单路PWM控制原理吃透然后灵活地应用到更复杂的系统中去。
返回列表