STM32 控制 SG90 舵机指南

一、SG90 舵机基础知识

1、舵机参数

参数 数值
工作电压 4.8V - 6V
工作电流 100mA - 250mA
控制信号 PWM
PWM频率 50Hz(周期 20ms)
脉冲宽度 0.5ms - 2.5ms
转动角度 0° - 180°

2、脉冲宽度与角度对应关系

复制代码
0°   → 0.5ms  → 2.5% 占空比
45°  → 1.0ms  → 5.0% 占空比
90°  → 1.5ms  → 7.5% 占空比
135° → 2.0ms  → 10.0% 占空比
180° → 2.5ms  → 12.5% 占空比

二、硬件连接(非常重要)

电源隔离警告

复制代码
错误:STM32 5V → 舵机(会烧毁板子)
正确:外部5V电源 → 舵机

1、接线方式

复制代码
STM32F103C8T6          SG90舵机
─────────────────────────────────
PA8 (TIM1_CH1)  ──────▶ 橙色线(信号)
3.3V/5V         ──────▶ 红色线(电源,可选)
GND             ──────▶ 棕色线(地)

外部5V电源
5V ───────────────────▶ 红色线
GND ──────────────────▶ 棕色线(与STM32共地)

2、电源注意事项

情况 解决方案
单舵机 STM32 5V引脚可勉强使用
多舵机 必须外接5V/2A电源
舵机抖动 并联100μF电容
复位重启 电源功率不足

三、STM32 定时器配置(核心)

1、PWM 参数计算

复制代码
系统时钟:72MHz
预分频器:72-1 = 71
定时器时钟:72MHz / 72 = 1MHz(1μs计数)

PWM周期:20ms = 20000μs
自动重装载值:20000-1 = 19999

脉冲宽度:
0°   → 0.5ms  → CCR = 500
90°  → 1.5ms  → CCR = 1500
180° → 2.5ms  → CCR = 2500

四、代码实现

1、舵机驱动头文件(servo.h)

c 复制代码
#ifndef __SERVO_H
#define __SERVO_H

#include "stm32f1xx_hal.h"

/* 舵机参数定义 */
#define SERVO_MIN_PULSE   500    // 0.5ms
#define SERVO_MAX_PULSE   2500   // 2.5ms
#define SERVO_MID_PULSE   1500   // 1.5ms
#define PWM_PERIOD        20000  // 20ms

/* 舵机结构体 */
typedef struct {
    TIM_HandleTypeDef *htim;     // 定时器句柄
    uint32_t channel;            // PWM通道
    uint16_t current_angle;       // 当前角度
    uint16_t target_angle;        // 目标角度
    uint8_t is_moving;            // 运动状态
} Servo_t;

/* 函数声明 */
void Servo_Init(Servo_t *servo, TIM_HandleTypeDef *htim, uint32_t channel);
void Servo_SetAngle(Servo_t *servo, uint16_t angle);
void Servo_SmoothMove(Servo_t *servo, uint16_t target_angle, uint16_t step_delay);
void Servo_Sweep(Servo_t *servo, uint16_t min_angle, uint16_t max_angle, uint16_t delay_ms);

#endif

2、舵机驱动源文件(servo.c)

c 复制代码
#include "servo.h"
#include "math.h"

/* 舵机初始化 */
void Servo_Init(Servo_t *servo, TIM_HandleTypeDef *htim, uint32_t channel)
{
    servo->htim = htim;
    servo->channel = channel;
    servo->current_angle = 90;      // 默认90度
    servo->target_angle = 90;
    servo->is_moving = 0;
    
    // 启动PWM
    HAL_TIM_PWM_Start(htim, channel);
    
    // 设置初始位置
    Servo_SetAngle(servo, 90);
}

/* 角度转脉冲宽度 */
static uint16_t Angle_To_Pulse(uint16_t angle)
{
    if(angle > 180) angle = 180;
    
    // 线性映射:0°→500, 180°→2500
    return SERVO_MIN_PULSE + 
           (angle * (SERVO_MAX_PULSE - SERVO_MIN_PULSE) / 180);
}

/* 设置舵机角度 */
void Servo_SetAngle(Servo_t *servo, uint16_t angle)
{
    uint16_t pulse_width;
    
    // 角度限幅
    if(angle > 180) angle = 180;
    if(angle < 0) angle = 0;
    
    // 转换为脉冲宽度
    pulse_width = Angle_To_Pulse(angle);
    
    // 设置PWM比较值
    __HAL_TIM_SET_COMPARE(servo->htim, servo->channel, pulse_width);
    
    servo->current_angle = angle;
}

/* 平滑转动(防止舵机抖动) */
void Servo_SmoothMove(Servo_t *servo, uint16_t target_angle, uint16_t step_delay)
{
    int16_t current = servo->current_angle;
    int16_t target = target_angle;
    int16_t step = (target > current) ? 1 : -1;
    
    servo->is_moving = 1;
    
    while(current != target) {
        current += step;
        Servo_SetAngle(servo, current);
        HAL_Delay(step_delay);
    }
    
    servo->is_moving = 0;
}

