目录
11. 通用定时器-PWM
1. 实验内容及步骤:
1. 通过定时器2,经过分频和重装载值生成50Hz=20ms的周期的中断,并且在50次中断后(即1s),让LED反转。
2. 通过定时器2通道1(PA0)的PWM输出,实现LED呼吸灯。
2. 硬件说明
本电路的硬件如下图所示,LED灯与GPIOB5相连,低电平亮,高电平灭。
3. 步骤详细讲解
3.1定时器配置
1. 时钟的选择,选择内部时钟源;
2. 计数模式的选择,选择向上计数;
3. 定时器分频选择,内部时钟源=2*APB1=72M,分频720得到定时器的时钟100KHz
4. 自动重装载:配置为2000,即100KHz/2000=50Hz=20ms(周期);
5. 中断源配置:配置只有上下溢出中断,使能中断。
1. 时钟选择
内部时钟源:
TIMx_SMCR寄存器中的SMS[2:0]=000, 关闭从模式
本实验中使用内部时钟:
TIMx_SMCR &= ~((u32)0x07<<0); //关闭从模式
TIMx_CR1寄存器的CEN(第0位),决定定时器的使能/禁止。
内部时钟源(CK_INT) CEN位被写成’1’。
本实验中:
TIMx_CR1 |= ((u32)0x01<<0); //使能计数器
2. 计数方向选择
TIMx_CR1的CMS[1:0]=00, 边沿对齐模式。计数器依据方向位(DIR)向上或向下计数。
TIMx_CR1的DIR位,决定了定时器的计数方向。
本实验中配置位向上计数模式:
TIMx_CR1 &= ~((u32)0x03<<5); // 边沿对齐模式
TIMx_CR1 &= ~((u32)0x01<<4); //向上计数模式
3. 定时器时钟分频
定时器时钟为CK_CNT=F(CK_PSC)/(PSC[15:0]+1)。
本实验中:
已知定时器2的时钟源F(CK_PSC)=2*APB1(72MHz)
分频值720-1,定时器时钟CK_CNT=72M/(720-1+1)=100KHz
4. 自动重装在值
本实验中:
自动重装载值为2000,则定时器中断周期为:100KHz/2000=50Hz=20ms。
5. 更新中断使能
配置只有上下溢出中断,由TIMx_CR1寄存器中的USR(第二位)决定。
TIMx_CR1 |= ((u32)0x01<<2); //只有上下溢出才中断
更新中断使能在TIMx_DIER寄存器中的UIE(第0位)。
本实验中:
TIMx_DIER |= (u32)0x01<<0; //使能中断
NVIC中断优先级配置和使能;
SET_NVIC_IP(TIM2_IRQn,14); //NVIC中断使能(这个是自己写的函数)
此时配置定时器2中断优先级为14,使能NVIC TIM2中断管理器。
6. 中断函数
在中断函数中,通过判断TIMx_SR状态寄存器中的UIF(第0位)来确定是否为更新中断。
为1,则表示为更新中断,需要软件清除。
中断函数如下所示:
进入中断50次后(即1S),LED灯反转。
3.2定时器输出PWM配置
PWM配置步骤:
1. PWM的GPIO配置;
2. TIM2配置;
3. PWM占空比设置;
4. 配置有效电平极性;
5. 配置PWM的模式;
6. 配置相应的预装载寄存器缓冲;
7. PWM输出使能;
1. PWM的GPIO配置
TIM2的比较通道1(PA0)应该配置为推挽复用输出。
本函数自己写的:
GPIOX_PIN_SetMode(GPIOA,0,GPIOx_AF_PP|GPIOx_OUT_10M); //PA0 复用推挽输出
2. TIM2配置
如3.1 定时器配置一致。
本实验中配置TIM2的时钟为:72M/720=100KHz(定时器时钟)100K/2000=50Hz=20ms(周期)
3.PWM占空比设置
PWM占空比由TIMx_CCRx寄存器进行配置,寄存器的说明如下所示:
本实验中配置初始占空比为50%,即:
TIMx_CCRx= 1000; //占空比50%
4. 配置有效电平极性
OCx的极性即比较后有效结果时,输出的电平。其寄存器说明如下所示:
TIMx_CCER寄存器中的CC1P位(位1),决定有效电平是高电平还是低电平。
本实验中配置为高电平有效:
TIMx_CCER &= ~(0x01<<1); //OC1高电平有效
5. 配置PWM的模式
在TIMx_CCMRx寄存器中的OCxM位写入’110’(PWM模式1)或’111’(PWM模式2)。TIMx_CCMRx寄存器中的OCxM位如下所示:
在TIMx_CCMRx寄存器中的OCxM位写入’110’(PWM模式1)或’111’(PWM模式2)
向上计数配置:
PWM模式1:当TIMx_CNT<TIMx_CCRx时PWM信号参考OCxREF为高;
向下计数的配置:
当TIMx_CNT>TIMx_CCRx时参考信号OCxREF为低;
本实验中使用PWM模式,使用PWM模式1:
TIMx_CCMRx &= ~(0x07<<4); //清空输出比较1模式
TIMx_CCMRx |= 0x06<<4; //模式1
6. 配置相应的预装载寄存器
这一步骤是配置其作用主要为:写比较寄存器后,是否需要缓冲。有缓冲的话,更新事件到来才更新到相应的寄存器;没有的话就直接更新到相应的寄存器。有没有问题都不是很大。
本实验中开启预装载使能:
TIMx_CCMR1 |= 0x01<<3; //输出比较1预装载使能
7.PWM输出使能
TIMx_CCER寄存器中的CCxE位控制OCx输出使能。OCx位如下图所示:
使用PWM需要使能输出:
TIMx_CCER |= (0x01<<0); //开启输出
8.呼吸灯实现
没10ms进行PWM的值增加10或减小10,实现呼吸灯的效果。
4. 程序设计(寄存器)
定时器中断见源码,这里通过TIM2-PWM实现LED成呼吸灯
配置步骤:
1. GPIO时钟使能,TIM时钟使能;
2. 配置PWM输出的GPIO为:推挽输出;
3. 配置定时器TIM2的分频,预装载值,上下计数模式,定时器使能;
4. 中断管理器NVIC使能,优先级配置(如果需要中断的话);
5. 配置PWM占空比,模式,有效极性,比较使能;
6. 编写PWM修改函数。
源码:这里只列出配置TIM2-PWM部分
//初始化定时器2
//APB1 = 72MHz
void TIM2_Config(u16 psc,u16 arr)
{
//时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
//时钟选择(内部时钟源)
TIM2->SMCR &= ~((u32)0x07<<0); //关闭从模式
//计数方向选择
TIM2->CR1 &= ~((u32)0x03<<5); // 边沿对齐模式
TIM2->CR1 &= ~((u32)0x01<<4); //向上计数模式
//预分频
TIM2->PSC = psc; //分频
//重装载值
TIM2->ARR = arr; //重装在值
//清空计数器
TIM2->CNT = 0; //清空当前计数器
//使能中断
TIM2->CR1 |= ((u32)0x01<<2); //只有上下溢出才中断
TIM2->DIER |= (u32)0x01<<0; //允许更新中断
SET_NVIC_IP(TIM2_IRQn,14); //NVIC中断使能
//使能定时器
TIM2->CR1 |= ((u32)0x01<<0); //使能计数器
}
/*
TIM2_PWM_配置
*/
void TIM2_PWM_Config(void)
{
//GPIO初始化
GPIOX_PIN_SetMode(GPIOA,0,GPIOx_AF_PP|GPIOx_OUT_10M); //PA0 复用推挽输出
//TIM2初始化
TIM2_Config(720-1,2000-1); // 72M/720=100KHz 100K/2000=50Hz=20ms(周期)
//比较寄存器
TIM2->CCR1 = 1000; //占空比50%
//输出比较1模式(模式1)
TIM2->CCMR1 &= ~(0x07<<4); //清空输出比较1模式
TIM2->CCMR1 |= 0x06<<4; //模式1
//输出比较1预装载使能
TIM2->CCMR1 |= 0x01<<3; //输出比较1预装载使能
//比较1输出极性
TIM2->CCER &= ~(0x01<<1); //OC1高电平有效
//使能比较1输出
TIM2->CCER |= (0x01<<0); //开启
}
/*
设置比较输出1的PWM
*/
void Set_TIM2_PWM_CH1(u16 pwm)
{
TIM2->CCR1 = pwm;
}
void TIM2_IRQHandler(void)
{
static u16 i=0;
if(TIM2->SR&0x01) //判断更新中断
{
i++;
if(i>=50-1) //50次 1S
{
i=0;
//BPB_OUT(5) = !BPB_OUT(5);
printf("1S计时\r\n");
}
}
TIM2->SR &= ~((u32)0x01<<0); //清除中断标志位
}
5. 程序设计(标准库)
配置步骤:
1. GPIO时钟使能,TIM时钟使能;
2. 配置PWM输出的GPIO为:推挽输出。通过GPIO_InitTypeDef结构体配置;
3. 配置定时器TIM2的分频,预装载值,上下计数模式。通过TIM_TimeBaseInitTypeDef结构体配置;
4. 中断管理器NVIC使能,优先级配置(如果需要中断的话)。通过NVIC_InitTypeDef结构体进行配置;
5. 配置PWM占空比,模式,有效极性,比较使能。通过TIM_OCInitTypeDef结构体配置;
6. 定时器使能函数,使用:TIM_Cmd函数使能;
7. PWM修改函数为:TIM_SetCompare1函数。
源码:这里只列出配置TIM2-PWM部分
#define TIM_x TIM2
#define TIM_NVIC_IRQ TIM2_IRQn
#define TIM_NVIC_PP 13
#define TIM_NVIC_SP 0
#define PWM_GPIOX GPIOA
#define PWM_GPIOX_PIN GPIO_Pin_0
#define PWM_GPIOX_MODE GPIO_Mode_AF_PP //复用推挽
//初始化定时器2
//APB1 = 72MHz
void TIM2_Config(u16 psc,u16 arr)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
//时钟
RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM2EN,ENABLE); //TIM2定时器
//定时器基本配置
TIM_TimeBaseInitStruct.TIM_Prescaler = psc; //分频
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInitStruct.TIM_Period = arr; //从装在值
TIM_TimeBaseInit(TIM_x,&TIM_TimeBaseInitStruct);
//中断配置
TIM_ITConfig(TIM_x,TIM_IT_Update,ENABLE);
//优先级配置
NVIC_InitStruct.NVIC_IRQChannel = TIM_NVIC_IRQ;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = TIM_NVIC_PP;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = TIM_NVIC_SP;
NVIC_Init(&NVIC_InitStruct); //设置中断优先级
//定时器使能
TIM_Cmd(TIM_x,ENABLE);
}
/*
TIM2_PWM_配置
*/
void TIM2_PWM_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;
//GPIO初始化
GPIO_InitStruct.GPIO_Mode = PWM_GPIOX_MODE;
GPIO_InitStruct.GPIO_Pin = PWM_GPIOX_PIN;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(PWM_GPIOX, &GPIO_InitStruct); //复用推挽输出
//TIM2初始化
TIM2_Config(720-1,2000-1); // 72M/720=100KHz 100K/2000=50Hz=20ms(周期)
//通道配置
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; //PWM1模式
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; //比较使出使能
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; //有效电平 高电平
TIM_OCInitStruct.TIM_Pulse = 1000; //占空比50%
TIM_OC1Init(TIM_x, &TIM_OCInitStruct);
}
//中断函数
void TIM2_IRQHandler(void)
{
static u16 i=0;
if(TIM_GetITStatus(TIM_x,TIM_IT_Update)) //判断更新中断
{
i++;
if(i>=50-1) //50次 1S
{
i=0;
//BPB_OUT(5) = !BPB_OUT(5);
printf("1S计时\r\n");
}
}
TIM_ClearITPendingBit(TIM_x,TIM_IT_Update); //清除中断标志位
}
6. 程序设计(HAL库)
HAL库与标准库的定时器区别主要在:HAL库进行高度封装,使用相对简便,很多判断标志位和清除标志位都由HAL库完成了。
总结出HAL库的操作:初始化+使能。
配置步骤:
1. GPIO时钟使能,TIM时钟使能;
2. 通过HAL_TIM_Base_Init函数初始化定时器操作;
3. 通过HAL_TIM_Base_Start_IT函数使能定时器,并使能中断;
4. 通过HAL_NVIC_SetPriority、HAL_NVIC_EnableIRQ使能NVIC中断管理和优先级;
5. 通过HAL_TIM_OC_ConfigChannel初始化PWM配置;
6. 通过HAL_TIM_OC_Start使能PWM;
7. 可在HAL_TIM_Base_MspInit回调函数中初始化GPIO;
8. 在中断中调用HAL_TIM_IRQHandler中断处理函数(其具有标志位判断和清除功能,能够降低开发难度);
9. 在更新回调函数(HAL_TIM_PeriodElapsedCallback)中编写更新中断内容;
源码:这里只列出配置TIM2-PWM部分
#include "My_GP_TIM.h"
#include "stdio.h"
#include "My_exti.h"
#include "My_bit.h"
#include "My_gpio.h"
//TIM2
TIM_HandleTypeDef TIM2_HandleStruct;
#define TIM_x TIM2
#define TIM_NVIC_IRQ TIM2_IRQn
#define TIM_NVIC_PP 13
#define TIM_NVIC_SP 0
#define PWM_GPIOX GPIOA
#define PWM_GPIOX_PIN GPIO_PIN_0
#define PWM_GPIOX_MODE GPIO_MODE_AF_PP //复用推挽
//初始化定时器2
//APB1 = 72MHz
void TIM2_Config(u16 psc,u16 arr)
{
//时钟
__HAL_RCC_TIM2_CLK_ENABLE(); //TIM2定时器
//定时器基本配置
TIM2_HandleStruct.Instance = TIM_x;
TIM2_HandleStruct.Init.Prescaler = psc; //分频
TIM2_HandleStruct.Init.CounterMode = TIM_COUNTERMODE_UP; //向上计数
TIM2_HandleStruct.Init.Period = arr; //从装在值
TIM2_HandleStruct.Channel = HAL_TIM_ACTIVE_CHANNEL_1; //通道1
HAL_TIM_Base_Init(&TIM2_HandleStruct);
//中断配置
HAL_TIM_Base_Start_IT(&TIM2_HandleStruct);
//优先级配置
HAL_NVIC_SetPriority(TIM_NVIC_IRQ,13,0); //响应优先级为13 最低
HAL_NVIC_EnableIRQ(TIM_NVIC_IRQ); //使能中断线
}
/*
TIM2_PWM_配置
*/
void TIM2_PWM_Config(void)
{
GPIO_InitTypeDef GPIO_Init;
TIM_OC_InitTypeDef TIM_OC_InitStruct;
//时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
//GPIO初始化
GPIO_Init.Mode = PWM_GPIOX_MODE;
GPIO_Init.Pin = PWM_GPIOX_PIN;
GPIO_Init.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(PWM_GPIOX,&GPIO_Init); //复用推挽输出
//TIM2初始化
TIM2_Config(720-1,2000-1); // 72M/720=100KHz 100K/2000=50Hz=20ms(周期)
//通道配置
TIM_OC_InitStruct.OCMode = TIM_OCMODE_PWM1; //PWM1模式
TIM_OC_InitStruct.OCPolarity = TIM_OCPOLARITY_HIGH; //有效电平 高电平
TIM_OC_InitStruct.OCIdleState = TIM_OCIDLESTATE_SET; //比较使出使能
TIM_OC_InitStruct.Pulse = 1000; //占空比50%
HAL_TIM_OC_ConfigChannel(&TIM2_HandleStruct,&TIM_OC_InitStruct,TIM_CHANNEL_1);
HAL_TIM_OC_Start(&TIM2_HandleStruct,TIM_CHANNEL_1);
}
//中断函数
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&TIM2_HandleStruct);
}
//更新事件回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static u16 i=0;
i++;
if(i>=50-1) //50次 1S
{
i=0;
//BPB_OUT(5) = !BPB_OUT(5);
printf("1S计时\r\n");
}
}
7. 实验结果
1. 每隔1S,定时器中断中会输出“1S计时”;
2. PWM呼吸灯实验,LED灯会呈现呼吸效果;