STM32 智能垃圾桶项目笔记(四):PWM 回顾与舵机(SG90)控制实现 - 实践
本系列笔记是笔者学习 B 站 up 主 “技术探索者” STM32 系列视频所作的记录,不理解的地方推荐观看视频~
目录
- 一、前言
- 二、PWM 快速回顾与舵机(SG90)控制
- 2.1 PWM 核心概念快速回顾
- 2.2 舵机(SG90)工作原理与控制逻辑
- 2.3 CubeMX 配置(定时器 2 生成 50Hz PWM)
- 2.4 舵机驱动代码实现(driver_SG90)
- 2.5 主函数测试与预期现象
- 三、下一篇预告:语音合成模块(SYN6288)
- 四、总结
一、前言
大家好,我是 Hello_Embed。上一篇我们完成了蓝牙模块(JDY-31)的通信验证,实现了 “手机指令控制 LED”。本次笔记将聚焦智能垃圾桶的 “执行核心”—— 舵机 SG90,通过 PWM 信号控制舵机转动,为后续 “自动开盖” 功能铺垫。
舵机控制的核心是 特定频率的 PWM 信号,因此先快速回顾 PWM 的核心概念(具体细节可参考笔者之前的 “STM32HAL 快速入门” 系列笔记),再逐步讲解舵机的原理、配置与代码实现。
二、PWM 快速回顾与舵机(SG90)控制
2.1 PWM 核心概念快速回顾
PWM(脉宽调制)本质是 “高低电平周期性交替的矩形波”,核心参数为频率和占空比,是控制舵机、电机、LED 亮度的基础。
2.1.1 核心参数定义
占空比:高电平持续时间占整个周期的比例,公式为
占空比 = 高电平时间(t2) / 周期(t)× 100%
。例:5V 设备接入占空比 50% 的 PWM 信号,实际等效工作电压约为
5V × 50% = 2.5V
,可通过调节占空比控制设备功率(如电机调速)。PWM 频率:单位时间内周期的数量,公式为
PWM 频率 = 时钟频率 / 预分频值 / (ARR + 1)
(ARR 为自动重载值)。
2.1.2 PWM 输出原理
定时器计数器(CNT)从 0 向上累加,与捕获比较寄存器(CCR)实时比较:
- 当
CNT < CCR
时,输出高电平; - 当
CNT ≥ CCR
时,输出低电平; - 当
CNT = ARR
时,CNT 清零,开始下一个周期。
例:若 ARR=1000、CCR=800,则高电平占比 800/1000=80%
(高电平有效)。
2.1.3 PWM 控制 LED 示例
以定时器 3 为例,配置预分频值 71、ARR=1000(频率 1kHz),代码启动 PWM 并设置占空比:
// 启动定时器3 通道1 PWM 输出
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
// 设置 CCR1=500,占空比 50%(1000 周期内高电平 500)
TIM3->CCR1 = 500;
逻辑分析仪抓取的 50% 占空比波形如下:
若将 CCR1 改为 200,占空比则变为 20%。
2.2 舵机(SG90)工作原理与控制逻辑
2.2.1 舵机特性与原理
SG90 是 180° 模拟舵机,核心由 “信号调制芯片、电机、减速齿轮、电位器” 组成,工作原理如下:
- 接收 PWM 控制信号后,内部基准电路生成 20ms 周期、1.5ms 高电平的基准信号;
- 控制信号的高电平时间与基准信号比较,产生电压差;
- 电压差驱动电机正转 / 反转,通过减速齿轮带动电位器旋转;
- 当电位器电压与控制信号电压一致时,电压差为 0,电机停止,舵机稳定在目标角度。
舵机使用说明图如下:
2.2.2 舵机控制核心:PWM 时基脉冲
SG90 需 20ms 周期(频率 50Hz) 的 PWM 信号,高电平时间决定转动角度,180° 舵机的 “高电平时间 - 角度” 对应关系如下:
高电平时间 | 对应角度 | 占空比(20ms 周期) |
---|---|---|
0.5ms | 0° | 2.5% |
1.0ms | 45° | 5% |
1.5ms | 90° | 7.5% |
2.0ms | 135° | 10% |
2.5ms | 180° | 12.5% |
关键结论:只要生成 50Hz PWM 信号,并调节高电平时间在 0.5ms~2.5ms 之间,即可控制舵机转动到对应角度。
2.3 CubeMX 配置(定时器 2 生成 50Hz PWM)
由于定时器 1 无法配置通道 1 为 PWM 输出,选择 定时器 2 通道 1(PA0 引脚) 生成 50Hz PWM 信号,配置步骤如下:
2.3.1 参数计算(50Hz 频率)
已知系统时钟 72MHz,目标 PWM 频率 50Hz,根据频率公式推导参数:
- 频率 = 72MHz / (预分频值 + 1) / (ARR + 1) = 50Hz
- 选择预分频值 = 720 - 1(720 分频后,时钟频率 = 72MHz / 720 = 100kHz);
- 则 ARR + 1 = 100kHz / 50Hz = 2000 → ARR = 2000 - 1 = 1999。
2.3.2 定时器 2 配置
- 进入
Timers → TIM2
,模式选择Internal Clock
; - 参数配置:
- Prescaler(预分频值):
719
(720 分频); - Counter Period(ARR):
1999
(2000 周期); - PWM 模式:通道 1 设为
PWM Generation CH1
; - 极性:默认
High
(高电平有效,不影响舵机控制);
- Prescaler(预分频值):
- 引脚:TIM2 通道 1 对应 PA0,无需手动修改;
- 配置截图如下:
2.3.3 生成工程
确认基础配置(HCLK=72MHz、Debug=Serial Wire)不变,点击 Generate Code
生成工程,编译确保无错误。
2.4 舵机驱动代码实现(driver_SG90)
新建 driver_SG90.c
和 driver_SG90.h
驱动文件,封装 “舵机初始化” 和 “角度设置” 函数,核心是将 “角度” 线性映射为 “CCR 值”。
2.4.1 驱动头文件(driver_SG90.h)
定义角度对应的 CCR 值宏(0° 对应 50、180° 对应 250,推导:2000 周期内 0.5ms 占 50 个计数单位,2.5ms 占 250 个计数单位):
#ifndef __DRIVER_SG90_H
#define __DRIVER_SG90_H
#include "tim.h" // 包含定时器句柄定义
/* 舵机角度-CCR 值映射宏(2000 周期,50Hz) */
#define SG90_0_DEGREE 50 // 0° 对应 CCR 值(0.5ms 高电平)
#define SG90_90_DEGREE 150 // 90° 对应 CCR 值(1.5ms 高电平)
#define SG90_180_DEGREE 250 // 180° 对应 CCR 值(2.5ms 高电平)
/* 函数声明 */
void SG90_Init(void); // 舵机初始化(启动 PWM + 复位到 90°)
void SG90_SetAngle(uint8_t angle); // 设置舵机角度(0-180°)
#endif
2.4.2 驱动源文件(driver_SG90.c)
实现初始化和角度设置函数,包含角度范围限制和线性映射逻辑:
#include "driver_SG90.h"
// 声明定时器2 句柄(CubeMX 自动生成在 tim.c 中)
extern TIM_HandleTypeDef htim2;
/**
* @brief 舵机 SG90 初始化函数
* @param 无
* @retval 无
* @note 启动定时器2 通道1 PWM,初始化舵机到中间位置(90°)
*/
void SG90_Init(void)
{
// 启动 TIM2 通道1 PWM 输出(舵机信号线接 PA0)
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
// 初始化角度为 90°(避免舵机上电后无规律转动)
SG90_SetAngle(90);
}
/**
* @brief 设置舵机 SG90 目标角度
* @param angle:目标角度(范围 0-180°,超过 180° 自动限制为 180°)
* @retval 无
* @note 线性映射:角度 → CCR 值(0°→50,180°→250)
*/
void SG90_SetAngle(uint8_t angle)
{
uint16_t ccr_value = 0; // 存储计算后的 CCR 值
// 限制角度范围(避免超出舵机物理极限)
if (angle > 180)
{
angle = 180;
}
// 线性映射公式:ccr = 最小CCR + (最大CCR-最小CCR) × 角度/180
ccr_value = SG90_0_DEGREE + (uint16_t)((SG90_180_DEGREE - SG90_0_DEGREE) * angle / 180);
// 设置 TIM2 通道1 的 CCR 值(更新 PWM 高电平时间)
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, ccr_value);
}
2.5 主函数测试与预期现象
在 main.c
中调用舵机驱动函数,实现 “每秒转动 45°” 的测试逻辑:
#include "driver_SG90.h"
// 全局变量:舵机当前角度、计时变量
uint8_t sg90_current_angle = 0;
uint32_t sg90_tick = 0;
int main(void)
{
SG90_Init();
while (1)
{
// 500ms 定时(避免转动过快导致舵机卡顿)
if (HAL_GetTick() - sg90_tick > 500)
{
sg90_tick = HAL_GetTick(); // 更新计时基准
// 设置舵机到当前角度
SG90_SetAngle(sg90_current_angle);
// 角度累加 45°,超过 180° 重置为 0°
sg90_current_angle += 45;
if (sg90_current_angle >= 180)
{
sg90_current_angle = 0;
}
}
}
}
三、下一篇预告:语音合成模块(SYN6288)
完成舵机控制后,下一篇将聚焦智能垃圾桶的 “语音提示” 功能 —— 基于语音合成模块 SYN6288 实现 “开盖提示”“关盖提示” 等语音输出。内容包括:
- SYN6288 模块特性与接线(串口通信);
- CubeMX 串口配置(与模块匹配的波特率);
- 语音合成代码实现(发送文本指令,模块转换为语音);
- 功能整合:舵机开盖时触发 “欢迎使用” 语音,关盖时触发 “谢谢使用” 语音。
四、总结
本次笔记围绕 “PWM 控制舵机” 展开,核心收获包括:
- 回顾 PWM 频率与占空比的计算逻辑,理解 “特定频率 PWM 信号” 对舵机控制的重要性;
- 掌握 SG90 舵机的控制原理(20ms 周期 PWM + 高电平时间 - 角度映射);
- 封装通用舵机驱动函数,实现 “角度 - CCR 值” 线性映射,为后续 “自动开盖” 功能提供可复用代码。
至此,智能垃圾桶的 “感知(超声波)- 通信(蓝牙)- 执行(舵机)” 三大核心模块已全部覆盖,下一篇将通过语音模块丰富项目交互体验。请关注 Hello_Embed,后续笔记持续更新!