STM32F103五分钟入门系列(十五)输出比较(PWM输出)+各类测试
Posted 自信且爱笑‘
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32F103五分钟入门系列(十五)输出比较(PWM输出)+各类测试相关的知识,希望对你有一定的参考价值。
学习板:STM32F103ZET6
参考:
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五分钟入门系列(十六)输入捕获(精雕细琢-.-)