STM32的HAL库开发—TIMER(定时器) —通用定时器-PWM功能

一、通用定时器输出比较部分框图介绍

输出比较部分主框图如下:

1、 捕获/比较通道1的主电路—输出部分

图中,灰色部分为输入的,右边为输出的。 

捕获/比较预装载寄存器就是主框图里边的捕获/比较1寄存器,捕获/比较1寄存器的影子寄存器就是图中的捕获比较影子寄存器,影子寄存器是没有读写权限的。

一般首先由程序员写入捕获/比较寄存器的值,也就是CCR1。然后捕获/比较寄存器的值会转移到捕获/比较影子寄存器里边。捕获/比较影子寄存器的值与计数器的值进行比较。比较的结果来到输出控制部分。

捕获/比较寄存器的值在compare_transfer为1的时候才会转移到影子寄存器里边。第一个条件就是当写入CCR1H或者CCR1L时候,writing_in_progress为1,经过一个非,就是0,再经过与门结果为0,也就是说在写入捕获/比较寄存器的时候,值不会转移到影子寄存器里边。第二个条件为CC1S[0]和CC1S[1],这两位为配置通道1是输出还是输入,如果配置成输出,那么这两位都是0,则经过或非门以后结果为1。第三个条件为设置OC1PE位,这个位是设置捕获/比较寄存器是否有缓冲功能,如果设置成0,则无缓冲功能,经过一个非门,在经过一个或门则结果为1,那么捕获/比较寄存器的值会立即转移到影子寄存器里边。反之如果设置的是1,就是有缓冲功能,不会立即转移到影子寄存器里边。当UEV为1的时候,就是发生了更新事件的时候,才能转移到影子寄存器里边。

 2、输出控制部分

 首先将通道配置成输出模式,也就是将TIMx_CCMR1的CC1S[1:0]位配置为00。

第二步就是设置输出比较模式,设置TIM_CCMR1寄存器的CO1M[2:0]位,对于PWM功能有两个模式,一个是PWM模式1,另一个是PWM模式2。

第三步是配置强制清零功能,这个一般不用。强制清零就是看ETR引脚电平,如果设置了OC1CE位,那个就是开启强制清零功能,一旦ETRF为高电平,则OC1REF直接输出0。

OC1REF称为输出参考信号,受ETRF强制清零控制、输出比较模式选择、还有前面的比较输出值影响。确定了OC1REF信号后。来到了极性选择器,设置TIM_CCER寄存器的CC1P位,如果设置成1,则低电平有效,因为高电平过来经过一个反相器为低电平,则输出低电平,低电平过来,经过反相器变成高电平,则输出高电平。

最后开启输出使能位,TIM_CCER寄存器的CC1E位,设置为1。

二、通用定时器输出PWM原理

 计数器的值随着时间的增加而递增,当计数器的值小于捕获/比较寄存器的值的时候,IO输出值为0,当计数器的值大于捕获/比较影子寄存器值得时候,IO口输出高电平。到达ARR寄存器值得时候从0重新开始递增。

总结:ARR寄存器决定频率或者周期,CCRX捕获/比较寄存器决定PWM占空比。

三、PWM模式

PWM模式1:设置TIMX_CCMR1寄存器的OC1M[2:0]为110,在向上计数时,一旦TIMXCNT<TIMXCCR1时通道1为有效电平,否则为无效电平。OC1REF高电平有效。也就是说递增模式下,计数器的值小于捕获比较寄存器的值,OC1REF为高电平,大于捕获比较寄存器的值,OC1REF为低电平。在向下计数时,一旦TIMXCNT>TIMXCCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。

注意:OC1REF是高电平有效。

总之在PWM模式1情况下,向上计数时,计数器值小于捕获比较寄存器(CCRx)时,OC1REF为高电平,大于为低电平。向下计数时,计数器值大于捕获比较寄存器(CCRx)时,OC1REF为低电平,小于为低高电平。

上面说的都是OC1REF这个信号的电平,至于IO口输出高低电平还由CCXP位决定,如果设置成0,就是直接输出OC1REF信号,设置成0,就反向输出。

