STM32实战教程:SG90舵机控制原理深度解析及代码详解
2025-09-28 来源:cnblogs
知识点1【SG90的简述】
SG90是一款微型舵机(Micro Servo),由TowerPro等厂商提供,广泛用于机器人,舵机云台,舵机控制教学等项目中。
1、基本参数

2、工作原理
SG90内部有电机,齿轮组,电位器和控制板。通过单根线输入(相对舵机)PWM控制信号
3、外观展示

4、接线说明
| 线颜色 | 说明 | 接法说明 |
|---|---|---|
| 棕色 | GND | 接单片机的地线 |
| 红色 | VCC(5V) | 建议接外部5V供电 |
| 橙色 | PWM信号 | 接 STM32 的 PWM 输出引脚 |
知识点2【SG90分类】
SG90有180°舵机和360°舵机
需要角度控制 → 选 180° 定位舵机 (Standard SG90)
需要可变速度 → 选 360° 连续旋转舵机 (Continuous‑Rotation SG90)
接收同样的 50 Hz PWM 脉冲
驱动电路不再做角度锁定,而是将脉宽映射为“速度与方向”控制
输出轴持续旋转,直到 PWM 脉冲停止或脉宽回到中立值
区别如下图:
| 特性 | 180° 定位舵机 | 360° 连续旋转舵机 |
|---|---|---|
| 控制参数意义 | 脉宽→角度 | 脉宽→速度方向 |
| 是否定位 | 是 | 否 |
| 适合应用 | 定点定位、角度扫描 | 速度驱动、轮式驱动 |
| 代码实现区别 | 设置脉宽一次,持续输出; | 持续输出脉宽做速度控制; |
| 停止方式 | 断开 PWM 脉冲 | 脉宽回 1.5 ms 或断开 |
知识点3【180°舵机原理解析】
控制方式:PWM(脉冲宽度调制)
1、舵机通过PWM控制旋转角度
2、通常使用50Hz(周期20ms)的PWM信号。
3、控制信号的高电平时间(脉宽)决定转角,如下图表:
| 脉宽(ms) | 角度(大约值) |
|---|---|
| 0.5 ms | 0° |
| 1.5 ms | 90° |
| 2.5 ms | 180° |
知识点4【360°舵机原理解析】
工作流程:
| 脉宽 | 旋转方向与速度 |
|---|---|
| < 1.5 ms | 反向旋转,脉宽越短速度越快 |
| ≈ 1.5 ms | 停止(无转矩输出) |
| > 1.5 ms | 正向旋转,脉宽越长速度越快 |
知识点5【注意事项】
1、电源要求
SG90的推荐工作电压时5V,不要直接使用STM32板的3.3V供电,否则容易抖动,或不工作。
2、不要强行转动舵机输出轴
容易破坏内部齿轮或位置反馈电位器。
知识点4【代码演示】
我是用的是STM32F10x系列的,TIM2CH1。
main.c
#include 'stm32f10x.h'
#include 'stm32f10x_conf.h'
#include 'usart.h'
#include 'tim.h'
int main(void)
{
//有限级组的配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
TIM2_CH1_Init();
TIM2_CH1_GPIO_Init();
Usart1_Init(9600);
TIM3_Init();
while(1)
{
}
}
tim.c
#include 'tim.h'
#define MAX_SPEED 60.0f
const u16 period = 1000;
u16 pulse = 0;
int speed = 0;
int state = 30;
void TIM2_CH1_Init(void)
{
TIM_TimeBaseInitTypeDef TIM2_TimeBaseStruct;
TIM_OCInitTypeDef TIM2_OCStruct;
NVIC_InitTypeDef NVIC_TIM2Struct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_TimeBaseStructInit(&TIM2_TimeBaseStruct);
TIM2_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM2_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM2_TimeBaseStruct.TIM_Period = period - 1;
TIM2_TimeBaseStruct.TIM_Prescaler = 1440 - 1;
TIM2_TimeBaseStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM2_TimeBaseStruct);
pulse = speed * (2/180) *50 + 25;
TIM_OCStructInit(&TIM2_OCStruct);
TIM2_OCStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM2_OCStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM2_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM2_OCStruct.TIM_Pulse = pulse;
TIM_OC1Init(TIM2,&TIM2_OCStruct);
TIM_Cmd(TIM2,ENABLE);
}
void TIM3_Init(void)
{
TIM_TimeBaseInitTypeDef TIM3_TimeBaseStruct;
NVIC_InitTypeDef NVIC_TIM3Struct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
TIM_TimeBaseStructInit(&TIM3_TimeBaseStruct);
TIM3_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM3_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM3_TimeBaseStruct.TIM_Period = 20000 - 1;
TIM3_TimeBaseStruct.TIM_Prescaler = 7200 - 1;
TIM3_TimeBaseStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3,&TIM3_TimeBaseStruct);
NVIC_TIM3Struct.NVIC_IRQChannel = TIM3_IRQn;
NVIC_TIM3Struct.NVIC_IRQChannelCmd = ENABLE;
NVIC_TIM3Struct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_TIM3Struct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_TIM3Struct);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
TIM_Cmd(TIM3,ENABLE);
}
void TIM2_CH1_GPIO_Init(void)
{
GPIO_InitTypeDef GPIOA_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_StructInit(&GPIOA_InitStruct);
GPIOA_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIOA_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIOA_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA,&GPIOA_InitStruct);
}
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
speed += state;
if(speed >= 60)
{
state = -30;
}
else if(speed <= -60)
{
state = 30;
}
//角度处理
pulse = (u16)(speed * (1.0f/20) *(period / MAX_SPEED) + 1.5f / 20 * 1000);
printf('Speed:%d r/s\n',speed);
TIM_SetCompare1(TIM2,pulse);
}
}
usart.c
//串口1初始化
void Usart1_Init(u32 Baud)
{
GPIO_InitTypeDef GPIOB_InitStruct;
USART_InitTypeDef USART1_InitStruct;
//时钟配置 USART1,TX:PA9,RX:PA10
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//端口配置
GPIOB_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIOB_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIOB_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIOB_InitStruct);
GPIOB_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIOB_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&GPIOB_InitStruct);
//串口初始化
USART1_InitStruct.USART_BaudRate = Baud;
USART1_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART1_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART1_InitStruct.USART_Parity = USART_Parity_No;
USART1_InitStruct.USART_StopBits = USART_StopBits_1;
USART1_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1,&USART1_InitStruct);
//使能串口
USART_Cmd(USART1,ENABLE);
}
//串口1发送数据函数发送数据
void USART1_Trans(u8 c)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
USART_SendData(USART1,c);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);
}
int fputc(int c,FILE *stream)
{
USART1_Trans((u8)c);
return c;
}
代码运行结果

