声明:

此为学习(尚硅谷)过程中的笔记和自己的思考(STM32f103ZET6),大家理性判断!

1.定时器的分类

1.1 系统滴答定时器(SysTick)

SysTick(System Tick Timer)是 ARM Cortex-M 内核内置的一个 24位递减计数,主要用于:

  • 操作系统(RTOS)的时间基准(如任务调度)。

  • 高精度延时(如 HAL_Delay())。

  • 周期性中断触发(如每 1ms 执行一次函数)。

SysTick 的特点

  • 集成在 Cortex-M 内核中,不依赖外设定时器(如 TIM1~TIM7)。

  • 24 位递减计数器,最大计数值 0xFFFFFF

  • 自动重载(Auto-Reload),计数到 0 后自动加载 LOAD 寄存器的值。

  • 可配置中断(计数到 0 时触发 SysTick_Handler)。

  • 时钟源可选

    • 内核时钟(HCLK)(通常 72MHz for STM32F103)。

    • 外部参考时钟(HCLK/8)(较少使用)。

已知这是系统使用来计时的定时器,还能用它来设置中断吗?

相当于系统计数的时候达到条件就触发一次中断。

 寄存器介绍

  SysTick控制和状态寄存器(CTRL)

  • ENABLE: 使能SysTick计数器(1=启用,0=禁用)。

  • TICKINT: 计数到0时是否触发中断(1=触发,0=不触发)。

  • CLKSOURCE: 选择时钟源(1=处理器时钟(AHB 72M),0=外部参考时钟(AHB/8))。

  • COUNTFLAG: 只读标志位,计数器归0时置1,读取后自动清零。

SysTick重装载寄存器(LOAD)

设置重装载值(即定时周期)。

写入的值 = 定时周期数 - 1(因为计数器从LOAD值递减到0)。

例如:若时钟为1MHz,需1ms定时,则写入 999(因为 1000-1)。

SysTick当前数值寄存器(VAL)

读取: 获取当前计数器的值。

写入: 任何值会清零计数器,并清除COUNTFLAG标志。

注意: 计数器在启用时会自动从LOAD值重新加载并递减。

SysTick 校准数值寄存器(CALIB)

 一般不用。




1.2 基本定时器(TIM6、TIM7)

功能:

  • 计时:从预设值开始递减或递增计数,达到设定条件时触发中断或事件。

  • 中断生成:可用于周期性唤醒MCU(如从低功耗模式)。

  • 触发其他外设:如ADC、DAC的同步信号。

时钟来自内部时钟源,如果APB1预分频系数为1,则频率不变。否则频率×2.

自动重装载寄存器分有两个寄存器:预加载寄存器+影子寄存器

当更新自动重装载寄存器中的值的时候,会先将值写进预加载寄存器,当计数更新的时候才会写进影子寄存器。

计数器是否溢出,取决于影子寄存器的值

1.3 通用定时器(TIM 2/3/4/5)

拥有基本定时器全部的功能。

时钟源:可选的时钟源

1.与基本定时器一样的内部时钟源。

2.外部时钟源(1):主要用来输入捕获

在这个图中定时器有4个通道,只有CH1和CH2可以成为时钟源信号

  • 通过定时器的特定输入引脚(TIMx_CH)接入外部时钟信号

  • 通常用于:

    • 需要与外部设备同步

    • 精确测量外部信号频率

3.外部时钟源(2):

  • 通过ETR(External Trigger)引脚输入外部时钟.

4. 内部触发输入(ITRx)

  • 来自其他定时器的输出(一个定时器作为另一个定时器的时钟源)

  • 典型应用:

    • 定时器级联(扩展定时范围)

    • 创建更复杂的定时关系

1.4 高级定时器(TIM1 TIM8)

除了拥有基本定时器的所有功能外,还具有

  • 死区时间可编程的互补输出;
  • 重复计数器。
  • 断路输出信号(刹车输入信号);

