STM32实战:基于STM32F103的SG90舵机控制(PWM精准控制)

文章目录

一、前言

1.1 技术背景

SG90是一种微型舵机,广泛应用于航模、机器人、智能小车等领域。它具有体积小、重量轻、价格低廉的特点,是学习和使用舵机的理想选择。

舵机的工作原理是通过PWM(脉冲宽度调制)信号来控制转动角度。PWM信号的周期固定为20ms(50Hz),脉冲宽度在0.5ms到2.5ms之间变化,对应舵机的0°到180°角度。

1.2 本文目标

通过本教程,你将学会:

  • 理解SG90舵机的工作原理和PWM控制机制
  • 掌握STM32定时器的PWM输出配置
  • 实现舵机的角度控制和连续旋转
  • 实现多舵机同步控制
  • 掌握舵机控制的精度优化方法

1.3 技术栈

硬件平台:

  • STM32F103C8T6(最小系统板)
  • SG90微型舵机
  • 外部电源(5V 2A,舵机供电用)
  • USB转TTL串口模块(调试用)

软件环境:

  • STM32CubeMX 6.x
  • Keil MDK 5 或 STM32CubeIDE
  • HAL库

开发工具:

  • ST-Link V2 下载器
  • 示波器(可选,用于观察PWM波形)

二、环境准备

2.1 硬件连接

SG90舵机引脚说明
引脚 颜色 功能说明
信号 橙色/黄色 PWM信号输入
电源+ 红色 电源正极(+5V)
电源- 棕色/黑色 电源地(GND)
连接图
复制代码
STM32F103C8T6          SG90舵机
    PA0 (TIM2_CH1) ----> 信号线(橙色)
    
外部电源5V -------------> 电源+(红色)
外部电源GND -----------> 电源-(棕色)
外部电源GND -----------> STM32 GND(共地)

⚠️ 重要提示:

  • SG90工作时电流可达100-250mA,STM32的IO口无法直接驱动
  • 必须使用外部5V电源为舵机供电
  • STM32和舵机电源必须共地(GND相连)

2.2 STM32CubeMX配置

  1. 创建新工程,选择STM32F103C8Tx芯片
  2. 配置时钟:设置系统时钟为72MHz
  3. 配置定时器PWM
    • 启用TIM2
    • 选择Channel1为PWM Generation CH1
    • 设置Prescaler为71(72MHz/(71+1)=1MHz)
    • 设置Counter Period为19999(1MHz/20000=50Hz)
  4. 配置GPIO
    • PA0自动配置为TIM2_CH1

2.3 舵机控制原理

PWM参数计算
复制代码
系统时钟:72MHz
定时器预分频:71
定时器时钟:72MHz / (71+1) = 1MHz
PWM周期:20ms = 50Hz
自动重装载值:1MHz / 50Hz = 20000

角度对应脉冲宽度:
- 0°   → 0.5ms  → CCR = 0.5ms * 1MHz = 1000
- 90°  → 1.5ms  → CCR = 1.5ms * 1MHz = 3000
- 180° → 2.5ms  → CCR = 2.5ms * 1MHz = 5000

角度计算公式:
CCR = 1000 + (angle / 180.0) * 4000

三、PWM基础驱动实现

3.1 驱动程序架构

应用程序
舵机控制API
PWM配置层
定时器HAL库
STM32硬件
角度控制
绝对角度
相对角度
平滑转动
多舵机管理
舵机1-TIM2_CH1
舵机2-TIM2_CH2
舵机3-TIM3_CH1

3.2 头文件定义

📄 创建文件:Inc/sg90_servo.h

c 复制代码
#ifndef __SG90_SERVO_H
#define __SG90_SERVO_H

#include "stm32f1xx_hal.h"

// 舵机参数定义
#define SERVO_MIN_ANGLE     0       // 最小角度
#define SERVO_MAX_ANGLE     180     // 最大角度
#define SERVO_MIN_PULSE     1000    // 最小脉宽(0.5ms对应1000)
#define SERVO_MAX_PULSE     5000    // 最大脉宽(2.5ms对应5000)
#define SERVO_PERIOD        20000   // PWM周期(20ms对应20000)

