STM32的通用定时器的定时、输出PWM功能的使用
本次还是使用发光二极管来验证定时器的使用
前言
首先还是要了解STM32库函数中提供的使用定时器所需要配置的结构体成员的含义
typedef struct
{
uint16_t TIM_Prescaler;
uint16_t TIM_CounterMode;
uint32_t TIM_Period;
uint16_t TIM_ClockDivision;
uint8_t TIM_RepetitionCounter;
} TIM_TimeBaseInitTypeDef;
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;
首先验证定时器的定时功能,再配置定时器使用输出比较功能来输出不同脉宽的方波
一、定时器详解
STM32F407ZGT6的定时器外设是由APB1提供的时钟,频率为主频率的二分之一,我们配置的系统时钟为168MHz,这里的APB1时钟频率就为84MHz,至于时间的算法,并不像51单片机那样复杂,我们只需要通过分频系数和计数器目标值来计算出频率就可以算出周期,例如我在原频率的基础上进行(8400-1)分频,计数器计数到(5000-1),至于这里为什么要-1,就和8位数据的最大值是255一样,从0开始算,一共有256个数,但是最大值是256-1;那么计算方法就是84000000/8400/5000 = 2,注意这里的2是频率并不是周期,那么我们换算成周期,1/2 = 0.5s;也就是500ms;基于以上的思想,我们来配置定时器的初始化结构体成员:
void Timer_TestInit(uint16_t arr, uint16_t psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(TIMER_TEST_RCC_CLOCK, ENABLE);
TIM_TimeBaseInitStructure.TIM_Period = arr;
TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIMER_TEST_NUM, &TIM_TimeBaseInitStructure);
TIM_ITConfig(TIMER_TEST_NUM, TIM_IT_Update, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = TIMER_TEST_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIMER_TEST_NUM, ENABLE);
}
我们这里也使用到了中断,目的是使用定时器500ms中断一次,我们在中断内进行发光二极管的状态翻转;所以下面我们要编写中断的服务函数:
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
{
LED_NUM_0 = !LED_NUM_0;
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
记得一定要清除中断标志位;
二、定时器的输出比较功能
1.什么是PWM
所谓PWM,就是脉冲宽度调制,学过电子技术的达瓦里式多多少少有过耳闻,我们在学习DCDC电路中曾经了解过一个Buck斩波电路,通过控制MOSFET的导通关断的时间不同,使本来连续的电压波形变成脉冲宽度不同的方波,因为由电感和大电容的存在,输出电压并不会大幅度波动,而是神奇的输出一个小于输入电压的值,这里控制MOSFET的信号也是PWM,与之不同的是这里MOSFET导通关断造成的是模拟信号,但我们控制MOSFET的是数字信号。也就是说我们能够通过PWM来控制导通关断的时间以达到控制器件的状态,映射到发光二极管上就是它的亮灭程度了,联想到直流电动机上那就是可以控制它的转速了,注意STM32的引脚不能直接驱动直流电机!那么STM32定时器的输出比较功能怎么去理解呢,简单的来说,就是我们通过分频系数和计数器确定PWM的频率后,它就是不变的了,我们能改变的是脉宽在一个周期内的时间,这是不能改变它的周期和频率的,假设我的周期是1,那么我设定比较值为0.5,那么计数器计数器到0.5后则会翻转一次电平,形成一个周期内的电平跳变,通过这样循环往复就形成频率恒定一个周期内脉冲宽度可以不同的方波。基于以上所有的思想,我们来配置STM32定时器的输出比较功能:
void Timer_TestPWMInit(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(TIMER_TEST_PWM_RCC_CLOCK, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource9, GPIO_AF_TIM14);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOF, &GPIO_InitStructure);
TIM_TimeBaseInitStructure.TIM_Period = arr;
TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIMER_TEST_PWM_NUM, &TIM_TimeBaseInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OC1Init(TIMER_TEST_PWM_NUM, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIMER_TEST_PWM_NUM, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIMER_TEST_PWM_NUM, ENABLE);
TIM_Cmd(TIMER_TEST_PWM_NUM, ENABLE);
}
我们在LED初始化这个地方进行一些小小的改动:
void Led_Init(LedStatus_TypeDef_t InitState)
{
#if LED_MODE
GPIO_InitTypeDef GPIO_InitSturcture;
RCC_AHB1PeriphClockCmd(LEDx_RCC_CLOCK, ENABLE);
GPIO_InitSturcture.GPIO_Pin = LED0_PIN_NUM | LED1_PIN_NUM;
GPIO_InitSturcture.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitSturcture.GPIO_OType = GPIO_OType_PP;
GPIO_InitSturcture.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_InitSturcture.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(LEDx_GPIO_PROT, &GPIO_InitSturcture);
LED_NUM_0 = InitState;
LED_NUM_1 = InitState;
#else
Timer_TestPWMInit(500 - 1, 84 - 1);
TIM14->CCR1 = 0;
#endif
}
上面我们的定时器输出比较通道使用的的是通道一,通过TIM14的CCR1寄存器直接操作,使用库函数所提供的改变比较值的函数也是对操作的寄存器进行封装,在这里我们自己也对这一操作进行封装:
void Led_SetPWMDuty(uint32_t duty)
{
TIM14->CCR1 = duty;
}
2.通过改变脉宽来改变亮灭程度
老样子还是呼吸灯:
for (; i < 500; i++)
{
Led_SetPWMDuty(i);
delay_ms(2);
}
for (; i > 0; i--)
{
Led_SetPWMDuty(i);
delay_ms(2);
}
最后烧录进去验证即可
总结
这里第一个使用定时器固定时间改变LED状态就不演示了,如果呼吸灯不能形象的去理解PWM的功能可以用示波器测一下输出的波形;
|