对于1:互补输出可以看作是输出一个周期频率相同,相位相反的TIMx_CH1和TIMx_CH1N信号

常常用于电机的驱动,但是电机的驱动常常需要高电流。我们常常用H桥电路来驱动比较大的负载。

但是MOS管有一个特性就是关的慢,开的快很可能出现Q1没关断,Q2就开启的情况,如下图:

很容易就会烧毁MOS管。

此时我们需要关就立刻关,开要延时开

这就是所谓的死区时间

重复计数器就是依靠REP寄存器来设置重复计数寄存器的值(例如重复计数寄存器为2),CNT必须要溢出3次。

3. 系统滴答定时器实现

 用系统定时器写中断实现

Systick.c

#include"Systick.h"
#include"led.h"
void systick_init(void)
{
    SysTick->LOAD =72000-1;//1ms一次中断

    SysTick->CTRL|=SysTick_CTRL_CLKSOURCE;//时钟源
    SysTick->CTRL|=SysTick_CTRL_TICKINT;//时钟中断
    SysTick->CTRL|=SysTick_CTRL_ENABLE;//使能

}


uint16_t count=0;
void SysTick_Handler(void)
{
    count++;
    if(count==1000)
    {
        led_tegger(LED1);
        count=0;
    }
}

而在hal库中则只需要

4.基本定时器实现中断

#include"tim6.h"

void tim6_init(void)
{
    //1.开始中
    RCC->APB1ENR|=RCC_APB1ENR_TIM6EN;

    TIM6->CR1&=~TIM_CR1_DIR;
    TIM6->PSC=72-1;
    TIM6->ARR=1000-1;

    TIM6->DIER|=TIM_DIER_UIE;

    NVIC_SetPriorityGrouping(3);
    NVIC_SetPriority(TIM6_IRQn,3);
    NVIC_EnableIRQ(TIM6_IRQn);


}
void tim6_start(void)
{
    TIM6->CR1|=TIM_CR1_CEN;
}
void tim6_stop(void)
{
    TIM6->CR1&=~TIM_CR1_CEN;
}




main.c 

void TIM6_IRQHandler(void)
{
	TIM6->SR&=~TIM_SR_UIF;
	i++;
	if(i==1000)
	{
		led_tegger(LED2);
		i=0;
	}
}

5.通用定时器——PWM

5.1 理论

PWM(Pulse Width Modulation,脉宽调制)是一种通过快速开关数字信号来模拟模拟量效果的技术,广泛应用于功率控制、电机驱动、LED调光等领域。(惯性)。

一般设置好频率和周期,更改占空比来调节大小。

其中有CNT、CCR和ARR,占空比=  \frac{CCR}{ARR+1}

5.2 实现方法

输出比较:主要用来控制输出方波,主要用于输出PWM波形

输出比较的8种工作模式:

PWM1模式:当CNT<CCR时,输出高电平。当CNT>=CCR时,输出低电平。

PWM2模式则相反,当CNT>CCR时,输出高电平。当CNT<=CCR时,输出低电平。

举例:冻结模式 (TIM_OCMode_Timing / Frozen)

  • 功能:计数器与比较寄存器匹配(CNT=CCR)时不影响输出电平

  • 应用:纯定时器使用,不改变输出状态

  • 寄存器配置:OCxM=000

5.3 相关寄存器

5.4 实现

#include"tim5.h"

void tim5_init(void)
{
    //1.时钟配置
    RCC->APB1ENR|=RCC_APB1ENR_TIM5EN;
    RCC->APB2ENR|=RCC_APB2ENR_IOPAEN;

    //引脚配置 PA0 TIM5 CH1 复用推挽输出 CNF 10 MODE11
    GPIOA->CRL|=GPIO_CRL_CNF0_1;
    GPIOA->CRL&=~GPIO_CRL_CNF0_0;
    GPIOA->CRL|=GPIO_CRL_MODE0;

    TIM5->PSC=7199;
    TIM5->ARR=99;
    //向上计数
    TIM5->CR1&=~TIM_CR1_DIR;

    //开始配置输出比较通道一
    TIM5->CCMR1&=~TIM_CCMR1_CC1S;
    TIM5->CCMR1&=~TIM_CCMR1_OC1M_0;
    TIM5->CCMR1|=TIM_CCMR1_OC1M_2;
    TIM5->CCMR1|=TIM_CCMR1_OC1M_1;

    //
    TIM5->CCR1=50;
    TIM5->CCER&=~TIM_CCER_CC1P;//极性
    TIM5->CCER|=TIM_CCER_CC1E; //使能
    
}

