【正点原子探索者STM32F407开发板例程连载+教学】第14章 PWM输出实验-TIMER

2019-07-20 07:41发布

第十四章 PWM输出实验

  [mw_shl_code=c,true] 1.硬件平台:正点原子探索者STM32F407开发板 2.软件平台:MDK5.1 3.固件库版本:V1.4.0 [/mw_shl_code]
上一章,我们介绍了STM32F4的通用定时器TIM3,用该定时器的中断来控制DS1的闪烁,这一章,我们将向大家介绍如何使用STM32F4TIM3来产生PWM输出。在本章中,我们将使用TIM14的通道1来产生PWM来控制DS0的亮度。本章分为如下几个部分: 14.1 PWM简介 14.2 硬件设计 14.3 软件设计 14.4 下载验证  

14.1 PWM简介

脉冲宽度调制(PWM),是英文Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制,PWM原理如图14.1.1所示:
14.1.1 PWM原理示意图        14.1.1就是一个简单的PWM原理示意图。图中,我们假定定时器工作在向上计数PWM模式,且当CNT<CCRx时,输出0,当CNT>=CCRx时输出1。那么就可以得到如上的PWM示意图:当CNT值小于CCRx的时候,IO输出低电平(0),当CNT值大于等于CCRx的时候,IO输出高电平(1),当CNT达到ARR值的时候,重新归零,然后重新向上计数,依次循环。改变CCRx的值,就可以改变PWM输出的占空比,改变ARR的值,就可以改变PWM输出的频率,这就是PWM输出的原理。 STM32F4的定时器除了TIM67。其他的定时器都可以用来产生PWM输出。其中高级定时器TIM1TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出!这里我们仅使用TIM14CH1产生一路PWM输出。    要使STM32F4的通用定时器TIMx产生PWM输出,除了上一章介绍的寄存器外,我们还会用到3个寄存器,来控制PWM的。这三个寄存器分别是:捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。接下来我们简单介绍一下这三个寄存器。 首先是捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有2个:TIMx _CCMR1TIMx _CCMR2,不过TIM14只有一个。TIMx_CCMR1控制CH12,而TIMx_CCMR2控制CH34。以下我们将以TIM14为例进行介绍。TIM14_CCMR1寄存器各位描述如图14.1.2所示:  14.1.2 TIM14_CCMR1寄存器各位描述 该寄存器的有些位在不同模式下,功能不一样,所以在图14.1.2中,我们把寄存器分了2层,上面一层对应输出而下面的则对应输入。关于该寄存器的详细说明,请参考《STM32F4xx中文参考手册》第476页,16.6.4节。这里我们需要说明的是模式设置位OC1M,此部分由3位组成。总共可以配置成7种模式,我们使用的是PWM模式,所以这3位必须设置为110/111。这两种PWM模式的区别就是输出电平的极性相反。另外CC1S用于设置通道的方向(输入/输出)默认设置为0,就是设置通道作为输出使用。注意:这里是因为我们的TIM14只有1个通道,所以才只有第八位有效,高八位无效,其他有多个通道的定时器,高八位也是有效的,具体请参考《STM32F4xx中文参考手册》对应定时器的寄存器描述。 接下来,我们介绍TIM14的捕获/比较使能寄存器(TIM14_CCER),该寄存器控制着各个输入输出通道的开关。该寄存器的各位描述如图14.1.3所示:  14.1.3 TIM14_ CCER寄存器各位描述 该寄存器比较简单,我们这里只用到了CC1E位,该位是输入/捕获1输出使能位,要想PWMIO口输出,这个位必须设置为1,所以我们需要设置该位为1。该寄存器更详细的介绍了,请参考《STM32F4xx中文参考手册》第478页,16.6.5这一节。同样,因为TIM14只有1个通道,所以才只有低四位有效,如果是其他定时器,该寄存器的其他位也可能有效。 最后,我们介绍一下捕获/比较寄存器(TIMx_CCR1~4),该寄存器总共有4个,对应4个通道CH1~4。不过TIM14只有一个,即:TIM14_CCR1,该寄存器的各位描述如图14.1.4所示:  14.1.4 寄存器TIM14_ CCR1各位描述 在输出模式下,该寄存器的值与CNT的值比较,根据比较结果产生相应动作。利用这点,我们通过修改这个寄存器的值,就可以控制PWM的输出脉宽了。 如果是通用定时器,则配置以上三个寄存器就够了,但是如果是高级定时器,则还需要配置:刹车和死区寄存器(TIMx_BDTR),该寄存器各位描述如图14.1.5所示:  14.1.5寄存器TIMx_ BDTR各位描述 该寄存器,我们只需要关注最高位:MOE位,要想高级定时器的PWM正常输出,则必须设置MOE位为1,否则不会有输出。注意:通用定时器不需要配置这个。其他位我们这里就不详细介绍了,请参考《STM32F4xx中文参考手册》第386页,14.4.18这一节。 本章,我们使用的是TIM14的通道1,所以我们需要修改TIM14_CCR1以实现脉宽控制DS0的亮度。至此,我们把本章要用的几个相关寄存器都介绍完了,本章要实现通过TIM14_CH1输出PWM来控制DS0的亮度。下面我们介绍通过库函数来配置该功能的步骤。 首先要提到的是,PWM实际跟上一章节一样使用的是定时器的功能,所以相关的函数设置同样在库函数文件stm32f4xx_tim.hstm32f4xx_tim.c文件中。 1)开启TIM14GPIO时钟,配置PF9选择复用功能AF9TIM14)输出。 要使用TIM14,我们必须先开启TIM14的时钟,这点相信大家看了这么多代码,应该明白了。这里我们还要配置PF9为复用(AF9)输出,才可以实现TIM14_CH1PWM经过PF9输出。 库函数使能TIM14时钟的方法是: RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14,ENABLE);      //TIM14时钟使能 这在前面章节已经提到过。当然,这里我们还要使能GPIOF的时钟。然后我们要配置PF9引脚映射至AF9,复用为定时器14,调用的函数为: GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14); //GPIOF9复用为定时器14 这个方法跟我们串口实验讲解一样,调用的同一个函数,至于函数的使用,在我们的4.4小节有详细的讲解。最后设置PF9为复用功能输出这里我们只列出GPIO初始化为复用功能的一行代码: GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;       //复用功能        这里还需要说明一下,对于定时器通道的引脚关系,大家可以查看STM32F4对应的数据手册,比如我们PWM实验,我们使用的是定时器14的通道1,对应的引脚PF9可以从数据手册表中查看:  2)初始化TIM14,设置TIM14ARRPSC等参数。 在开启了TIM14的时钟之后,我们要设置ARRPSC两个寄存器的值来控制输出PWM的周期。当PWM周期太慢(低于50Hz)的时候,我们就会明显感觉到闪烁了。因此,PWM周期在这里不宜设置的太小。这在库函数是通过TIM_TimeBaseInit函数实现的,在上一节定时器中断章节我们已经有讲解,这里就不详细讲解,调用的格式为: TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx 3)设置TIM14_CH1PWM模式,使能TIM14CH1输出。 接下来,我们要设置TIM14_CH1PWM模式(默认是冻结的),因为我们的DS0是低电平亮,而我们希望当CCR1的值小的时候,DS0就暗,CCR1值大的时候,DS0就亮,所以我们要通过配置TIM14_CCMR1的相关位来控制TIM14_CH1的模式。在库函数中,PWM通道设置是通过函数TIM_OC1Init()~TIM_OC4Init()来设置的,不同的通道的设置函数不一样,这里我们使用的是通道1,所以使用的函数是TIM_OC1Init() void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct) 这种初始化格式大家学到这里应该也熟悉了,所以我们直接来看看结构体TIM_OCInitTypeDef的定义: typedef struct {   uint16_t TIM_OCMode;   uint16_t TIM_OutputState;    uint16_t TIM_OutputNState; */   uint16_t TIM_Pulse;           uint16_t TIM_OCPolarity;      uint16_t TIM_OCNPolarity;     uint16_t TIM_OCIdleState;    uint16_t TIM_OCNIdleState;  } TIM_OCInitTypeDef; 这里我们讲解一下与我们要求相关的几个成员变量: 参数TIM_OCMode设置模式是PWM还是输出比较,这里我们是PWM模式。 参数TIM_OutputState用来设置比较输出使能,也就是使能PWM输出到端口。 参数TIM_OCPolarity用来设置极性是高还是低。 其他的参数TIM_OutputNStateTIM_OCNPolarityTIM_OCIdleStateTIM_OCNIdleState是高级定时器才用到的。 要实现我们上面提到的场景,方法是: TIM_OCInitTypeDef  TIM_OCInitStructure;     TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择模式PWM TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性低 TIM_OC1Init(TIM14, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM1 4OC1 4)使能TIM14 在完成以上设置了之后,我们需要使能TIM14。使能TIM14的方法前面已经讲解过: TIM_Cmd(TIM14, ENABLE);  //使能TIM14 5)修改TIM14_CCR1来控制占空比。 最后,在经过以上设置之后,PWM其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改TIM14_CCR1则可以控制CH1的输出占空比。继而控制DS0的亮度。 在库函数中,修改TIM14_CCR1占空比的函数是: void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare2) 理所当然,对于其他通道,分别有一个函数名字,函数格式为TIM_SetComparex(x=1,2,3,4) 通过以上5个步骤,我们就可以控制TIM14CH1输出PWM波了。这里特别提醒一下大家,高级定时器虽然和通用定时器类似,但是高级定时器要想输出PWM,必须还要设置一个MOE(TIMx_BDTR的第15),以使能主输出,否则不会输出PWM。库函数设置的函数为: void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState)