// 定时器和通道定义
#define SERVO_TIM           TIM2
#define SERVO_TIM_CHANNEL   TIM_CHANNEL_1
#define SERVO_TIM_HANDLE    htim2

// 舵机结构体
typedef struct
{
    TIM_HandleTypeDef *htim;        // 定时器句柄
    uint32_t channel;               // PWM通道
    uint8_t current_angle;          // 当前角度
    uint8_t target_angle;           // 目标角度
    uint16_t min_pulse;             // 最小脉宽
    uint16_t max_pulse;             // 最大脉宽
} Servo_TypeDef;

// 函数声明
void Servo_Init(Servo_TypeDef *servo, TIM_HandleTypeDef *htim, uint32_t channel);
void Servo_SetAngle(Servo_TypeDef *servo, uint8_t angle);
void Servo_SetAngleSmooth(Servo_TypeDef *servo, uint8_t angle, uint16_t step_delay);
void Servo_SetPulse(Servo_TypeDef *servo, uint16_t pulse);
uint8_t Servo_GetAngle(Servo_TypeDef *servo);
void Servo_Stop(Servo_TypeDef *servo);

// 角度转换宏
#define ANGLE_TO_PULSE(angle)   (SERVO_MIN_PULSE + ((angle) * (SERVO_MAX_PULSE - SERVO_MIN_PULSE) / 180))
#define PULSE_TO_ANGLE(pulse)   (((pulse) - SERVO_MIN_PULSE) * 180 / (SERVO_MAX_PULSE - SERVO_MIN_PULSE))

#endif /* __SG90_SERVO_H */

3.3 驱动实现

📄 创建文件:Src/sg90_servo.c

c 复制代码
#include "sg90_servo.h"
#include "stdio.h"

// 定时器句柄(外部定义)
extern TIM_HandleTypeDef htim2;

/**
 * @brief 舵机初始化
 * @param servo 舵机结构体指针
 * @param htim 定时器句柄
 * @param channel PWM通道
 */
void Servo_Init(Servo_TypeDef *servo, TIM_HandleTypeDef *htim, uint32_t channel)
{
    // 初始化结构体成员
    servo->htim = htim;
    servo->channel = channel;
    servo->current_angle = 90;      // 默认中位
    servo->target_angle = 90;
    servo->min_pulse = SERVO_MIN_PULSE;
    servo->max_pulse = SERVO_MAX_PULSE;
    
    // 启动PWM输出
    HAL_TIM_PWM_Start(htim, channel);
    
    // 设置初始位置(中位)
    Servo_SetAngle(servo, 90);
    
    printf("Servo initialized at 90 degrees\\r\\n");
}

/**
 * @brief 设置舵机角度
 * @param servo 舵机结构体指针
 * @param angle 目标角度(0-180)
 */
void Servo_SetAngle(Servo_TypeDef *servo, uint8_t angle)
{
    uint16_t pulse;
    
    // 限制角度范围
    if(angle > SERVO_MAX_ANGLE)
    {
        angle = SERVO_MAX_ANGLE;
    }
    else if(angle < SERVO_MIN_ANGLE)
    {
        angle = SERVO_MIN_ANGLE;
    }
    
    // 计算脉宽值
    pulse = ANGLE_TO_PULSE(angle);
    
    // 设置PWM占空比
    __HAL_TIM_SET_COMPARE(servo->htim, servo->channel, pulse);
    
    // 更新当前角度
    servo->current_angle = angle;
    servo->target_angle = angle;
    
    printf("Servo angle set to %d degrees (pulse: %d)\\r\\n", angle, pulse);
}

/**
 * @brief 平滑转动到目标角度
 * @param servo 舵机结构体指针
 * @param angle 目标角度(0-180)
 * @param step_delay 每步延时(毫秒)
 * @note 通过逐步改变角度实现平滑转动,避免舵机抖动
 */
