STM32F103驱动SG90舵机+OLED显示全套教程(原理 + 接线 + 源码 + 实物演示)
文章前言
本文基于 STM32F103C8T6 最小系统板,搭配经典 SG90 微型舵机、0.96 寸 I2C OLED 屏幕,完整讲解硬件接线、舵机底层控制原理、定时器 PWM 配置逻辑,附带可直接编译运行的工程源码,搭配实物演示效果,零基础也能跟着复现。

工程实现功能:
- 利用 TIM4_CH1 (PB6) 输出 50Hz 标准舵机 PWM 信号,控制 SG90 0~180° 精准转动;
- 角度与 PWM 脉宽线性换算,内部整数运算无浮点,运行效率更高;
- OLED 实时打印 PWM 频率、周期、当前设定角度、输出脉宽;
- 上电自动循环角度演示:0°→45°→90°→135°→180° 往复切换,每个角度停留 1 秒;
- 封装完整舵机驱动函数,支持直接设置角度、手动输入脉宽、读取当前参数、停止 PWM 输出。
一、整体工作流程概述
整套设备的控制逻辑非常清晰,完整执行流程如下:
- 程序输入 0~180° 目标角度,通过公式换算成 500~2500μs 高电平脉宽;
- STM32 定时器 TIM4 配置 20ms 周期(50Hz)PWM,PB6 输出对应宽度脉冲;
- 脉冲信号送入 SG90 舵机信号引脚,舵机内部闭环电路解析目标位置;
- 舵机自带电位器实时采集实际转轴角度,对比目标值自动驱动电机修正位置;
- 转轴到达目标角度后电机停转,同时 OLED 刷新当前全部控制参数。
简单概括:STM32 输出不同宽度脉冲给舵机下达角度指令,舵机依靠内部反馈自行完成定位,屏幕实时展示控制参数。
二、硬件接线与供电说明
2.1 模块引脚对应表
| 外设模块引脚 | STM32F103 引脚 | 功能说明 |
|---|---|---|
| SG90 橙色信号线 (Signal) | PB6 | TIM4_CH1 PWM 控制输出 |
| SG90 红色电源线 (VCC) | 外部独立 5V 电源正极 | 舵机工作供电 |
| SG90 棕色地线 (GND) | STM32 公共 GND | 共地参考电平 |
| OLED SCL 时钟 | PB8 | 软件 I2C 时钟 |
| OLED SDA 数据 | PB9 | 软件 I2C 数据 |
| OLED VCC | 开发板 3.3V | 屏幕供电 |
| OLED GND | STM32 公共 GND | 屏幕接地 |