结束
代码重在练习!
代码重在练习!
代码重在练习!
- 意法半导体中国本地造STM32微控制器启动规模量产
- 意法半导体全新STM32C5系列,重新定义入门级微控制器性能与价值,赋能万千智能设备
- 使用 Keil Studio for Visual Studio Code开发 STM32 设备
- 基于机智云与STM32的智能拐杖安全监测系统在养老物联网中的应用
- 内置全栈安全,一站式满足CRA法案与IEC 62443标准——米尔STM32MP257核心板
- 如何用 STM32 FLASH 实现等效 100 万次擦写的 EEPROM 功能?
- 实战解析:通过一个小项目掌握STM32所有外设
- STM32学了两年半,却还是不会做项目
- 意法半导体推出最新STM32MP21微处理器,兼具高性价比、低功耗、高灵活性
- 基于STM32的矿井作业环境监测系统设计与实现
- 六大全新产品系列推出,MCX A微控制器家族迎来创新
- 意法半导体全新STM32C5系列,重新定义入门级微控制器性能与价值,赋能万千智能设备
- 模组复用与整机重测在SRRC、CCC、CTA/NAL认证中的实践操作指南
- 有源晶振与无源晶振的六大区别详解
- 英飞凌持续巩固全球微控制器市场领导地位
- 使用 Keil Studio for Visual Studio Code开发 STM32 设备
- 从控制到系统:TI利用边缘AI重塑嵌入式MCU的边界
- 蓝牙信道探测技术原理与开发套件实践
- Microchip 推出生产就绪型全栈边缘 AI 解决方案,赋能MCU和MPU实现 智能实时决策
- LoRa、LoRaWAN、NB-IoT与4G DTU技术对比及工业无线方案选型分析




