一、PWM简介
- PWM是 Pulse Width Modulation 的缩写,中文意思就是脉冲宽度调制,简称脉宽调制。是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。
- PWM对模拟信号电平进行数字编码的一种方法,数字芯片当中只能输出0,1,可以通过PWM输出模拟信号。
- PWM对应模拟信号等效图
上图是一个模拟信号,下图是对模拟信号的数字编码,给出高电平和低电平的时间,即一个周期,在这个周期内,高电平所占据的时间和低电平所占据的时间就可以等效于在这个时刻的模拟量的值。假设模拟量是0.5V,周期是1K,在1K周期之内,30%是高电平,70%是低电平,就可以等效于0.5V这么一个电压。对于单片机的IO口是无法直接输出0.5V的,高电平就是3.3V,低电平就是0V,所以它不会出现0.5V。要输出0.5V就需要通过PWM进行一个模拟输出。
- 占空比: 是一个脉冲周期内,高电平的时间与整个周期时间的比例
二、STM32 PWM介绍
- STM32除了基本定时器TIM6和TIM7之外,其他定时器都可以产生PWM输出。
- 高级定时器(TIM1、TIM8)可以同时产生多达7路的PWM输出,通用定时器可以同时产生多达4路的PWM输出
- PWM输出图
对外输出脉宽可调的方波信号,占空比可调,信号频率是通过定时器ARR和预分频器来决定的,占空比通过CCR寄存器来控制。
从图中可以看出PWM输出的频率是始终不变的,变化的是CCR(占空比)的值,改变占空比其实就是改变PWM输出等效模拟电压值。
- PWM输出模式总共有8种,常用的主要有两种,PWM1,PWM2,PWM1,PWM2不同之处在于PWM的极性
- CCR:输出比较值
- 有效和无效可以理解为输出的是高电平还是低电平,库函数配置时,有一个成员参数是用来设置输出极性的,当输出极性是高电平的时候,有效对应的就是高电平,无效对应的就是低电平。
PWM边沿对齐模式
- 边沿对齐模式:递增或者递减
- 假设设置定时器计数方式是递增模式,CNT从0开始计数,计数到自动重载值就会产生一个上溢事件,如果配置中断就会产生中断,然后又从0开始向上递增。
- 以PWM1为例:CNT从0开始计数,ARR自动重装载值是8,最大就计数到8,CCR是4,假设输出极性配置为高电平,输出有效就是高电平,CNT一直从0到4,输出有效电平1,CNT≥CCR输出无效电平,计数器到4之后,就是低电平,一直到8,到8时产生一个溢出事件,溢出事件后又从0开始向上计数
中心对齐模式
- 和之前类似,只是要切换两次,第一次是在递增模式,假设ARR=8,CCR=4,中心对齐模式就是先递增后递减,递增:CNT<CCR输出有效,将输出极性设置为高电平,有效就是高电平,直到CNT计数到4的时候,不满足条件,输出无效电平,输出低电平,递增到ARR-1的时候,会产生一个上溢事件,产生上溢事件之后,从ARR的值开始向下递减,递减:CNT>CCR输出无效,输出低电平,直到CNT≤CCR为有效,递减到4时,切换为高电平,递减到时,产生一个下溢事件,下溢事件如果配置中断就会产生一个中断,又从0开始向上递增,开始循环
三、PWM输出配置步骤
- 使能定时器及端口时钟,并设置引脚复用器映射。基本定时器没有PWM(TIM6、TIM7)功能,不能使用
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
// 开启复用功能始终
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
// 引脚复用,部分重映射
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE);
// 将GPIO模式设置为复用功能
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
- 初始化定时器参数,包含自动重装值,分频系数,计数方式等
voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
- 初始化PWM输出参数,包含PWM模式、输出极性,使能等
void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); typedef struct { // 比较输出模式,8种,常见PWM1,PWM2 uint16_t TIM_OCMode; // 比较输出使能,使能PWM到对应的IO口 uint16_t TIM_OutputState; // 互补输出使能(高级定时器使用) uint16_t TIM_OutputNState; // 脉冲宽度设置 uint32_t TIM_Pulse; // 输出极性,高电平,低电平 uint16_t TIM_OCPolarity; uint16_t TIM_OCNPolarity; uint16_t TIM_OCIdleState; uint16_t TIM_OCNIdleState; } TIM_OCInitTypeDef;
- 开启定时器
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
- 修改TIMx_CCRx的值控制占空比,2是指通道2,看你选择哪一个通道,就写几。参数1:定时器;参数2:比较值,即占空比的值(CCR)
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint32_t Compare1);
- 使能TIMx在CCRx上的预装载寄存器,CCR要和CNT进行比较
void TIM_OCxPreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
使能 TIMx 在 ARR 上的预装载寄存器允许位。参数1:选择定时器;参数2:使能或禁止预装载。 void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
- 高级定时器用来触发PWM输出
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);
四、硬件设计
- 两个指示灯DS0和DS1,分别接在PB5和PE5
- 发光二极管的阳极连接一个限流电阻,连接到3.3V,阴极连接到一个GPIO,输出低电平,这个时候发光二极管导通发光,要让他熄灭,就给它一个高电平,阴极和阳极的电平一样,所以它会熄灭
五、软件设计
本章所要实现的功能是:通过 TIM3 的 CH2 输出一个 PWM 信号,控制 DS0 指示灯由暗变亮,再由亮变暗,类似于人的呼吸。程序框架如下:
- 初始化 PB5 管脚为 PWM 输出功能
- PWM 输出控制程序
PWM.c
#include "PWM.h"
void TIM3_CH2_PWM_Init(u16 per, u16 psc)
{
// 定义结构体
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 使能定时器、端口、重映射时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 设置为复用推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; // 设置引脚号为 LED1_PIN
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速度为 50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化 LED1 对应端口和引脚
// 部分重映射,将TIM3_CH2指定到PB5引脚
// 参数1:部分重映射
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
TIM_TimeBaseInitStructure.TIM_Period = per;
TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
// 初始化PWM参数
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //配置输出模式,使用PWM1模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; // LED低电平有效,所以极性设置低
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
// 参数1:定时器;参数2:结构体变量
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
/*
预装载寄存器功能:当启用预装载寄存器功能时,对捕获/比较寄存器(CCR)的修改不会立即生效,
而是在下一个更新事件(Update Event)发生时才被加载到影子寄存器中。
这样可以避免在定时器更新事件期间寄存器被修改,从而导致输出不稳定。
*/
// 使能TIMx在CCRx上的预装载寄存器
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
// 使能 TIMx 在 ARR 上的预装载寄存器允许位
TIM_ARRPreloadConfig(TIM3, ENABLE);
// 开启定时器
TIM_Cmd(TIM3, ENABLE);
}
PWM.h
#ifndef __PWM_H
#define __PWM_H
#include "system.h"
void TIM3_CH2_PWM_Init(u16 per, u16 psc);
#endif
main.c
#include "LED.h" // 包含 LED 模块的头文件
#include "system.h"
#include "SysTick.h"
#include "PWM.h"
// 主函数
int main()
{
u16 i = 0; // 动态调节CCR的值
u8 fx = 0; // 标志位,用于切换方向,增减CCR
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_Init(); // 初始化 LED1 和 LED2
// 0.5ms,2KHz,频率设置太高会有闪烁感,频率设置太低亮度不明显
TIM3_CH2_PWM_Init(500, 72 - 1);
// 主循环
while(1)
{
if(fx == 0)
{
i ++;
if(i == 300)
fx = 1;
}
else
{
i --;
if(i == 0)
fx = 0;
}
// 通道2,设置的极性是低,CCR逐渐增大,低电平越来越多,LED低电平点亮,LED就会越来越亮
TIM_SetCompare2(TIM3, i);
// 延时是让呼吸灯显得流畅
delay_ms(5);
}
}
文档信息
- 本文作者:magicianplus
- 本文链接:https://magicianplus.github.io/2025/01/06/%E7%AC%AC18%E7%AB%A0-PWM%E5%91%BC%E5%90%B8%E7%81%AF%E5%AE%9E%E9%AA%8C/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)