文章目录
-
- 一、前言
-
- [1.1 技术背景](#1.1 技术背景)
- [1.2 本文目标](#1.2 本文目标)
- [1.3 技术栈](#1.3 技术栈)
- 二、环境准备
-
- [2.1 硬件连接](#2.1 硬件连接)
- [2.2 STM32CubeMX配置](#2.2 STM32CubeMX配置)
- [2.3 舵机控制原理](#2.3 舵机控制原理)
- 三、PWM基础驱动实现
-
- [3.1 驱动程序架构](#3.1 驱动程序架构)
- [3.2 头文件定义](#3.2 头文件定义)
- [3.3 驱动实现](#3.3 驱动实现)
- [3.4 主程序测试](#3.4 主程序测试)
- 四、多舵机控制实现
-
- [4.1 多舵机管理架构](#4.1 多舵机管理架构)
- 五、精度优化与高级功能
-
- [5.1 脉宽微调校准](#5.1 脉宽微调校准)
- [5.2 速度控制实现](#5.2 速度控制实现)
- 六、测试验证
-
- [6.1 功能测试](#6.1 功能测试)
- [6.2 波形测试](#6.2 波形测试)
- 七、故障排查与问题解决
-
- [7.1 硬件问题](#7.1 硬件问题)
- [7.2 软件问题](#7.2 软件问题)
- 八、进阶应用
-
- [8.1 舵机云台控制](#8.1 舵机云台控制)
- [8.2 机械臂控制](#8.2 机械臂控制)
- 九、总结
-
- [9.1 核心知识点回顾](#9.1 核心知识点回顾)
- [9.2 扩展学习方向](#9.2 扩展学习方向)
- [9.3 学习资源](#9.3 学习资源)
一、前言
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配置
- 创建新工程,选择STM32F103C8Tx芯片
- 配置时钟:设置系统时钟为72MHz
- 配置定时器PWM :
- 启用TIM2
- 选择Channel1为PWM Generation CH1
- 设置Prescaler为71(72MHz/(71+1)=1MHz)
- 设置Counter Period为19999(1MHz/20000=50Hz)
- 配置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 核心知识点回顾
- PWM原理:理解周期、脉宽、占空比与舵机角度的关系
- 定时器配置:掌握STM32定时器PWM模式的配置方法
- 舵机控制:实现角度控制、平滑转动、速度控制
- 多舵机管理:使用定时器多通道实现多舵机同步控制
- 精度优化:通过校准和参数调整提高控制精度
9.2 扩展学习方向
- PID控制:实现舵机位置的闭环控制
- 总线舵机:学习UART/I2C控制的智能舵机
- 舵机动力学:了解舵机的速度、加速度特性
- ROS集成:将舵机控制集成到机器人操作系统
9.3 学习资源
官方文档:
官方GitHub: