15 . PWM 学习实验

Posted 技术世界低调点儿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了15 . PWM 学习实验相关的知识,希望对你有一定的参考价值。

PWM 学习实验

我们介绍了STM32F1 的通用定时器,使用TIM4 的更新溢出中断控制D2 指示灯闪烁。现在我们来学习如何使用通用定时器产生PWM 输出。本章要实现的功能是:通过TIM3 的通道1 输出PWM 信号,控制D7 指示灯的亮度。分为如下几部分内容:

  1. PWM 简介
  2. 通用定时器PWM
  3. 输出配置步骤
  4. 硬件设计
  5. 软件设计

PWM 简介

PWM 是 Pulse Width Modulation 的缩写,中文意思就是脉冲宽度调制,简称脉宽调制。它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,其控制简单、灵活和动态响应好等优点而成为电力电子技术最广泛应用的控制方式,其应用领域包括测量,通信,功率控制与变换,电动机控制、伺服控制、调光、开关电源,甚至某些音频放大器,因此学习PWM 具有十分重要的现实意义。

其实我们也可以这样理解,PWM 是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。PWM 信号仍然是数字的,因为在给定的任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。电压或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去的。通的时候即是直流供电被加到负载上的时候,断的时候即是供电被断开的时候。只要带宽足够,任何模拟值都可
以使用PWM 进行编码。
PWM 对应模拟信号的等效图,如图所示:

从图中可以看到,上图a 是一个正弦波即模拟信号,b 是一个数字脉冲波形即数字信号。我们知道在计算机系统中只能识别是1 和0,对于STM32F1 芯片,要么输出高电平(3.3V),要么输出低电平(0),假如要输出1.5V 的电压,那么就必须通过相应的处理,比如本章所要讲解的PWM 输出,其实从上图也可以看到,只要保证数字信号脉宽足够就可以使用PWM 进行编码,从而输出1.5V 的电压。

STM32F1 PWM 介绍
STM32F1 除了基本定时器TIM6 和TIM7,其他定时器都可以产生PWM 输出。其中高级定时器TIM1 和TIM8 可以同时产生多达7 路的PWM 输出。而通用定时器也能同时产生多达4 路的PWM 输出,这些在定时器中断章节中已经介绍过。PWM 的输出其实就是对外输出脉宽可调(即占空比调节)的方波信号,信号频率是由自动重装寄存器ARR 的值决定,占空比由比较寄存器CCR 的值决定。其示意图如图所示:

从图中可以看到,PWM 输出频率是不变的,改变的是CCR 寄存器内的值,此值的改变将导致PWM 输出信号占空比的改变。占空比其实就是一个周期内高电平时间与周期的比值。PWM 输出比较模式总共有8 种,具体由寄存器CCMRx 的位OCxM[2:0]配置。我们这里只讲解最常用的两种PWM 输出模式:PWM1 和PWM2,其他几种模式可以参考《STM32F10x 中文参考手册》13、14 定时器章节。PWM1 和PWM2 这两种模式用法差不多,区别之处就是输出电平的极性不同。如图所示:

PWM 模式根据计数器CNT 计数方式,可分为边沿对齐模式和中心对齐模式。
(1)PWM 边沿对齐模式
当TIMx_CR1 寄存器中的DIR 位为低时执行递增计数,计数器CNT 从0 计数到自动重载值(TIMx_ARR 寄存器的内容),然后重新从0 开始计数并生成计数器上溢事件。以PWM 模式1 为例。只要TIMx_CNT < TIMx_CCRx, PWM 参考信号OCxREF便为有效的高电平,否则为无效的低电平。如果TIMx_CCRx 中的比较值大于自动重载值(TIMx_ARR 中),则OCxREF 保持为“ 1”。如果比较值为0,则OCxREF保持为“ 0”。如图所示:

当TIMx_CR1 寄存器中的DIR 位为高时执行递减计数,计数器CNT 从自动重载值(TIMx_ARR 寄存器的内容)递减计数到0,然后重新从TIMx_ARR 值开始计数并生成计数器下溢事件。以PWM 模式1 为例。只要TIMx_CNT >TIMx_CCRx, PWM 参考信号OCxREF便为无效的低电平,否则为有效的高电平。如果TIMx_CCRx 中的比较值大于自动重载值(TIMx_ARR 中),则OCxREF 保持为“ 1”。此模式下不能产生0%的PWM 波形。