void Servo_SetAngleSmooth(Servo_TypeDef *servo, uint8_t angle, uint16_t step_delay)
{
    int8_t step;
    uint8_t current;
    
    // 限制角度范围
    if(angle > SERVO_MAX_ANGLE)
    {
        angle = SERVO_MAX_ANGLE;
    }
    else if(angle < SERVO_MIN_ANGLE)
    {
        angle = SERVO_MIN_ANGLE;
    }
    
    // 确定转动方向
    if(angle > servo->current_angle)
    {
        step = 1;  // 顺时针转动
    }
    else if(angle < servo->current_angle)
    {
        step = -1; // 逆时针转动
    }
    else
    {
        return;    // 已在目标位置
    }
    
    // 逐步转动
    current = servo->current_angle;
    while(current != angle)
    {
        current += step;
        Servo_SetAngle(servo, current);
        HAL_Delay(step_delay);
    }
    
    printf("Servo smoothly moved to %d degrees\\r\\n", angle);
}

/**
 * @brief 直接设置脉宽值
 * @param servo 舵机结构体指针
 * @param pulse 脉宽值(1000-5000)
 * @note 用于精细调节或测试
 */
void Servo_SetPulse(Servo_TypeDef *servo, uint16_t pulse)
{
    // 限制脉宽范围
    if(pulse > servo->max_pulse)
    {
        pulse = servo->max_pulse;
    }
    else if(pulse < servo->min_pulse)
    {
        pulse = servo->min_pulse;
    }
    
    // 设置PWM占空比
    __HAL_TIM_SET_COMPARE(servo->htim, servo->channel, pulse);
    
    // 更新当前角度(估算)
    servo->current_angle = PULSE_TO_ANGLE(pulse);
    
    printf("Servo pulse set to %d\\r\\n", pulse);
}

/**
 * @brief 获取当前角度
 * @param servo 舵机结构体指针
 * @return 当前角度(0-180)
 */
uint8_t Servo_GetAngle(Servo_TypeDef *servo)
{
    return servo->current_angle;
}

/**
 * @brief 停止舵机输出
 * @param servo 舵机结构体指针
 * @note 停止PWM输出,舵机将失去保持力
 */
void Servo_Stop(Servo_TypeDef *servo)
{
    HAL_TIM_PWM_Stop(servo->htim, servo->channel);
    printf("Servo stopped\\r\\n");
}

3.4 主程序测试

📄 创建文件:Src/main.c

c 复制代码
#include "main.h"
#include "sg90_servo.h"

// 定时器句柄
TIM_HandleTypeDef htim2;
UART_HandleTypeDef huart1;

// 舵机实例
Servo_TypeDef servo1;

// 函数声明
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM2_Init(void);
static void MX_USART1_UART_Init(void);

/**
 * @brief TIM2初始化(PWM输出)
 */