四、通用定时器PWM输出实验配置步骤

1、HAL_TIM_PWM_Init()函数,配置定时器基础工作参数。这个函数与HAL_TIM_Base_init()函数功能是一样的。

2、HAL_TIM_PWM_MspInit(),配置NVIC、CLOCK、GPIO等。

3、HAL_TIM_PWM _Configchannel()函数,配置PWM模式/比较值。

4、HAL_TIM_PWM _Start()函数,使能输出并启动计数器。

5、__HAL_TIM_SET_COMPARE()宏定义,修改比较值控制占空比(可选)。

6、__HAL_TIM_ENABLE_OCXPRELOAD()宏定义,使能通道预装载(可选)。

这些函数在下面给出的程序里边都有注释  基本都使用了 ,除了最后一个没有使用,最后一个设置输出比较寄存器具有缓冲功能,在第3个函数通道配置函数里边有设置。

五、实验程序

5.1 寄存器配置PWM

使用寄存器配置程序:这个使用TIM2的通道一引脚  PA0

#include "./BSP/PWM/pwm.h"

void TIM_PWM_Init(void)
{
	//这里注意 这个时钟使能必须写在操作寄存器的前面  不然向寄存器里边写值 没有反应
	__HAL_RCC_TIM2_CLK_ENABLE();//开启定时器2时钟
	
	TIM2->CR1 |= (1 << 7);//ARPE位  开启ARR缓冲
	
	TIM2->EGR |= (1 << 0);//UG 开启更新事件
	
	TIM2->CCMR1 |= (1 <<3);//OC1PE位 开启输出比较寄存器缓冲功能
	
	TIM2->CCMR1 |= 0X60;//OC1M[2:0] 设置输出比较通道1模式为 PWM模式1
	
	TIM2->CCER |= (1 << 0);//CC1E位  开启0C1信号输出对应引脚
	
	TIM2->CCER |= (1 << 1);//设置的是输出极性为低电平有效
	
	//TIM2->PSC = (uint16_t)((72000000 / 17000) >> 16);   ;//设置定时器时钟预分频系数为7200
	
	//TIM2->ARR = (uint16_t)(72000000/(17000*(TIM2->PSC + 1)));//设置ARR重装载值为10
	
	//配置1KHz ARR寄存器就16位  不设置PSC结果超65535
	TIM2->PSC = 9;
	TIM2->ARR = 7200000/1000 - 1;
	
	//配置2KHz
	//TIM2->ARR = 72000000/2000 - 1;
	
	TIM2->CCR1 = 0.3 * (TIM2->ARR);//设置捕获/比较寄存器值为3  也就是百分之30占空比
		
	TIM2->CR1 |= (1 << 0);//使能计数器 
		
	__HAL_RCC_GPIOA_CLK_ENABLE();//开启GPIOA时钟
	
	GPIOA->CRL |= 0x03;//设置MODE0[1:0]为11 输出50MHz模式
	
	GPIOA->CRL &= ~(1<<2);//设置CNF0[1:0]为10 复用推挽输出模式
	GPIOA->CRL |=  (1<<3);
}

在配置寄存器过程中,发现如果定时器的时钟不开启,那么写入寄存器的值无效。这个我之前不知道。同时发现在计算PWM频率的时候,使用的是ARR + 1的值,不是ARR的值。同理计算占空比的时候,按CCR1的值比上ARR + 1。 

PWM任意频率公式:

TIM2->PSC = (uint16_t)((72000000 / Freq) >> 16);   ;
TIM2->ARR = (uint16_t)(72000000/(Freq*(TIM2->PSC + 1)));

TIM2->ARR = 72000000/Freq;

前两行和第三行意思一样,前面那个意思就是PSC设置成0,然后ARR的值就是把PSC为0带入公式,实测效果一样。

5.2 库函数配置PWM

pwm.h头文件程序

#ifndef __PWM_H
#define __PWM_H

#include "stm32f1xx.h"
void TIM_PWM_Init(void);
#endif

pwm.c源程序

#include "./BSP/PWM/pwm.h"