(2)PWM 中心对齐模式
在中心对齐模式下,计数器CNT 是工作在递增/递减模式下。开始的时候,计数器CNT 从0 开始计数到自动重载值减1(ARR-1),生成计数器上溢事件;然后从自动重载值开始向下计数到1 并生成计数器下溢事件。之后从0 开始重新计数。如图所示:

我们以ARR=8,CCRx=4 为例进行介绍。第一阶段计数器CNT 工作在递增计数方式,从0 开始计数,当TIMx_CNT <TIMx_CCRx 时,PWM 参考信号OCxREF 为有效的高电平,当TIMx_CNT >= TIMx_CCRx 时,PWM 参考信号OCxREF 为无效的低电平。第二阶段计数器CNT 工作在递减计数方式,从ARR 开始递减计数,当TIMx_CNT > TIMx_CCRx 时,PWM 参考信号OCxREF 为无效的低电平,当TIMx_CNT<= TIMx_CCRx 时,PWM 参考信号OCxREF 为有效的高电平。
中心对齐模式又分为中心对齐模式1/2/3 三种,具体由寄存器CR1 位CMS[1:0]配置。具体的区别就是比较中断标志位CCxIF 在何时置1:中心模式1在CNT 递减计数的时候置1,中心对齐模式2 在CNT 递增计数时置1,中心模式3 在CNT 递增和递减计数时都置1。上述涉及到的寄存器可以参考《STM32F10x 中文参考手册》-13、14、15 定时器章节的寄存器部分,里面有详细寄存器功能介绍。如果看不懂的可以暂时放下,因为我们使用的是库函数开发。

通用定时器PWM 输出配置步骤

接下来我们介绍下如何使用库函数对通用定时器的PWM 输出进行配置。这个也是在编写程序中必须要了解的。其实PWM 输出和上一章一样也是通用定时器的一个功能,因此还是要用到定时器的相关配置函数,具体步骤如下:(定时器相关库函数在stm32f10x_tim.c 和stm32f10x_tim.h 文件中)
(1)使能定时器及端口时钟,并设置引脚复用器映射因为PWM 输出也是通用定时器的一个功能,所以需要使能相应定时器时钟。由于PWM 输出通道是对应着STM32F1 芯片的IO 口,所以需要使能对应的端口时钟,并将对应IO 口配置为复用输出功能。例如本章PWM 呼吸灯实验,我们使用的是TIM3 的CH1 通道输出PWM 信号,因此需要使能TIM3 时钟,调用的库函数如下:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);// 使能TIM3 时钟

而TIM3 的CH1 通道对应的管脚是PA6,但是我们开发板上的LED 灯并没有接在PA6 引脚上,如果要让这个通道映射到LED 所接的IO 口上,则需要使用GPIO的复用功能重映射,在《STM32F1xx 中文参考手册》-8 通用和复用功能I/O(GPIO和AFIO)-8.3.7 定时器复用功能重映射章节都有介绍如下:

LED 模块电路的8 个小灯,其中D7 就是连接在PC6 口的,所以可以将TIM3_CH1配置为完全重映像即可映射到PC6 脚,这样PC6 就可以输出PWM 了。使用到外设的复用功能重映射就需要开启AFIO 时钟,开启AFIO 时钟函数如下:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

从图中可以看到,TIM3_CH1 有部分重映像和完全重映像选择,那么就需要调用引脚复用映射功能函数:

void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalStateNewState);

第二个参数很好理解,用来使能还是失能;第一个参数是选择是部分重映射还是完全重映射,这个参数也很好选择,因为可选的参数在stm32f10x_gpio.h都已经列出来非常详细,如下:

这里我们使用的是TIM3_CH1 完全重映射, 所以参数为GPIO_FullRemap_TIM3。调用函数如下:

GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);//改变指定管脚的映射

最后还要记得将管脚模式配置为复用推挽输出

GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出

(2)初始化定时器参数,包含自动重装值,分频系数,计数方式等要使用定时器功能,必须对定时器内相关参数初始化,其库函数如下:

voidTIM_TimeBaseInit(TIM_TypeDefTIMx,TIM_TimeBaseInitTypeDefTIM_TimeBaseInitStruct);

这个在定时器中断章节就已经介绍。
(3)初始化PWM 输出参数,包含PWM 模式、输出极性,使能等初始化定时器后,需要设置对应通道PWM 的输出参数,比如PWM 模式、输出极性、是否使能PWM 输出等。PWM 通道设置函数如下:

void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

我们知道每个通用定时器有多达4 路PWM 输出通道,所以TIM_OCxInit 函数名中的x 值可以为1/2/3/4。函数的第一个参数相信大家一看就清楚,是用来选择定时器的。第二个参数是一个结构体指针变量,同样我们看下这个结构体TIM_OCInitTypeDef 成员变量:

typedef struct

uint16_t TIM_OCMode; //比较输出模式
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;

这里我们就讲解下比较常用的PWM 模式所需的成员变量:
TIM_OCMode:比较输出模式选择,总共有8 种,最常用的是PWM1 和PWM2。
TIM_OutputState:比较输出使能,用来使能PWM 输出到IO 口。
TIM_OCPolarity:输出极性,用来设定输出通道电平的极性,是高电平还是低电平。

结构体内其他的成员变量TIM_OutputNState , TIM_OCNPolarity ,TIM_OCIdleState 和TIM_OCNIdleState 是高级定时器才用到的。如大家使用到高级定时器,可以查看中文参考手册高级定时器章节。
所以如果我们要配置TIM3 的CH1 为PWM1 模式,输出极性为低电平,并且使能PWM 输出,可以如下配置:

TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
TIM_OC1Init(TIM3,&TIM_OCInitStructure); //输出比较通道1 初始化

(4)开启定时器
前面几个步骤已经将定时器及PWM 配置好,但PWM 还不能正常使用,只有开启定时器了才能让它正常工作,开启定时器的库函数如下:

void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);

第一个参数是用来选择定时器。
第二个参数是用来使能或者失能定时器,也就是开启或者关闭定时器功能。

同样可以选择ENABLE 和DISABLE。例如我们要开启TIM3,那么调用此函数如下:

TIM_Cmd(TIM3,ENABLE); //开启定时器

(5)修改TIMx_CCRx 的值控制占空比其实经过前面几个步骤的配置,PWM 已经开始输出了,只是占空比和频率是固定的,例如要实现呼吸灯效果,那么就需要调节TIM3 通道1 的占空比,通过修改TIM3_CCR1 值控制。调节占空比函数是:

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint32_t Compare1);

对于其他通道, 分别有对应的函数名, 函数格式是

TIM_SetComparex(x=1/2/3/4)。

(6)使能TIMx 在CCRx 上的预装载寄存器使能输出比较预装载库函数是:

void TIM_OCxPreloadConfig(TIM_TypeDef* TIMx, uint16_tTIM_OCPreload);

第一个参数用于选择定时器,第二个参数用于选择使能还是失能输出比较预装载寄存器,可选择为TIM_OCPreload_Enable、 TIM_OCPreload_Disable。

(7)使能TIMx 在ARR 上的预装载寄存器允许位使能TIMx 在ARR 上的预装载寄存器允许位库函数是:

void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalStateNewState);

第一个参数用于选择定时器,第二个参数用于选择使能还是失能。将以上几步全部配置好后,我们就可以控制通用定时器相应的通道输出PWM波形了,这里要特别提醒下,虽然高级定时器和通用定时器类似,但是高级定时器要想输出PWM 波形,必须要设置一个MOE 位(TIMx_BDTR 的第15 位),以使能主输出,否则不会输出PWM。库函数设置的函数为:

void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalStateNewState);

软件设计

硬件电路非常简单,只使用到开发板上的LED(D7),因为D7 指示灯接在PC6 管脚,而通过对TIM3_CH1 复用功能完全重映射就可以映射到PC6 脚。所以可以通过TIM3 的CH1 输出PWM 信号,实现D7 指示灯呼吸灯的控制。
所以我们要实现的功能是:通过TIM3 的CH1 输出一个PWM 信号,控制D7 指示灯由暗变亮,再由亮变暗,类似于人的呼吸。程序框架如下:
(1)初始化PC6 管脚为PWM 输出功能
(2)PWM 输出控制程序
在前面介绍通用定时器PWM 配置步骤时,就已经讲解如何初始化PWM。我们添加pwm.c 文件, stm32f10x_tim.c 库文件。定时器操作的库函数都放在stm32f10x_tim.c 和stm32f10x_tim.h 文件中,所以使用到定时器功能就必须加入stm32f10x_tim.c文件,同时还要包含对应的头文件路径。