void tim5_start(void)
{
    TIM5->CR1|=TIM_CR1_CEN;
}

void tim5_stop(void)
{
    TIM5->CR1&=~TIM_CR1_CEN;
}

void tim5_duty(uint8_t duty)
{
    TIM5->CCR1=duty;
}

6.通用定时器之输入捕获

6.1 原理介绍

注意:

对于PCS和ARR的值我们要仔细考虑。我们通常将ARR设置为最大值65535。保证CNT不溢出。

最好设置PCS为72的倍速,例如将PCS=72,则一个CNT就是1us,而测量的最大周期就是\frac{1}{65535*1*10^-6}=15.259,相当于最小只能测16Hz的频率。所以此时频率要大于16Hz,不然就会溢出。

对于不同频率的信号我们的做法:

对于频率较低:

1.我们可以增加PCS的值。

2.我们可以把CNT溢出的次数给加上。

对于频率较高的信号:

1.将PCS减小。

2.可以测量将10次上升沿加在一起的次数,然后除以10。

6.2 寄存器介绍

IC1F是设置滤波器的,可以不位置。IC1PSC是设置分频的。

CC1IE:就是开启CC1中断

6.3 实现

#include "tim4.h"


void tim4_init(void)
{
    //1.时钟PB6
    RCC->APB2ENR|=RCC_APB2ENR_IOPBEN;
    RCC->APB1ENR|=RCC_APB1ENR_TIM4EN;

    //2.配置    浮空输入 mode 00  CNF 01
    GPIOA->CRL&=~GPIO_CRL_MODE6;
    GPIOA->CRL|=GPIO_CRL_CNF6_0;
    GPIOA->CRL&=~GPIO_CRL_CNF6_1;

    //3.配置定时器上升与arr这些
    TIM4->CR1&=~TIM_CR1_DIR;
    TIM4->ARR=65535;
    TIM4->PSC=71;

    //.4配置一些捕获相关的配置
    TIM4->CR2 &= ~TIM_CR2_TI1S;

    TIM4->CCMR1 &= ~TIM_CCMR1_IC1F;
    TIM4->CCMR1 |=TIM_CCMR1_CC1S_0;
    TIM4->CCMR1 &=~TIM_CCMR1_CC1S_1;
    TIM4->CCER &= ~TIM_CCER_CC1P;
    //
    //TIM4->CCMR1 &= ~TIM_CCMR1_IC1PSC;
    TIM4->CCER|=TIM_CCER_CC1E;
    TIM4->DIER|=TIM_DIER_CC1IE;
    //中断有关配置
    NVIC_SetPriorityGrouping(3);
    NVIC_SetPriority(TIM4_IRQn,3);
    NVIC_EnableIRQ(TIM4_IRQn);



}

void tim4_start(void)
{
    TIM4->CR1|=TIM_CR1_CEN;
}
void tim4_stop(void)
{
    TIM4->CR1&=~TIM_CR1_CEN;
}

uint8_t cnt=0;
uint16_t value;

void TIM4_IRQHandler(void)
{
   if(TIM4->SR & TIM_SR_CC1IF)
   {
        cnt++;
        TIM4->SR &= ~TIM_SR_CC1IF;
        if(cnt==1)
        {
            TIM4->CNT=0;
        }
        else if(cnt==2)
        {
            value=TIM4->CCR1;
            cnt=0;
        }
   }
}



7.通用定时器之触发输入和从模式

