timer

定时器简介

STM32微控制器提供了多种类型的定时器,以满足不同应用场景的需求。以下是STM32定时器的基本介绍、分类、功能特点及应用示例: 本篇主要是基本定时器(Basic Timer)的编程与配置,PWM等功能放在独立的章节中讲解。

基本分类

  1. 高级定时器(Advanced Timer)

    • 典型型号:TIM1和TIM8(在某些高配型号中)
    • 特点
      • 16位向上/向下计数器
      • 多个捕获/比较通道(通常为4个或更多),用于多路PWM输出或信号捕获
      • 互补输出(死区控制),适合无刷直流电机(BLDC)等应用
      • DMA支持,用于在定时事件发生时无需CPU干预的数据传输
      • 同步功能,可以与其他定时器或外部信号同步计数
    • 应用:复杂的PWM生成、电机控制、多通道同步操作等
  2. 通用定时器(General Purpose Timer)

    • 典型型号:TIM2、TIM3、TIM4、TIM5等
    • 特点
      • 16位计数器
      • 多个捕获/比较通道(通常为4个),支持PWM输出、输入捕获、脉冲宽度测量等
      • 多种计数模式(向上计数、向下计数、中心对齐等)
      • 中断与DMA支持
      • GPIO关联,可以直接驱动GPIO进行PWM输出或边沿检测
    • 应用:通用定时任务、信号频率测量、PWM输出、简单电机控制等
  3. 基本定时器(Basic Timer)

    • 典型型号:TIM6和TIM7
    • 特点
      • 功能简化,仅有计数器和定时中断功能
      • 不支持输入捕获或PWM输出
      • 适用于需要精确定时中断但无需复杂输出控制的应用
    • 应用:周期性任务调度、简单延时、DAC触发信号等

通用功能与特性

时基单元

  • 预分频器(PSC):对输入时钟进行分频,以设置合适的计数频率。
  • 计数器(CNT):递增(或递减)计数,直到达到预设值后溢出。
  • 自动重装载寄存器(ARR):设定计数器溢出时的重置值,用于确定定时周期。

时钟源

  • 可选择内部时钟源(如HSI、LSI、HSE、LSE等)或外部时钟源。
  • 经过AHB/APB总线预分频后供给定时器。

中断与DMA

  • 支持各种定时器事件触发中断,如溢出、更新、捕获/比较匹配等。
  • DMA请求可减少CPU干预,提高数据处理效率。

GPIO关联

  • 通用定时器和高级定时器通过通道映射至GPIO引脚,实现PWM输出、边沿检测等功能。

应用示例

  • 定时中断:设置定时器周期性溢出,触发中断服务程序执行周期性任务或更新状态。

  • 脉宽调制(PWM):配置定时器比较通道,生成不同占空比的PWM波形,用于电机速度控制、LED亮度调节等。

  • 信号捕获与测量:利用输入捕获功能捕捉外部信号的上升沿、下降沿或特定电平变化,用于测量信号频率、周期、脉冲宽度等。

  • 同步操作:多个定时器之间或与外部事件同步计数,实现复杂的同步控制逻辑。

编程与配置

STM32定时器可以通过直接操作寄存器或使用STM32官方提供的HAL库、LL库等进行配置和编程。使用库函数可以简化开发过程,提供更高级别的抽象和统一的接口。编程步骤通常包括:

  1. 时钟使能:通过RCC(Reset and Clock Control)寄存器或库函数启用定时器所需的时钟源。

  2. 定时器初始化:配置定时器工作模式(向上计数、向下计数、中心对齐等)、预分频系数、自动重装载值、捕获/比较通道参数等。

  3. 中断/DMA配置:如果使用中断,需在NVIC(Nested Vectored Interrupt Controller)中设置优先级并使能中断;如果使用DMA,需配置DMA通道、传输参数等。

  4. 启动定时器:使能定时器计数,并在必要时启动捕获/比较通道。

  5. 中断服务程序编写:当定时器事件发生时,编写相应的中断服务程序来处理定时任务或捕获数据。

示例代码

/* -----------------------------------------
	经过查看Data Sheet知道stm32f103c8t6
	仅仅支持
	TIM1/TIM8,TIM2~5
-----------------------------------------*/
#include "reg_tim.h"

