当前位置: 首页 > ops >正文

STM32的PWM

PWM作为硬件中几乎不可或缺的存在,学会 PWM,等于打通了 STM32 的“定时器体系”。学一次,STM32 全系列(甚至 AVR、PIC、ESP32)都能通用。硬件只要一个 I/O 就能驱动功率模块,非常省成本。不会 PWM,几乎没法独立做电机控制、灯光调节、舵机、开关电源类项目。

1.PWM 的本质是什么(从电子角度说)

PWM(Pulse Width Modulation)——脉冲宽度调制

它并不是直接输出“某个电压值”,而是输出一个 固定周期、可变占空比 的方波。

当你用这个方波去驱动:

LED → 人眼视觉暂留会“平均”亮度。

电机 → 电机电感+惯性会把高频方波“平均”成类似直流的电压。

舵机 → 内部单片机通过高电平脉宽判断位置。

开关电源 → 控制 MOSFET 的导通时间来调节能量传输。

占空比定义

假设周期 = T, 高电平时间 = Th,
占空比 D = Th / T

0% → 恒低

100% → 恒高

50% → 高低时间相等

2.输出比较通道

1.最左边:比较与“输出模式控制器”

输入:

CNT > CCR1、CNT = CCR1 两个比较结果

ETR(外部触发脚)

TIM1_CCMR1 里的配置位:OC1M[2:0](选择 PWM1/2、切换、强制高/低等),OC1CE(OCref 清除使能)

做的事:
根据选定的 输出比较模式(OC1M)和比较结果,产生一个“理想参考波形” OC1REF。

PWM1:CNT < CCR1 → OC1REF=有效;否则无效

PWM2:相反

Toggle/强制高/低:按模式固定/翻转

若 OC1CE=1 且 ETR 上升沿到来 → 强制把 OC1REF 拉为无效(快速关断/保护用)

记忆:OC1REF 只是“内部参考信号”,还没考虑死区、极性、关闭时状态。

2中间:死区发生器(高级定时器专属)

输入: OC1REF
控制位: TIM1_BDTR.DTG[7:0](死区时间编码)

输出: 两路互补且带空窗的信号:

OC1_DT:给主输出(OC1)

OC1N_DT:给互补输出(OC1N)

它们之间永不同时为有效,并在换向时插入 死区时间,避免上下管直通。

F1 的 DTG 为 8 位分段编码(三段倍率),实际死区 = 编码值 × t_DTS(或乘 2/8/16 的倍率,具体按手册分段公式换算)。核心记住:数越大,死区越长。

3右侧两组多路选择器:运行/空闲/故障时的“强制电平”

你看到每路(OC1、OC1N)在进入极性反相器之前都有一个小 MUX,它的若干个输入是:

来自死区单元的 OC1_DT / OC1N_DT(“正常工作波形”)

常量 ‘0’ 或 ‘1’(“强制电平”)

它们在什么情况下选谁?
取决于高级定时器的 关断/空闲策略:

运行关断(OSSR):当通道被软件关断(CC1E/CC1NE=0)但定时器还在运行时,输出要进入哪个“安全电平”。

空闲关断(OSSI):当主输出被关闭(MOE=0,比如 Break 触发或还没使能)时,引脚保持哪个“空闲电平”。

OISx/OISxN(在 TIM1_CR2):定义“空闲电平”到底是 0 还是 1(注意是在极性反相之前的电平)。

一句话:MUX 负责在 正常波形 和 强制 0/1 之间选择,OSSR/OSSI/OISx 决定“关掉时该保持什么电平”。

4极性(反相器)

主通道用 TIM1_CCER.CC1P

互补通道用 TIM1_CCER.CC1NP

当这些位为 1 时,对应通道在 MUX 之后做一次逻辑反相。

注意:OISx 的 0/1 是在反相之前定义的,所以最终引脚电平 =(OISx 设定)→(再看 CC1P/CC1NP 是否反相)。

5最右:输出使能电路 & 全局门控

通道局部使能:TIM1_CCER.CC1E(主)、CC1NE(互补)

全局主输出使能(高级定时器特有):TIM1_BDTR.MOE 必须为 1,否则各通道即便 CCxE=1 也出不来波形

还会受 刹车/锁定(BDTR.BKE、LOCK)等保护逻辑控制

结合 OSSR/OSSI/OISx,在被禁止时输出保持“安全/空闲”电平

最终,经过这些门控后,才真正到达芯片引脚 OC1 与 OC1N。

把整条链路串一下(典型的 PWM1 互补输出场景)

比较:CNT 与 CCR1 比 → 输出模式控制器按 OC1M 生成 OC1REF

死区:OC1REF → 生成互补且不重叠的 OC1_DT / OC1N_DT(间隔 = DTG)