7.1 定时器的触发信号

分两大类:

1.触发输入信号:(TRGI)Trigger input

输入来控制本定时器的一些动作复位,使能,计数),此时本定时器就处于主从模式中的从模式。

2. 触发输出信号(TRGO)Trigger output

从本定时器输出到其他定时器或其他外设的信号。

用于与其他定时器级联(用于触发其他定时器的复位,或用于输出其他外设工作),此时本定时器就是主从模式中的主模式

7.2 相关寄存器的介绍

对于(000、001、010、011):

由定时器的内部触发输入,别的定时器通过触发输出到本定时器(触发输入),但注意:芯片内部的连接定死的

对于111:外部触发输入(ETRF):

外部信号经过极性选择、边沿检测、输入滤波成为TRGI信号。通过从模式控制器控制本定时器的复位、计时等工作。

100:TI1的边沿检测器(TI1F_ED)

  • TI1F_ED 是定时器输入通道1(TIMx_CH1)的双边沿检测信号,即 TI1 引脚上的上升沿(Rising Edge)和下降沿(Falling Edge)都会触发 TI1F_ED

  • 与普通的 TI1FP1(仅上升沿或下降沿触发) 不同,TI1F_ED 不区分方向,只要 TI1 有电平跳变(上升或下降),就会触发该信号。

101、110:定时器输入(TI1FP1):

来自于定时器本身的通道1和通道2信号,通过滤波器和边沿检测器,成为TI1FP1、TI2FP2信号,他们是上升沿或者下降沿,只能选择一种。最终成为TRGI信号。

!!!这些TRGI信号要控制定时器,首先要把定时器配置成从模式

7.3 实现

测量占空比实验用到从模式功能

#include"tim4.h"
uint16_t duty;
void tim4_init(void)
{
    RCC->APB2ENR|=RCC_APB2ENR_IOPBEN;
    RCC->APB1ENR|=RCC_APB1ENR_TIM4EN;

    //2.配置引脚 浮空输入PB6  mode 00    cnf 10
    GPIOB->CRL&=~GPIO_CRL_MODE6;
    GPIOB->CRL&=~GPIO_CRL_CNF6_1;
    GPIOB->CRL|=GPIO_CRL_CNF6_0;

    TIM4->CR1&=~TIM_CR1_DIR;

    TIM4->PSC=71;
    TIM4->ARR=65535;

    //不滤波
    TIM4->CCMR1&=~TIM_CCMR1_IC1F;

    //边沿检测  CC1检测上升沿信号 CC2检测下降沿信号
    TIM4->CCER&=~TIM_CCER_CC1P;
    TIM4->CCER|= TIM_CCER_CC2P;


    //映射IC1  TI   IC2 T1,将通道一检测的信号分别输入到CC1和CC2

    TIM4->CCMR1|=TIM_CCMR1_CC1S_0;
    TIM4->CCMR1&=~TIM_CCMR1_CC1S_1;

    TIM4->CCMR1|=TIM_CCMR1_CC2S_1;
    TIM4->CCMR1&=~TIM_CCMR1_CC2S_0;
    
    
    TIM4->CCMR1&=~TIM_CCMR1_IC1PSC;
    TIM4->CCMR1&=~TIM_CCMR1_IC2PSC;


    //配置外部输入的从模式  触发选择 滤波后的定时器输入1(TI1FP1)
    TIM4->SMCR|=(TIM_SMCR_TS_2|TIM_SMCR_TS_0);
    TIM4->SMCR&=~TIM_SMCR_TS_1;

    //从模式选择复位模式   就是通道一的信号进入从模式,计数器清零并重新计数
    TIM4->SMCR|=TIM_SMCR_SMS_2;
    TIM4->SMCR&=~TIM_SMCR_SMS_1;
    TIM4->SMCR&=~TIM_SMCR_SMS_0;

     //使能
    TIM4->CCER|=TIM_CCER_CC1E;
    TIM4->CCER|=TIM_CCER_CC2E;


}

