STM32F103五分钟入门系列(十五)输出比较(PWM输出)+各类测试

Posted 自信且爱笑‘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32F103五分钟入门系列(十五)输出比较(PWM输出)+各类测试相关的知识,希望对你有一定的参考价值。

学习板:STM32F103ZET6

参考:

STM32F103五分钟入门系列(十二)定时器中断

PWM输出

前言

在之前的《定时器中断》中总结了定时器相关的内容,在那篇博客中也提到过输入捕获和输出比较,本博和接下来的一篇博客总结这两块内容。其中本博总结的的是下图标注的部分,通过输出一个PWM脉冲,来深入了解输出比较方面的东西。

博主很喜欢从寄存器入手总结一个东西,因为从寄存器中可以找到许多平时被库函数封装起来的东西,这些东西非常有利于理解相应的内容。本博也不例外,会从寄存器再到库函数慢慢了解PWM输出的真正含有。

一、定时器输出PWM简介

STM32除了基本定时器TIM6、TIM7,其他定时器都可以产生PWM输出。其中高级定时器TIM1、TIM8可以同时产生7路的PWM输出;通用定时器TIM2~5也可以同时输出4路的PWM。共可以输出30路的PWM。

其工作原理如下:

如上图,为一个向上计数的计数器,从0计数到重装载值ARR后,再回到0开始重新计数。

现给它一个“阈值”CCRX,超过这个“阈值”的计数范围(时间段)可以让某IO口输出高电平,未超过该“阈值”的计数范围(时间段)可以让某IO口输出低电平;当然也可以反过来:超出“阈值”输出低电平,未超出“阈值”输出高电平。这样就可以输出稳定的方波了。

上图所示,横坐标:0~t2、t2 ~t4、t4 ~t6,即是计数的一个周期,又是输出PWM的一个周期,可见,影响PWM周期的因素只是这个重装载值ARR。

上图所示。当:

超出“阈值”输出高电平,未超出“阈值”输出低电平

此时横坐标:0~t1、t2 ~t3、t4 ~t5 计数时间段输出的是PWM低电平,其它时间段输出高电平,占空比:(t2-t1)/t2=(t4-t3)/(t4-t2)…

当:

超出“阈值”输出低电平,未超出“阈值”输出高电平

此时横坐标:0~t1、t2 ~t3、t4 ~t5 计数时间段输出的是PWM高电平,其它时间段输出低电平,占空比:t1/t2=(t3-t2)/(t4-t2)…

可见,这个“阈值”CCRx影响这输出PWM占空比,当然影响占空比的因素还有“模式”,即超出“阈值”后的部分究竟是高电平还是低电平。

综上,只要配置好定时器、设置好重装载值(确定周期)、设置好模式(确定占空比)、设置好“阈值”CCRx(确定占空比)就可以输出PWM了。当然也可以让其它设置固定,只改变“阈值”的情况下,输出周期一定,占空比变化的PWM。

二、输出比较相关寄存器

