一、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));
总结
核心要点:
- PWM频率必须是 50Hz
- 脉冲宽度 0.5ms - 2.5ms
- 必须外接电源,不能直接用STM32供电
- 使用 平滑转动 避免舵机抖动