14.2 硬件设计

本实验用到的硬件资源有: 1)  指示灯DS0 2)  定时器TIM14 这两个我们前面都已经介绍了,因为TIM14_CH1可以通过PF9输出PWM,而DS0就是直接节在PF9上面的,所以电路上并没有任何变化。

14.3 软件设计

打开实验9 PWM输出实验代码可以看到,我们相比上一节,并没有添加其他任何固件库文件,而是添加了我们编写的PWM配置文件pwm.cpwm.h pwm.c源文件代码如下: //TIM14 PWM部分初始化 //PWM输出初始化 //arr:自动重装值  psc:时钟预分频数 void TIM14_PWM_Init(u32 arr,u32 psc) {                                                      GPIO_InitTypeDef GPIO_InitStructure;        TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;        TIM_OCInitTypeDef  TIM_OCInitStructure;               RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14,ENABLE); //TIM14时钟使能           RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); //使能PORTF时钟               GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14); //GF9复用为TIM14               GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;             //GPIOF9        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;        //复用功能        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;      //速度50MHz        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      //推挽复用输出        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;        //上拉        GPIO_Init(GPIOF,&GPIO_InitStructure);              //初始化PF9                 TIM_TimeBaseStructure.TIM_Prescaler=psc;  //定时器分频        TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式        TIM_TimeBaseStructure.TIM_Period=arr;   //自动重装载值        TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;               TIM_TimeBaseInit(TIM14,&TIM_TimeBaseStructure);//初始化定时器14               //初始化TIM14 Channel1 PWM模式         TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //PWM调制模式1       TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能        TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性低        TIM_OC1Init(TIM14, &TIM_OCInitStructure);  //初始化外设TIM1 4OC1        TIM_OC1PreloadConfig(TIM14, TIM_OCPreload_Enable);  //使能预装载寄存器     TIM_ARRPreloadConfig(TIM14,ENABLE);//ARPE使能        TIM_Cmd(TIM14, ENABLE);  //使能TIM14                                                       }    此部分代码包含了上面介绍的PWM输出设置的前5个步骤。这里我们关于TIM14的设置就不再说了。 接下来,我们看看主程序里面的main函数如下: int main(void) {        u16 led0pwmval=0;           u8 dir=1;        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2        delay_init(168);  //初始化延时函数        uart_init(115200);//初始化串口波特率为115200       TIM14_PWM_Init(500-1,84-1);   //定时器时钟为84M,分频系数为84,所以计数频率 //84M/84=1Mhz,重装载值500,所以PWM频率为 1M/500=2Khz.         while(1)         {              delay_ms(10);               if(dir)led0pwmval++;//dir==1 led0pwmval递增               else led0pwmval--; //dir==0 led0pwmval递减              if(led0pwmval>300)dir=0;//led0pwmval到达300后,方向为递减               if(led0pwmval==0)dir=1;      //led0pwmval递减到0后,方向改为递增                 TIM_SetCompare1(TIM14,led0pwmval);     //修改比较值,修改占空比        } } 这里,我们从死循环函数可以看出,我们将led0pwmval这个值设置为PWM比较值,也就是通过led0pwmval来控制PWM的占空比,然后控制led0pwmval的值从0变到300,然后又从300变到0,如此循环,因此DS0的亮度也会跟着信号的占空比变化从暗变到亮,然后又从亮变到暗。至于这里的值,我们为什么取300,是因为PWM的输出占空比达到这个值的时候,我们的LED亮度变化就不大了(虽然最大值可以设置到499),因此设计过大的值在这里是没必要的。至此,我们的软件设计就完成了。

14.4 下载验证

在完成软件设计之后,将我们将编译好的文件下载到探索者STM32F4开发板上,观看其运行结果是否与我们编写的一致。如果没有错误,我们将看DS0不停的由暗变到亮,然后又从亮变到暗。每个过程持续时间大概为3秒钟左右。 实际运行结果如下图14.4.1所示:  14.4.1 PWM控制DS0亮度
 实验详细手册和源码下载地址:http://www.openedv.com/posts/list/41586.htm  正点原子探索者STM32F407开发板购买地址http://item.taobao.com/item.htm?id=41855882779
  
 
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
3条回答
lnhlg
1楼-- · 2019-07-20 08:58
为什么将TIM14换成TIM13,LED便不会亮了?
正点原子
2楼-- · 2019-07-20 11:07
回复【2楼】lnhlg:
---------------------------------
说明你代码还有bug
dashenge
3楼-- · 2019-07-20 11:09
 精彩回答 2  元偷偷看……

一周热门 更多>