关断策略:

正常运行且 CC1E/CC1NE=1 且 MOE=1 → MUX 选 带死区的波形

若你清了 CC1E(通道禁用)且 OSSR=1 → MUX 选 强制 0/1(保持安全电平)

若发生 Break 或 MOE=0 且 OSSI=1 → MUX 选 空闲电平(由 OIS1/OIS1N 定义)

极性:按 CC1P/CC1NP 反相或不反相

输出门:再经过 CC1E/CC1NE 与 MOE 的最终门控 → 到 OC1/OC1N 引脚

各寄存器在这张图里各司其职(速查)

CCMR1:OC1M(决定 OC1REF 的生成方式),OC1CE(ETR 清除 OC1REF)

BDTR:DTG(死区)、MOE(主输出总开关)、OSSR/OSSI(运行/空闲的关断选择)

CR2:OIS1/OIS1N(空闲电平设定)

CCER:CC1E/CC1NE(局部开关)、CC1P/CC1NP(极性)

两个常见“为什么”

为什么有 OC1 和 OC1N 两路?
做半桥/全桥时分别去驱动上管与下管,需要互补且不重叠的门控信号,死区发生器确保不直通。

为什么关掉时还要管 0/1?
关断瞬间的引脚电平关系到功率管安全与外部电路的默认状态,OSSR/OSSI + OISx 就是为“安全、可预测”而设计的。

3.PWM 生成过程(边沿对齐模式)

假设:

PSC = 71(分频 72)

ARR = 99

CCR1 = 30

执行过程:

时钟源:
CK_TIMER = 72 MHz(假设来自 APB1 倍频)
CK_CNT = CK_TIMER / (PSC + 1) = 72 MHz / 72 = 1 MHz
→ CNT 每 1 μs 加 1。

计数周期:
CNT 从 0 → 99(ARR),共 100次计数 → 周期 = 100×1 μs = 1 ms → f = 100Hz。

比较过程:

CNT < CCR1(0–29) → 输出为高(PWM1 模式,极性高)。

CNT = 30时 → 比较事件触发,输出变低。

CNT 继续到 99 → 更新事件(CNT 归零),输出回到高。

形成波形:
周期 1 ms,高电平 500 μs → 占空比 = 30/ 100 = 30%。

模式比较

4.PWM基本结构

代码例子(呼吸灯)