2.2 推荐供电接线拓扑
外部 5V 电源正极 → SG90 红线 外部 5V 电源负极分两路:一路接 SG90 棕线,一路接 STM32 所有 GND STM32 PB6 引脚 → SG90 橙色信号线
这个位置需要注意一下:舵机的负极,可能是黑色的线,也可能是棕色的线。
2.3 两个关键接线注意事项
- 必须共地 STM32 输出高低电平全部以自身 GND 为参考基准,若舵机电源地和单片机地断开,舵机无法识别脉冲信号,会出现完全不转、剧烈抖动、角度乱跑故障。哪怕舵机使用独立电源,地线一定要相连。
- 舵机禁止单片机 GPIO 供电,建议独立 5V 供电 SG90 启动、换向、受阻时瞬时电流很大,直接用开发板板载 3.3V/5V 供电会引发一系列问题:
- 单片机电压骤降,频繁自动复位;
- OLED 屏幕闪烁、文字显示错乱;
- 舵机转动无力、持续抖动;
- 开发板稳压芯片过热。 解决方案:外接稳定 5V 电源给舵机供电,电源输入端并联滤波电容,减少电流冲击干扰。
三、SG90 舵机内部控制原理
SG90 属于位置闭环式微型舵机,内部集成四大核心部件:小型直流电机、减速齿轮组、角度反馈电位器、专用驱动解码电路,无需单片机读取转轴位置,自带闭环调节。
完整工作逻辑:
- 舵机内部电路持续测量输入 PWM 高电平时长,解析出程序设定的目标角度;
- 电位器跟随舵盘同步旋转,输出对应实际物理角度的反馈电压;
- 驱动电路对比目标角度电压和实际反馈电压,计算位置误差;
- 存在误差时自动控制电机正转 / 反转,经过齿轮减速放大扭矩带动舵盘;
- 转轴匹配目标角度、误差趋近于 0 时电机断电停转;
- 若外力掰动舵盘偏离设定位置,闭环系统会立刻重新驱动电机复位。
核心优势:单片机只需要下发角度指令,不需要编写位置采集、PID 调节代码,舵机自主完成定位控制。
四、舵机标准 PWM 控制信号详解
SG90 行业通用控制标准:固定周期 20ms,对应频率 50Hz,转轴角度仅由高电平脉宽决定,和普通调速 PWM 区分开。 一个完整周期波形规则:20ms 内,0.5ms~2.5ms 高电平,剩余时长全部为低电平。
4.1 角度 - 脉宽对应参数表
| 目标角度 | 高电平脉宽 | 20ms 周期占空比 |
|---|---|---|
| 0° | 500us | 2.5% |
| 45° | 1000us | 5.0% |
| 90°(中位) | 1500us | 7.5% |
| 135° | 2000us | 10.0% |
| 180° | 2500us | 12.5% |
4.2 角度与脉宽换算公式
角度转脉宽(程序核心换算逻辑): 脉宽 (us) = 500 + 角度 × 2000 ÷ 180
脉宽反向计算对应角度: 角度 = (脉宽 - 500) × 180 ÷ 2000
4.3 使用提醒
不同厂商 SG90 机械限位存在个体差异,500~2500us 为通用标准区间。禁止脉宽超出该范围,舵机堵转后会大电流发热,严重磨损内部齿轮。
五、STM32 TIM4 定时器 PWM 生成原理
本工程系统主时钟 72MHz,PB6 复用功能为 TIM4_CH1,定时器配置参数如下:
- 定时器原始时钟:72MHz
- 预分频 PSC = 71(分频 72),计数频率 = 72MHz / 72 = 1MHz
- 单次计数单位:1us
- 自动重装载值 ARR = 19999,完整周期 = 20000 × 1us = 20ms
- PWM 输出频率 = 1 ÷ 0.02s = 50Hz
通道配置为 PWM1 模式,计数规则: 计数器数值 < CCR1(比较寄存器)时,PB6 输出高电平; 计数器数值 ≥ CCR1 时,PB6 输出低电平。
设计亮点:计数单位刚好 1 微秒,CCR 寄存器写入数值直接等于高电平脉宽,换算逻辑极简,新手极易理解。
| CCR1 寄存器值 | PB6 高电平时长 | 对应舵机角度 |
|---|---|---|
| 500 | 500us | 0° |
| 1500 | 1500us | 90° |
| 2500 | 2500us | 180° |
六、源代码工程模块说明
6.1 工程文件分工
下文完整源码分为 OLED 驱动、SG90 舵机驱动、主函数三大部分,可直接复制到 Keil 工程使用。
| 文件 | 核心函数 | 功能作用 |
|---|---|---|
| SG90.c | SG90_Init() | 初始化 PB6、TIM4,上电舵机默认中位 90° |
| SG90.c | SG90_SetAngle() | 输入 0~180 角度,自动换算脉宽更新 CCR1 |
| SG90.c | SG90_SetPulseWidth() | 手动自定义 500~2500us 脉宽,用于舵机标定 |
| SG90.c | SG90_Stop() | 清空 CCR1,停止输出有效舵机脉冲 |
| SG90.c | SG90_GetAngle() / SG90_GetPulseWidth() | 读取当前设定参数,供 OLED 显示调用 |
| main.c | SG90_ShowState() | 在 OLED 屏幕打印全部 PWM、角度参数 |
main.c代码
/* SG90 servo teaching demonstration for STM32F103C8T6. */
#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "SG90.h"
#define DEMO_HOLD_TIME_MS 1000U
static void SG90_ShowState(uint16_t angle);
int main(void)
{
static const uint16_t demo_angles[] =
{
0U, 45U, 90U, 135U, 180U, 135U, 90U, 45U
};
uint8_t index = 0U;
delay_init();
NVIC_Configuration();
SG90_Init();
OLED_Init();
OLED_ColorTurn(0);
OLED_DisplayTurn(0);
SG90_ShowState(SG90_GetAngle());
delay_ms(DEMO_HOLD_TIME_MS);
while(1)
{
SG90_SetAngle(demo_angles[index]);
SG90_ShowState(demo_angles[index]);
index++;
if(index >= (sizeof(demo_angles) / sizeof(demo_angles[0])))
{
index = 0U;
}
delay_ms(DEMO_HOLD_TIME_MS);
}
}
static void SG90_ShowState(uint16_t angle)
{
OLED_Clear();
OLED_ShowString(0, 0, (u8 *)"SG90 SERVO DEMO", 12);
OLED_ShowString(0, 12, (u8 *)"PB6 / TIM4_CH1", 12);
OLED_ShowString(0, 24, (u8 *)"PWM:50Hz T:20ms", 12);
OLED_ShowString(0, 38, (u8 *)"Angle:", 12);
OLED_ShowNum(42, 36, angle, 3, 16);
OLED_ShowString(70, 38, (u8 *)"deg", 12);
OLED_ShowString(0, 52, (u8 *)"Pulse:", 12);
OLED_ShowNum(42, 52, SG90_GetPulseWidth(), 4, 12);
OLED_ShowString(70, 52, (u8 *)"us", 12);
OLED_Refresh();
}
SG90.c代码
#include "SG90.h"
#include "stm32f10x_tim.h"
/* 保存最近一次设置的角度和脉宽,供 OLED 显示或程序查询。 */
static uint16_t sg90_angle = 90U;
static uint16_t sg90_pulse_us = SG90_PULSE_CENTER_US;
/*
* SG90 PWM 输出初始化:
* 1. STM32F103C8T6 默认引脚映射中,PB6 对应 TIM4_CH1。
* 2. 本工程系统时钟为 72 MHz,TIM4 的计数时钟同样为 72 MHz。
* 3. 预分频器设置为 72-1,使定时器每 1 us 计数一次。
* 4. 自动重装载值设置为 20000-1,得到 20 ms(50 Hz)的 PWM 周期。
* 5. 初始脉宽为 1500 us,对应舵机中间位置 90°。
*/
void SG90_Init(void)
{
GPIO_InitTypeDef gpio_init;
TIM_TimeBaseInitTypeDef timer_init;
TIM_OCInitTypeDef pwm_init;
/* 开启 GPIOB 和 TIM4 外设时钟。 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
/* PB6 配置为复用推挽输出,由 TIM4_CH1 控制引脚电平。 */
gpio_init.GPIO_Pin = GPIO_Pin_6;
gpio_init.GPIO_Mode = GPIO_Mode_AF_PP;
gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpio_init);
/* TIM4 使用内部时钟。 */
TIM_InternalClockConfig(TIM4);
/* 计数分辨率为 1 us,PWM 周期为 20000 us。 */
timer_init.TIM_Prescaler = 72U - 1U;
timer_init.TIM_CounterMode = TIM_CounterMode_Up;
timer_init.TIM_Period = SG90_PWM_PERIOD_US - 1U;
timer_init.TIM_ClockDivision = TIM_CKD_DIV1;
timer_init.TIM_RepetitionCounter = 0U;
TIM_TimeBaseInit(TIM4, &timer_init);
/* 配置通道 1 为高电平有效的 PWM1 模式。 */
TIM_OCStructInit(&pwm_init);
pwm_init.TIM_OCMode = TIM_OCMode_PWM1;
pwm_init.TIM_OutputState = TIM_OutputState_Enable;
pwm_init.TIM_OCPolarity = TIM_OCPolarity_High;
pwm_init.TIM_Pulse = SG90_PULSE_CENTER_US;
TIM_OC1Init(TIM4, &pwm_init);
TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM4, ENABLE);
/* 启动定时器,开始从 PB6 输出 PWM。 */
TIM_Cmd(TIM4, ENABLE);
}
/*
* 设置舵机角度。
* 采用整数运算将 0°~180°线性换算为 500~2500 us,
* 加入一半除数用于四舍五入,避免使用浮点运算占用额外资源。
*/
void SG90_SetAngle(uint16_t angle)
{
uint32_t pulse_range;
uint16_t pulse_us;
/* 防止角度超过舵机允许的范围。 */
if(angle > SG90_ANGLE_MAX)
{
angle = SG90_ANGLE_MAX;
}
pulse_range = SG90_PULSE_MAX_US - SG90_PULSE_MIN_US;
pulse_us = (uint16_t)(SG90_PULSE_MIN_US +
(((uint32_t)angle * pulse_range + (SG90_ANGLE_MAX / 2U)) /
SG90_ANGLE_MAX));
sg90_angle = angle;
sg90_pulse_us = pulse_us;
/* CCR1 的数值就是高电平持续时间,单位为 us。 */
TIM_SetCompare1(TIM4, pulse_us);
}
/*
* 直接设置 PWM 高电平脉宽。
* 该接口可用于不同舵机的行程标定,同时会反算并保存对应角度。
*/
void SG90_SetPulseWidth(uint16_t pulse_us)
{
uint32_t pulse_offset;
/* 将脉宽限制在 SG90 的安全控制范围内。 */
if(pulse_us < SG90_PULSE_MIN_US)
{
pulse_us = SG90_PULSE_MIN_US;
}
else if(pulse_us > SG90_PULSE_MAX_US)
{
pulse_us = SG90_PULSE_MAX_US;
}
pulse_offset = pulse_us - SG90_PULSE_MIN_US;
sg90_angle = (uint16_t)((pulse_offset * SG90_ANGLE_MAX +
((SG90_PULSE_MAX_US - SG90_PULSE_MIN_US) / 2U)) /
(SG90_PULSE_MAX_US - SG90_PULSE_MIN_US));
sg90_pulse_us = pulse_us;
TIM_SetCompare1(TIM4, pulse_us);
}
/* 将比较值清零,使 PB6 不再产生有效控制脉冲。 */
void SG90_Stop(void)
{
TIM_SetCompare1(TIM4, 0U);
}
/* 返回最近一次设置或换算得到的角度。 */
uint16_t SG90_GetAngle(void)
{
return sg90_angle;
}
/* 返回最近一次设置或换算得到的脉宽,单位为 us。 */
uint16_t SG90_GetPulseWidth(void)
{
return sg90_pulse_us;
}
SG90.h代码
#ifndef __SG90_H
#define __SG90_H
#include "stm32f10x.h"
/*
* SG90 舵机驱动参数
* 控制周期:20 ms(频率为 50 Hz)
* 高电平脉宽:500 us~2500 us,对应角度 0°~180°
*/
#define SG90_ANGLE_MIN 0U
#define SG90_ANGLE_MAX 180U
#define SG90_PULSE_MIN_US 500U
#define SG90_PULSE_CENTER_US 1500U
#define SG90_PULSE_MAX_US 2500U
#define SG90_PWM_PERIOD_US 20000U
/* 初始化 PB6 和 TIM4_CH1,舵机初始位置为 90° */
void SG90_Init(void);
/* 设置舵机角度,输入超过 180°时自动限制为 180° */
void SG90_SetAngle(uint16_t angle);
/* 直接设置高电平脉宽,输入值自动限制在 500~2500 us */
void SG90_SetPulseWidth(uint16_t pulse_us);
/* 停止输出 PWM;再次设置角度或脉宽即可恢复输出 */
void SG90_Stop(void);
/* 获取最近一次设置的舵机角度 */
uint16_t SG90_GetAngle(void);
/* 获取最近一次设置的高电平脉宽,单位为 us */
uint16_t SG90_GetPulseWidth(void);
#endif
6.2 源码编写特点
- 全部采用整数运算换算角度,无浮点运算,减少单片机算力消耗;
- 自带角度限幅保护,输入超过 180 或小于 0 自动钳位,防止堵转;
- 函数模块化封装,可单独调用任意角度,移植到其他项目只需要修改定时器引脚;
- 软件 I2C 驱动 OLED,无需硬件 I2C 引脚占用,兼容性更强。
七、实物演示效果章节
7.1 上电初始状态
开发板通电后,系统自动初始化定时器、OLED 屏幕、舵机外设,舵机直接转动至中位 90°。 OLED 屏幕固定显示头部信息:
SG90 SERVO DEMO
PB6 / TIM4_CH1
PWM:50Hz T:20ms
Angle:090 deg
Pulse:1500 us