1、捕获/比较模式寄存器 1(TIMx_CCMR1)(CH1、CH2

该寄存器设置通道1和通道2,(通用定时器有4个通道,高级定时器7个通道),其中低8位设置CH1,高8位设置CH2

位1:0

位1:0在捕获/比较中共用,不好理解就直接放图:

①位1:0=00,CC1通道配置为输出:

②位1:0=01,CC1通道被配置为输入,IC1映射在TI1上

③位1:0=10,CC1通道被配置为输入,IC1映射在TI2上

④位1:0=11,CC1通道被配置为输入,IC1映射在TRC上

位2

该位置1时可将输出PWM的响应时间缩短2个时钟周期

位3

该位使能输出比较1预装载值,输出比较的预装载置存储在TIMx_CCR1寄存器中,如果该位置0,则随时可以把重装载值写入到TIMx_CCR1寄存器;如果该位置1,则只有更新事件到来时才可以将重装载值写入到TIMx_CCR1寄存器。

位6:4

主要看一下110、111的情况,首先要清楚CCR1中装载的值是本博开头说的那个“阈值”,这个“阈值”要和计数器当前值比较的。

①当位6:4=110时为PWM1模式—向上计数。

本博开头《PWM简介》所说的就是向上计数,当时分为以下两种情况:

超出“阈值”输出高电平,未超出“阈值”输出低电平
超出“阈值”输出低电平,未超出“阈值”输出高电平

位6:4=110时的描述:

110:PWM模式1- 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为无效电平;在向下计数时,TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。

这个描述怎么理解呢?如上图:

当计数器的值小于“阈值”(TIMx_CNT<TIMx_CCR1)时,通道1位有效电平,即上图的0~t1、t2 ~t3、t4 ~t5时间段为有效电平,剩下时间段为无效电平。

同理:当位6:4=111时,当计数器的值小于“阈值”(TIMx_CNT<TIMx_CCR1)时,通道1位无效电平,即上图的0~t1、t2 ~t3、t4 ~t5时间段为无效电平,剩下时间段为有效电平。

注意:有效电平不是高电平,有效电平可能是高电平,也可能是低电平,有效电平的极性在CCER寄存器中设置。

综上:

(1)CCRE寄存器把有效电平极性设置为高电平有效,CCMR1寄存器位6:4=110,为PWM1模式时:

(2)CCRE寄存器把有效电平极性设置为低电平有效,CCMR1寄存器位6:4=110,为PWM1模式时:

(3)CCRE寄存器把有效电平极性设置为高电平有效,CCMR1寄存器位6:4=111,为PWM2模式时:

(4)CCRE寄存器把有效电平极性设置为低电平有效,CCMR1寄存器位6:4=111,为PWM2模式时:

位7

该位设置C1REF是否受ETRF影响

位15:8

这高8位是设置通道2的,与前面通道1的总结一模一样,不在赘述。

2、捕获/比较模式寄存器 2(TIMx_CCMR2)(CH3、CH4

该寄存器与CCMR1一样,不过是对通道3和通道4的设置。不再赘述。

3、捕获/比较使能寄存器(TIMx_CCER)

位1:0

位0是使能位,使能CC1通道输出

.
位1是设置通道的输出极性位,之前CCMR1已经总结过了,0时有效电平是高电平,1时有效电平是低电平。

剩下的位5:4、位9:8、13:12分别设置CH2、CH3、CH4,与刚刚总结的CH1设置一样,不再赘述。

4、自动重装载寄存器(TIMx_ARR)

该寄存器应该在《定时器中断》这篇博客中总结的,不过那篇博客当时无心去总结寄存器,就没有总结,现在补上…

该寄存器是16位寄存器,不像看门狗的重装载寄存器,只有7位。用来存储计数器的装载值的,每次计数到0(向下计数、上/下计数)或计数到最大值(向上计数)时都要重装载初值。

5、捕获/比较寄存器 1、2、3、4(TIMx_CCRx)

该寄存器也是16位寄存器,装载的是之前所说的那个“阈值”。

三、输出比较的相关库函数

1、初始化通道函数TIM_OCxInit()

以通道1的设置函数TIM_OC1Init()为例说明。通道2函数TIM_OC1Init()设置时由于在CCMR1高8位,所以设置时需要把一些定义的16进制数左移再赋值,比较好理解,同通道3、4。

参数:

参数1:TIMx,表示选择哪个定时器

参数2:TIM_OCInitTypeDef* TIM_OCInitStruct

①TIM_OCMode:

就是对CCMR寄存器的位6:4的设置

②TIM_OutputState

对CCER寄存器位0置1,开启OC1信号输出:

③TIM_OutputNState()

该参数设置TIM互补输出比较状态,高级定时器 TIM1 和 TIM8 才用到

④TIM_Pulse

该值指定要加载到捕获比较寄存器中的脉冲值,所以这里用不到

⑤TIM_OCPolarity

设置输出极性,对CCER寄存器的位1设置


⑥TIM_OCNPolarity

指定互补输出极性。在高级定时器 TIM1 和 TIM8 才用到。

⑦TIM_OCIdleState

指定空闲状态期间的TIM输出比较引脚状态。在高级定时器 TIM1 和 TIM8 才用到。

⑧TIM_OCNIdleState

指定空闲状态期间的TIM输出比较引脚状态在高级定时器 TIM1 和 TIM8 才用到。

所以对于通用定时器,有用的参数为:TIM_OCMode、TIM_OutputState、TIM_OCPolarity

例:

(1)CCRE寄存器把有效电平极性设置为高电平有效,CCMR1寄存器位6:4=110,为PWM1模式时:

设置:

	TIM_OCInitTypeDef TIM_OCInitStructure;
	 
	 TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
	 TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	 TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;
	 TIM_OC2Init(TIM3, &TIM_OCInitStructure);

(2)CCRE寄存器把有效电平极性设置为低电平有效,CCMR1寄存器位6:4=110,为PWM1模式时:
设置:

	TIM_OCInitTypeDef TIM_OCInitStructure;
	 
	 TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
	 TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	 TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;
	 TIM_OC2Init(TIM3, &TIM_OCInitStructure);

(3)CCRE寄存器把有效电平极性设置为高电平有效,CCMR1寄存器位6:4=111,为PWM2模式时:

设置:

	TIM_OCInitTypeDef TIM_OCInitStructure;
	 
	 TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2;
	 TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	 TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;
	 TIM_OC2Init(TIM3, &TIM_OCInitStructure);

(4)CCRE寄存器把有效电平极性设置为低电平有效,CCMR1寄存器位6:4=111,为PWM2模式时:
设置:

	TIM_OCInitTypeDef TIM_OCInitStructure;
	 
	 TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2;
	 TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	 TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;
	 TIM_OC2Init(TIM3, &TIM_OCInitStructure);

2、修改占空比函数TIM_SetCompareX()

以设置通道1占空比的函数TIM_SetCompare1()为例:


参数:

参数1:表示哪个定时器

参数2:为自定义数,本博开头所述“阈值”来决定占空比,这个“阈值”就是与计数器目前值比较的那个数,存储在CCR寄存器中,如果对这个寄存器的值一直设置变化,则相应的输出PWM的占空比也一直变化。该寄存器是16位寄存器,所以这个值可以在0~0xffff之间取值。.

3、使能预装载寄存器TIM_OCxPreloadConfig()

以使能通道1的预装载寄存器为例:


参数:

第一个参数是选择哪个定时器

第二个参数:

是对CCMR寄存器位3操作,将该位置1


此时只有更新事件到来之时才给CCR寄存器里装载值(就是那个“阈值”、比较值)

四、输出比较的编程顺序

1、使能相关时钟

(1)定时器时钟使能

输出比较会用到定时器,所以定时器的时钟必须有,如使能定时器2:


根据搭载在哪个外设上,选用对应的使能函数:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

(2)使能复用时钟

因为GPIO正常情况下只做输入输出(个别除外),察看《STM32F103ZET6》

上图中【main】表示正常情况下IO口做什么,此时只需要使能对应IO时钟即可。【Default】表示除了main功能外还有其它功能,此时就需要开启复用时钟。【Remap】表示重映射,将其它IO口的功能映射到本IO上,此时需要 GPIO_PinRemapConfig设置映射关系,重映射分为部分重映射和完全重映射,根据需要映射即可。可察看《STM32F103ZET6》或者《STM32中文参考手册》:

2、重映射GPIO_PinRemapConfig()(如果需要

如将TIM3_CH2重映射到PB5引脚

上图可见是【部分重映射】

GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);

当然将TIM3_CH2重映射到PC7引脚,这里就需要【完全重映射】:

GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);

3、配置IO GPIO_Init()

使用GPIO_Init()函数设置对应IO口

4、初始化定时器、设置定时器TIM_TimeBaseInit()

STM32F103五分钟入门系列(十二)定时器中断中也总结过,使用TIM_TimeBaseInit()设置定时器

5、设置定时器相关通道PWM模式、使能对应通道输出 TIM_OCxInit()

这个本博之前总结到了,使用的库函数: TIM_OCxInit()

6、使能预装载寄存器TIM_OCxPreloadConfig()

7、使能定时器

STM32F103五分钟入门系列(十二)定时器中断中也总结过,使用TIM_Cmd()使能定时器

8、修改占空比函数TIM_SetCompareX()(如果需要

这个本博之前总结到了,使用的库函数:TIM_SetCompareX()

五、举例1+测试

1、题

题:

使用定时器3通道1输出1KHZ的脉冲,占空比为50%。

2、分析

分析:

默认状态下,通用定时器内部时钟为72MHZ,若这里采用7200预分频系数(最大65535),则计数器时钟为:72MHZ/7200=10KHZ,即每计一次数的时间为0.1ms。

重装载值决定输出PWM的周期,若输出1KHZ,则输出PWM的周期为1ms,则只需计10个数就完成一个周期,重装载一次初值,所以重装载值设置为9。

占空比为50%,所有比较值(“阈值”)为重装载值的一半,为5(0~5,和5 ~9 ~0)

查《STM32F103ZET6》可知,定时器3通道1为PA6的复用引脚,故不需要重映射,且最后可在PA6引脚上测得PWM输出。

PWM模式可采用PWM1,有效电平为高电平,即下图:

3、pwm.h编写

pwm.h文件固定格式:

#ifndef _PWM_
#define _PWM_
void PWM_Init(void);
#endif

4、pwm.c编写

(1)使能定时器时钟、使能IO时钟、使能复用时钟

定时器3搭载在APB1外设上,所以:

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);

(2)配置IO

	GPIO_InitTypeDef  GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

(3)初始化定时器、设置定时器

	TIM_TimeBaseInitTypeDef  TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_Period=10;
	TIM_TimeBaseInitStructure.TIM_Prescaler=7200;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);