TIM_HandleTypeDef htim;
TIM_OC_InitTypeDef sConfig;
void TIM_PWM_Init(void)
{
	//定时器的基本配置
	htim.Instance = TIM3;
	//配置ARR寄存器缓冲功能
	htim.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
	//配置计数器向上计数模式  通用定时器还有向下计数模式和中心对齐模式
	htim.Init.CounterMode = TIM_COUNTERMODE_UP;
	//设置PSC分频系数 这里分频的是系统时钟 来自APB1
	htim.Init.Prescaler = 0;
	//设置ARR寄存器的值
	htim.Init.Period = 72000000/17000;	
	
	//定时器初始化
	HAL_TIM_PWM_Init(&htim);
	
	htim.Channel = HAL_TIM_ACTIVE_CHANNEL_1; 
	
	//设置输出比较模式
	sConfig.OCMode = TIM_OCMODE_PWM1;
	//设置比较值得
	sConfig.Pulse = 0.3 * 72000000/17000;
	//设置输出比较极性的 也就是  CCxP位
	sConfig.OCPolarity = TIM_OCPOLARITY_HIGH;
	HAL_TIM_PWM_ConfigChannel(&htim,&sConfig,TIM_CHANNEL_1);
	
	//开启定时器PWM模式 主要使能计数器和CC1E位 设置CC1输出使能。
	HAL_TIM_PWM_Start(&htim, TIM_CHANNEL_1);
	
	//设置输出极性 CC1P 位 设置成1为低电平有效 0为高电平有效
	TIM3->CCER |= (1 << 1);
	
	//设置占空比 也就是设置CCR1寄存器的值 
	TIM3->CCR1 = 0.7 * 72000000/17000;
	//__HAL_TIM_SET_COMPARE(&htim,TIM_CHANNEL_1,0.2 * 72000000/17000);
}

void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM3)
	{
		__HAL_RCC_TIM3_CLK_ENABLE();//开启定时器3时钟
		
		__HAL_RCC_GPIOA_CLK_ENABLE();//开启GPIOA时钟
		
		GPIO_InitTypeDef GPIO_Init;
		
		//由于GPIO输出需要从片上外设TIM3来  配置成复用推挽  从手册可以找到
		GPIO_Init.Mode = GPIO_MODE_AF_PP;
		GPIO_Init.Pin = GPIO_PIN_6;
		GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;
		GPIO_Init.Pull = GPIO_PULLUP;//f1系列这个没有用
		
		HAL_GPIO_Init(GPIOA, &GPIO_Init);
	}
}
	

main.c主函数程序 

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/PWM/pwm.h"

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
	sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    delay_init(72);                     /* 延时初始化 */
    led_Init();                         /* LED初始化 */
	TIM_PWM_Init();
	
    while(1)
    { 
		LED0(1);
		LED1(0);
		delay_ms(500);
		
		LED0(0);
		LED1(1);
		delay_ms(500);
		
    }
}

5.3 通用定时器PWM输出实验

实验:通过定时器输出的PWM控制LED0,实现类似手机呼吸灯的效果

LED0接的是PB5,PB5正常复用功能没有对应的定时器通道,但是他的重映射连接的是定时器3的通道二,这个从数据手册可以找到。 

因此在程序里需要使用AFIO配置TIM3的重映射。 

pwm.h头文件程序

#ifndef __PWM_H
#define __PWM_H

#include "stm32f1xx.h"
void TIM_PWM_Init(uint16_t psc,uint16_t arr);
#endif

pwm.c

#include "./BSP/PWM/pwm.h"


TIM_HandleTypeDef htim;

