目录
STM32F407定时器概述
一共14个,包含12个16位计数器和2个32位计数器。
一、通用定时器(以TIM2~5为例)
1、概述
可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)等。
使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。
STM32 的每个通用定时器都是完全独立的,没有互相共享的任何资源。
2、计数器框图
1.定时器时钟来源(含内部时钟84MHz和各路外部时钟)产生时基电路时钟源CK_PSC
2.时基电路 3.输入捕获 4.输出比较 5.相关捕获/比较寄存器
图源中文参考手册p393
名词注解:
TIMx_ETR:TIMER外部触发引脚 ETR:外部触发输入
ETRP:分频后的外部触发输入 ETRF:滤波后的外部触发输入
ITRx:内部触发x(由其他定时器触发,也是外部触发方式)(定时器级联时使用)
TI1F_ED:TI1的边沿检测器
TI1FP1/2:滤波后定时器1/2的输入
TRGI:触发输入 TRGO:触发输出
CK_PSC:分频器时钟输入
CK_CNT:定时器时钟。(定时周期的计算就靠它)
TIMx_CHx:TIMER的输入脚 TIx:应该叫做定时器输入信号x
ICx:输入比较x ICxPS:分频后的ICx
OCx:输出捕获x OCxREF:输出参考信号
1)时基电路时钟源CK_PSC的四种来源:
1)内部时钟CK_INT
2)ETRF时钟:定时器外部引脚TIMx_ETR,经过极性选择、边沿检测、预分频器后再经过输入滤波,产生ETRF时钟信号
3)ITR0~ITR3:内部触发输入口,用于定时器级联(一个定时器的输出作为另一个定时器的输入),来自于其他定时器
4)TI1F_ED、TI1FP1、TI1FP2,来源于定时器外部通道引脚TIMx_CH1、TIMx_CH2
CK_PSC经过预分频后生成CK_CNT,为计数器最终的时钟来源。
2)时基单元
可编程定时器的主要模块由一个 16 位/32 位计数器及其相关的自动重装寄存器组成。此计数
器可采用递增方式计数。计数器的时钟可通过预分频器进行分频。
下图为基本定时器框图,截取只为了表达时基单元的结构
时基单元包括:
● 计数器寄存器 (TIMx_CNT)
● 预分频器寄存器 (TIMx_PSC)
● 自动重载寄存器 (TIMx_ARR)
时基电路配置计数器模式为向上/向下/中央对齐:
通用定时器可以向上计数、向下计数、向上向下双向计数。
①向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
②向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。
③中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
3)预分频器时序
预分频缓冲器为影子寄存器,当预分频控制寄存器的值发生变化时(分频系数改动),为避免在一个计数周期内出现两种频率的波形,影子寄存器会控制本次计数周期结束后下一个计数周期内才会发生频率变化
4)计数器时序
通过计算计数器溢出频率,求导数即为计数器溢出时间
计数器可以再有预装和无预装两种时序工作(即有无缓冲寄存器),ARPE=1有预装
二、定时器中断试验(内部时钟源)
1、常用库函数:stm32f4xx_tim.c/.h
时钟源选择库函数:
2、寄存器配置
1)计数器TIMx_CNT
2)TIMx 预分频器 TIMx_PSC
3)TIMx 自动重载寄存器 TIMx_ARR
4)TIMx 控制寄存器 1 TIMx_CR1
5)TIMx DMA/中断使能寄存器 TIMx_DIER
3、定时器初始化
代码流程:使能定时器时钟→选择时钟源→定时器时基初始化(设置分频系数PSC、自动重载值ARR、计数模式、计数器选择)→开启定时器中断,配置NVIC→使能定时器→编写中断服务函数
1)选择时钟
内部时钟的配置
内部时钟来源于APB1,经倍频(由于APB1为42MHz,相对于AHB的168MHz为4分频,所以需要倍频2倍)得到CK_INT(CK_PSC 84MHz),再经过N分频产生CK_CNT
一般采用内部时钟,单片机默认也是内部时钟,所以可以忽略,但若要使用其他外部时钟,一般使用外部时钟模式2:
2)定时器时基初始化
typedef struct
{
uint16_t TIM_Prescaler; //预分频系数
uint16_t TIM_CounterMode; //计数模式
uint16_t TIM_Period; // 自动装载值ARR
uint16_t TIM_ClockDivision; //不用
uint8_t TIM_RepetitionCounter; //不用
} TIM_TimeBaseInitTypeDef;
例子程序要求:
通过定时器中断配置,每500ms中断一次(即1s中断两次),然后中断服务函数中控制LED实现LED1状态取反(闪烁)。
公式:Tout(溢出时间)=(ARR+1) (PSC+1)/Tclk
Tclk:APB1的时钟经过倍频得到的时钟(未经过预分频)
∵计数器的时钟频率为f(CK_CNT)=Tclk/ (PSC+1),时钟周期为1/f
∴时钟周期*(重载值+1)为溢出时间
∵Tclk=84MHz,为方便计算,设置PSC预分频系数为8399
∴f(CK_CNT)=Tclk/ (PSC+1)=10kHz
∵Tout=500ms,10kHz计数多少次为500ms?计数5000次
∴设置ARR为4999
再例子:要求1s采集100个数字如何设置ARR和PSC
1s采集100个数据,即10ms采集一个数据,即Tout溢出时间为10ms(10ms产生一个中断)
已知Tclk=84Mhz,同样设置PSC=8399,有fCLK_CNT=10kHz
Tout(溢出时间)=(ARR+1) (PSC+1)/Tclk=(ARR+1) /fCLK_CNT=(ARR+1) /10k=10ms
∴ARR=99,即设置自动重载值为99,计数到99后自动产生一个中断
3)定时器中断开启与配置
4、定时器中断服务函数
注意:此中断服务函数可以写在TIM.c中,也可以在哪里需要使用写到哪里(如main.c)
中间一句可以根据自己实际需求进行更改
代码(参考正点原子实验8定时器中断实验和江协科技6-1定时器中断实验)
三、定时器中断实验(外部时钟源)
1、写明时钟来源
2、在定时器初始化函数中增加对IO口的配置
此IO口必须为某一定时器的ETR管脚
例如PA0
3、代码
timer.c(来源于江协科技F1,与F4有区别)
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
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);
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;
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 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_PriorityGroupConfig(NVIC_PriorityGroup_2);
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);
}
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2);
}
/*
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
*/