【STM32】定时器

一、定时器功能

  • 1.定时器基本定时功能,也就是定一个时间,然后让定时器每隔这个时间产生一个中断,来实现每隔一个固定时间执行一段程序的目的。例如做时钟,秒表时都需要用一个定时中断的功能。
  • 2.定时器的输出比较功能,产生PWM波形,用于驱动电机等设备
  • 3.定时器的输入捕获功能,实现测量方波频率
  • 4.定时器的编码器接口,可以更方便的读取正交编码器的输出波形

二、定时器简介

  • 1.对输入的时钟进行计数,并在计数值达到设定值时触发中断。(定时触发中断)16位计数器,预分频器,自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最长59.65s(1/72M/65536/65536)的定时。
    1)自动重装寄存器:计数的目标值,计时时钟后申请中断
    2)预分频器:对计数器时钟进行分频,让计数更加灵活
  • 2.不但具备基本的定时中断功能,而且还包含内外时钟源选择,输入捕获,输出比较,编码器接口,主从触发模式等多种功能

三、定时器种类

  • 定时器分为高级定时器、通用定时器、基本定时器。
    在这里插入图片描述

STM32F103C8T6只有四个定时器: TIM1, TIM2, TIM3, TIM4。

3.1 基本定时器

在这里插入图片描述

  • 主模式触发DAC的功能:能让内部的硬件在不受程序的控制下来实现自动运行。
  • TIM2_CH1_ETR: TIM2的CH1和ETR都复用在了PA0。

3.2 通用定时器

在这里插入图片描述

从模式控制器不属于触发控制器的一部分,它们是通用定时器中两个不同的功能模块。

  • 触发控制器
    主要功能是管理定时器的触发输出(TRGO),用于将定时器的内部事件(如定时溢出、比较匹配等)转化为触发信号,去触发其他外设(如另一个定 时器、DAC、ADC等),实现多外设之间的同步协作,重点在“输出触发信号”。
  • 从模式控制器
    作用是控制定时器工作在“从模式”下的行为。当定时器作为从设备时,从模式控制器会根据外部输入的触发信号(或内部其他触发源),决定定时器的计数、复位等操作方式(比如选择复位模式、门控模式、触发模式等),重点在“接收触发信号并控制自身工作模式”。
  • 简言之触发控制器聚焦于“对外触发”,从模式控制器聚焦于“接收触发并控制自身”,二者功能独立,并非包含关系。

外部时钟模式:

  • 外部时钟模式1(External Clock Mode 1)
    核心特征:属于从模式的一种,依赖外部触发信号通过TRGI路径输入,由从模式控制器控制计数。
    具体流程:

    1. 外部信号从定时器通道引脚(如TIMx_CH1~CH4)输入
    2. 经输入滤波、边沿检测后生成TIxFPx信号
    3. 通过捕获比较模块处理后产生TRGI触发信号
    4. 从模式控制器接收TRGI信号,按配置(如触发模式)控制计数器计数
  • 外部时钟模式2(External Clock Mode 2)
    核心特征不经过从模式控制器,直接由ETR引脚信号驱动计数。
    具体流程:

    1. 外部时钟信号从专用ETR引脚输入
    2. 经极性选择、预分频、滤波处理后生成ETRF信号
    3. ETRF信号直接连接到计数器时钟输入端,驱动计数

简言之:

  • 模式1:依赖通道引脚→TRGI→从模式控制器(属于从模式范畴)
  • 模式2:依赖ETR引脚→直接驱动计数(独立于从模式控制器)

3.3 高级定时器

在这里插入图片描述
重复计数计数器:原本是一个计数周期更新一次,现在可以几个周期更新一次相当于又一次分屏。

四、定时中断

4.1 基本结构图

在这里插入图片描述

4.2 预分频器时序

在这里插入图片描述

  • 预分频器的输入时钟(CK_PSC):这是输入到预分频器的原始时钟信号,是定时器计数的基础时钟源之一。
  • 计数器使能(CNT_EN):控制计数器是否使能的信号,高电平通常表示计数器使能,可进行计数操作。
  • 定时器时钟(CK_CNT):经过预分频器处理后,提供给计数器的时钟信号,计数器将基于此时钟进行计数。
  • 计数器寄存器:显示计数器的计数值变化,图中从 F7 开始计数,直到达到自动重装寄存器(ARR)的值(这里 ARR = FC),之后会产生更新事件并重置计数值。
  • 更新事件(UEV):当计数器计数值达到自动重装寄存器(ARR)的值时,会产生更新事件,用于触发定时器的一些操作,如图中显示产生了更新事件。

