目录
#TIM定时器中断#
TIM简介
- TIM(Timer)定时器
- 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断(定时触发中断/计数器)在STM32中,定时器基准时钟主频是72MHZ
对72MHZ记72个数,就是1MHZ也就是1us的时间
T(时钟周期)=1/fosc(频率) 计时时间=n(计数次数)*T,1s/72M*72=1us
1Hz对应1s,1KHz对应1ms,1MHz对应1us
- 16位计数器(执行计数定时)、预分频器(对计数器的时钟进行分频,使计数更灵活)、自动重装寄存器(计数目标值)的时基单元,在72MHz计数时钟下可以实现最大59.65s(预分频器设置最大,自动重装设置最大→T=59.65≈1/(72M/65536/65536))的定时
若想计时更长时间,STM32定时器支持级联模式(一个定时器输出当作另一个定时器输入→T=59.65*65536*65536=8035年)
- 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
- 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
定时器类型
类型 |
编号 |
总线 |
功能 |
高级定时器 |
TIM1、TIM8 |
APB2 |
拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能 |
通用定时器 |
TIM2、TIM3、TIM4、TIM5 |
APB1 |
拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能 |
基本定时器 |
TIM6、TIM7 |
APB1 |
拥有定时中断、主模式触发DAC的功能 |
- STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
定时器框图
基本定时器
预分频器:对72MHZ的计数时钟进行预分频(1→2分频,输出频率=输入频率/2=36MHZ),预分频16位,最大值为65535,也就是65536分频
计数器:对预分频后的计数时钟进行计数,计数时钟每来一个上升沿,计数器值加1,计数器16位,值从1加到65535,再加就为0重新开始;计数器一直自增到目标值,就会产生中断,完成定时任务
自动重装寄存器:16位,存入计数目标值,当计数器自增到目标值,就会产生中断信号,并且清零计数器,计数器自动开始下一次的计时(向上计数)(更新中断)
UI更新中断→NVIC(NVIC定时器通道)→CPU
U更新事件,不触发中断,但触发内部其他电路工作(DAC)
主模式触发DAC功能:内部硬件在不受程序控制下实现自动运行
思路①:设置定时器中断→中断程序调用代码手动触发DAC转换→DAC输出(影响主程序运行和其他中断响应)
思路②:主模式:定时器更新事件映射到触发输出TRGO(Trigger Out)位置→DAC触发转换引脚并触发
通用定时器
计数器
向上计数模式(加):0→重装值→0(申请中断)
#define TIM_CounterMode_Up ((uint16_t)0x0000)
向下计数模式(减):重装值→0→重装值(申请中断)
#define TIM_CounterMode_Down ((uint16_t)0x0010)
中央对齐模式(加减):0(增)→重装值(申请中断)→(减)0(申请中断)
#define TIM_CounterMode_CenterAligned1 ((uint16_t)0x0020)
#define TIM_CounterMode_CenterAligned2 ((uint16_t)0x0040)
#define TIM_CounterMode_CenterAligned3 ((uint16_t)0x0060)
时钟输入
若对ETR时钟进行计数,把定时器当作计数器来用(外部时钟模式2)
TRGI(Trigger In),主要作为触发输入(可触发定时器从模式),当作外部时钟输入(外部时钟模式1)
通过这一路外部时钟:
①ETR引脚信号(占用触发输入模式)
②ITR时钟信号(来自其他定时器的TRGO输出),主模式输出TRGO可以通向其他定时器(接到它们的ITR引脚),这一路可以实现定时器级联功能
外部时钟模式1:输入可以是ETR引脚(一般情况),其他定时器,CH1引脚边沿,CH1引脚,CH2引脚
若使用外部时钟,首选ETR引脚外部时钟模式2的输入
输出比较电路(四个输出控制)
输入捕获电路(四个通道)
捕获/比较寄存器
是输入捕获和输出比较电路共用的
高级定时器
与通用定时器三个不同的地方
定时中断基本结构图
中断信号产生会先在状态寄存器内一个中断标志位,标志位会通过中断输出控制到NVIC申请中断
中断输出控制
定时器模块有很多地方要申请中断(更新/触发信号/输入捕获,输出比较),需要某个中断就允许,不需要就禁止(中断输出控制就是一个中断输出允许位)
时序图
预分频器
一个计数周期的工作流程
开始时计数器未使能,计数器时钟不运行,计数器使能后,前半段,预分频器系数为1,计数器的时钟等于预分频器前的时钟。后半段,预分频器系数变为2,计数器的时钟就变为预分频器前时钟的一半。在计数器时钟的驱动下,下面的计数器寄存器也跟随时钟的上升沿不断自增。在FC之后,计数值变为0→ARR自动重装值就是FC。当计数值计到和重装值相等,并且下一个时钟来临时计数值才清零。同时,产生一个更新事件。
预分频器缓冲机制
①预分频器有俩个寄存器,预分频控制寄存器(读写使用,不直接决定分频系数)和预分频缓冲寄存器(起作用)
将预分频寄存器0→1,会导致在一个计数周期内,前半部分和后半部分的频率不一样,计数频率改变,改变分频值,但变化不会立刻产生,而是等到本次周期结束,产生了更新事件,预分频器的值才会被传递到缓冲寄存器里,才会生效(缓冲寄存器起作用)
因此,即使在计数中途改变预分频值,计数频率仍保持原来的频率,直到本轮计数完成(产生更新事件),在下一轮计数时,改变后的分频值才会起作用
②预分频器内部实际也是靠计数分频,当预分频值为1时,计数器就010101这样计数,回到0时输出一个脉冲(输出频率=输入频率的二分频)
预分频器的值和实际的分频系数之间有一个数的偏移
- 计数器计数频率:CK_CNT(定时器经过PSC预分频器之后,驱动计数器计数) = CK_PSC(预分频器输入时钟频率) / (PSC + 1);
- 预分频系数(参数/因子)=PSC(分频值)+1
计数器
由于分频系数为2,CK_CNT=CK_INT/2
计数器溢出频率:
CK_CNT_OV (定时器溢出频率/计数周期频率)
= CK_CNT(计数) / (ARR 自动重装载值+ 1)
= CK_PSC(输入时钟频率) / (PSC(预分频器的值) + 1) / (ARR自动重装值 + 1)
=72HMZ/(PSC+1)/(ARR+1)
ARR自动重装载值,定时器计数到ARR+1次时触发溢出或更新事件
计数器无预装
当计数器自增时,加入一个自动加载寄存器(自动重装寄存器,FF→36),计数目标值就改成36,计数器自增到36直接开始下一轮计数
计数器有预装
在计数器自增时,加入一个自动加载寄存器(自动重装寄存器,计数目标F5→36)
影子寄存器真正起作用,目前还是F5,所以计数目标计数到F5,产生更新事件,同时更改的36才被传递到影子寄存器,在下一个计数周期,36才作为计数目标
影子寄存器的存在,让值的变化和更新事件同步发生,防止在运行途中更改造成错误
RCC时钟树
STM32中用来产生和配置时钟,并且把配置好的时钟发送到各个外设的系统
时钟是所有外设的基础→最先需要配置好时钟
在主程序执行之前会执行一个Systemlnit函数→配置时钟树
时钟产生电路
俩个高速晶振,提供系统时钟,可分配AHB,APB2,APB1时钟
内部和外部都有一个8MHZ的晶振,但外部更稳定
①先是内部高频振荡器输入时钟
②当外部高频振荡器倍频到72MHZ,就选择其输入时钟
若外部晶振出问题,可能会导致程序时钟慢了大概10倍(会选择内部8MHZ运行)
CSS时钟安全系统,监测外部时钟运行状态,外部时钟失效,自动转换为内部时钟,保证时钟运行
时钟分配电路
72MHZ进入AHB总线(AHB内预分频器,在Systemlnit分配系数为1→AHB时钟72MHZ),进入AHB1总线(分配系数2→APB1总线时钟为72/2=36MHZ)
APB2系数为1,频率不变,为72MHz
TIM定时器代码编写
1.初始化TIM定时器
①开启RCC时钟(定时器基准时钟和整个外设时钟都会打开)
②选择时基单元时钟源,对于定时中断,选择内部时钟源
③配置时基单元(预分频器,自动重装器,计数模式)用结构体配置
④配置输出中断控制,允许更新中断输出到NVIC
⑤配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级
⑥配置运行控制,整个模块配置完成后,需要使能计数器,使计数器运行
计数器使能,计数器计数,定时器更新并触发中断
⑦配置中断函数
2.库函数的认识
定时器库函数:
①TIM_DeInit(TIM_TypeDef* TIMx);
*恢复初始配置
②TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
*时基单元初始化
③TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
*将结构体变量赋一个默认值
④TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
*使能计数器(运行控制)
⑤TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
使能外设中断输出信号
时基单元时钟源选择部分:
1. RCC内部时钟
TIM_InternalClockConfig(TIM_TypeDef* TIMx);
内部时钟模式
2. ETR外部时钟
1)TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
ETR通过外部时钟模式1输入时钟
2)TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
ETR通过外部时钟模式2输入时钟
若不需要触发输入功能,俩函数可以互换
TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
单独配置ETR引脚预分频器,极性,滤波器参数
3. ITRX其他定时器
TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
外部时钟模式1
4. TIX捕获通道
TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource, uint16_t TIM_ICPolarity, uint16_t ICFilter);
定时器型号,TIx具体引脚,输入极性,滤波器
外部时钟模式1
自动重装值和预分频值可能会在初始化之后还需修改
①TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
单独写预分频值
②TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
改变计数器的计数模式
③TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
自动重装器预装功能配置
④TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
给计数器写入一个值(手动输入)
⑤TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);
给自动重装器写入一个值
⑥uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
获取当前计数器的值
⑦uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);
获取当前预分频器的值
获取标志位和清楚标志位(之前课程写过)
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
vo id TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
STM32-CSDN博客(包含NVIC初始化与使用)
3.代码中出现的参数
滤波器工作原理中TIM_Clock_Division_CKD分频
滤波器可以滤掉信号抖动干扰
在采样频率f采取N个采样点,若都是相同电平,表示稳定
若N个采样值不全相同,就说明信号有抖动,保持上一次的输出,或者直接输出低电平,以此来保证输出信号好在一定程度上的滤波
采样频率f和采样点数N都是滤波器的参数
频率越低,采样点数越多,滤波效果越好,但信号延迟越大
采样频率f从内部时钟或者由内部时钟加一个时钟分频产生,分频多少TIM_Clock_Division_CKD决定
#define TIM_CKD_DIV1 ((uint16_t)0x0000) 1分频=不分频
#define TIM_CKD_DIV2 ((uint16_t)0x0100)
#define TIM_CKD_DIV4 ((uint16_t)0x0200)
4.Timer.c代码编写
#include "stm32f10x.h" // Device header
int16_t number;
//初始化函数
void Timer_Init(void)
{
/*
初始化定时器
①开启RCC时钟(定时器基准时钟和整个外设时钟都会打开)
②选择时基单元时钟源,对于定时中断,选择内部时钟源
③配置时基单元(预分频器,自动重装器,计数模式)用结构体配置
④配置输出中断控制,允许更新中断输出到NVIC
⑤配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级
⑥配置运行控制,整个模块配置完成后,需要使能计数器,使计数器运行
计数器使能,计数器计数,定时器更新并触发中断
⑦配置中断函数
*/
//初始化TIM2 通用定时器
//①开启时钟 APB1开启时钟
RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM2,ENABLE);
//②选择时基单元时钟,tim.h,选择内部时钟
TIM_InternalClockConfig(TIM2);
//③配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up ;//向上计数
TIM_TimeBaseInitStructure.TIM_Period=10000-1;//ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;//PSC预分频器的值
//定时1s-1HZ
//计数器溢出频率CK_CNT_OV=72MHZ/(PSC+1)/(ARR+1)
//时间周期=1/CK_CNT_OV
//PSC和ARR取值范围是0~65535
//因为从0开始,所以减1
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//高级定时器的重复计数器的值,初级没有
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
//④使能更新中断
TIM_ITConfig(TIM2,TIM_IT_Update, ENABLE);//更新中断
//⑤配置NVIC
//NVIC分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//NVIC初始化
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
//⑥启动计数器
TIM_Cmd(TIM2, ENABLE);
}
//⑦配置中断函数
void TIM2_IRQHandler(void)
{
//检查中断标志位
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
number++;
//清楚标志位
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
5.Timer.h
#ifndef __TIMER_H_
#define __TIMER_H_
void Timer_Init(void);
extern int16_t number;
#endif
6.main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1,1,"number:");
while(1)
{
OLED_ShowNum(2,3,number,5);
}
}
OLED显示问题:
在OLED上显示number数字从1开始
原因:
TimeBaseInit函数生成一个更新事件,立刻重新装载预分频器和重复计数器的值
预分频器有个缓冲器,写入的值只有在更新事件时才会真正起作用
这个函数为了让写入的值立刻起作用,手动生成一个更新事件,但更新事件和更新中断同时发生,更新中断会置更新中断标志位,初始化后,更新中断立刻进入
解决:
在TimeBaseInit函数后面,开启中断的前面,手动调用一下 TIM_ClearFlag(TIM2,TIM_FLAG_Update)手动清除标志位
或者简单粗暴,直接 OLED_ShowNum(2,3,number-1,5);
TIM定时外部时钟
Timer.c代码编写
①更换内部时钟为ETR外部时钟模式2
②使用PA0作为ETR引脚,进行GPIO初始化
#include "stm32f10x.h" // Device header
int16_t number;
//初始化函数
void Timer_Init(void)
{
/*
初始化定时器
①开启RCC时钟(定时器基准时钟和整个外设时钟都会打开)
②选择时基单元时钟源,对于定时中断,选择内部时钟源
③配置时基单元(预分频器,自动重装器,计数模式)用结构体配置
④配置输出中断控制,允许更新中断输出到NVIC
⑤配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级
⑥配置运行控制,整个模块配置完成后,需要使能计数器,使计数器运行
计数器使能,计数器计数,定时器更新并触发中断
⑦配置中断函数
*/
//初始化TIM2 通用定时器
//①开启时钟 APB1开启时钟
RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
//手册推荐浮空输入,其使用条件是,外部输入信号功率很小,内部上拉电阻可能会影响到这个输入信号,可使用浮空输入
//推荐上拉输入,因为红外输出是低电平,下拉对电平不太敏感
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//②选择时基单元时钟, ETR通过外部时钟模式2输入时钟,引脚需要用到GPIO,配置GPIO
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted,0x0F);
//③配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up ;//向上计数
TIM_TimeBaseInitStructure.TIM_Period=10-1;//ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler=1-1;//PSC预分频器的值
//定时1s-1HZ
//计数器溢出频率CK_CNT_OV=72MHZ/(PSC+1)/(ARR+1)
//时间周期=1/CK_CNT_OV
//PSC和ARR取值范围是0~65535
//因为从0开始,所以减1
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//高级定时器的重复计数器的值,初级没有
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2,TIM_FLAG_Update);//手动清除标志位
//④使能更新中断
TIM_ITConfig(TIM2,TIM_IT_Update, ENABLE);//更新中断
//⑤配置NVIC
//NVIC分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//NVIC初始化
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
//⑥启动计数器
TIM_Cmd(TIM2, ENABLE);
}
//⑦配置中断函数
void TIM2_IRQHandler(void)
{
//检查中断标志位
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
number++;
//清楚标志位
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
main.c代码编写
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1,1,"number:");
OLED_ShowString(3,1,"CNT:");
while(1)
{
OLED_ShowNum(2,3,number,5);
OLED_ShowNum(4,3,TIM_GetCounter(TIM2),5);
//时基单元没有分频,遮挡CNT加1,加到9,申请中断,number加1
//有预分频,遮挡几次CNT加1
}
}