目录
前言
一 PWM介绍
1.1 PWM简介
1.2 STM32F103 PWM介绍
1.3 时钟周期与占空比
二.引脚映像关系
2.1引脚映像与寄存器
2.2 复用功能映像
三. PWM 配置步骤
3.1相关原理图
3.2配置流程
3.2.1 步骤一二:
3.2.2 步骤三:
3.2.3 步骤四五六七:
3.2.4 步骤八:
3.3 PWM 详细代码
3.3.1 PWM.C
3.3.2 main.c
四.PWM波形
4.1 波形查看
4.2 PWM更新频率
4.2.1不使用delay
4.2.2 使用delay
前言
步骤一:通过配置ARR(自动重装载值寄存器)和PSC(预分频器)的值,来设置CNT(计数器)的定时周期、计数频率。
步骤二:再改变CCR(捕获/比较寄存器)的值,通过CNT与CCR的比较,可对PWM占空比进行调整。
经过步骤一和步骤二:即可输出频率和占空比都可以调制的PWM波形
注:
ARR(自动重装载值寄存器)
PSC(预分频器)
CNT(计数器)
CCR(捕获/比较寄存器)
一 PWM介绍
1.1 PWM简介
脉冲宽度调制:PWM是一种数字信号控制技术,其中数字信号的占空比被用来控制模拟信号的幅度。占空比是指在一个周期内,信号处于高电平状态的时间与总周期时间的比例。
PWM是"Pulse Width Modulation"的缩写,中文意思是“脉冲宽度调制”。这是一种模拟信号控制方法,通过改变电信号的占空比来控制功率输出或模拟信号的幅度。PWM广泛应用于各种电子系统中,包括但不限于以下几个领域:
电机控制:PWM用于控制电机的转速和力矩,通过调整电机驱动器的输入电压或电流的占空比来实现。
LED调光:在LED照明中,PWM可以控制LED的亮度,通过改变电流的占空比来调节亮度,而不会改变LED的色温。
音频信号合成:PWM也用于数字音频处理,通过调制脉冲的宽度来合成模拟音频信号。
电源管理:在开关电源中,PWM用于控制开关元件的开关频率和占空比,以调节输出电压和电流。
通信:某些通信协议使用PWM来传输数据,通过调制脉冲的宽度来编码信息。
测量和控制:PWM信号可以用于测量距离、速度等物理量,也可以用于控制各种执行器。
PWM信号的主要特点包括:
- 周期性:PWM信号是周期性重复的,具有固定的频率。
- 占空比:PWM信号的占空比是指高电平状态在整个周期中所占的比例。
- 分辨率:PWM的分辨率取决于信号的周期和能够分辨的最小脉冲宽度,高分辨率的PWM可以提供更平滑的模拟控制。
- 易于生成和控制:PWM信号可以通过数字电路或微控制器轻松生成和调整。
在实际应用中,PWM信号通常由定时器或专用的PWM硬件生成,然后通过数字到模拟转换器(DAC)或直接通过功率放大器输出到负载。通过精确控制PWM信号的频率和占空比,可以实现对各种电子设备的精确控制。
1.2 STM32F103 PWM介绍
在STM32F103中除了基本定时器(定时器6和定时器7),通用和高级定时器都可以用来进行PWM输出。
1.3 时钟周期与占空比
在时基单元中,我们通过对PSC、ARR 大小进行配置,来设置计数器CNT的定时周期、计数频率。
因为前面的时基单元中,已经设置完定时器时钟频率。可以通过TIMx_CCRx(捕获/比较寄存器,也就是上面的CCR),输出占空比可调的PWM波形
注:因为CNT在前面设置向上或向下计数模式后就不用更改了,所以到这一步只需要对CRR的值进行设置,也可以通过while()循环,不断给TIMx_CCRx寄存器赋新的值,来进行脉宽占空比的调整。
输出频率和占空比都可以调制的PWM波形
•PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
•PWM占空比: Duty = CCR / (ARR + 1)
•PWM分辨率: Reso = 1 / (ARR + 1)
二.引脚映像关系
2.1引脚映像与寄存器
高级定时器:TIM1和TIM8是高级定时器
通用定时器:TIM2、TIM3、TIM4和TIM5是通用定时器
基本定时器:TIM6和TIM7是基本的定时器
2.2 复用功能映像
引脚重映射:比如当您需要将TIM3的某些通道映射到不同的GPIO引脚上时,可以使用复用功能映像。例如,当默认的TIM3通道引脚不能满足您的硬件设计需求,或者您需要将多个通道映射到同一个引脚上时,可以使用复用功能映像来改变引脚映射。
PWM输出到特定引脚:如果您需要将PWM信号输出到特定的GPIO引脚,而这个引脚不是TIM3的默认输出引脚,您可以通过复用功能映像来实现。例如,将TIM3的CH2映射到PB5,或者将所有四个通道映射到PC6、PC7、PC8、PC9。
下面以TIM3为例
TIM3是STM32微控制器中的一个通用定时器,它具有四个独立的通道,分别是CH1、CH2、CH3和CH4。这些通道可以被配置为输入捕获、输出比较或PWM输出模式,用于各种定时和控制应用。
每个通道都有自己的捕获/比较寄存器(CCR),可以独立设置,以实现不同的定时和控制功能。例如,TIM3_CH1默认引脚为PA6,TIM3_CH1部分重映像引脚为PB4,TIM3_CH1完全重映像引脚为PC6。
三. PWM 配置步骤
3.1相关原理图
这里使用PWM控制LED呼吸灯。这里控制PC8的绿色LED灯,实现呼吸灯的效果。
这里的PC8端口,是TIM3的通道3使用完全重映射
//部分重映射 GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE); //完全重映射 GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);
既:GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);
3.2配置流程
一:使能定时器3和相关IO口时钟。
使能定时器3时钟:RCC_APB1PeriphClockCmd();
使能GPIOC时钟:RCC_APB2PeriphClockCmd();
二:初始化IO口为复用功能输出。函数:GPIO_Init();
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
三:这里我们是要把PC8用作定时器的PWM输出引脚,所以要重映射配置,所以需要开启AFIO时钟。同时设置重映射。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
四:初始化定时器:ARR,PSC等:TIM_TimeBaseInit();
五:初始化输出比较参数:TIM_OC3Init();
六:使能预装载寄存器: TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
七:使能定时器:TIM_Cmd();
八:不断改变比较值CCRx,达到不同的占空比效果:TIM_SetCompare4();
3.2.1 步骤一二:
一:使能定时器3和相关IO口时钟。
使能定时器3时钟:RCC_APB1PeriphClockCmd();
使能GPIOC时钟:RCC_APB2PeriphClockCmd();
二:初始化IO口为复用功能输出。函数:GPIO_Init();
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; GPIO_InitTypeDef GPIO_InitStructure; /* 步骤一:使能定时器3和相关IO口时钟。开启时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); /* 步骤二:初始化IO口为复用功能输出 */ GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出 GPIO_Init(GPIOC,&GPIO_InitStructure);
3.2.2 步骤三:
三:这里我们是要把PC8用作定时器的PWM输出引脚,所以要重映射配置,所以需要开启AFIO时钟。同时设置重映射。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
/* 步骤三:设置重映射 */ GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE); GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);//改变指定管脚的映射
3.2.3 步骤四五六七:
四:初始化定时器:ARR,PSC等:TIM_TimeBaseInit();
五:初始化输出比较参数:TIM_OC3Init();
六:使能预装载寄存器: TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
七:使能定时器:TIM_Cmd();
/* 步骤四:初始化定时器 */ 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); /* 步骤五:初始化输出比较参数:TIM_OC3Init(); */ TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;//计数值与TIM_Pulse匹配,输出低电平 TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable; TIM_OC3Init(TIM3,&TIM_OCInitStructure); //输出比较通道3初始化 /* 步骤六:使能预装载寄存器 */ TIM_OC3PreloadConfig(TIM3,TIM_OCPreload_Enable);//使能TIMx在 CCR3 上的预装载寄存器 TIM_ARRPreloadConfig(TIM3,ENABLE);//使能预装载寄存器 /* 步骤七:使能定时器3 */ TIM_Cmd(TIM3,ENABLE); //使能定时器
3.2.4 步骤八:
八:不断改变比较值CCRx,达到不同的占空比效果:TIM_SetCompare4();
int main() { u16 i=0; u8 fx=0; delay_init(); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2组 LED_Init(); TIM3_CH3_PWM_Init(500,72-1); //0.5毫秒,(频率是2KHZ) while(1) { if(fx==0) { i++; if(i==500) { fx=1; } } else { i--; if(i==0) { fx=0; } } //可直接改变CCR的值(通道3也就是CCR3的值) TIM_SetCompare3(TIM3,i); //i值最大可以取499,因为ARR最大值是499. delay_ms(5); } }
3.3 PWM 详细代码
3.3.1 PWM.C
pwm.c
#include "pwm.h" #include "led.h" /******************************************************************************* * 函 数 名 : TIM3_CH3_PWM_Init * 函数功能 : TIM3通道3 PWM初始化函数 * 输 入 : per:重装载值 psc:分频系数 * 输 出 : 无 *******************************************************************************/ void TIM3_CH3_PWM_Init(u16 per,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; GPIO_InitTypeDef GPIO_InitStructure; /* 步骤一:使能定时器3和相关IO口时钟。开启时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); /* 步骤二:初始化IO口为复用功能输出 */ GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出 GPIO_Init(GPIOC,&GPIO_InitStructure); /* 步骤三:设置重映射 */ GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE); GPIO_PinRemapConfig(GPIO_FullRemap_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); /* 步骤五:初始化输出比较参数:TIM_OC3Init(); */ TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//计数值与TIM_Pulse匹配,输出低电平 TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable; TIM_OC3Init(TIM3,&TIM_OCInitStructure); //输出比较通道3初始化 /* 步骤六:使能预装载寄存器 */ TIM_OC3PreloadConfig(TIM3,TIM_OCPreload_Enable);//使能TIMx在 CCR3 上的预装载寄存器 TIM_ARRPreloadConfig(TIM3,ENABLE);//使能预装载寄存器 /* 步骤七:使能定时器3 */ TIM_Cmd(TIM3,ENABLE); //使能定时器 }
3.3.2 main.c
main.c
#include "sys.h" #include "delay.h" #include "usart.h" #include "led.h" #include "key.h" #include "time.h" #include "pwm.h" int main() { u16 i=0; u8 fx=0; delay_init(); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2组 LED_Init(); TIM3_CH3_PWM_Init(500,72-1); //周期0.5毫秒,(频率是2KHZ) /* PSC = 72; ARR = 500; 周期 = 500/1000000 = 5/10000 =0.0005秒 = 0.5毫秒 频率 = 2KHZ */ /*LED亮灭时间2秒(亮1秒,灭1秒), t = 1000/500 =2; */ while(1) { if(fx==0) { i++; if(i==500) { fx=1; } } else { i--; if(i==0) { fx=0; } } //可直接改变CCR的值(通道3也就是CCR3的值) TIM_SetCompare3(TIM3,i); //i值最大可以取499,因为ARR最大值是499. delay_ms(2); } }
四.PWM波形
4.1 波形查看
假设 CPU 的时钟频率为 1 MHz(1 微秒/周期),并且循环体内的指令执行需要 10 个周期(这是一个粗略的估计,下面实际测试是差不多的),那么每次循环大约需要 10 微秒。因此,一个完整的亮灭周期大约需要:
这里的if(fx==500),递增和递减共需:500*2 = 1000次计算:
1000×10 微秒=10000 微秒=10 毫秒1000×10微秒=10000微秒=10毫秒
所以,LED 亮灭一次的周期大约为 10 毫秒。这意味着 LED 每 10 毫秒亮灭一次。但请注意,这个估计值可能与实际值有所不同,具体取决于 CPU 的执行速度和循环体内的指令数量。
波形大致为:因为pwm周期
4.2 PWM更新频率
注意:
更改delay_ms()延时函数的大小:是修改LED灯亮灭的周期;
LED亮灭周期是由频率(时基单元PSC ARR CNT)和占空比(CCR)控制的;
dalay_ms()函数是为了控制PWM信号的更新速率:在代码中,
delay_ms()
函数控制了i
值更新的速率,即控制了 PWM 信号占空比变化的速率。如果没有这个延迟,i
的值会非常快地在 0 到 500 之间变化,导致 PWM 信号的频率非常高,这可能超出了人眼的感知范围,使得 LED 的亮度看起来是恒定的。
4.2.1不使用delay
1 不使用delay_ms()函数
循环中没有包含任何延迟,这意味着
i
的值会非常快速地在 0 到 500 之间变化,没有任何停留。由于没有延迟,i
的值变化得太快,导致人眼无法察觉到 LED 的亮度变化,看起来就像是 LED 一直亮着。
4.2.2 使用delay
2 使用delay_ms(1)函数后:这里就是LED的亮灭周期为1秒
- 从 0 增加到 500 需要 500 次循环。
- 从 500 减少到 0 也需要 500 次循环。
- 因此,一个完整的亮灭周期需要 1000 次循环。
每次循环的时间为 1 毫秒,所以一个完整的亮灭周期的时间为:
1000×1 毫秒=1000 毫秒=1 秒1000×1毫秒=1000毫秒=1秒
如下图