static void MX_TIM2_Init(void)
{
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_OC_InitTypeDef sConfigOC = {0};
    
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 71;              // 72MHz/(71+1)=1MHz
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 19999;              // 1MHz/20000=50Hz
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    
    if(HAL_TIM_Base_Init(&htim2) != HAL_OK)
    {
        Error_Handler();
    }
    
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if(HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
    {
        Error_Handler();
    }
    
    if(HAL_TIM_PWM_Init(&htim2) != HAL_OK)
    {
        Error_Handler();
    }
    
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if(HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
    {
        Error_Handler();
    }
    
    // PWM模式配置
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 3000;                 // 初始脉宽(中位)
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    
    if(HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
    {
        Error_Handler();
    }
    
    // 配置GPIO
    HAL_TIM_MspPostInit(&htim2);
}

/**
 * @brief USART1初始化(调试输出)
 */
static void MX_USART1_UART_Init(void)
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    
    if(HAL_UART_Init(&huart1) != HAL_OK)
    {
        Error_Handler();
    }
}

/**
 * @brief GPIO初始化
 */
static void MX_GPIO_Init(void)
{
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
}

/**
 * @brief printf重定向到串口
 */
int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
    return ch;
}

int main(void)
{
    // HAL库初始化
    HAL_Init();
    
    // 配置系统时钟
    SystemClock_Config();
    
    // 初始化外设
    MX_GPIO_Init();
    MX_TIM2_Init();
    MX_USART1_UART_Init();
    
    printf("\\r\\n=== SG90 Servo Control Demo ===\\r\\n");
    
    // 初始化舵机
    Servo_Init(&servo1, &htim2, TIM_CHANNEL_1);
    
    // 等待舵机到位
    HAL_Delay(1000);
    
    // 主循环 - 演示各种控制方式
    while(1)
    {
        printf("\\r\\n--- Test 1: Basic angle control ---\\r\\n");
        
        // 测试1:基本角度控制
        Servo_SetAngle(&servo1, 0);     // 转到0度
        HAL_Delay(1000);
        
        Servo_SetAngle(&servo1, 90);    // 转到90度
        HAL_Delay(1000);
        
        Servo_SetAngle(&servo1, 180);   // 转到180度
        HAL_Delay(1000);
        
        Servo_SetAngle(&servo1, 90);    // 回到中位
        HAL_Delay(1000);
        
        printf("\\r\\n--- Test 2: Smooth movement ---\\r\\n");
        
        // 测试2:平滑转动
        Servo_SetAngleSmooth(&servo1, 0, 20);    // 平滑转到0度
        HAL_Delay(500);
        
        Servo_SetAngleSmooth(&servo1, 180, 20);  // 平滑转到180度
        HAL_Delay(500);
        
        Servo_SetAngleSmooth(&servo1, 90, 20);   // 回到中位
        HAL_Delay(1000);
        
        printf("\\r\\n--- Test 3: Sweep motion ---\\r\\n");
        
        // 测试3:扫描运动
        for(uint8_t angle = 0; angle <= 180; angle += 10)
        {
            Servo_SetAngle(&servo1, angle);
            HAL_Delay(100);
        }
        
        for(uint8_t angle = 180; angle > 0; angle -= 10)
        {
            Servo_SetAngle(&servo1, angle);
            HAL_Delay(100);
        }
        
        HAL_Delay(1000);
    }
}

/**
 * @brief 系统时钟配置(72MHz)
 */
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
    
    if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
        Error_Handler();
    }
    
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
                                   RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    
    if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
    {
        Error_Handler();
    }
}

void Error_Handler(void)
{
    __disable_irq();
    while(1)
    {
    }
}

四、多舵机控制实现

4.1 多舵机管理架构

STM32F103的TIM2和TIM3各有4个通道,可以同时控制最多8个舵机。

📄 创建文件:Inc/multi_servo.h

c 复制代码
#ifndef __MULTI_SERVO_H
#define __MULTI_SERVO_H

#include "sg90_servo.h"

// 最大舵机数量
#define MAX_SERVO_NUM   8

// 舵机ID定义
typedef enum
{
    SERVO_1 = 0,
    SERVO_2,
    SERVO_3,
    SERVO_4,
    SERVO_5,
    SERVO_6,
    SERVO_7,
    SERVO_8
} Servo_ID_t;

// 多舵机管理器
typedef struct
{
    Servo_TypeDef servos[MAX_SERVO_NUM];    // 舵机数组
    uint8_t servo_count;                     // 已初始化舵机数量
} Servo_Manager_t;

// 函数声明
void MultiServo_Init(Servo_Manager_t *manager);
void MultiServo_Add(Servo_Manager_t *manager, Servo_ID_t id, 
                    TIM_HandleTypeDef *htim, uint32_t channel);
void MultiServo_SetAngle(Servo_Manager_t *manager, Servo_ID_t id, uint8_t angle);
void MultiServo_SetAngleSmooth(Servo_Manager_t *manager, Servo_ID_t id, 
                                uint8_t angle, uint16_t step_delay);
void MultiServo_SetAllAngle(Servo_Manager_t *manager, uint8_t angle);
void MultiServo_SyncMove(Servo_Manager_t *manager, uint8_t *angles, 
                         uint16_t step_delay);

#endif /* __MULTI_SERVO_H */

📄 创建文件:Src/multi_servo.c

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

/**
 * @brief 初始化多舵机管理器
 * @param manager 管理器结构体指针
 */
void MultiServo_Init(Servo_Manager_t *manager)
{
    manager->servo_count = 0;
    
    for(uint8_t i = 0; i < MAX_SERVO_NUM; i++)
    {
        manager->servos[i].htim = NULL;
        manager->servos[i].channel = 0;
        manager->servos[i].current_angle = 90;
        manager->servos[i].target_angle = 90;
    }
}