void TIM_PWM_Init(uint16_t psc,uint16_t arr)
{
	//定时器的基本配置
	htim.Instance = TIM3;
	//配置ARR寄存器缓冲功能
	htim.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
	//配置计数器向上计数模式  通用定时器还有向下计数模式和中心对齐模式
	htim.Init.CounterMode = TIM_COUNTERMODE_UP;
	//设置PSC分频系数 这里分频的是系统时钟 来自APB1
	htim.Init.Prescaler = psc;
	//设置ARR寄存器的值
	htim.Init.Period = arr;	
	
	//定时器初始化
	HAL_TIM_PWM_Init(&htim);
	
	TIM_OC_InitTypeDef sConfig;
	//设置输出比较模式
	sConfig.OCMode = TIM_OCMODE_PWM1;
	
	//设置比较值得  这个要设置ARR + 1 一半 为50%占空比
	sConfig.Pulse = (arr + 1) / 2;
	//设置输出比较极性的 也就是  CCxP位
	sConfig.OCPolarity = TIM_OCPOLARITY_LOW;
	
	//这个注意一定要写  不然他会改CC2S的值 
	sConfig.OCFastMode = TIM_OCFAST_DISABLE;
	
	HAL_TIM_PWM_ConfigChannel(&htim,&sConfig,TIM_CHANNEL_2);
	
	//开启定时器PWM模式 主要使能计数器和CC1E位 设置CC2输出使能。
	HAL_TIM_PWM_Start(&htim, TIM_CHANNEL_2);
	
}

void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM3)
	{
		__HAL_RCC_TIM3_CLK_ENABLE();//开启定时器3时钟
		
		__HAL_RCC_GPIOB_CLK_ENABLE();//开启GPIOB时钟		
		
		__HAL_RCC_AFIO_CLK_ENABLE();//开启AFIO时钟
		
		__HAL_AFIO_REMAP_TIM3_PARTIAL();//开启定时器3的部分重映射 将TIM3 通道二映射到PB5上
		//AFIO->MAPR |= (1 << 11);
		//AFIO->MAPR &= ~(1 < 10);
		
		GPIO_InitTypeDef GPIO_Init;
		
		//由于GPIO输出需要从片上外设TIM3来  配置成复用推挽  从手册可以找到
		GPIO_Init.Mode = GPIO_MODE_AF_PP;
		GPIO_Init.Pin = GPIO_PIN_5;
		GPIO_Init.Speed = GPIO_SPEED_FREQ_LOW;
		GPIO_Init.Pull = GPIO_NOPULL;//f1系列这个没有用
		
		HAL_GPIO_Init(GPIOB, &GPIO_Init);
		
		
	}
}
	

这里注意一定要设置OCFastMode  不然他会改CC2S的值 
sConfig.OCFastMode = TIM_OCFAST_DISABLE;

补充:后面学习的时候发现,一般当定义一个句柄的时候最好初始0  不然里边的值是随机的 会影响配置

例如:TIM_IC_InitTypeDef sConfig = {0};

 main.c主程序

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/PWM/pwm.h"

uint16_t pwm = 0;
uint8_t mode = 0; //mode 为0 比较寄存器值递增  1 比较寄存器值递减
extern TIM_HandleTypeDef htim;
int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
	sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    delay_init(72);                     /* 延时初始化 */
    led_Init();                         /* LED初始化 */
	TIM_PWM_Init(71,499);
	
	//这个因为之前没有设置快速输出模式  影响了CC1S位 测试使用的
	//写入CC2S必须停止CCER的CC2E位
//	TIM3->CCER &= ~(1 << 4);
//	TIM3->CCMR1 &= (~(1 << 8));
//	TIM3->CCMR1 &= (~(1 << 9));
//	TIM3->CCER |= (1 << 4);
    while(1)
    { 		
		if(mode ==0)
		{
			pwm++;
		}
		else{
			pwm--;
		}
		//因为满占空比就是ARR的值
		if(pwm == TIM3->ARR)
		{
			mode = 1;
		}
		else if(pwm == 0)
		{
			mode = 0;
		}
		//TIM3->CCR2 = pwm;
		__HAL_TIM_SET_COMPARE(&htim,TIM_CHANNEL_2,pwm);
		delay_ms(10);
    }
}

实现呼吸灯,在while前面那几行是因为最开始不好用,后来发现CC2S位设置的输入模式,用于测试用的。 手册里写的CCER寄存器里边的CC2E位为0,才能写入CC2S。

作者:猿~~~

物联沃分享整理
物联沃-IOTWORD物联网 » STM32的HAL库开发—TIMER(定时器) —通用定时器-PWM功能

发表回复