/* 舵机扫描 */
void Servo_Sweep(Servo_t *servo, uint16_t min_angle, uint16_t max_angle, uint16_t delay_ms)
{
    // 从最小角度转到最大角度
    Servo_SmoothMove(servo, min_angle, 10);
    HAL_Delay(500);
    
    Servo_SmoothMove(servo, max_angle, 10);
    HAL_Delay(500);
    
    // 回到中间位置
    Servo_SmoothMove(servo, (min_angle + max_angle) / 2, 10);
}

3、定时器配置(tim.c)

c 复制代码
#include "tim.h"

TIM_HandleTypeDef htim1;

/* TIM1 初始化(用于舵机控制) */
void MX_TIM1_Init(void)
{
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_OC_InitTypeDef sConfigOC = {0};
    TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
    
    htim1.Instance = TIM1;
    htim1.Init.Prescaler = 72-1;          // 72MHz / 72 = 1MHz
    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim1.Init.Period = PWM_PERIOD-1;     // 20ms周期
    htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim1.Init.RepetitionCounter = 0;
    htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    HAL_TIM_Base_Init(&htim1);
    
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig);
    
    HAL_TIM_PWM_Init(&htim1);
    
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);
    
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = SERVO_MID_PULSE;     // 初始90度
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
    sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
    
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
    
    sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
    sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
    sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
    sBreakDeadTimeConfig.DeadTime = 0;
    sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
    sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
    sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
    HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);
}

4、主程序(main.c)

c 复制代码
#include "main.h"
#include "tim.h"
#include "servo.h"

Servo_t my_servo;

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    
    MX_TIM1_Init();
    
    // 舵机初始化(PA8 = TIM1_CH1)
    Servo_Init(&my_servo, &htim1, TIM_CHANNEL_1);
    
    while (1)
    {
        // 方法1:直接设置角度
        Servo_SetAngle(&my_servo, 0);      // 0度
        HAL_Delay(1000);
        
        Servo_SetAngle(&my_servo, 90);     // 90度
        HAL_Delay(1000);
        
        Servo_SetAngle(&my_servo, 180);    // 180度
        HAL_Delay(1000);
        
        // 方法2:平滑转动
        Servo_SmoothMove(&my_servo, 45, 15);   // 平滑转到45度
        HAL_Delay(500);
        
        Servo_SmoothMove(&my_servo, 135, 15);  // 平滑转到135度
        HAL_Delay(500);
        
        // 方法3:自动扫描
        Servo_Sweep(&my_servo, 30, 150, 20);   // 30°~150°扫描
        HAL_Delay(1000);
    }
}

五、CubeMX 配置步骤

1、TIM1 配置

复制代码
Pinout & Configuration → Timers → TIM1
├── Mode
│   ├── Clock Source: Internal Clock
│   └── Channel 1: PWM Generation CH1
├── Parameter Settings
│   ├── Prescaler: 71
│   ├── Counter Period: 19999
│   ├── Counter Mode: Up
│   └── Auto-reload preload: Enable
└── GPIO Settings
    └── PA8: TIM1_CH1 (Alternate Function Push Pull)

2、GPIO 配置

复制代码
PA8: Alternate Function Push Pull
Speed: Very High

六、多舵机控制方案

1、方案一:单定时器多通道

c 复制代码
/* 使用TIM1控制4个舵机 */
Servo_t servos[4];

void Multi_Servo_Init(void)
{
    Servo_Init(&servos[0], &htim1, TIM_CHANNEL_1);  // PA8
    Servo_Init(&servos[1], &htim1, TIM_CHANNEL_2);  // PA9
    Servo_Init(&servos[2], &htim1, TIM_CHANNEL_3);  // PA10
    Servo_Init(&servos[3], &htim1, TIM_CHANNEL_4);  // PA11
}

void Robot_Arm_Control(uint16_t angles[4])
{
    for(int i = 0; i < 4; i++) {
        Servo_SetAngle(&servos[i], angles[i]);
    }
}

2、方案二:软件PWM(任意IO)

c 复制代码
/* 软件PWM控制舵机 */
void Soft_Servo_Control(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, uint16_t angle)
{
    uint16_t high_time = Angle_To_Pulse(angle);
    
    // 产生20ms周期PWM
    for(int i = 0; i < 50; i++) {  // 持续1秒
        HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET);
        Delay_us(high_time);
        HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_RESET);
        Delay_us(20000 - high_time);
    }
}

参考代码 stm32控制舵机sg90来进行正常转动 www.youwenfan.com/contentcsv/72876.html

七、常见问题与解决方案

1、舵机不转

原因 解决方法
电源不足 外接5V/2A电源
信号线接错 检查橙色线连接
地线未共地 确保所有GND连接
PWM频率错误 确认50Hz(20ms)

2、舵机抖动