void tim4_start(void)
{
    TIM4->CR1|=TIM_CR1_CEN;
}

void tim4_stop(void)
{
    TIM4->CR1&=~TIM_CR1_CEN;
}

uint16_t pwm_freq(void)
{
    return 1000000/TIM4->CCR1;
}

double pwm_duty(void)
{
    return (TIM4->CCR2*1.0/TIM4->CCR1);
}



就是说TI1的信号通过滤波器和边沿检测器变成3路:TRGI、IC1、IC2,此时信号上升沿到来,TRGI进行复位操作,下降沿到来CC2捕获数据,然后上升沿再到来,CCR1捕获。

当 TIM4 配置为 PWM 输入模式(复位模式 + 双沿捕获) 时:

  1. 上升沿(IC1)

    • 复位计数器(CNT=0) 并重新开始计数。

    • 捕获当前 CNT 值到 CCR1(此时 CCR1 = 上一个周期的时间)。

  2. 下降沿(IC2)

    • 捕获当前 CNT 值到 CCR2(此时 CCR2 = 高电平时间)。

  3. 下一个上升沿

    • 再次复位 CNT,并更新 CCR1 为新的周期值。

8.高级定时器之重复计数器

8.1 介绍

 输出有限个PWM波:

思路:我们需要使用重复计数器,计数5次产生中断停止。(相当于灯泡亮5次)

用法:预分频器也是有影子寄存器的,但是它不像自动重装载寄存器一样有ARPE重装载寄存器中预装载的使能,预分频器只能通过产生更新事件(UG)来刷新所有的寄存器使预分频器新的值写入。

搭配

我们设置一个更新事件(UG)就可以将我们设置的ARR,PSC,RAR全都更新成我们设置的值。

并且搭配URS我们设置的更新事件就不会触发中断。

8.2 实现

#include"tim1.h"
//tim1 ch1  PA8
void tim1_init(void)
{
    RCC->APB2ENR|=RCC_APB2ENR_IOPAEN;
    RCC->APB2ENR|=RCC_APB2ENR_TIM1EN;

    //配置复用推挽输出mode 11 cnf 10
    GPIOA->CRH|=GPIO_CRH_MODE8;
    GPIOA->CRH|=GPIO_CRH_CNF8_1;
    GPIOA->CRH|=GPIO_CRH_CNF8_0;

    //2.配置模式了上升
    TIM1->CR1&=~TIM_CR1_DIR;
  

    TIM1->PSC=7199;
    TIM1->ARR=9999;
    TIM1->CCR1=5000;
    TIM1->RCR=4;
 //进行使能和上升极性的选择
	TIM1->CCER|=TIM_CCER_CC1E;
    TIM1->CCER&=~TIM_CCER_CC1P;

    //配置模式
    TIM1->CCMR1&=~TIM_CCMR1_CC1S;
    TIM1->CCMR1|=TIM_CCMR1_OC1M_2;
    TIM1->CCMR1|=TIM_CCMR1_OC1M_1;
    TIM1->CCMR1&=~TIM_CCMR1_OC1M_0;


	TIM1->BDTR |= TIM_BDTR_MOE;// 主输出使能(高级定时器必须开启)

    
    TIM1->CR2|=TIM_CR1_URS;
    //事件更新
    TIM1->EGR|=TIM_EGR_UG;
    TIM1->DIER|=TIM_DIER_UIE;

    NVIC_SetPriorityGrouping(3);
    NVIC_SetPriority(TIM1_UP_IRQn,3);
    NVIC_EnableIRQ(TIM1_UP_IRQn);

}
void tim1_start(void)
{
    TIM1->CR1|=TIM_CR1_CEN;
}
void tim1_stop(void)
{
    TIM1->CR1&=~TIM_CR1_CEN;
}


void TIM1_UP_IRQHandler(void)
{
    printf("jinru ");
    TIM1->SR&=~TIM_SR_UIF;
    tim1_stop();
}        


Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