/**
 * @brief 添加舵机到管理器
 * @param manager 管理器结构体指针
 * @param id 舵机ID
 * @param htim 定时器句柄
 * @param channel PWM通道
 */
void MultiServo_Add(Servo_Manager_t *manager, Servo_ID_t id, 
                    TIM_HandleTypeDef *htim, uint32_t channel)
{
    if(id >= MAX_SERVO_NUM)
    {
        return;
    }
    
    Servo_Init(&manager->servos[id], htim, channel);
    
    if(id >= manager->servo_count)
    {
        manager->servo_count = id + 1;
    }
}

/**
 * @brief 设置指定舵机角度
 * @param manager 管理器结构体指针
 * @param id 舵机ID
 * @param angle 目标角度
 */
void MultiServo_SetAngle(Servo_Manager_t *manager, Servo_ID_t id, uint8_t angle)
{
    if(id >= manager->servo_count)
    {
        return;
    }
    
    Servo_SetAngle(&manager->servos[id], angle);
}

/**
 * @brief 平滑设置指定舵机角度
 * @param manager 管理器结构体指针
 * @param id 舵机ID
 * @param angle 目标角度
 * @param step_delay 步进延时
 */
void MultiServo_SetAngleSmooth(Servo_Manager_t *manager, Servo_ID_t id, 
                                uint8_t angle, uint16_t step_delay)
{
    if(id >= manager->servo_count)
    {
        return;
    }
    
    Servo_SetAngleSmooth(&manager->servos[id], angle, step_delay);
}

/**
 * @brief 设置所有舵机到同一角度
 * @param manager 管理器结构体指针
 * @param angle 目标角度
 */
void MultiServo_SetAllAngle(Servo_Manager_t *manager, uint8_t angle)
{
    for(uint8_t i = 0; i < manager->servo_count; i++)
    {
        Servo_SetAngle(&manager->servos[i], angle);
    }
}

/**
 * @brief 同步移动多个舵机
 * @param manager 管理器结构体指针
 * @param angles 目标角度数组
 * @param step_delay 步进延时
 * @note 所有舵机同时开始移动,同时到达目标位置
 */
void MultiServo_SyncMove(Servo_Manager_t *manager, uint8_t *angles, 
                         uint16_t step_delay)
{
    uint8_t completed[MAX_SERVO_NUM] = {0};
    uint8_t all_completed = 0;
    int8_t steps[MAX_SERVO_NUM];
    
    // 计算每个舵机的步进方向
    for(uint8_t i = 0; i < manager->servo_count; i++)
    {
        if(angles[i] > manager->servos[i].current_angle)
        {
            steps[i] = 1;
        }
        else if(angles[i] < manager->servos[i].current_angle)
        {
            steps[i] = -1;
        }
        else
        {
            steps[i] = 0;
            completed[i] = 1;
        }
    }
    
    // 同步移动
    while(!all_completed)
    {
        all_completed = 1;
        
        for(uint8_t i = 0; i < manager->servo_count; i++)
        {
            if(!completed[i])
            {
                uint8_t current = manager->servos[i].current_angle;
                
                if(current != angles[i])
                {
                    current += steps[i];
                    Servo_SetAngle(&manager->servos[i], current);
                    all_completed = 0;
                }
                else
                {
                    completed[i] = 1;
                }
            }
        }
        
        if(!all_completed)
        {
            HAL_Delay(step_delay);
        }
    }
}

五、精度优化与高级功能

5.1 脉宽微调校准

不同品牌的SG90舵机可能存在个体差异,需要进行校准。

📄 创建文件:Src/servo_calibration.c

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

// 校准参数存储结构
typedef struct
{
    uint16_t min_pulse;     // 0度对应的脉宽
    uint16_t max_pulse;     // 180度对应的脉宽
    uint16_t mid_pulse;     // 90度对应的脉宽
} Servo_Calibration_t;

// 默认校准参数
Servo_Calibration_t servo_cal = {
    .min_pulse = 1000,      // 0.5ms
    .max_pulse = 5000,      // 2.5ms
    .mid_pulse = 3000       // 1.5ms
};

/**
 * @brief 设置舵机校准参数
 * @param min_p 0度脉宽
 * @param max_p 180度脉宽
 */