c 复制代码
/* 添加死区控制 */
void Servo_SetAngle_DeadZone(Servo_t *servo, uint16_t angle)
{
    static uint16_t last_angle = 0;
    
    // 小于5度变化不响应
    if(abs(angle - last_angle) < 5) {
        return;
    }
    
    Servo_SetAngle(servo, angle);
    last_angle = angle;
}

3、舵机发热

c 复制代码
/* 到达目标位置后关闭PWM */
void Servo_PowerSave(Servo_t *servo)
{
    if(!servo->is_moving) {
        HAL_TIM_PWM_Stop(servo->htim, servo->channel);
        HAL_Delay(100);
        HAL_TIM_PWM_Start(servo->htim, servo->channel);
    }
}

八、高级应用

1、舵机校准

c 复制代码
/* 舵机校准函数 */
void Servo_Calibration(Servo_t *servo)
{
    printf("舵机校准开始...\n");
    
    printf("请输入0度对应的脉冲宽度(默认500): ");
    uint16_t min_pulse = Get_User_Input();
    
    printf("请输入180度对应的脉冲宽度(默认2500): ");
    uint16_t max_pulse = Get_User_Input();
    
    // 保存校准参数到Flash
    Save_Calibration(min_pulse, max_pulse);
}

2、速度控制

c 复制代码
/* 带速度控制的舵机 */
typedef struct {
    Servo_t base;
    uint16_t speed;        // 度/秒
    uint32_t last_update;
} Speed_Servo_t;

void Speed_Servo_Move(Speed_Servo_t *servo, uint16_t target_angle)
{
    uint32_t now = HAL_GetTick();
    uint16_t angle_step = servo->speed * (now - servo->last_update) / 1000;
    
    if(angle_step < 1) angle_step = 1;
    
    if(servo->base.current_angle < target_angle) {
        Servo_SetAngle(&servo->base, 
                      servo->base.current_angle + angle_step);
    } else if(servo->base.current_angle > target_angle) {
        Servo_SetAngle(&servo->base, 
                      servo->base.current_angle - angle_step);
    }
    
    servo->last_update = now;
}

九、实际应用示例

1、云台控制

c 复制代码
/* 二轴云台 */
typedef struct {
    Servo_t pan_servo;     // 水平舵机
    Servo_t tilt_servo;    // 垂直舵机
} Gimbal_t;

void Gimbal_SetPosition(Gimbal_t *gimbal, uint16_t pan, uint16_t tilt)
{
    Servo_SmoothMove(&gimbal->pan_servo, pan, 10);
    Servo_SmoothMove(&gimbal->tilt_servo, tilt, 10);
}

2、机械臂控制

c 复制代码
/* 4自由度机械臂 */
uint16_t robot_angles[4] = {90, 90, 90, 90};  // 初始角度

void Robot_Arm_Demo(void)
{
    // 抓取动作
    robot_angles[0] = 45;   // 底座旋转
    robot_angles[1] = 120;  // 大臂
    robot_angles[2] = 60;   // 小臂
    robot_angles[3] = 90;   // 夹爪
    Robot_Arm_Control(robot_angles);
}

十、调试工具

1、示波器检查

复制代码
应看到:20ms周期的PWM波形
高电平宽度:0.5ms ~ 2.5ms

2、串口调试

c 复制代码
printf("当前角度: %d°, PWM脉宽: %d\n", 
       my_servo.current_angle, 
       Angle_To_Pulse(my_servo.current_angle));

总结

核心要点

  1. PWM频率必须是 50Hz
  2. 脉冲宽度 0.5ms - 2.5ms
  3. 必须外接电源,不能直接用STM32供电
  4. 使用 平滑转动 避免舵机抖动
相关推荐
学不懂飞行器16 小时前
【电赛保姆级教程】电赛视觉怎么选?怎么调?从OpenMV到边缘计算硬核避坑指南(附高鲁棒通信源码)
人工智能·stm32·边缘计算·电赛·视觉
高翔·权衡之境16 小时前
主题11:分层思想——OSI模型的现实映射
人工智能·嵌入式硬件·物联网·软件工程·信息与通信
战族狼魂16 小时前
上位机软件开发完整学习路线与项目实战指南
单片机·c#·wpf
崇山峻岭之间16 小时前
单片机485实验
单片机·嵌入式硬件
声讯电子16 小时前
AR1106 声源定位模组 让设备真正“听懂方向”
单片机·机器人·舵机·声源定位·双麦克风阵列·听声辨位
qq_4107321717 小时前
嵌入式开发-memcpy与memmove 技术详解
java·linux·开发语言·嵌入式硬件
星夜夏空9917 小时前
STM32单片机学习(29) —— SPI引脚和外设初始化
stm32·单片机·学习
xiangw@GZ17 小时前
DDR专题-CK 时钟、MT/s 与带宽的关系
单片机·嵌入式硬件
三品吉他手会点灯18 小时前
STM32F103 学习笔记-23-常用存储器原理与分类
笔记·stm32·单片机·嵌入式硬件·学习