TIM3 通道1 的PWM 初始化函数
要使用定时器的PWM 输出功能,我们必须先对它进行配置。TIM3 通道1 的PWM 初始化代码如下:

/****************************************************************
***************
* 函数名: TIM3_CH1_PWM_Init
* 函数功能: TIM3 通道1 PWM 初始化函数
* 输入: per:重装载值  psc:分频系数
* 输出: 无
*****************************************************************
**************/
void TIM3_CH1_PWM_Init(u16 per,u16 psc)

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;

/* 开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

/* 配置GPIO 的模式和IO 口*/
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
GPIO_Init(GPIOC,&GPIO_InitStructure);
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_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
TIM_OC1Init(TIM3,&TIM_OCInitStructure); //输出比较通道1 初始化
TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable); //使能TIMx 在CCR1 上的预装载寄存器
TIM_ARRPreloadConfig(TIM3,ENABLE);//使能预装载寄存器
TIM_Cmd(TIM3,ENABLE); //使能定时器

在TIM3_CH1_PWM_Init()函数中,首先使能GPIOC 端口时钟、TIM3 时钟和AFIO 时钟,其次配置TIM3_CH1 为完全复用重映射功能,并将PC6 管脚模式配置为复用推挽输出。然后配置定时器结构体TIM_TimeBaseInitStructure,初始化PWM 输出参数,由于我们的LED 指示灯是低电平点亮,而我们希望当CCR1 的值小的时候,LED 暗, CCR1 值大的时候,LED 亮,所以我们设置为PWM1 模式,输出极性为低电平,使能PWM 输出。最后就是开启TIM3。这一过程在前面步骤介绍中已经提了。程序中我们看到最后调用了TIM_OC1PreloadConfig() 和TIM_ARRPreloadConfig,他们是用来使能TIM3 在CCR1 上的预装载寄存器和自动重装载寄存器,第一个库函数必须调用,第二个的话如果不调用也没有关系。TIM3_CH1_PWM_Init()函数有两个参数,用来设置定时器的自动装载值和分频系数,方便大家修改PWM 频率。其实如果你会使用通用定时器TIM3 的CH1 输出PWM,那么其他通用定时器通道都一样。

主函数

编写好PWM 初始化函数后,接下来就可以编写主函数了,代码如下:

/****************************************************************
***************
* 函数名: main
* 函数功能: 主函数
* 输入: 无
* 输出: 无
*****************************************************************
**************/
int main()

u16 i=0;
u8 fx=0;
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组分2 组
LED_Init();
TIM3_CH1_PWM_Init(500,72-1); //频率是2Kh
while(1)

	if(fx==0)
	
	i++;
	if(i==300)
	
	fx=1;
	
	
	else
	
	i--;
	if(i==0)
	
	fx=0;
	

TIM_SetCompare1(TIM3,i); //i 值最大可以取499,因为ARR 最大值是499.
delay_ms(10);


主函数实现的功能很简单,首先初始化对应的硬件端口时钟和IO 口,然后调用我们前面编写的TIM3_CH1_PWM_Init 函数,这里我们设定定时器自动重装载值为500,预分频系数为72-1,定时周期即为500us,频率即为2KHz,这里为什么减1 在定时器中断已经介绍。初始化后,定时器开始工作,PC6 开始输出PWM 波形,波形频率为2K,你也可以修改这个频率值,但是要注意,不能将频率设置过大,否则会看到D7 指示灯有明显的闪烁。通过变量fx 控制i 的方向,如果fx=0,i 值累加,否则递减,然后将这个变化的i 值传递给TIM_SetCompare1 函数,这个函数功能是改变占空比的,因此可以实现D1 指示灯亮度的调节,呈现呼吸灯的效果。程序中将i 值控制在300 内,主要是因为PWM 输出波形占空比达到这个值时,D1 指示灯亮度变化就不明显了,而且我们在初始化定时器时,将自动重装载值设置为499,所以这个i 值也不能超过。

以上是关于15 . PWM 学习实验的主要内容,如果未能解决你的问题,请参考以下文章

verilog编写可调PWM波形

PWM信号经RC滤波之后得到这种波形怎么办?

关于STM32单片机PWM输出实验定时器的问题

ESP32学习笔记(15)——LEDC(PWM)接口使用

15 . PWM 学习实验

15 . PWM 学习实验