STM32开发项目:硬件外设定时器的配置与使用
日期 | 作者 | 版本 | 说明 |
---|---|---|---|
2020.09.26 | Tao | V1.0 | 完善了框架与内容,发布第一版 |
2020.12.06 | Tao | V1.1 | 增加了输入捕获的使用 |
2020.12.07 | Tao | V1.2 | 增加了BLDC电机速度闭环控制的项目实例 |
2022.09.13 | Tao | V1.3 | 增加了设置高级定时器互补PWM输出死区时间的说明 |
2022.10.13 | Tao | V1.4 | 在“输出PWM”小节中增加了注意事项说明,对比了高级定时器与通用定时器的配置区别,并修改了部分示例代码。 |
文章目录
简要介绍
以STM32F103为例,对定时器外设做一个简单的介绍。STM32F103内部共有 8 个定时器,分为基本定时器,通用定时器和高级定时器。基本定时器 TIM6 和 TIM7 是一个 16 位的只能向上计数的定时器,只能定时,没有外部 IO。通用定时器 TIM2/3/4/5 是一个 16 位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,每个定时器有四个外部 IO。高级定时器 TIM1/8是一个 16 位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,还可以有三相电机互补输出信号,每个定时器有 8 个外部 IO。
常用配置
设置更新中断
使用Timer产生更新中断时,应当注意通用定时器与高级定时器的几点区别:
- 时钟配置上存在区别(通用定时器、基本定时器与高级定时器的总线不同);
- 基础配置结构体的部分成员仅对高级定时器与通用定时器有效(例如
TIM_RepetitionCounter
,TIM_ClockDivision
,TIM_CounterMode
); - 高级定时器的中断服务函数名(
void TIM1_UP_IRQHandler()
)与通用定时器、基本定时器的中断服务函数名(void TIM2_IRQHandler()
)不同;
通用定时器(以Timer2为例)
- 外设配置
#define TIM2_Prescaler 7200 //10KHz,100us
#define TIM2_PWM_FREQUENCY 2
#define TIM2_OC2_PWM_DutyCycle 50 //50%
#define TIM2_Period (72000000.0/TIM2_Prescaler/TIM2_PWM_FREQUENCY)
#define TIM2_OC2_Pulse (TIM2_OC2_PWM_DutyCycle*TIM2_Period/100)
void Timer_Config()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_DeInit(TIM2);
TIM_TimeBaseInitStructure.TIM_Prescaler = TIM2_Prescaler - 1;
TIM_TimeBaseInitStructure.TIM_Period = TIM2_Period - 1;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_ARRPreloadConfig(TIM2, ENABLE);
}
- 设置中断优先级
void NVIC_Config()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
- 中断服务函数
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET)
{
Timer2Updated();
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
- 开启定时器
TIM_Cmd(TIM2, ENABLE);
高级定时器(以Timer1为例)
- 外设配置。
#define TIM1_Prescaler 7200 //10KHz,100us
#define TIM1_PWM_FREQUENCY 2
#define TIM1_OC1_PWM_DutyCycle 50 //50%
#define TIM1_Period (72000000.0/TIM1_Prescaler/TIM1_PWM_FREQUENCY)
#define TIM1_OC1_Pulse (TIM1_OC1_PWM_DutyCycle*TIM1_Period/100)
void Timer_Config()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
TIM_DeInit(TIM1);
TIM_TimeBaseInitStructure.TIM_Prescaler = TIM1_Prescaler - 1;
TIM_TimeBaseInitStructure.TIM_Period = TIM1_Period - 1;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM1, TIM_FLAG_Update);
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
TIM_ARRPreloadConfig(TIM1, ENABLE);
}
- 设置中断优先级
void NVIC_Config()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
- 中断服务函数。注意高级定时器服务函数名称与通用定时器服务函数的区别。
void TIM1_UP_IRQHandler(void)
{
if(TIM_GetITStatus(TIM1,TIM_IT_Update)!=RESET)
{
Timer1Updated();
TIM_ClearFlag(TIM1,TIM_FLAG_Update);
TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
}
}
- 开启定时器
TIM_Cmd(TIM1, ENABLE);
基本定时器(以Timer6为例)
- 外设配置
#define TIM6_Prescaler 7200 //10KHz,100us
#define TIM6_PWM_FREQUENCY 2
#define TIM6_OC1_PWM_DutyCycle 50 //50%
#define TIM6_Period (72000000.0/TIM6_Prescaler/TIM6_PWM_FREQUENCY)
#define TIM6_OC1_Pulse (TIM6_OC1_PWM_DutyCycle*TIM6_Period/100)
void Timer_Config()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
TIM_DeInit(TIM6);
TIM_TimeBaseInitStructure.TIM_Prescaler = TIM6_Prescaler - 1;
TIM_TimeBaseInitStructure.TIM_Period = TIM6_Period - 1;
TIM_TimeBaseInit(TIM6, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM6, TIM_FLAG_Update);
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
TIM_ARRPreloadConfig(TIM6, ENABLE);
}
- 设置中断优先级
void NVIC_Config()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = TIM6_UP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
- 中断服务函数。注意高级定时器服务函数名称与通用定时器服务函数的区别。
void TIM6_IRQHandler(void)
{
if(TIM_GetITStatus(TIM6,TIM_IT_Update)!=RESET)
{
Timer6Updated();
TIM_ClearFlag(TIM6,TIM_FLAG_Update);
TIM_ClearITPendingBit(TIM6,TIM_IT_Update);
}
}
- 开启定时器
TIM_Cmd(TIM6, ENABLE);
输出PWM
注意事项
使用Timer输出PWM时,应当注意通用定时器与高级定时器的几点区别:
- 时钟配置上存在区别(通用定时器与高级定时器的总线不同)
- 基础配置结构体的部分成员仅对高级定时器有效(例如
TIM_RepetitionCounter
) - 开启/关闭方波的方法不一样(高级定时器需要先打开
TIM_CtrlPWMOutputs(TIM1, ENABLE)
,然后调用TIM_CCxCmd(TIM2, TIM_Channel_1, TIM_CCx_Enable)
进行通道输出控制;通用定时器直接使用TIM_CCxCmd(TIM2, TIM_Channel_1, TIM_CCx_Enable)
进行通道输出控制。 - 使用Timer输出PWM时,无需打开中断。当然也可以同时打开定时器的PWM输出与更新中断。
- 配置高级定时器(TIM1 & TIM8)的PWM输出时,
TIM_OCInitTypeDef TIM_OCInitStructure;
结构体一定要初始化每个成员,否则可能造成意外的错误!
例如,配置TIM1的PWM通道1输出时,初始化TIM_OCInitStructure
的全部成员:
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_OCInitStructure.TIM_Pulse = TIM1_OC1_Pulse;
// 一定要初始化全部成员变量,否则可能在配置高级定时器时发生意外错误
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
TIM_OC1Init(timerPeriph, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(timerPeriph, TIM_OCPreload_Enable);
通用定时器(以Timer2为例)
- 外设配置
#define TIM2_Prescaler 720
#define TIM2_PWM_FREQUENCY 1000
#define TIM2_OC1_PWM_DutyCycle 50 //50%
#define TIM2_Period (72000000.0/TIM2_Prescaler/TIM2_PWM_FREQUENCY)
#define TIM2_OC1_Pulse (TIM2_OC1_PWM_DutyCycle*TIM2_Period/100)
void Timer_Config()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_DeInit(TIM2);
TIM_TimeBaseInitStructure.TIM_Prescaler = TIM2_Prescaler - 1;
TIM_TimeBaseInitStructure.TIM_Period = TIM2_Period - 1;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ARRPreloadConfig(TIM2, ENABLE);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse = TIM2_OC1_Pulse;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_CCxCmd(TIM2, TIM_Channel_1, TIM_CCx_Disable);
}
- 方波端口配置
void GPIO_Config()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOE, &GPIO_InitStructure);
}
- PWM常用操作
//开启定时器
TIM_Cmd(TIM2, ENABLE);
//开启PWM输出通道
TIM_CCxCmd(TIM2, TIM_Channel_1, TIM_CCx_Enable);
//设置占空比(dutyCycle: 0~1)
TIM_SetCompare1(TIM2, dutyCycle*(TIM2->ARR + 1));
高级定时器(以Timer1为例)
- 外设配置
#define TIM1_Prescaler 720
#define TIM1_PWM_FREQUENCY 1000
#define TIM1_OC1_PWM_DutyCycle 50 //50%
#define TIM1_Period (72000000.0/TIM1_Prescaler/TIM1_PWM_FREQUENCY)
#define TIM1_OC1_Pulse (TIM1_OC1_PWM_DutyCycle*TIM1_Period/100)
void Timer_Config()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
TIM_DeInit(TIM1);
TIM_TimeBaseInitStructure.TIM_Prescaler = TIM1_Prescaler - 1;
TIM_TimeBaseInitStructure.TIM_Period = TIM1_Period - 1;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
TIM_ARRPreloadConfig(TIM1, ENABLE);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse = TIM1_OC1_Pulse;
// 一定要初始化全部成员变量,否则可能在配置高级定时器时发生意外错误
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_CtrlPWMOutputs(TIM1, ENABLE);
TIM_CCxCmd(TIM1, TIM_Channel_1, TIM_CCx_Disable);
}
- 方波端口配置
void GPIO_Config()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOE, &GPIO_InitStructure);
}
- PWM常用操作
//开启定时器
TIM_Cmd(TIM1, ENABLE);
//开启PWM输出
TIM_CtrlPWMOutputs(TIM1, ENABLE);
//开启PWM输出通道
TIM_CCxCmd(TIM1, TIM_Channel_1, TIM_CCx_Enable);
//设置占空比(dutyCycle: 0~1)
TIM_SetCompare1(TIM1, dutyCycle*(TIM1->ARR + 1));
输入捕获
通用定时器
通用定时器与高级定时器的输入捕获配置流程基本相同,它们的区别在于:高级定时器的CC中断与Update中断是不同的中断服务函数名(TIM1_UP_IRQHandler
, TIM1_CC_IRQHandler
),而通用定时器的CC中断与Update中断共享一个中断服务函数名,需要在中断服务函数中进行中断类型的判断。
if(TIM_GetITStatus(TIM5, TIM_IT_Update) == SET) //捕获到 更新 中断
{
}
if(TIM_GetITStatus(TIM5, TIM_IT_CC1) == SET)//捕获 1 发生捕获事件
{
}
高级定时器
- 外设配置
解释一下滤波器的作用:
数字滤波器由一个事件计数器组成,它记录到N个事件后会产生一个输出的跳变。也就是说连续N次采样,如果都是高电平,则说明这是一个有效的触发,就会进入输入捕捉中断(如果设置了的话)。这样就可以滤除那些高电平脉宽低于8个采样周期的脉冲信号,从而达到滤波的作用。
void Timer_Config()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
TIM_DeInit(TIM1);
TIM_TimeBaseInitStructure.TIM_Prescaler = TIM1_Prescaler - 1;
TIM_TimeBaseInitStructure.TIM_Period = TIM1_Period - 1;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
// TIM_ARRPreloadConfig(TIM1, ENABLE);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_4;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x08;
TIM_ICInit(TIM1, &TIM_ICInitStructure);
TIM_ClearFlag(TIM1, TIM_FLAG_CC4 | TIM_IT_Update);
TIM_ITConfig(TIM1, TIM_IT_CC4| TIM_IT_Update, ENABLE);
}
- 端口配置
以Timer1 Ch4部分重映射到PE14为例,GPIO应当配置为输入模式:
//Timer1部分重映射 TIM1_CH3->PE13 TIM1_CH4->PE14
GPIO_PinRemapConfig(GPIO_FullRemap_TIM1, ENABLE);
GPIO_ConfigPort('E', 14, GPIO_Mode_IPU, 0);
- 中断配置
配置NVIC的中断优先级:
void NVIC_Config()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 12;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 13;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
编写中断服务函数:
void TIM1_UP_IRQHandler(void)
{
if(TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)
{
/*Something to do when update IRQ*/
//TIM_ClearFlag(TIM1,TIM_FLAG_Update);
TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
}
}
void TIM1_CC_IRQHandler(void)
{
if(TIM_GetITStatus(TIM1, TIM_IT_CC4) != RESET)
{
/*Something to do when CC IRQ*/
///Use function: uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx) to get CC value.
//TIM_ClearFlag(TIM1,TIM_FLAG_CC4);
TIM_ClearITPendingBit(TIM1,TIM_IT_CC4);
}
}
输出带死区控制的互补PWM
高级定时器
- 外设配置
#define TIM1_Prescaler 720
#define TIM1_PWM_FREQUENCY 1000
#define TIM1_OC1_PWM_DutyCycle 50 //50%
#define TIM1_Period (72000000.0/TIM1_Prescaler/TIM1_PWM_FREQUENCY)
#define TIM1_OC1_Pulse (TIM1_OC1_PWM_DutyCycle*TIM1_Period/100)
void Timer_Config()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
TIM_DeInit(TIM1);
TIM_TimeBaseInitStructure.TIM_Prescaler = TIM1_Prescaler - 1;
TIM_TimeBaseInitStructure.TIM_Period = TIM1_Period - 1;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
TIM_ARRPreloadConfig(TIM1, ENABLE); //启用ARR的影子寄存器(直到产生更新事件才更改设置)
/* Automatic Output enable, Break, dead time and lock configuration*/
TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable; //运行模式下输出
TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable; //空闲模式下输出选择
TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_OFF; //锁定设置,锁定级别1
// TIM_BDTRInitStructure.TIM_DeadTime = 0xAC; //死区时间3us
TIM_BDTRInitStructure.TIM_DeadTime = 72; //死区时间1us
TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable; //刹车功能使能
TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_Low; //刹车输入极性,即刹车控制引脚接GND时,PWM停止
TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Disable; //自动输出使能
TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);
/* 刹车控制引脚为TIM1_BKIN pin(PB.12),将PB12接GND,channel和其互补通道,都变为刹车后的电平,具体为0还是1,要看如下两个设置:
.TIM_OCIdleState = TIM_OCIdleState_Reset; //刹车之后,PWM通道变为0
.TIM_OCNIdleState = TIM_OCNIdleState_Reset; //刹车之后,PWM互补通道变为0
注意:如果没必要,还是不要开启刹车功能,因为会对PWM产生影响,特别是当PB12悬空时,波形将会有很大的波动。
这里不打开刹车功能,即.TIM_Break = TIM_Break_Disable;
*/
/*
正确的deadtime的计算方法(经理论与示波器测试成功)
TIM_BDTRInitStructure.TIM_DeadTime=255 //这句设定的就是寄存器TIMx_BDTR的后8位,即DTG[7:0],所以最大值为255
从下面的代码中的“第五步”中,实际上就相当于TIM1->BDTR=0x71FF;
查看"STM32中文参考手册2009.pdf"的TIMx_BDTR(第248页),列寄存器TIMx_BDTR的后8位如下:
位7:0 UTG[7:0]: 死区发生器设置 (Dead-time generator setup)
这些位定义了插入互补输出之间的死区持续时间。假设DT表示其持续时间:
DTG[7:5]=0xx => DT=DTG[7:0] × Tdtg, Tdtg = Tdts;
DTG[7:5]=10x => DT=(64+DTG[5:0]) × Tdtg, Tdtg = 2 × Tdts;
DTG[7:5]=110 => DT=(32+DTG[4:0]) × Tdtg, Tdtg = 8 × Tdts;
DTG[7:5]=111 => DT=(32+DTG[4:0]) × Tdtg, Tdtg = 16× Tdts;
Tdts为系统时钟周期时长,Tdtg为死区时间计算步长。主要思想就是把DTG的八位,掰成两半用。一半决定步长,另一半是与步长相乘的乘数,乘数可以自行设定,步长*乘数=死区时间。
在72M的定时器时钟下,TDTS = 1/72M = 13.89ns:
项目 情况1 情况2 情况3 情况4
步长位置 DTG[7] DTG[7:6] DTG[7:5] DTG[7:5]
步长值(二进制) 0xx 10x 110 111
步长是周期几倍 1 2 8 16
乘数位置 DTG[6:0] DTG[5:0] DTG[4:0] DTG[4:0]
乘数最大值 127 64+63 63+31 32+31
乘数范围 0~127 64~127 32~63 34~63
等价几倍周期 0~127 128~254 256~504 512~1008
周期13.89ns时,死区范围ns 0~1764 1778~3528 3556~7000 7112~14001
示例:
需要3us的死区时间,那么属于情况2,DTG[7:6] = 0b10,步长=27.78,
需要的乘数 = 3000÷27.78-64=108-64=44=0b101100,DTG[7:0]=0b10101100=0xAC
*/
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //CH2 PWM2模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; //比较互补输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; //互补输出极性
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset; //指定空闲状态下的TIM输出比较的引脚状态。
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset; //指定空闲状态下的TIM互补输出比较的引脚状态。
TIM_OCInitStructure.TIM_Pulse = TIM1_OC1_Pulse;
TIM_OC1Init(TIM1, &TIM_OCInitStructure); //根据指定的参数初始化外设TIMx
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); //启用CCR1寄存器的影子寄存器(直到产生更新事件才更改设置)
//OCx输出信号与参考信号相同,只是它的上升沿相对参考信号的上升沿有一个延迟
//OCxN输出信号与参考信号相同,只是它的上升沿相对参考信号的下降沿有一个延迟
}
- 方波端口配置
void GPIO_Config()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//Timer1 Ch1 - PA8
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//Timer1 Ch1N - PB13
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
- 设置死区时间
/**
* @brief Set the dead time of PWM
* @param deatTime: 0~14 us
*/
void User_PWM_SetDeadTime(uint8_t deadTime)
{
/*
正确的deadtime的计算方法(经理论与示波器测试成功)
TIM_BDTRInitStructure.TIM_DeadTime=255 //这句设定的就是寄存器TIMx_BDTR的后8位,即DTG[7:0],所以最大值为255
从下面的代码中的“第五步”中,实际上就相当于TIM1->BDTR=0x71FF;
查看"STM32中文参考手册2009.pdf"的TIMx_BDTR(第248页),列寄存器TIMx_BDTR的后8位如下:
位7:0 UTG[7:0]: 死区发生器设置 (Dead-time generator setup)
这些位定义了插入互补输出之间的死区持续时间。假设DT表示其持续时间:
DTG[7:5]=0xx => DT=DTG[7:0] × Tdtg, Tdtg = Tdts;
DTG[7:5]=10x => DT=(64+DTG[5:0]) × Tdtg, Tdtg = 2 × Tdts;
DTG[7:5]=110 => DT=(32+DTG[4:0]) × Tdtg, Tdtg = 8 × Tdts;
DTG[7:5]=111 => DT=(32+DTG[4:0]) × Tdtg, Tdtg = 16× Tdts;
Tdts为系统时钟周期时长,Tdtg为死区时间计算步长。主要思想就是把DTG的八位,掰成两半用。一半决定步长,另一半是与步长相乘的乘数,乘数可以自行设定,步长*乘数=死区时间。
在72M的定时器时钟下,TDTS = 1/72M = 13.89ns:
项目 情况1 情况2 情况3 情况4
步长位置 DTG[7] DTG[7:6] DTG[7:5] DTG[7:5]
步长值(二进制) 0xx 10x 110 111
步长是周期几倍 1 2 8 16
Step Value (Tdts = 13.89ns) 1/72M 1/36M 1/9M 2/9M
Step Value (Tdts = 13.89ns) 13.89ns 27.78ns 111.11ns 222.22ns
乘数位置 DTG[6:0] DTG[5:0] DTG[4:0] DTG[4:0]
乘数最大值 0+127 64+63 32+31 32+31
乘数范围 0~127 64~127 32~63 32~63
等价几倍周期 0~127 128~254 256~504 512~1008
周期13.89ns时,死区范围ns 0~1764 1778~3528 3556~7000 7112~14001
示例:
需要3us的死区时间,那么属于情况2,DTG[7:6] = 0b10,步长=27.78,
需要的乘数 = 3000÷27.78-64=108-64=44=0b101100,DTG[7:0]=0b10101100=0xAC
*/
uint16_t _deadTime = 0;
if (deadTime > 14)
return;
// 7<deadTime<=14, deadTime = 8, 9, 10, 11, 12, 13, 14
else if (deadTime > 7)
{
_deadTime = 0xE0 | (uint8_t)(deadTime * 100000 / 22222 - 32);
}
// 3<deadTime<=7, deadTime = 4, 5, 6, 7
else if (deadTime > 3)
{
_deadTime = 0xC0 | (uint8_t)(deadTime * 100000 / 11111 - 32);
}
// 1<deadTime<=3, deadTime = 2, 3
else if (deadTime > 1)
{
_deadTime = 0x80 | (uint8_t)(deadTime * 100000 / 2778 - 64);
}
// deadTime = 0, 1
else
{
_deadTime = 0x00 | (uint8_t)(deadTime * 100000 / 1389 - 0);
}
TIM1->BDTR = (0x0C00 | _deadTime);
// Dead time setting is validated after restart the PWM.
// User_PWM_Open(0);
// User_PWM_Open(1);
}
- PWM常用操作
//开启定时器
TIM_Cmd(TIM1, ENABLE);
//开启PWM输出
TIM_CtrlPWMOutputs(TIM1, ENABLE);
//设置占空比(dutyCycle: 0~1)
TIM_SetCompare1(TIM1, dutyCycle*(TIM1->ARR + 1));
使用指南
实用Timer设置函数(以Timer时钟72M为例)
设置PWM频率
/**
* @brief Set the frequency of PWM
* 需要特别注意的是,考虑到PWM占空比设置精度,本函数实现的PWM调节最大频率为1MHz。
* 对于固定频率的PWM,可以参考本函数的实现方法,
* 选择合适的分频系数手动设置分频系数,以保证最大的PWM占空比调整精度。
*
* 注意1: 在STM32F407中,高级定时器的时钟频率与通用定时器的时钟频率是不一样的,
* 需要修改本函数(增加对定时器类型的判断)才能正确的设置PWM频率。
*
* 注意2: 只有高级定时器与通用定时器才能产生PWM。
* @param TIMx: 高级定时器与通用定时器TIM 1, 2, 3, 4, 5 and 8(在STM32F103中,定时器的时钟频率可以都设置为72MHz)
* @param freq: 0.2~1MHz
*/
void User_PWM_SetFrequency(TIM_TypeDef *TIMx, float freq)
{
//duty cycle of 4 channels
float dutyCycle1;
float dutyCycle2;
float dutyCycle3;
float dutyCycle4;
//Range of frequency is 0.2Hz to 1MHz.
//参数不正确,直接返回
if (freq > 1000000)
return;
if (freq < 0.2)
return;
//根据频率设置Timer的分频系数
//0.2 <= frequency < 100, psc = 7200, f = 10KHz
//占空比设置精度为1/50000~1/100
if(freq < 100)
{
TIMx->PSC = 7200 - 1;
}
//100 <= frequency < 1k, psc = 720, f = 100KHz
//占空比设置精度为1/1000~1/100
else if(freq < 1000)
{
TIMx->PSC = 720 - 1;
}
//1K <= frequency < 10K, psc = 72, f = 1MHz
//占空比设置精度为1/1000~1/100
else if(freq < 10000)
{
TIMx->PSC = 72 - 1;
}
//10K <= frequency < 100K, psc = 6, f = 12MHz
//占空比设置精度为1/1200~1/120
else if(freq < 100000)
{
TIMx->PSC = 6 - 1;
}
//100K <= frequency <= 1M, psc = 1, f = 72MHz
//占空比设置精度为1/720~1/72
else if(freq <= 1000000)
{
TIMx->PSC = 1 - 1;
}
else
{
}
//Get the duty cycle of the pwm before changing the frequency.
dutyCycle1 = (float) TIMx->CCR1 / (TIMx->ARR + 1);
dutyCycle2 = (float) TIMx->CCR2 / (TIMx->ARR + 1);
dutyCycle3 = (float) TIMx->CCR3 / (TIMx->ARR + 1);
dutyCycle4 = (float) TIMx->CCR4 / (TIMx->ARR + 1);
//Set the update frequency of the timer.
TIMx->ARR = 72000000.0 / (TIMx->PSC + 1) / freq - 1;
//Set the duty cycle of the timer.
TIMx->CCR1 = dutyCycle1 * (TIMx->ARR + 1);
TIMx->CCR2 = dutyCycle2 * (TIMx->ARR + 1);
TIMx->CCR3 = dutyCycle3 * (TIMx->ARR + 1);
TIMx->CCR4 = dutyCycle4 * (TIMx->ARR + 1);
}
设置PWM占空比
/**
* @brief Set the duty cycle of PWM
* @param TIMx: 高级定时器与通用定时器TIM 1, 2, 3, 4, 5 and 8
* @param channel: 0,1,2,3, 高级定时器与通用定时器有4个PWM通道
* @param dutyCycle: 0~1 (需要注意PWM频率不同时,可以设置的占空比精度是不一样的)
*/
void User_PWM_SetDutyCycle(TIM_TypeDef *TIMx, uint8_t channel, float dutyCycle)
{
//Range of duty cycle is 0 to 1.
//参数不正确,直接返回
if (dutyCycle > 1)
return;
if (dutyCycle < 0)
return;
//Set the duty cycle of the PWM.
switch (channel)
{
case 0:
//TIM_SetCompare1(TIMx, dutycycle * (TIMx->ARR + 1));
TIMx->CCR1 = dutyCycle * (TIMx->ARR + 1);
break;
case 1:
//TIM_SetCompare2(TIMx, dutycycle * (TIMx->ARR + 1));
TIMx->CCR2 = dutyCycle * (TIMx->ARR + 1);
break;
case 2:
//TIM_SetCompare3(TIMx, dutycycle * (TIMx->ARR + 1));
TIMx->CCR3 = dutyCycle * (TIMx->ARR + 1);
break;
case 3:
//TIM_SetCompare4(TIMx, dutycycle * (TIMx->ARR + 1));
TIMx->CCR4 = dutyCycle * (TIMx->ARR + 1);
break;
default:
break;
}
}
更新中断频率
设置Timer的更新中断与设置PWM频率是类似的,因为PWM的频率即为更新中断的频率,它们的区别在于:
- 设置更新中断频率时无需设置CCR1~4(PWM的占空比)的值
- 基本定时器可以设置更新中断频率,但是不能设置PWM的频率
/**
* @brief Set the update frequency of timer
* 本函数实现的最大更新中断频率为1MHz。
*
* 注意1: 在STM32F407中,高级定时器的时钟频率与通用定时器的时钟频率是不一样的,
* 需要修改本函数(增加对定时器类型的判断)才能正确的设置更新中断的频率。
*
* 注意2: 基本定时器、通用定时器、高级定时器都可以设置更新中断的频率。
* @param TIMx: 基本定时器、通用定时器、高级定时器TIM 1~8(在STM32F103中,定时器的时钟频率可以都设置为72MHz)
* @param freq: 0.2~1MHz
*/
void User_Timer_SetUpdateFrequency(TIM_TypeDef *TIMx, float freq)
{
//Range of frequency is 0.2Hz to 1MHz.
//参数不正确,直接返回
if (freq > 1000000)
return;
if (freq < 0.2)
return;
//根据频率设置Timer的分频系数
//0.2 <= frequency < 100, psc = 7200, f = 10KHz
//占空比设置精度为1/50000~1/100
if(freq < 100)
{
TIMx->PSC = 7200 - 1;
}
//100 <= frequency < 1k, psc = 720, f = 100KHz
//占空比设置精度为1/1000~1/100
else if(freq < 1000)
{
TIMx->PSC = 720 - 1;
}
//1K <= frequency < 10K, psc = 72, f = 1MHz
//占空比设置精度为1/1000~1/100
else if(freq < 10000)
{
TIMx->PSC = 72 - 1;
}
//10K <= frequency < 100K, psc = 6, f = 12MHz
//占空比设置精度为1/1200~1/120
else if(freq < 100000)
{
TIMx->PSC = 6 - 1;
}
//100K <= frequency <= 1M, psc = 1, f = 72MHz
//占空比设置精度为1/720~1/72
else if(freq <= 1000000)
{
TIMx->PSC = 1 - 1;
}
else
{
}
//Set the update frequency of the timer.
TIMx->ARR = 72000000.0 / (TIMx->PSC + 1) / freq - 1;
}
设置死区时间
/**
* @brief Set the dead time of PWM
* @param deatTime: 0~14 us
*/
void User_PWM_SetDeadTime(uint8_t deadTime)
{
/*
正确的deadtime的计算方法(经理论与示波器测试成功)
TIM_BDTRInitStructure.TIM_DeadTime=255 //这句设定的就是寄存器TIMx_BDTR的后8位,即DTG[7:0],所以最大值为255
从下面的代码中的“第五步”中,实际上就相当于TIM1->BDTR=0x71FF;
查看"STM32中文参考手册2009.pdf"的TIMx_BDTR(第248页),列寄存器TIMx_BDTR的后8位如下:
位7:0 UTG[7:0]: 死区发生器设置 (Dead-time generator setup)
这些位定义了插入互补输出之间的死区持续时间。假设DT表示其持续时间:
DTG[7:5]=0xx => DT=DTG[7:0] × Tdtg, Tdtg = Tdts;
DTG[7:5]=10x => DT=(64+DTG[5:0]) × Tdtg, Tdtg = 2 × Tdts;
DTG[7:5]=110 => DT=(32+DTG[4:0]) × Tdtg, Tdtg = 8 × Tdts;
DTG[7:5]=111 => DT=(32+DTG[4:0]) × Tdtg, Tdtg = 16× Tdts;
Tdts为系统时钟周期时长,Tdtg为死区时间计算步长。主要思想就是把DTG的八位,掰成两半用。一半决定步长,另一半是与步长相乘的乘数,乘数可以自行设定,步长*乘数=死区时间。
在72M的定时器时钟下,TDTS = 1/72M = 13.89ns:
项目 情况1 情况2 情况3 情况4
步长位置 DTG[7] DTG[7:6] DTG[7:5] DTG[7:5]
步长值(二进制) 0xx 10x 110 111
步长是周期几倍 1 2 8 16
Step Value (Tdts = 13.89ns) 1/72M 1/36M 1/9M 2/9M
Step Value (Tdts = 13.89ns) 13.89ns 27.78ns 111.11ns 222.22ns
乘数位置 DTG[6:0] DTG[5:0] DTG[4:0] DTG[4:0]
乘数最大值 0+127 64+63 32+31 32+31
乘数范围 0~127 64~127 32~63 32~63
等价几倍周期 0~127 128~254 256~504 512~1008
周期13.89ns时,死区范围ns 0~1764 1778~3528 3556~7000 7112~14001
示例:
需要3us的死区时间,那么属于情况2,DTG[7:6] = 0b10,步长=27.78,
需要的乘数 = 3000÷27.78-64=108-64=44=0b101100,DTG[7:0]=0b10101100=0xAC
*/
uint16_t _deadTime = 0;
if (deadTime > 14)
return;
//7<deadTime<=14, deadTime = 8, 9, 10, 11, 12, 13, 14
else if (deadTime > 7)
{
_deadTime = 0xE0 | (uint8_t) (deadTime * 100000 / 22222 - 32);
}
//3<deadTime<=7, deadTime = 4, 5, 6, 7
else if (deadTime > 3)
{
_deadTime = 0xC0 | (uint8_t) (deadTime * 100000 / 11111 - 32);
}
//1<deadTime<=3, deadTime = 2, 3
else if (deadTime > 1)
{
_deadTime = 0x80 | (uint8_t) (deadTime * 100000 / 2778 - 64);
}
//deadTime = 0, 1
else
{
_deadTime = 0x00 | (uint8_t) (deadTime * 100000 / 1389 - 0);
}
TIM1->BDTR = (0x0C00 | _deadTime);
//Dead time setting is validated after restart the PWM.
// User_PWM_Open(0);
// User_PWM_Open(1);
}
项目实例
BLDC电机速度闭环控制
笔者参与的BLDC电机速度闭环控制开发项目中使用了定时器的输入捕获功能,通过测量霍尔传感器产生的脉冲宽度计算电机转速。定时器等相关外设的配置上文有介绍,本章节重点介绍中断服务函数的实现。
- 声明用于记录脉冲计数的全局变量
uint32_t BLDCMotor_PulseCount = 0;
- 在定时器的更新中断中将此全局变量复位
其作用相当于超出测试量程后将速度计算值复位为0。
void TIM1_UP_IRQHandler(void)
{
if(TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)
{
BLDCMotor_PulseCount = 0;
TIM_ClearFlag(TIM1,TIM_FLAG_Update);
TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
}
}
- 在定时器的输入捕获中断服务函数中计算脉冲宽度
为了提高输入捕获的精度,应该尽可能减小服务函数的执行时间。多数情况下应该是调用函数TIM_GetCaptureX
获取对应通道的计数值,然后将连续两次调用的计数值相减得到脉冲计数。为了提高效率,本项目中的服务函数直接将计数值清零,下次获取的计数值将直接等于脉冲计数。
void TIM1_CC_IRQHandler(void)
{
if(TIM_GetITStatus(TIM1, TIM_IT_CC4) != RESET)
{
/**
* Pulse capture has strict requirements on time,
* so it needs to operate the register directly.
*/
BLDCMotor_PulseCount = Filter_MovingAverage_VariantSize(TIM1->CCR4, MOSP_FMA_INDEX, 5);
TIM1->CNT = 0;
TIM1->SR = (uint16_t)~TIM_FLAG_CC4;
// Timer4_IT_CC1_Triggered();
// TIM_ClearFlag(TIM1,TIM_FLAG_CC4);
// TIM_ClearITPendingBit(TIM1,TIM_IT_CC4);
}
}
- 脉冲长度与电机转速的转化
霍尔传感器的脉冲宽度以定时器的时钟计数量表示,首先要将它转换为时间/频率的单位。脉冲频率与电机转速的比例是一个受直流无刷电机磁极子个数影响的固定参数,同时考虑到可能存在的减速机构改变了传动比,因此设置了两个可以调整的参数用于计算最终的电机转速。
float BLDC_PulseToSpeed(uint32_t pulse) //unit: rpm
{
float frequency;
if (pulse == 0)
return 0;
//验证计算参数是否有效
if (HoldingReg_GetData(3) == 0 || HoldingReg_GetData(4) == 0)
{
return -1;
}
frequency = (float) SystemCoreClock / (TIM1_Prescaler * pulse);
return frequency * 60 / HoldingReg_GetData(3) / HoldingReg_GetData(4); //unit: rpm
}