在上篇文章中介绍了定时器,其中有提到PWM,那PWM是用来做什么的呢?本文章主要来说说PWM的输出比价和输入捕获两种操作。
一,PWM
1.1概述
脉冲宽度调制(PulseWidthModulation,PWM)简称脉宽调制。通俗讲,PWM是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。
在模拟电路中,模拟信号的值可以连续进行变化,在时间和值的幅度上都几乎没有限制,基本上可以取任何实数值,输入与输出也呈线性变化。所以在模拟电路中,电压和电流可直接用来进行控制对象,例如家用电器设备中的音量开关控制、采用卤素灯泡灯具的亮度控制等等。
但模拟电路有诸多的问题:例如控制信号容易随时间漂移,难以调节;功耗大;易受噪声和环境干扰等。生成模拟信号对于数字电路而言往往需要额外的数字-模拟转换器。
与模拟电路不同,数字电路是在预先确定的范围内取值,在任何时刻,其输出只可能为ON和OFF两种状态,所以电压或电流会通/断方式的重复脉冲序列加载到模拟负载。PWM技术是一种对模拟信号电平的数字编码方法,通过使用高分辨率计数器(调制频率)调制方波的占空比,从而实现对一个模拟信号的电平进行编码。其最大的优点是从处理器到被控对象之间的所有信号都是数字形式的,无需再进行数模转换过程;而且对噪声的抗干扰能力也大大增强(噪声只有在强到足以将逻辑值改变时,才可能对数字信号产生实质的影响),这也是PWM在通讯等信号传输行业得到大量应用的主要原因。
模拟信号能否使用PWM进行编码调制,仅依赖带宽,这即意味着只要有足够的带宽,任何模拟信号值均可以采用PWM技术进行调制编码。
在PWM中,信号的周期保持恒定,但脉冲的宽度(即脉冲持续时间)可以根据需要调整。通过改变脉冲宽度的百分比,可以调节输出信号的平均电平,从而实现对电路或装置的控制。例如,通过改变PWM信号的占空比(高电平时间占总周期的比例),可以控制LED的亮度,电机的转速等。
1.2 特点
1)周期性:PWM信号具有固定的周期,即脉冲重复的时间间隔。
2)占空比:PWM信号的占空比表示高电平时间占总周期的比例,通常以百分比来表示。占空比越大,输出信号的平均电平越高。
3)精度:PWM信号的精度取决于其周期和分辨率。周期越短、分辨率越高,PWM信号的精度越高。
4)用途广泛:PWM广泛应用于电子设备中,包括电机控制、LED亮度调节、音频数字模拟转换、PWM微分调制,通信行业等领域。
二、输出比较
若要实现PWM输出,我们需要用到定时器的输出比较功能。
当然,在我们查看《stm32f4xx中文参考手册》时,发现这4个通道的功能似乎有所重复:
实际上这两者是有区别的,输出比较模式和PWM模式都可以用来输出PWM波,在功能上两者有相同之处,对于一个定时器这两种方式都可以做到四路输出PWM,每一路PWM占空比都可调。
也有不同之处,输出比较模式可以方便的调节每一路PWM波的频率,可以输出四路频率不同,占空比不同的PWM。但是PWM模式如果想要调节PWM波的频率,那么就只能重新设置预分频系数或者自动重装载寄存器ARR,并且输出的四路PWM频率必定一致。
PWM模式是输出比较模式的子集。
2.1 PWM模式
我们假定定时器工作在向上计数 PWM模式,且
当 CNT<CCRx时, 输出 0,低电平
当 CNT>=CCRx时,输出 1,高电平
那么就可以得到如上的 PWM示意图:
- 0 - t1段,定时器计数器TIMx_CNT值小于CCRx值,输出低电平。
- t1 - t2段,定时器计数器TIMx_CNT值大于CCRx值,输出高电平。
当TIMx_CNT值达到ARR时,定时器溢出,重新向上计数...循环此过程至此一个PWM周期完成。
改变 CCRx的值,就可以改变 PWM输出的占空比,改变 ARR的值,就可以改变 PWM输出的 频率,这就是 PWM输出的原理。
占空比:指高电平占整个周期的比例。
PWM 模式分为两种,PWM1 和PWM2
例如PWM1 的计数是递增,当CNT< CCR时,所选的通道有效,输出为高电平,若CNT>CCR,则通道无效,输出为低电平。
2.2 编程
编程实现定时器PWM模式,输出PWM波形。
1)输出比较结构体
输出比较结构体TIM_OCInitTypeDef 用于输出比较模式,与TIM_OCxInit 函数配合使用完成指定定时器输出通道初始化配置。
(1) TIM_OCMode:比较输出模式选择,总共有八种,常用的为PWM1/PWM2。它设定CCMRx 寄存器OCxM[2:0] 位的值。
(2) TIM_OutputState:比较输出使能,决定最终的输出比较信号OCx 是否通过外部引脚输出。它设定TIMx_CCER 寄存器CCxE/CCxNE 位的值。
(3) TIM_OutputNState: 比较互补输出使能,决定OCx 的互补信号OCxN 是否通过外部引脚输出。它设定CCER 寄存器CCxNE 位的值。
(4) TIM_Pulse:比较输出脉冲宽度,实际设定比较寄存器CCR 的值,决定脉冲宽度。可设置范围为0 至65535。
(5) TIM_OCPolarity:比较输出极性,可选OCx 为高电平有效或低电平有效。它决定着定时器通道有效电平。它设定CCER 寄存器的CCxP 位的值。
(6) TIM_OCNPolarity:比较互补输出极性,可选OCxN 为高电平有效或低电平有效。它设定TIMx_CCER 寄存器的CCxNP 位的值。
(7) TIM_OCIdleState:空闲状态时通道输出电平设置,可选输出1 或输出0,即在空闲状态(BDTR_MOE 位为0) 时,经过死区时间后定时器通道输出高电平或低电平。它设定CR2 寄存器的OISx 位的值。
(8) TIM_OCNIdleState:空闲状态时互补通道输出电平设置,可选输出1 或输出0,即在空闲状态(BDTR_MOE 位为0) 时,经过死区时间后定时器互补通道输出高电平或低电平,设定值必须与TIM_OCIdleState 相反。它设定是CR2 寄存器的OISxN 位的值。
本次例子只需用到蓝底的结构体成员配置。
2)分频系数、ARR值、CCR比较值
输送给定时器4的时钟频率为:APB1时钟频率*2/84=48*2/84=1Mhz
PWM周期为:在1Mhz的时钟频率下。数2000个数 1/1Mhz*2000=2ms
占空比为:高电平时间/PWM周期=数480个数/数2000个数=480/2000=24%
pwm频率为:1/pwm周期 = 1/2ms=500Hz
通过逻辑分析仪可以看出计算值和测量值一致。
3)完整程序
pwm.c
#include "public.h"
/**
* @brief 初始化TIM4的通道1引脚PB6
* @note 因为这个PB6是一个普通的GPIO引脚,如果要将这个引脚作为TIM4 CH1 ,那么得进行引脚复用。
* @param void
* @retval None
*/
void TIMx_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//引脚复用
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_TIM4);
}
/**
* @brief 配置定时器TIM4 输出PWM
* @note
* @param void
* @retval None
*/
void TIMx_PWM_Config(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
//APB1 42Mhz TIM4=84Mhz
TIM_TimeBaseStructure.TIM_Period= 2000-1; //ARR计数最大值
TIM_TimeBaseStructure.TIM_Prescaler= 84-1; //PSC分频系数
/*
84Mhz/84=1Mhz 数一个数:1/1000000 s
数2000个数: 2000* 1/1000000= 2/1000 =0.002s = 2ms
*/
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
//pwm 模式配置
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//当定时器计数值小于CCR时为高电平
TIM_OCInitStructure.TIM_Pulse=2000-1; //CCR
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
TIM_OC1Init(TIM4, &TIM_OCInitStructure);
//注意这里,是将结构体中的CCR值写入寄存器,如果后续想改变CCR,则使用其他方法
TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_Cmd(TIM4, ENABLE);
}
pwm.h
#ifndef __PWM_H
#define __PWM_H
void TIMx_GPIO_Init(void);
void TIMx_PWM_Config(void);
#endif
public.h
#ifndef __PUBLIC_H
#define __PUBLIC_H
#include "stm32f4xx.h"
#include "pwm.h"
#include "SysTick.h"
#endif
main.c
#include "public.h"
int main(void)
{
//2号分组:2bit给抢占 2bit给响应 0-3 0-3
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SysTick_Init();
//1.初始化定时器TIM4的通道引脚
TIMx_GPIO_Init();
//2.定时器TIM4初始化
TIMx_PWM_Config();
//修改CCR比较值,调节不同的占空比
TIM_SetCompare1(TIM4,1000-1);
while(1)
{
}
}
三、输入捕获
输入捕获模式可以用来测量脉冲宽度或者测量频率。STM32的定时器,除了TIM6、TIM7,其他的定时器都有输入捕获的功能。
3.1 测量频率
当捕获通道TIx 上出现上升沿时,发生第一次捕获,计数器CNT 的值会被锁存到捕获寄存器CCR 中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录),并 把捕获寄存器中的值读取到value1 中。
当出现第二次上升沿时,发生第二次捕获,计数器CNT 的值会再次被锁存到捕获寄存器CCR 中,并再次进入捕获中断,在捕获中断中,把捕获寄存器 的值读取到value3 中,并清除捕获记录标志。利用value3 和value1 的差值我们就可以算出信号的 周期(频率)。
3.2 测量脉宽
当捕获通道TIx 上出现上升沿时,发生第一次捕获,计数器CNT 的值会被锁存到捕获寄存器CCR 中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录),并 把捕获寄存器中的值读取到value1 中。
然后把捕获边沿改变为下降沿捕获,目的是捕获后面的 下降沿。当下降沿到来的时候,发生第二次捕获,计数器CNT 的值会再次被锁存到捕获寄存器 CCR 中,并再次进入捕获中断,在捕获中断中,把捕获寄存器的值读取到value2,利用value2-value1的差值即可算出脉冲宽度。
1.测量频率 : value3 - value1
2.测量脉宽 : value2 - value1
提问:如果value2到value1这段时间,CNT的值发生多次溢出怎么办?
以下是一些可能的处理方法:
-
使用溢出计数器(Overflow Counter):
- 在一些微控制器或芯片中,可以设置一个溢出计数器来记录CNT发生的溢出次数。
- 当CNT发生溢出时,溢出计数器会增加。通过监视溢出计数器的值,可以在捕获过程中进行正确的计算,以考虑到CNT的溢出。
-
使用高分辨率计数器(High-Resolution Counter):
- 一些高级的微控制器可能提供了更高分辨率的计数器,以减少CNT溢出对捕获精度的影响。
- 使用高分辨率计数器可以更精确地记录时间,从而减少对CNT溢出的依赖。
-
中断处理:
- 在每次CNT溢出时,可以触发中断处理程序。在中断处理程序中,可以进行相应的处理,如更新溢出计数器、调整捕获时间等。
-
软件补偿:
- 如果无法避免CNT溢出对捕获精度的影响,可以通过软件补偿的方式进行修正。
- 通过记录CNT的溢出次数和对应的时间信息,可以在软件中进行补偿计算,以准确地计算PWM信号的周期或脉冲宽度。
本文中使用的标准库中在配置结构体中选择的模式中自动处理了溢出情况。
3.3 输入模式
由上述我们知道,输入捕获模式,不仅可以单独测量脉宽,还可以单独测量频率,那么是否可以同时测量脉宽和频率呢?(这种也是最常用的方式),答案是肯定可以,只不过需要占用两个捕获寄存器(单独测量脉宽和频率只需要一个)。
输入通道(TIx) 会占用两个捕获通道(ICx)
具体过程:
- 信号由输入通道TI1 进入,信号会被分为两路,一路是 TI1FP1,另外一路是TI2FP2。其中一路是周期,另一路是占空比。(作为触发输入的那一路信号对应的就是 周期,另一路就是对应占空比。作为触发输入的那一路信号还需要设置极性,是上升沿还是下降 沿捕获,一旦设置好触发输入的极性,另外一路硬件就会自动配置为相反的极性捕获,无需软件 配置。一句话概括就是:选定输入通道,确定触发信号,然后设置触发信号的极性即可)
- 将从模式控制器配置为复位模式(配置寄存器SMCR 的位 SMS[2:0] 来实现),即当我们启动触发信号开始进行捕获的时候,同时把计数器CNT 复位清零。
PWM 信号由输入通道TI1 进入,配置TI1FP1 为触发信号,上升沿捕获。当上升沿的时候IC1 和 IC2 同时捕获,计数器CNT 清零。
到了下降沿的时候,IC2 捕获,此时计数器CNT 的值被锁存 到捕获寄存器CCR2 中,到了下一个上升沿的时候,IC1 捕获,计数器CNT 的值被锁存到捕获寄 存器CCR1 中。
可知CCR2 (第一次下降沿value2 - 第一次上升沿value1)测量的是脉宽,CCR1 (第二次上升沿value3 - 第一次下降沿)测量的是周期。
3.4 应用
1)电机控制
输入捕获功能可用于监测电机的霍尔传感器反馈或者反电动势(back electromotive force, BEMF),从而确定电机的转子位置和速度,以便进行适时的换相,实现电机的高效控制。
2)实时监控和反馈控制
位置监测与控制:通过捕获传感器反馈信号,可以实时监测设备或系统的位置,并提供精确的反馈控制,如自动化生产线、机器人操作等。
速度闭环控制:捕获速度反馈信号,用于实现速度闭环控制系统,如电梯速度控制、风扇转速控制等。
3) 串口通信同步(底层原理)
在串口通信中,数据的传输需要严格的同步,以确保发送端和接收端能够正确地识别和解析数据。输入捕获功能可以用于同步接收端的数据接收时钟,从而实现可靠的串口通信。
发送端:发送端根据通信协议的波特率(Baud Rate)产生一个连续的数据流,并将其转换成电平信号发送。波特率定义了每秒钟传输的比特数,因此发送端的时钟需要与波特率相匹配。
接收端:接收端使用输入捕获功能来捕获串口接收线上的数据边沿,通常是起始位(Start Bit)的边沿。通过捕获起始位的边沿,接收端可以确定数据的到达时间,并据此同步接收时钟。
同步原理:接收端根据捕获的起始位边沿和预设的波特率来生成一个本地时钟,该本地时钟与发送端的时钟同步。当本地时钟与发送端的时钟同步时,接收端就能够精确地在每个数据位的中间捕获数据,并正确地解析数据。
3.5 编程
使用输入捕获功能,测量上节课生成的PWM波形,测试是否准确。
1)输入捕获结构体
2)完整程序
capture.c
#include "public.h"
void TIMx_Capture_Config(void)
{
TIMx_GPIO_Config();
TIMx_NVIC_Config();
//配置TIM8时基
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
//APB2 84Mhz TIM8=168Mhz
TIM_TimeBaseStructure.TIM_Period= 50000; //ARR计数最大值 输入捕获有什么影响?
TIM_TimeBaseStructure.TIM_Prescaler= 168-1;//PSC分频系数
/*
168Mhz/168=1Mhz 数一个数:1/1000000 s 1/1Mhz = 168/168M x
*/
TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure);
//输入捕获配置
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //输入捕获通道选择
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
//???
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//设置捕获通道的信号直连和非直连
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//1分频,即捕获信号的每个有效边沿都捕获
TIM_ICInitStructure.TIM_ICFilter = 0x0;//滤波,经历几个周期跳变,认为波形稳定。
TIM_PWMIConfig(TIM8, &TIM_ICInitStructure);
/* Select the TIM8 Input Trigger: TI1FP1 */
TIM_SelectInputTrigger(TIM8, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM8, TIM_SlaveMode_Reset);
TIM_SelectMasterSlaveMode(TIM8,TIM_MasterSlaveMode_Enable);
TIM_Cmd(TIM8, ENABLE);
TIM_ClearITPendingBit(TIM8, TIM_IT_CC1);
TIM_ITConfig(TIM8, TIM_IT_CC1, ENABLE);
}
static void TIMx_GPIO_Config(void)
{
//1.TIM8 PC6 配置成输入捕获通道
GPIO_InitTypeDef GPIO_InitStructure;
/* TIM4 clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE);
/* GPIOB clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);
/* TIM8 chenne1 configuration : PC.6 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN ;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* Connect PC.6 pin to TIM8 */
GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_TIM8);
}
static void TIMx_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM8_CC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
capture.h
#ifndef __CAPTURE_H
#define __CAPTURE_H
void TIMx_Capture_Config(void);
static void TIMx_GPIO_Config(void);
static void TIMx_NVIC_Config(void);
#endif
main.c
#include "public.h"
int main(void)
{
//2号分组:2bit给抢占 2bit给响应 0-3 0-3
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SysTick_Init();
//1.初始化定时器TIM4的通道引脚
TIMx_GPIO_Init();
//2.定时器TIM4初始化
TIMx_PWM_Config();
//修改CCR比较值,调节不同的占空比
//TIM_SetCompare1(TIM4,1000-1);
//3.配置定时器的输入捕获功能,捕获产生的PWM频率和脉宽
TIMx_Capture_Config();
while(1)
{
}
}
四、对齐模式
在PWM中,对齐模式(Alignment Mode)指的是PWM周期的开始与结束与计数器(通常是PWM定时器)的计数状态之间的关系。对齐模式决定了PWM信号的脉冲何时开始和结束,进而影响着PWM信号的特性和行为。
4.1 边沿对齐模式
在边沿对齐模式(Edge-Aligned Mode)中,PWM信号的脉冲边沿与PWM周期的起始边沿或结束边沿对齐。这意味着脉冲信号的上升沿或下降沿与PWM周期的上升沿或下降沿对齐。
1)在边沿对齐模式下PWM波形的特点
-
边沿对齐:脉冲的开始或结束与PWM周期的边沿对齐。
-
灵活性:脉冲的宽度可以在PWM周期的任意时刻开始或结束,只要保持与PWM周期边沿的对齐即可。
-
简单性:边沿对齐模式下的PWM波形生成较为简单,因为脉冲的边沿直接与PWM周期的边沿对齐。
-
适用性:适用于许多常见的PWM控制应用,例如电机驱动、LED亮度调节等。
边沿对齐模式是PWM中最常见和最简单的模式之一,适用于许多常见的应用场景。通过控制脉冲的宽度和占空比,可以实现对输出信号的精确控制。
4.2 中心对齐模式
在中心对齐模式(Center-Aligned Mode)中,PWM信号的脉冲在PWM周期的中心对齐,也就是说,脉冲的上升沿和下降沿都在PWM周期的一半时刻出现。
1)在中心对齐模式下PWM波形的特点
-
脉冲对称性:脉冲信号在PWM周期中心对齐,使得脉冲的上升沿和下降沿都出现在PWM周期的中点,从而实现了脉冲的对称性。
-
周期性:PWM周期保持不变,但是脉冲的起始和结束都发生在PWM周期的中心。
-
平滑输出:由于脉冲对称分布在PWM周期中心,中心对齐模式通常用于需要更平滑输出的应用,如驱动交流电机。
-
波形复杂性:相较于边沿对齐模式,中心对齐模式的PWM波形生成相对复杂一些,因为需要确保脉冲的上升沿和下降沿都在PWM周期的中心。
-
应用范围:适用于需要对波形对称性要求较高的应用场景,或者需要更平滑输出的应用,如音频应用。
中心对齐模式提供了对称的PWM波形输出,适用于一些特定的应用场景,能够满足对波形对称性和输出平滑性的要求。
这两种对齐模式的选择取决于具体的应用需求。边沿对齐模式通常更简单直观,适用于大多数应用;而中心对齐模式则适用于需要对称波形或其他特定需求的应用。在使用PWM时,可以根据具体的应用需求选择合适的对齐模式。