(4)设置定时器相关通道PWM模式、使能通道

	TIM_OCInitTypeDef  TIM_OCInitStructure;
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	TIM_OC1Init(TIM3,&TIM_OCInitStructure);

(5)使能预装载寄存器

 TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);

(6)使能定时器

TIM_Cmd(TIM3, ENABLE);

(7)设置占空比

可以在主函数中设置:

TIM_SetCompare1(TIM3, 5);

5、完整程序

//pwm.h
#ifndef _PWM_
#define _PWM_
void PWM_Init(void);
#endif
//pwm.c
#include "pwm.h"
#include "stm32f10x.h"
void PWM_Init(void)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseInitStructure;
	GPIO_InitTypeDef  GPIO_InitStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	
	TIM_TimeBaseInitStructure.TIM_Period=9;
	TIM_TimeBaseInitStructure.TIM_Prescaler=7200;
	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_High;
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	TIM_OC1Init(TIM3,&TIM_OCInitStructure);
	
	TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);

	TIM_Cmd(TIM3, ENABLE);
}
//main
#include "stm32f10x.h"
#include "pwm.h"
 int main(void)
 {	
	 PWM_Init();
	 TIM_SetCompare1(TIM3, 5);
	 while(1)
	 {
	 } 
 }
 

6、测试

