STM32定时器全解析:从基础到高级应用
注意:对于PCS和ARR的值我们要仔细考虑。我们通常将ARR设置为最大值65535。保证CNT不溢出。最好设置PCS为72的倍速,例如将PCS=72,则一个CNT就是1us,而测量的最大周期就是=15.259,相当于最小只能测16Hz的频率。所以此时频率要大于16Hz,不然就会溢出。对于不同频率的信号我们的做法:对于频率较低:1.我们可以增加PCS的值。2.我们可以把CNT溢出的次数给加上。对于频
声明:
此为学习(尚硅谷)过程中的笔记和自己的思考(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,占空比=
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,而测量的最大周期就是
=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 输入模式(复位模式 + 双沿捕获) 时:
上升沿(IC1):
复位计数器(CNT=0) 并重新开始计数。
捕获当前 CNT 值到 CCR1(此时
CCR1= 上一个周期的时间)。下降沿(IC2):
捕获当前 CNT 值到 CCR2(此时
CCR2= 高电平时间)。下一个上升沿:
再次复位 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();
}
更多推荐





































所有评论(0)