预分频器参数变化的影响
当预分频器的参数从 0 变到 1 时:
根据图中,由于改变预分频器时,本次周期还没有结束,如果此时改变参数会导致在一个周期内,时钟信号频率不相同。因此会先存入预分频缓冲器(也被称为影子寄存器),等到本次周期结束,下一个周期开始再真正改变预分频器的值。

4.3 计数器时序

在这里插入图片描述
与预分频器类似

4.4 计数器预装

在这里插入图片描述
在这里插入图片描述
预装影子寄存器原理与预分频器类似

4.5 时钟树

时钟树:STM32用来产生和配置时钟,并把配置好的时钟发送到各个外设系统,SystemInit就是用来配置时钟树的。(目前了解一下即可)
在这里插入图片描述

五、运用内外部时钟实现定时中断(附源码)

  • 大致步骤(参考上面定时中断基础结构图)
    在这里插入图片描述

5.1 内部时钟实现定时器中断

在这里插入图片描述

  • 接线图

源代码

  • Timer.h 模块代码
#ifndef __TIMER_H
#define __TIMER_H

void Timer_Init(void);

#endif

  • Timer.c 模块代码
#include "stm32f10x.h"                  // Device header

/**
  * 函    数:定时中断初始化
  * 参    数:无
  * 返 回 值:无
  */
void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	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的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
																//TIM_TimeBaseInit函数末尾,手动产生了更新事件
																//若不清除此标志位,则开启中断后,会立刻进入一次中断
																//如果不介意此问题,则不清除此标志位也可
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}
*/

  • main.c模块代码
在这里插入代码片#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"

uint16_t Num;			//定义在定时器中断里自增的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Timer_Init();		//定时中断初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Num:");			//1行1列显示字符串Num:
	
	while (1)
	{
		OLED_ShowNum(1, 5, Num, 5);			//不断刷新显示Num变量
	}
}

/**
  * 函    数:TIM2中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断
	{
		Num ++;												//Num变量自增,用于测试定时中断
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位
															//中断标志位必须清除
															//否则中断将连续不断地触发,导致主程序卡死
	}
}

把中断函数放在main函数中是因为把变量Num放在同一个文件中,或者使用extern关键词把Num放到展示到所有文件。

5.2 外部时钟实现定时中断

在这里插入图片描述
接线图

源代码

  • Timer.h 模块代码
#ifndef __TIMER_H
#define __TIMER_H

void Timer_Init(void);
uint16_t Timer_GetCounter(void);

#endif

  • Timer.c 模块代码
#include "stm32f10x.h"                  // Device header

/**
  * 函    数:定时中断初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数配置为外部时钟,定时器相当于计数器
  */
void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	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);						//将PA0引脚初始化为上拉输入
	
	/*外部时钟配置*/
	TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
																//选择外部时钟模式2,时钟从TIM_ETR引脚输入
																//注意TIM2的ETR引脚固定为PA0,无法随意更改
																//最后一个滤波器参数加到最大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的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
																//TIM_TimeBaseInit函数末尾,手动产生了更新事件
																//若不清除此标志位,则开启中断后,会立刻进入一次中断
																//如果不介意此问题,则不清除此标志位也可
																
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * 函    数:返回定时器CNT的值
  * 参    数:无
  * 返 回 值:定时器CNT的值,范围:0~65535
  */
uint16_t Timer_GetCounter(void)
{
	return TIM_GetCounter(TIM2);	//返回定时器TIM2的CNT
}

/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}
*/

  • main.c 模块代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"

uint16_t Num;			//定义在定时器中断里自增的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Timer_Init();		//定时中断初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Num:");			//1行1列显示字符串Num:
	OLED_ShowString(2, 1, "CNT:");			//2行1列显示字符串CNT:
	
	while (1)
	{
		OLED_ShowNum(1, 5, Num, 5);			//不断刷新显示Num变量
		OLED_ShowNum(2, 5, Timer_GetCounter(), 5);		//不断刷新显示CNT的值
	}
}

/**
  * 函    数:TIM2中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断
	{
		Num ++;												//Num变量自增,用于测试定时中断
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位
															//中断标志位必须清除
															//否则中断将连续不断地触发,导致主程序卡死
	}
}

关于【STM32】定时器的讲解就到这里,希望对你有所帮助,感谢观看ovo!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值