// --------------------------------------
// 系统时钟SysClock = 72,000,000Hz的情况
void reg_tim2_init(void)
{
    //TIM2时钟开启
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;

    TIM2->PSC = 7200 - 1;		//0.1ms
    TIM2->ARR = 5000;			//0.5s
    //ARPE=1,UDIS=0,CEN=1,CNT_EN=1
    TIM2->CR1 |= TIM_CR1_ARPE;		//reload
    TIM2->CR1 &= ~TIM_CR1_CMS;		//Edge-aligned mode
    TIM2->CR1 |= TIM_CR1_DIR;		//downcounter
    //配置
    //定时器使能
    TIM2->CR1 |= TIM_CR1_CEN;
}

// --------------------------------------
// Time2中断配置
#define NVIC_ADDR	0x08000000		//Flash Addr
#define NVIV_OFFSET	0x0000			//偏移 = 0x200 * N
void reg_time2_intr_init(void)
{
    //--------------------------------------------
    //              NVIC 配置
    //--------------------------------------------
    //配置中断向量表,Flash
    SCB->VTOR = NVIC_ADDR | (NVIV_OFFSET & (uint32_t)0x1FFFFF80);
    // 配置中断优先级,注意STM32用的是高4bit
    // 配置抢占式优先级和响应式优先级各自占2bit
    SCB->AIRCR |= 0x05FA0000 | 0x500;
    // 配置抢占式中段级别为0,响应式中断级别为1
    NVIC->IP[TIM2_IRQn] = 0x10;
    //使能中断
    NVIC->ISER[TIM2_IRQn >> 5] |= 1 << (TIM2_IRQn & 0x1f);

    //清除中断标记
    TIM2->SR &= ~TIM_SR_UIF;
    //开启TIM2中断
    TIM2->DIER |= TIM_DIER_UIE; // | TIM_DIER_TIE;
}


uint8_t flip_flag = 0;
void tim_intr_demo(void)
{
    reg_gpio_init();
    reg_uart_init();

    //----------------------------------
    // 感觉配置没问题,Debug的时候手动配置UIF,可以进入中断。
    // 但程序自己运行,硬件中断挂起感觉一直没有作用。
    // 后来调整了一下顺序,先配置tim,再开启中断。成功!!!
    //----------------------------------
    reg_tim2_init();
    reg_time2_intr_init();

    reg_uart_sendstr("tim2 init finished!\r\n",0);
    while(1)
    {
        if(0 != flip_flag)
        {
            flip_flag = 0;
            reg_uart_sendstr("tim2 irq_handle!\r\n",0);
        }
    }
}

void TIM2_IRQHandler(void)
{
    // -------------------------------------
    // 这里之所以不能按照预期产生闪烁效果
    // 我认为是该中断函数的进入不仅仅有TIM_SR_UIF中断
    // 还有其他情况下的中断我没有考虑到。
    // 因此在处理各种不同功能的时候,要先判断其中断标志
    // 然后再进行相应的操作
    // --------------------------------------
    // LED1_FLIP;
    if (TIM2->SR & TIM_SR_UIF)
    {
        flip_flag = 1;

        LED1_FLIP;

        //清除相应的中断标识	
        TIM2->SR &= ~TIM_SR_UIF;
    }
}
#include "stm32f10x_conf.h"

void timer_init(void)
{
    TIM_TimeBaseInitTypeDef timerConfig;
    // 0.使能定时器时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

    // 1.定时器基础配置
    TIM_TimeBaseStructInit(&timerConfig);
    timerConfig.TIM_Period = 5000 - 1;					//因此一个计数代表着0.1ms
    timerConfig.TIM_Prescaler = 7200;					//CoreClock is 72000000 = 72MHz
    timerConfig.TIM_CounterMode = TIM_CounterMode_Up;	//注意:无论向上或者向下计数,范围都是 0-自动加载值

    TIM_TimeBaseInit(TIM2,&timerConfig);

    //2. 中断配置
    TIM_ClearITPendingBit(TIM2,TIM_IT_Update);  //清除中断标志
    TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);    // 定时器中断使能

    //3. 使能定时器
    TIM_Cmd(TIM2,ENABLE);						// 开启定时器
}

//定时器中断配置
void timer_intr_init(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    //配置中断向量表地址
    NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0000);
    //指定抢占优先级为两位,响应优先级为2位
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    //配置TIM2,抢占优先级为0,响应优先级为1
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}


uint8_t sendflag = 0;
void timer_demo(void)
{
    timer_init();
    timer_intr_init();
    while(1)
    {

    }
}

void TIM2_IRQHandler(void) 
{
    //当发生TIM2中断的时候
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)  
    {
        //中断处理
        static int a = 0;
        if(a == 0)
        {
            a = 1;
            LED_OFF();
        }
        else
        {
            a = 0;
            LED_ON();
            sendflag = 1;
        }
    }

    //清除中断标志
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update); 
}