void Servo_SetCalibration(uint16_t min_p, uint16_t max_p)
{
    servo_cal.min_pulse = min_p;
    servo_cal.max_pulse = max_p;
    servo_cal.mid_pulse = (min_p + max_p) / 2;
}

/**
 * @brief 带校准的角度设置
 * @param servo 舵机结构体指针
 * @param angle 目标角度
 */
void Servo_SetAngleCalibrated(Servo_TypeDef *servo, uint8_t angle)
{
    uint16_t pulse;
    
    // 使用校准参数计算脉宽
    pulse = servo_cal.min_pulse + 
            (angle * (servo_cal.max_pulse - servo_cal.min_pulse) / 180);
    
    __HAL_TIM_SET_COMPARE(servo->htim, servo->channel, pulse);
    servo->current_angle = angle;
}

/**
 * @brief 自动校准程序
 * @note 通过串口交互,逐步调整找到准确的0度和180度位置
 */
void Servo_AutoCalibrate(Servo_TypeDef *servo)
{
    uint16_t pulse;
    char cmd;
    
    printf("\\r\\n=== Servo Calibration ===\\r\\n");
    printf("Finding 0 degree position...\\r\\n");
    
    // 校准0度位置
    pulse = 1000;
    while(1)
    {
        __HAL_TIM_SET_COMPARE(servo->htim, servo->channel, pulse);
        printf("Current pulse: %d. Is this 0 degree? (y/n/adjust +/-)\\r\\n", pulse);
        
        // 等待用户输入(简化示例)
        HAL_Delay(2000);
        
        // 这里应该读取串口输入
        // 实际应用中需要实现串口命令解析
        pulse += 50;
        
        if(pulse > 1500) break;  // 防止超出范围
    }
    
    servo_cal.min_pulse = pulse;
    
    printf("\\r\\nFinding 180 degree position...\\r\\n");
    
    // 校准180度位置
    pulse = 5000;
    while(1)
    {
        __HAL_TIM_SET_COMPARE(servo->htim, servo->channel, pulse);
        printf("Current pulse: %d. Is this 180 degree? (y/n/adjust +/-)\\r\\n", pulse);
        
        HAL_Delay(2000);
        
        pulse -= 50;
        
        if(pulse < 4500) break;
    }
    
    servo_cal.max_pulse = pulse;
    
    printf("\\r\\nCalibration complete!\\r\\n");
    printf("0 degree: %d\\r\\n", servo_cal.min_pulse);
    printf("180 degree: %d\\r\\n", servo_cal.max_pulse);
}

5.2 速度控制实现

c 复制代码
/**
 * @brief 带速度控制的转动
 * @param servo 舵机结构体指针
 * @param angle 目标角度
 * @param speed 转动速度(度/秒)
 */
void Servo_SetAngleWithSpeed(Servo_TypeDef *servo, uint8_t angle, uint8_t speed)
{
    int16_t delta;
    uint16_t step_time;
    int8_t step_dir;
    
    // 计算角度差
    delta = angle - servo->current_angle;
    
    if(delta == 0)
    {
        return;
    }
    
    // 确定转动方向
    step_dir = (delta > 0) ? 1 : -1;
    delta = (delta > 0) ? delta : -delta;
    
    // 计算每度所需时间(毫秒)
    step_time = 1000 / speed;
    
    // 逐步转动
    for(uint8_t i = 0; i < delta; i++)
    {
        servo->current_angle += step_dir;
        Servo_SetAngle(servo, servo->current_angle);
        HAL_Delay(step_time);
    }
}

六、测试验证

6.1 功能测试

测试用例1:角度精度测试

c 复制代码
void Test_AngleAccuracy(void)
{
    uint8_t test_angles[] = {0, 45, 90, 135, 180};
    
    for(uint8_t i = 0; i < 5; i++)
    {
        Servo_SetAngle(&servo1, test_angles[i]);
        printf("Set angle: %d, Actual: %d\\r\\n", 
               test_angles[i], Servo_GetAngle(&servo1));
        HAL_Delay(1000);
    }
}

测试用例2:重复定位精度测试