将PA6信号线引出到示波器,其波形如下:

六、例2+测试

1、题

题:

接上例,使用定时器3通道1输出500HZ的脉冲,占空比为70%。不过采用PWM2模式,有效电平为高电平

2、分析

分析:

默认状态下,通用定时器内部时钟为72MHZ,若这里采用7200预分频系数(最大65535),则计数器时钟为:72MHZ/7200=10KHZ,即每计一次数的时间为0.1ms。

重装载值决定输出PWM的周期,若输出500HZ,则输出PWM的周期为2ms,则只需计20个数就完成一个周期,重装载一次初值。所以重装载值设置为19。

CCRE寄存器把有效电平极性设置为高电平有效,CCMR1寄存器位6:4=111,为PWM2模式时:

现已知ARR=20,t2=2ms,则要想占空比为70%,需要t1=0.6ms、CCRx=6。

故只需修改下面代码:

初始定时器:

    TIM_TimeBaseInitTypeDef  TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_Period=19;
	TIM_TimeBaseInitStructure.TIM_Prescaler=7200;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);

设置定时器相关PWM模式、使能通道

	TIM_OCInitTypeDef  TIM_OCInitStructure;
	
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2;
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	TIM_OC1Init(TIM3,&TIM_OCInitStructure);

设置占空比:

TIM_SetCompare1(TIM3, 6);

3、完整程序

//pwm.h
#ifndef _PWM_
#define _PWM_
void PWM_Init(void);
#endif
//pwm.c
#include "pwm.h"
#include "stm32f10x.h"
void PWM_Init(void)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseInitStructure;
	GPIO_InitTypeDef  GPIO_InitStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	
	TIM_TimeBaseInitStructure.TIM_Period=19;
	TIM_TimeBaseInitStructure.TIM_Prescaler=7200;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);

	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2;
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	TIM_OC1Init(TIM3,&TIM_OCInitStructure);
	
	TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
	
	TIM_Cmd(TIM3, ENABLE);
}
//main.c
#include "stm32f10x.h"
#include "pwm.h"
 int main(void)
 {	
	 PWM_Init();
	 TIM_SetCompare1(TIM3, 6);
	 while(1)
	 {
	 }
 }

4、测试

将PA6信号线引出到示波器,其波形如下:

七、例3—控制呼吸灯(飞线法)(+测试)

1、题

题:

使用PWM输出控制LED从亮到暗再从暗到亮循环

2、分析

分析:

采用(六)中例子的PWM,500HZ,PWM2模式、有效电平高电平,在PA6输出。

将PA6与PB5用飞线连接起来,PB5设置为浮空输入,在主函数中不断改变占空比。

注意改变占空比时,TIM_SetCompare1()函数传递的值有范围的,这个值必须小于等于重装载值。

3、完整代码

led.h代码:

//led.h
#ifndef __LED_H 
#define __LED_H	 
void LED_Init(void);	
#endif

led.c代码:

//led.c
#include "led.h"
#include "stm32f10x.h"
void LED_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStruct;
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB以上是关于STM32F103五分钟入门系列(十五)输出比较(PWM输出)+各类测试的主要内容,如果未能解决你的问题,请参考以下文章

STM32F103(二十五)完美解决USART发送接收floatu16u32数据

STM32F103(二十五)完美解决USART发送接收floatu16u32数据

STM32F103五分钟入门系列(十六)输入捕获(精雕细琢-.-)

STM32F103五分钟入门系列外部中断大汇总

STM32F103五分钟入门系列SystemInit()函数SetSysClock()函数

STM32F103五分钟入门系列按键实验(库函数+寄存器)