7.2 实物现象观察要点
- 0° 时屏幕 Pulse 显示 500,180° 显示 2500,每递增 45° 脉宽增加 500us;
- 中位 90° 为 1500us,是舵机平衡位置;
- 若人为用手掰动舵盘,舵机会自动发力拉回程序设定角度,直观体现闭环控制特性;
- 调用 SG90_Stop 函数后,屏幕参数保留,但舵机失去锁止力,轻轻一推即可转动。
八、常见故障现象与排查方案
故障 1:舵机完全不转动
- 核查橙色信号线是否接 PB6;
- 确认舵机 5V 供电稳定,空载电压达标;
- 重点检查单片机与舵机电源共地线是否连通;
- 使用逻辑分析仪 / 示波器测量 PB6 引脚,确认存在 20ms 周期脉冲。
故障 2:舵机持续高频抖动
- 舵机供电功率不足,更换独立 5V 电源;
- 地线虚接、接触不良,参考电平波动;
- 机械结构卡滞、负载过重;
- 设定脉宽接近 0°/180° 机械极限,舵机持续堵转;
- 信号线过长,无屏蔽导致电磁干扰。
故障 3:屏幕角度和舵机实际转角不一致
OLED 仅显示程序下发的理论目标值,SG90 存在齿轮间隙、个体公差,属于正常现象。 解决办法:调用 SG90_SetPulseWidth 手动微调最小、最大脉宽,重新标定舵机实际限位。
故障 4:舵机转到两端限位发出持续嗡嗡声
代表脉宽超出舵机机械行程,电机堵转。立刻修改参数缩小脉宽区间,长时间堵转会烧毁舵机内部线圈、打坏齿轮。
故障 5:执行 SG90_Stop 后舵机不受控
该函数仅关闭 PWM 脉冲输出,不会切断舵机 5V 供电。失去脉冲信号后舵机无锁止力矩,负载会带动转轴偏移,属于正常设计。
更多推荐

所有评论(0)