STM32基本定时器TIM6和TIM7

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通道与它关联。
STM32基本定时器TIM6和TIM7_第1张图片

今天先学习基本定时器。
个人在学习定时器时的想法:定时超时能产生中断信号,本能反应,它涉及到中断编程就有可能涉及到设置NVIC(中断源优先级相关)和EXTI(外部中断/事件线EXTI0/1…/15相关),在前面实现SysTick定时编程中,由于SysTick并非片上外设所以并不需要设置NVIC,而STM32中非SysTick的定时器都属于片上外设,所以自然是要设置NVIC;EXTI是设置外部中断/事件线的,它必须关联于某个对应的IO引脚,在SysTick定时编程中不需要设置,在这里同样不需要设置。

2. 基本定时器的时基

定时器的基本功能是定时,定时的核心则是时基,看基本定时器的框图,
STM32基本定时器TIM6和TIM7_第2张图片

2.1 时钟源CK_INT

定时器的学习,从时钟源说起,也就是图中的TIMxCLK。在时钟树中,
STM32基本定时器TIM6和TIM7_第3张图片
定时器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资源。

你可能感兴趣的:(STM32单片机)