c 复制代码
void Test_Repeatability(void)
{
    for(uint8_t i = 0; i < 10; i++)
    {
        Servo_SetAngle(&servo1, 0);
        HAL_Delay(500);
        Servo_SetAngle(&servo1, 90);
        HAL_Delay(500);
    }
}

6.2 波形测试

使用示波器观察PA0引脚的PWM波形:

  • 周期:20ms(50Hz)
  • 0度时脉宽:0.5ms
  • 90度时脉宽:1.5ms
  • 180度时脉宽:2.5ms

七、故障排查与问题解决

7.1 硬件问题

问题1:舵机不转动

现象:

上电后舵机无反应,不转动。

原因分析:

  • 电源电压不足
  • PWM信号未正确输出
  • 接线错误

解决方案:

检查电源:

复制代码
使用万用表测量:
- 舵机电源引脚:应为4.8V-6V
- 电流能力:至少能提供100mA
- STM32与舵机共地:必须连接GND

检查PWM输出:

c 复制代码
// 在main中添加测试代码
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 3000);  // 1.5ms脉宽

// 使用示波器或LED测试PA0引脚
while(1)
{
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
    HAL_Delay(1);
}

问题2:舵机抖动或不稳定

现象:

舵机到达目标位置后持续抖动,或转动不平稳。

原因分析:

  • 电源电压不稳定
  • PWM信号受干扰
  • 延时时间不合适

解决方案:

增加电源滤波:

复制代码
在舵机电源引脚并联电容:
- 100uF电解电容(低频滤波)
- 100nF陶瓷电容(高频滤波)

优化PWM配置:

c 复制代码
// 使用更高的PWM分辨率
htim2.Init.Prescaler = 0;       // 不分频
htim2.Init.Period = 1439;       // 72MHz/1440=50kHz,再分频得到50Hz

// 或使用PWM模式2
sConfigOC.OCMode = TIM_OCMODE_PWM2;

使用平滑转动:

c 复制代码
// 避免直接跳变,使用平滑转动
Servo_SetAngleSmooth(&servo1, target_angle, 20);

7.2 软件问题

问题3:角度不准确

现象:

设置90度,实际角度偏差较大。

原因分析:

  • 脉宽计算错误
  • 定时器配置错误
  • 舵机个体差异

解决方案:

验证定时器配置:

c 复制代码
// 检查实际PWM频率
uint32_t timer_clock = HAL_RCC_GetPCLK1Freq() * 2;  // TIM2在APB1上
uint32_t pwm_freq = timer_clock / (htim2.Init.Prescaler + 1) / 
                    (htim2.Init.Period + 1);

printf("Timer clock: %lu Hz\\r\\n", timer_clock);
printf("PWM frequency: %lu Hz\\r\\n", pwm_freq);

进行校准:

c 复制代码
// 运行校准程序
Servo_AutoCalibrate(&servo1);

问题4:多舵机控制时部分舵机不工作

现象:

同时控制多个舵机时,部分舵机无响应。

原因分析:

  • 定时器通道配置错误
  • 电源功率不足
  • GPIO复用功能未正确配置

解决方案:

检查定时器通道配置:

c 复制代码
// 确保每个通道都正确初始化
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);

检查GPIO复用配置:

c 复制代码
// 在stm32f1xx_hal_msp.c中
void HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    if(htim->Instance == TIM2)
    {
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        // PA0-TIM2_CH1, PA1-TIM2_CH2, PA2-TIM2_CH3, PA3-TIM2_CH4
        GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | 
                              GPIO_PIN_2 | GPIO_PIN_3;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
}

八、进阶应用

8.1 舵机云台控制

实现二自由度云台(水平和垂直):

c 复制代码
// 云台结构体
typedef struct
{
    Servo_TypeDef *pan_servo;    // 水平舵机
    Servo_TypeDef *tilt_servo;   // 垂直舵机
    uint8_t pan_angle;           // 水平角度
    uint8_t tilt_angle;          // 垂直角度
} Gimbal_TypeDef;

/**
 * @brief 设置云台角度
 * @param gimbal 云台结构体指针
 * @param pan 水平角度(0-180)
 * @param tilt 垂直角度(0-180)
 */
