目录
13.1 定时器的分类
STM32F1 系列中,除了互联型的产品,共有 8 个定时器,分为基本定时器,通用定时器和高级定时器。基本定时器 TIM6 和 TIM7 是一个 16 位的只能向上计数的定时器,只能定时,没有外部IO。通用定时器 TIM2/3/4/5 是一个 16 位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,每个定时器有四个外部 IO。高级定时器 TIM1/8 是一个 16 位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,还可以有三相电机互补输出信号,每个定时器有 8 个外部 IO。
13.2 TIM基本定时器简介
13.2.1 定时器的概念和作用
STM32的TIM定时器是一种硬件计时器,用于在嵌入式系统中生成精确的时间基准。TIM实际上是"Timer/Counter (计时器/计数器) Interface Module"(计时器/计数器接口模块)的缩写。
STM32中的TIM定时器通常由一个或多个计数器、预分频器和比较器组成,并具有许多不同的工作模式。这些模式包括向上计数模式、向下计数模式、自动重载模式以及其他特殊模式。
计数器是STM32 TIM定时器的核心组件之一。它可以递增或递减,每当计数器计数达到特定值时,就会触发中断或输出信号。计数器可以被设置为向上或向下模式,并且具有可配置的计数位数。
预分频器是一个可编程的除法器,用于将外部时钟信号分频以产生所需的计数速度。预分频器的运作方式为:当计时器工作时,输入的时钟信号经过预分频器进行等分频处理,输出给计数器控制计数,从而实现了对计数器工作频率的控制。
比较器是另一个重要的组件,用于将计数器的值与预设的比较值进行比较。当两者相等时,可以触发中断或输出信号。通过设置比较器的阈值,可以实现周期性任务等功能。
除了以上基本组件,STM32 TIM定时器还提供了许多高级功能,例如输入捕获模式、PWM波形生成模式、编码器模式等。这些功能使得TIM定时器变得更加灵活和功能强大,可适应不同的应用场景。
总之,STM32 TIM定时器是一种非常重要的硬件计时器,在嵌入式系统中具有广泛的应用前景。通过配置它的各种参数和模式,我们可以实现各种功能,如延时、频率计数、PWM波形生成等,并提供高精度和可靠性的时间基准。
13.2.2 TIM基本定时器的工作原理和使用场景
TIM基本定时器的工作原理如下:当定时器启动时,计数器开始从0开始递增计数,并在计数器达到特定值时产生更新事件,可以触发中断或输出信号。通过设置预分频器的值,可以控制计时器的输入时钟频率,进而控制计数器的计数速度,从而实现所需的计时周期。
使用场景:
延时操作:通过配置计数器的初始值和计数周期,可以实现不同长度的延时操作。
脉冲计数:利用计数器递增的特性,可以将外部脉冲信号转化为可计数的低频信号,并通过比较器判断周期是否完成,实现对脉冲信号的计数和测量。
频率计数:与脉冲计数类似,通过设置计数器的计数周期和预分频器的分频系数,可以将高频脉冲信号转化为可计数的低频信号,并通过比较器判断周期是否完成,实现对频率的计数和测量。
PWM波形生成:通过设置计数器的计数周期和比较器的比较值,可以实现周期性的PWM波形输出,并通过比较器的占空比设置,调节PWM波形的占空比,从而控制电机、LED等设备的亮度或速度。
周期性任务:通过定时器的中断功能,可以创建周期性任务,例如定时读取温度传感器数据、驱动显示屏幕刷新等操作。
总之,STM32 TIM基本定时器是一种非常重要的硬件计时器,在嵌入式系统中具有广泛的应用前景。通过合理配置其参数和模式,可以实现各种延时、计数、PWM波形生成等功能,同时提供高精度、可靠性和稳定性的时间基准。
13.3 TIM基本定时器功能框图
13.3.1 时钟源
定时器时钟 TIMxCLK,即内部时钟 CK_INT,经 APB1 预分频器后分频提供,如果 APB1 预分频系数等于 1,则频率不变,否则频率乘以 2,库函数中 APB1 预分频的系数是 2,即 PCLK1=36M,所以定时器时钟 TIMxCLK=36*2=72M。
13.3.2 控制器
控制器用于控制定时器的:复位、使能、计数、触发DAC,涉及到的寄存器为:CR1/2、DIER、EGR、SR。
13.3.3 时基(定时器的心脏)
13.3.3.1 预分频器
16bit的预分频器PSC对内部的时钟CKPSC进行分频之后得到计数器时钟CK_CNT=CK_PSC/(PSC+1),计数器在CNT的马区动下,开始计数,计数一数时间为/Ck_CNT
13.3.3.2 计数器、自动重装载寄存器
定时器使能(CEN置1)后,计数器CNT在CK_CNT驱动下计数当TCNT值与ARR的设定值相等时,就自动生成事件并CNT自动请零,然后自动重新计数。
13.3.4 影子寄存器
影子寄存器是一种常用的嵌入式系统技术,通常用于存储设备外部接口的状态信息。影子寄存器与设备实际寄存器一一对应,可以在设备实际寄存器发生改变时快速更新,从而提高系统的响应速度和稳定性。
在实际使用中,影子寄存器通常由两个寄存器组成,一个是设备实际寄存器,另一个是影子寄存器。在设备初始化时,将设备实际寄存器的值复制到影子寄存器中,并在后续代码中仅使用影子寄存器进行读写操作。当设备实际寄存器的值发生改变时,通过中断或轮询等方式及时更新影子寄存器的值,从而保持最新的状态信息。
使用影子寄存器有以下几个优点:
提高响应速度:由于直接读取影子寄存器,无需访问设备实际寄存器,因此能够更快地获取状态信息并进行相应的处理。
提高系统稳定性:影子寄存器能够及时反映设备实际寄存器的值,避免了由于读取过程中设备实际寄存器的值发生变化导致的不一致。
提高代码可读性:使用影子寄存器可以简化代码逻辑,使代码更加易读、易懂和易维护。
总之,影子寄存器是一种常用的嵌入式系统技术,在外部接口的状态信息存储、处理和控制等方面发挥着重要作用,能够提高系统的响应速度和稳定性,并使代码更加简洁、易读和易于维护。
13.4 TIM基本定时器的初始化和配置方法
13.4.1 定时时间的计算
定时器的定时时间等于计数器的中断周期乘以中断的次数。计数器在 CK_CNT 的驱动下,计一个数的时间则是 CK_CLK 的倒数,等于: 1/(TIMxCLK/(PSC+1)),产生一次中断的时间则等于:1/(CK_CLK * ARR)。如果在中断服务程序里面设置一个变量 time,用来记录中断的次数,那么就可以计算出我们需要的定时时间等于: 1/CK_CLK* (ARR+1)*time。
例如:实现1ms的定时
PSC = 72–1 = 72M/(PSC+1) = 1MHz,1us
ARR = 1000-1,即从0计数到999,则计了1000次
中断周期 T = 100*1/1000000 = 1ms
13.4.2 时基初始化结构体
typedef struct
{
uint16_t TIM_Prescaler; //预分频器
uint16_t TIM_CounterMode; //计数模式
uint16_t TIM_Period; //定时器周期
uint16_t TIM_ClockDivision; //时钟分频
uint8_t TIM_RepetitionCounter; //重复计数器
} TIM_TimeBaseInitTypeDef;
(1) TIM_Prescaler:定时器预分频器设置,时钟源经该预分频器才是定时器时钟,它设定 TIMx_PSC寄存器的值。可设置范围为 0 至 65535,实现 1 至 65536 分频。
(2) TIM_CounterMode:定时器计数方式,可是在为向上计数、向下计数以及三种中心对齐模式。基本定时器只能是向上计数,即 TIMx_CNT 只能从 0 开始递增,并且无需初始化。
(3) TIM_Period:定时器周期,实际就是设定自动重载寄存器的值,在事件生成时更新到影子寄存器。可设置范围为 0 至 65535。
(4) TIM_ClockDivision:时钟分频,设置定时器时钟 CK_INT 频率与数字滤波器采样时钟频率分频比,基本定时器没有此功能,不用设置。
(5) TIM_RepetitionCounter:重复计数器,属于高级控制寄存器专用寄存器位,利用它可以非常容易控制输出 PWM 的个数。这里不用设置。
13.5 TIM基本定时器实验
13.5.1 实验要求
利用基本定时器TIM6/TIM7定时1s,1s时间到LED灯翻转一次,蜂鸣器鸣叫一声。
13.5.2 软件设计
13.5.2.1 设计思路
首先配置时基初始化结构体,开启定时器更新中断(即定时器时间到了),配置中断优先级,使能定时器,编写中断服务函数,编写主函数。
13.5.2.2 代码分析
bsp_TimeBase.h
#ifndef _BSP_TIMEBASE_H
#define _BSP_TIMEBASE_H
#include "stm32f10x.h"
#define BASIC_TIM6
#ifdef BASIC_TIM6 //使用基本定时器6
#define BASIC_TIM TIM6
#define BASIC_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define BASIC_TIM_CLK RCC_APB1Periph_TIM6
#define BASIC_TIM_Period (1000-1)
#define BASIC_TIM_Prescaler 71
#define BASIC_TIM_IRQ TIM6_IRQn
#define BASIC_TIM_IRQHandler TIM6_IRQHandler
#else //使用基本定时器7
#define BASIC_TIM TIM7
#define BASIC_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define BASIC_TIM_CLK RCC_APB1Periph_TIM7
#define BASIC_TIM_Period (1000-1)
#define BASIC_TIM_Prescaler 71
#define BASIC_TIM_IRQ TIM7_IRQn
#define BASIC_TIM_IRQHandler TIM7_IRQHandler
#endif
void BASIC_TIM_Init(void);
#endif /*_BSP_TIMBASE_H*/
bsp_TimeBase.c
#include "bsp_TimeBase.h"
static void BASIC_TIM_Mode_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
//开启定时器时钟,即内部CK_INT = 72M
BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK,ENABLE);
//自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
TIM_TimeBaseStructure.TIM_Period = BASIC_TIM_Period;
//时钟分频系数
TIM_TimeBaseStructure.TIM_Prescaler = BASIC_TIM_Prescaler;
//时钟分频因子,基本定时器没有不用管
//TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//计数器的计数模式,基本定时器只能向上计数,没有计数模式的设置
//TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
//重复计数器的值,基本定时器没有,不用管
//TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
//初始化定时器
TIM_TimeBaseInit(BASIC_TIM,&TIM_TimeBaseStructure);
//清除计数器的中断标志位
TIM_ClearFlag(BASIC_TIM,TIM_FLAG_Update);
//开启计数器中断
TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE);
//使能计数器
TIM_Cmd(BASIC_TIM,ENABLE);
}
static void BASIC_TIM_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
//设置中断优先级分组位0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
//设置中断源
NVIC_InitStruct.NVIC_IRQChannel = BASIC_TIM_IRQ;
//设置主优先级为0
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
//设置抢占优先级为3
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
void BASIC_TIM_Init(void)
{
BASIC_TIM_NVIC_Config();
BASIC_TIM_Mode_Config();
}
中断服务函数
#include "stm32f10x_it.h"
#include "bsp_BasicTim.h"
extern uint16_t time;
void BASIC_TIM_IRQHandler(void)
{
if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET )
{
time++;
TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);
}
}
main.c
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_buzzer.h"
#include "bsp_TimeBase.h"
#include "bsp_systick.h"
volatile uint32_t time = 0; //ms计时变量
int main(void)
{
LED_GPIO_Config();
BASIC_TIM_Init();
BUZZER_GPIO_Config();
while(1)
{
//1000 * 1ms = 1s
if(time == 1000)
{
time = 0;
LED1_Toggle;
BUZZER_Toggle;
}
}
}
13.5.3下载验证
可以观察到开发板上的LED灯和蜂鸣器的状态1秒切换一次。
谢谢阅读!