//-----------------------------
// STM32 TIM2_CH1 PWM 输出示例(1kHz,0~100%呼吸)
// 对应结构图流程:PSC -> CNT/ARR -> CCR -> 输出模式控制器 -> 极性 -> 输出使能 -> GPIO
//-----------------------------#include "stm32f10x.h"void PWM_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;TIM_OCInitTypeDef TIM_OCInitStruct;// 1) 开启 GPIOA、AFIO、TIM2 时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   // GPIO 模块时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);    // 复用功能时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);    // TIM2 时钟// 2) 部分重映射 TIM2_CH1 到 PA15,并关闭 JTAG(保留 SWD)//    图中相当于把“输出使能电路”的信号线连到 PA15 引脚GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);  GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);// 3) 配置 PA15 为复用推挽输出,交给定时器驱动GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_15;GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;     // 复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);// 4) 定时器内部时钟作为计数源TIM_InternalClockConfig(TIM2);// 5) 配置时基单元(PSC 和 ARR)—— 图中“计数器单元”TIM_TimeBaseInitStruct.TIM_ClockDivision   = TIM_CKD_DIV1;          // 滤波采样分频=1TIM_TimeBaseInitStruct.TIM_CounterMode     = TIM_CounterMode_Up;    // 向上计数模式TIM_TimeBaseInitStruct.TIM_Period          = 100 - 1;               // ARR=99,100个计数TIM_TimeBaseInitStruct.TIM_Prescaler       = 720 - 1;                // PSC=719,把72MHz分频到100kHz// PWM频率 = 100kHz / 100 = 1kHzTIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;                    // 重复计数器不用TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);// 6) 配置输出比较单元(CCR、模式、极性)—— 图中“输出模式控制器”TIM_OCStructInit(&TIM_OCInitStruct); TIM_OCInitStruct.TIM_OCMode      = TIM_OCMode_PWM1;         // PWM1模式:CNT<CCR为有效TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;  // 使能输出TIM_OCInitStruct.TIM_OCPolarity  = TIM_OCPolarity_High;     // 有效电平=高TIM_OCInitStruct.TIM_Pulse       = 0;                       // CCR1初值=0,占空比0%TIM_OC1Init(TIM2, &TIM_OCInitStruct);// 7) 开启 ARR 与 CCR1 预装载(影子寄存器)—— 避免更新毛刺TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);TIM_ARRPreloadConfig(TIM2, ENABLE);// 8) 启动定时器TIM_Cmd(TIM2, ENABLE);
}// 设置占空比(0~100)
void PWM_SetCompare1(uint16_t compare)
{TIM_SetCompare1(TIM2, compare); // 图中就是改变“红线阈值”
}int main(void)
{uint8_t i;PWM_Init();while(1){// 占空比 0% -> 100%for(i=0; i<=100; i++){PWM_SetCompare1(i); // 修改 CCR1Delay_ms(1);        // 延时 1ms}// 占空比 100% -> 0%for(i=0; i<=100; i++){PWM_SetCompare1(100 - i);Delay_ms(1);}}
}
结构图模块代码位置作用说明
PSC 预分频器(粉色块左下)TIM_Prescaler = 720 - 1;把 72 MHz 系统时钟分频到 100 kHz,减慢计数速度
CNT/ARR 计数器 + 自动重装器(粉色块中,锯齿波)TIM_Period = 100 - 1; + TIM_CounterMode = TIM_CounterMode_Up;CNT 从 0 数到 99 后自动回零形成锯齿波;计数模式为向上计数
CCR 捕获/比较器(绿色“CCR”块)TIM_Pulse = 0; + TIM_SetCompare1()设置红线阈值,当 CNT < CCR 输出高电平,当 CNT ≥ CCR 输出低电平,占空比由 CCR 决定
输出模式控制器(绿色框“CNT<CCR时REF有效”)TIM_OCMode = TIM_OCMode_PWM1;选择 PWM1 模式:CNT < CCR 时 REF = 有效电平
极性选择(绿色“极性选择”块)TIM_OCPolarity = TIM_OCPolarity_High;REF 有效电平为高电平
输出使能(极性选择后到 GPIO 前)TIM_OutputState = TIM_OutputState_Enable; + TIM_Cmd(TIM2, ENABLE);打开通道输出,启动定时器,让波形真正到达引脚
GPIO 复用输出(GPIO 块)GPIO_Mode_AF_PP; + GPIO_PinRemapConfig() + GPIO_Remap_SWJ_JTAGDisable;配置 PA15 为复用推挽输出,将 TIM2_CH1 信号映射到 PA15 并关闭 JTAG 占用

5.运用场景

应用场景PWM作用
LED调光通过占空比控制亮度,肉眼平滑过渡
直流电机调速占空比决定电机转速,反应快
舵机控制用 PWM 脉宽来表示角度(常见 1–2ms 脉宽)
开关电源控制 MOSFET 导通时间来稳定输出电压
音频信号生成PWM 高频调制后滤波得到模拟波形
加热器温控占空比决定加热功率
通信协议红外遥控等通过 PWM 编码数据
http://www.xdnf.cn/news/17800.html

相关文章:

  • Linux网络基础概念
  • NAT 和 PNAT
  • AI提高投放效率的核心策略
  • 使用原生css实现word目录样式,标题后面的...动态长度并始终在标题后方(生成点线)
  • JUC LongAdder并发计数器设计
  • 优先级反转问题
  • 基于阿里云音频识别模型的网页语音识别系统实现
  • Flink中基于时间的合流--双流联结(join)
  • 【Doris】-工具SQLConverter
  • Stagehand深度解析:从开源自动化工具到企业级RPA平台的演进之路
  • VisualStudio2022调试Unity C#代码步骤
  • 第2篇_Go语言基础语法_变量常量与数据类型
  • Android项目中Ktor的引入与使用实践
  • 在 Linux 服务器搭建Coturn即ICE/TURN/STUN实现P2P(点对点)直连
  • 图论Day3学习心得
  • 无脑整合springboot2.7+nacos2.2.3+dubbo3.2.9实现远程调用及配置中心
  • 计算机网络 THU 考研专栏简介
  • L2 级别自动驾驶 硬件架构设计
  • LeetCode 922.按奇偶排序数组2
  • ElasticSearch不同环境同步索引数据
  • Spring Ai 如何配置以及如何搭建
  • Jmeter自定义脚本
  • 零基础学会制作 基于STM32单片机智能加湿系统/加湿监测/蓝牙系统/监测水量
  • 探索无人机图传技术:创新视野与无限可能
  • 在 macOS 上顺利安装 lapsolver
  • OpenCV Python——VSCode编写第一个OpenCV-Python程序 ,图像读取及翻转cv2.flip(上下、左右、上下左右一起翻转)
  • 死锁总结及解决方案
  • 关于截屏时实现游戏暂停以及本地和上线不同步问题
  • 用GPT解释“GPT-5”是什么,有什么优势
  • python-pycharm切换python各种版本的环境与安装python各种版本的环境(pypi轮子下载)