1. STM32上定时器的分类
前面学习了STM32系统定时器SysTick,它的主要作用是为OS提供系统滴答,当然我们也可以利用它实现了精准延时。在STM32单片机中,除了属于CM3内核中的一个外设的系统定时器外,还有几个属于片上外设的定时器:基本定时器(TIM6和TIM7)、通用定时器(TIM2/3/4/5)和高级定时器(TIM1和TIM8)。强调,这里指的是除互联型的STM32F1系列单片机。
它们各自具有的功能特点可以详见《STM32中文参考手册_V10.pdf》-P298,这里简单描述:
(1)基本定时器(TIM6和TIM7):16位的只能向上计数的定时器,只能实现定时,没有外部IO通道与它关联。
(2)通用定时器(TIM2/3/4/5):16位的可向上或者向下、向上/向下的定时器,除了能实现定时功能,还可以实现输入捕获、输出比较功能(PWM),每个定时器有4个外部IO通道与它关联。
(3)高级定时器(TIM1和TIM8):16位的可向上或者向下、向上/向下的定时器,除了能实现定时功能,还可以实现输入捕获、输出比较功能(PWM)、输出互补等专用功能,每个定时器有4个外部IO通道与它关联。
今天先学习基本定时器。
个人在学习定时器时的想法:定时超时能产生中断信号,本能反应,它涉及到中断编程就有可能涉及到设置NVIC(中断源优先级相关)和EXTI(外部中断/事件线EXTI0/1…/15相关),在前面实现SysTick定时编程中,由于SysTick并非片上外设所以并不需要设置NVIC,而STM32中非SysTick的定时器都属于片上外设,所以自然是要设置NVIC;EXTI是设置外部中断/事件线的,它必须关联于某个对应的IO引脚,在SysTick定时编程中不需要设置,在这里同样不需要设置。
2. 基本定时器的时基
定时器的基本功能是定时,定时的核心则是时基,看基本定时器的框图,
2.1 时钟源CK_INT
定时器的学习,从时钟源说起,也就是图中的TIMxCLK。在时钟树中,
定时器2~7的时钟源是这样确定的:如果PCLK1的预分频系数为1,则它们的时钟源为PCLK1,否则它们的时钟源为PCLK1的2倍。PCLK1在前面的配置中,已经将APB1的预分频系数设置为2,即PCLK1为36MHz,所以定时器2~7的时钟源 = TIMxCLK = 72MHz。
2.2 计数器时钟CK_CNT
TIMxCLK经过PSC预分频器之后为CK_INT,作为CNT计数器的计数时钟。PSC可以对定时器时钟TIMxCLK进行1~65535之间任何一个数进行分频,CK_CNT = TIMxCLK / (PSC + 1)。PSC的值设置于TIMx_PSC寄存器。
2.3 计数器CNT
CNT是一个16位的计数器,只能往上递增计数,不能超过65535,当计数达到自动重装载寄存器里的数值时产生更新事件,CNT清零并从头开始计数,如果使能了中断的话,定时器还会产生溢出中断。使能的中断项有TIM_IT_Update等,详细见下面TIM_ITConfig()函数的讲解。CNT的值设置于TIMx_CNT寄存器。
2.4 自动重装寄存器ARR
ARR是一个16位的寄存器,里面装着计数器能计数的最大数值。ARR值设置于TIMx_ARR寄存器。
2.5 定时时间的计算
了解了定时器的运行时基,定时时间计算就很容易了。定时器的定时时间等于计时器的中断产生周期乘以中断的次数。定时器计一个数的时间是1 / CK_CNT,产生一次中断需要的时间是ARR / CK_CNT,利用产生多次中断即可延时多个中断产生所需要的时间。
定时器还有TIMx_CR1和TIMx_CR2控制寄存器、TIMx_DIER中断使能寄存器、TIMx_SR状态寄存器,TIMx_EGR事件产生寄存器,具体意义详见参考手册,因为是使用标准库编程,所以不再赘述。
编程常用的库函数有:
(1)使能/失能定时器中断
TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState)
参数1:TIMx的取值为TIM[1…8]
参数2:表示超时后产生的中断类型,有更新事件中断TIM_IT_Update、TIM_IT_CCx[x=1..4]、TIM_IT_COM、TIM_IT_Trigger、TIM_IT_Break
基本定时器定时取TIM_IT_Update即可。
参数3:NewState即为DISABLE / ENABLE
(2)清除中断标志位
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT)
参数1:TIMx的取值为TIM[1…8]
参数2:表示超时后产生的中断类型,有更新事件中断TIM_IT_Update、TIM_IT_CCx[x=1..4]、TIM_IT_COM、TIM_IT_Trigger、TIM_IT_Break
(3)使能/失能计数器
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
参数1:TIMx的取值为TIM[1…8]
参数2:NewState即为DISABLE / ENABLE
(4)开启/关闭定时器的时钟
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState)
定时器6是挂接在APB1总线上的,所以要开启该定时器的时钟需要调用RCC_APB1PeriphClockCmd函数
参数1:
RCC_APB1Periph_TIM2, RCC_APB1Periph_TIM3, RCC_APB1Periph_TIM4,
RCC_APB1Periph_TIM5, RCC_APB1Periph_TIM6, RCC_APB1Periph_TIM7,
RCC_APB1Periph_WWDG, RCC_APB1Periph_SPI2, RCC_APB1Periph_SPI3,
RCC_APB1Periph_USART2, RCC_APB1Periph_USART3, RCC_APB1Periph_USART4,
RCC_APB1Periph_USART5, RCC_APB1Periph_I2C1, RCC_APB1Periph_I2C2,
RCC_APB1Periph_USB, RCC_APB1Periph_CAN1, RCC_APB1Periph_BKP,
RCC_APB1Periph_PWR, RCC_APB1Periph_DAC, RCC_APB1Periph_CEC,
RCC_APB1Periph_TIM12, RCC_APB1Periph_TIM13, RCC_APB1Periph_TIM14可选
参数2:NewState即为DISABLE / ENABLE
3. 基本定时器描述结构体
在标准外设库中的stm32f10x_tim.h中有对定时器描述的4个结构体:
(1)时基初始化结构体TIM_TimeBaseInitTypeDef
(2)输出比较功能初始化结构体TIM_OCInitTypeDef
(3)输入捕获功能初始化结构体TIM_ICInitTypeDef
(4)刹车和死区功能初始化结构体TIM_BDTRInitTypeDef
基本定时器需要用到的描述结构体只是第(1)个TIM_TimeBaseInitTypeDef:
typedef struct
{
uint16_t TIM_Prescaler; //预分频器
uint16_t TIM_CounterMode; //计数模式,向上/向下
uint16_t TIM_Period; //定时器周期
uint16_t TIM_ClockDivision; //时钟分频
uint8_t TIM_RepetitionCounter; //重复计数器,利用它可以控制pwm个数,高级定时器用
} TIM_TimeBaseInitTypeDef;
(1)TIM_Prescaler:定时器预分频设置,定时器的时钟是经过预分频后时钟源CK_INT,它操作的是TIMx_PSC寄存器的值,可设置的范围为0~65535,注意TIMx_PSC会自动对设置值加1,所以实现的是1~65536分频
(2)TIM_CounterMode:定时器计数方式,取值可为:
#define TIM_CounterMode_Up ((uint16_t)0x0000) //向上
#define TIM_CounterMode_Down ((uint16_t)0x0010) //向下
#define TIM_CounterMode_CenterAligned1 ((uint16_t)0x0020) //中间对齐1
#define TIM_CounterMode_CenterAligned2 ((uint16_t)0x0040) //中间对齐2
#define TIM_CounterMode_CenterAligned3 ((uint16_t)0x0060) //中间对齐3
基本定时器只能向上计数,即TIMx_CNT只能从0开始递增,所以不需要对此值进行初始化
(3)TIM_Period:定时器周期,即设定自动重装载寄存器TIMx_ARR的值。设置值的范围是0~65536
(4)TIM_ClockDivision:时钟分频设置,设置定时器时钟CK_INT频率与数字滤波器采样时钟频率分拼比,基本定时器没有这功能,不管
(5)TIM_RepetitionCounter:重复计数器,高级定时器TIM1和TIM8才具有的功能,控制输出PWM波的输出个数
TIM_TimeBaseInitTypeDef结构体虽然有5个成员,但是对于基本定时器只需要设置TIM_Prescaler和TIM_Period。
4. 编程实践
实现功能:利用基本定时器TIM6定时1S,超时则反转两个板载LED灯的状态。
(1)开启TIM6定时器的时钟
(2)初始化定时器时基描述结构
(3)使能TIM6的更新中断
(4)开启定时器
(5)中断服务函数
硬件平台是正点原子的miniSTM32开发板。
BaseTIM.h:
#ifndef __BASETIM_H__
#define __BASETIM_H__
#include "stm32f10x_conf.h"
void Led_CfgInit(void);
void BaseTIM_CfgInit(void);
void NVIC_CfgInit(void);
#endif /* __BASETIM_H__ */
main.c中,初始化外接LED灯的GPIO:
//PA8-->LED0,PD2-->LED1
void Led_CfgInit(void)
{
GPIO_InitTypeDef GPIO_InitTypeStu;
//开启PA和PD的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD, ENABLE);
//PA8为推挽输出
GPIO_InitTypeStu.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitTypeStu.GPIO_Pin = GPIO_Pin_8;
GPIO_InitTypeStu.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitTypeStu);
GPIO_SetBits(GPIOA,GPIO_Pin_8); //初始化灭灯
//PD2为推挽输出
GPIO_InitTypeStu.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOD, &GPIO_InitTypeStu);
GPIO_ResetBits(GPIOD,GPIO_Pin_2); //初始化亮灯
}
初始化定时器的时基描述结构体:
//设置中断产生间隔为1ms,CLK_INT=72,预分频系数 = 1000
void BaseTIM_CfgInit(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStu;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
TIM_TimeBaseInitStu.TIM_Prescaler = 1000; //预分频系数
TIM_TimeBaseInitStu.TIM_Period = 72 - 1; //重装载值
TIM_TimeBaseInit(TIM6, &TIM_TimeBaseInitStu);
//注意要开启定时器中断,这里使用更新事件中断
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
//开启计数器
TIM_Cmd(TIM6, ENABLE);
}
初始化NVIC结构体:
void NVIC_CfgInit(void)
{
NVIC_InitTypeDef NVIC_InitStu;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //设置中断分组寄存器
NVIC_InitStu.NVIC_IRQChannel = TIM6_IRQn; //外部中断线,定时器6
NVIC_InitStu.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStu.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级
NVIC_InitStu.NVIC_IRQChannelSubPriority = 1; //子优先级
NVIC_Init(&NVIC_InitStu);
}
主函数实现模块函数的调用及阻塞:
int main(void)
{
Led_CfgInit();
BaseTIM_CfgInit();
NVIC_CfgInit();
while(1)
{
while (nTime < 1000); //1000次中断需要经历1s,超时反转led灯
GPIO_WriteBit(GPIOD, GPIO_Pin_2, ((BitAction)!GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_2)));
GPIO_WriteBit(GPIOA, GPIO_Pin_8, ((BitAction)!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)));
nTime = 0;
}
return 0;
}
在stm32f10x_it.c中实现定时器超时的中断处理函数:
void TIM6_IRQHandler(void)
{
//判断是否为定时器6的更新中断
if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)
{
nTime++;
//注意要清除中断标志
TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
}
}
注意如果没有在中断服务函数中清除中断标志,那么中断服务函数会被cpu反复执行,也就是说接下来的其他操作将永远得不到cpu资源。