void Gimbal_SetAngle(Gimbal_TypeDef *gimbal, uint8_t pan, uint8_t tilt)
{
    // 限制垂直角度(防止碰撞)
    if(tilt > 160) tilt = 160;
    if(tilt < 20) tilt = 20;
    
    Servo_SetAngle(gimbal->pan_servo, pan);
    Servo_SetAngle(gimbal->tilt_servo, tilt);
    
    gimbal->pan_angle = pan;
    gimbal->tilt_angle = tilt;
}

/**
 * @brief 云台扫描模式
 * @param gimbal 云台结构体指针
 */
void Gimbal_Scan(Gimbal_TypeDef *gimbal)
{
    for(uint8_t pan = 0; pan <= 180; pan += 10)
    {
        for(uint8_t tilt = 20; tilt <= 160; tilt += 10)
        {
            Gimbal_SetAngle(gimbal, pan, tilt);
            HAL_Delay(200);
        }
    }
}

8.2 机械臂控制

c 复制代码
// 简单三自由度机械臂
typedef struct
{
    Servo_TypeDef *base_servo;    // 底座旋转
    Servo_TypeDef *shoulder_servo; // 肩部
    Servo_TypeDef *elbow_servo;   // 肘部
} RobotArm_TypeDef;

/**
 * @brief 机械臂运动到指定位置
 * @param arm 机械臂结构体指针
 * @param base 底座角度
 * @param shoulder 肩部角度
 * @param elbow 肘部角度
 */
void RobotArm_MoveTo(RobotArm_TypeDef *arm, uint8_t base, 
                     uint8_t shoulder, uint8_t elbow)
{
    uint8_t angles[3] = {base, shoulder, elbow};
    Servo_Manager_t manager;
    
    MultiServo_Init(&manager);
    MultiServo_Add(&manager, 0, arm->base_servo->htim, arm->base_servo->channel);
    MultiServo_Add(&manager, 1, arm->shoulder_servo->htim, arm->shoulder_servo->channel);
    MultiServo_Add(&manager, 2, arm->elbow_servo->htim, arm->elbow_servo->channel);
    
    MultiServo_SyncMove(&manager, angles, 20);
}

九、总结

9.1 核心知识点回顾

  1. PWM原理:理解周期、脉宽、占空比与舵机角度的关系
  2. 定时器配置:掌握STM32定时器PWM模式的配置方法
  3. 舵机控制:实现角度控制、平滑转动、速度控制
  4. 多舵机管理:使用定时器多通道实现多舵机同步控制
  5. 精度优化:通过校准和参数调整提高控制精度

9.2 扩展学习方向

  • PID控制:实现舵机位置的闭环控制
  • 总线舵机:学习UART/I2C控制的智能舵机
  • 舵机动力学:了解舵机的速度、加速度特性
  • ROS集成:将舵机控制集成到机器人操作系统

9.3 学习资源

官方文档:

官方GitHub:

相关推荐
Deitymoon12 小时前
STM32——蓝牙模块双串口控制led
stm32·单片机·嵌入式硬件
xiangw@GZ14 小时前
智能锁TouchKey的抗干扰设计【2】-软件算法
嵌入式硬件
iCxhust14 小时前
微机原理实践教程(C语言篇)---A001闪烁灯
c语言·开发语言·汇编·单片机·嵌入式硬件·51单片机·微机原理
一起搞IT吧14 小时前
相机Camera日志实例分析之二十:相机Camx【照片后置4800/5000/6400万拍照】单帧流程日志详解
android·嵌入式硬件·数码相机·智能手机
笨笨饿15 小时前
69_如何给自己手搓一个串口
linux·c语言·网络·单片机·嵌入式硬件·算法·个人开发
FreakStudio19 小时前
MicroPython 内核开发者直接狂喜!这个 Claude 插件市场,把开发全流程做成了「对话式外挂」
python·单片机·嵌入式·面向对象·并行计算·电子diy
天诚智能门锁20 小时前
天诚公租房管控平台CAT.1人脸猫眼智能锁助力青神县公租房管理
人工智能·嵌入式硬件·物联网·智能家居·智能硬件
m0_377108141 天前
5月1日zzz
单片机