STM32学习笔记5-TIM定时器-1

发布于:2025-08-09 ⋅ 阅读:(19) ⋅ 点赞:(0)

定时器的基本功能:定时中断功能、内外时钟源选择

定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断

16位计数器(执行计数定时的一个寄存器)、预分频器(对计数器的时钟进行分频)、自动重装寄存器(计数的目标值,就是我想要计多少个时钟申请中断)的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时,可以使定时器进行级联,达到指数爆炸的定时时间。

不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能

根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

  • STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4

基本定时器

基本定时器只能选择内部时钟(CK_INT,默认72Mz)

预分频器:输出=输入/自己配置个数;实际分频系数=预分频器的值+1。

CNT计数器:会根据分配后的数据进行记录,当达到目标值时,产生中断,完成定时的任务;默认是向上计数;在通用和高级定时器中还有向下计数,和中央对齐(就是从0一直加,加到目标值后,又递减)

自动重装载寄存器:存储目标值,当计数器的值==自动重装载寄存器的目标值时,会产生一个中断信号 ,并清零计数器,与CNT计数器相连接。

对于自动重装载寄存器的中断信号,一般叫更新中断,之后会通过NVIC,再配置好NVIC的定时器通道,那定时器的更新中断可以被CPU响应。

:产生一个事件,同理在自动重装载寄存器中,为更新事件


定时器的一大特色:DAC主从触发模式:可以让内部的硬件在不受程序的控制下实现自动运行


在使用DAC时,可能会用DAC输出一段波形:需要每隔一段时间来触发一次DAC,让它输出下一个电压点;

如果用中断,会不断的触发中断来响应触发输出(TRGO),会很频繁

如用主从触发模式,会触发事件,由事件映射到触发输出(TRGO),然后TRGO直接接到DAC的触发转换引脚上。整个过程不需要软件的参与,实现了硬件的自动化。

通用定时器:

高级定时器

定时中断基本结构

中断输出控制:是一个中断输出的允许位,用来处理有必要的中断和无必要的中断

预分频器时序

  • 计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)

计数器时序

  • 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)

                                          = CK_PSC / (PSC + 1) / (ARR + 1)

        实际分频系数=预分频器的值+1。

ARR:自动重装载寄存器

在电路中含有阴影的,都有缓冲机制

在计数器里的ARR中就有缓冲器;且是手动配置;

计数器无预装时序

计数器有预装时序

RCC时钟树:

时钟树是STM32中用来产生和配置时钟,并且把配置好的时钟发送到各个外设的系统;

时钟是所有外设运行的基础,所以必须优先配置

STM32F10xxx参考手册(中文).pdf-13-15章

接线图:

定时器定时中断

定时器不涉及外部设备,所以放入System的文件夹中

void TIM_DeInit(TIM_TypeDef* TIMx); //恢复初始

void TIM_TimeBaseInit(TIM_TypeDef* TIMx,       TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct); //时基单元初始化

void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct); //对结构体变量赋一个默认值

void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState); 使能计数器——运行控制

void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState); //用来使能中断信号的——中断输出控制

//以下6个函数,选择外部时钟:

void TIM_InternalClockConfig(TIM_TypeDef* TIMx);  //选择内部时钟

void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource); //选择ITRx其他定时器的的时钟

void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,

                                uint16_t TIM_ICPolarity, uint16_t ICFilter);//选择Tix捕获通道的时钟

void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,

                             uint16_t ExtTRGFilter);//选择ETR通过外部时钟模式1输入时钟

void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,

                             uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter); //选择ETR通过外部时钟模式2输入时钟

void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,

                   uint16_t ExtTRGFilter);//单独来配置ETR引脚的预分频器、极性、滤波器参数的

void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);//单独写预分频值的

void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);//用来改变计数器的计数模式

void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState); //自动重装载寄存器功能配置

void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);//给计数器写一个值

void 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);

void 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);

main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Time.h"

//我们想让定时器每秒自动帮我们加一下Num这个变量
uint16_t Num;
int main(void){
	OLED_Init();
	Time_Init();
	OLED_ShowString(1,3,"hello world!"); //如果超出16个字,会从头到尾重新输入
	OLED_ShowString(2,3 ,"Num:");
	while(1){
		OLED_ShowNum(2,7,Num,5);
		OLED_ShowNum(3,7,TIM_GetCounter(TIM2),5);
	}
}
void TIM2_IRQHandler(void){
	//判断标志位是否存在
	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){
		Num++;
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}

Time.c
#include "stm32f10x.h"                  // Device header
	//1.RCC开启时钟
  //2.选择时基单元的时钟源,我们选择内部时钟源
	//3.配置时基单元,通过结构体配置
	//4.配置输出中断控制,允许更新中断输出到NVIC
	//5.配置NVIC,并打开定时器中断的通道,并分配一个优先级
	//6.运行控制,对定时器进行使能,配置中断函数
void Time_Init(void){
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);  //开启APB1的时钟函数,TIM2在APB1总线中
	//选择时基单元
	TIM_InternalClockConfig(TIM2);//系统默认是内部时钟,不写也可以
	//配置时基单元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //给输入的滤波器一个采样频率
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//计数方式
	//因为公式里CK_PSC / (PSC + 1) / (ARR + 1),所以两个值需要手动减1
	//设置1s,默认是72Mz,分频分7200,每一个波形就是10k,再设置ARR目标值为10000的时候为一个周期
	TIM_TimeBaseInitStructure.TIM_Period=10000-1;//ARR自动重装器的值
	TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;//预分配器的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//重复计数器的值,我们用的通用寄存器,所以直接写0就好了
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);//我们发现当我们按下复原键时,定时器是从1开始的,就说明复原的时候,自己走了一次,在此函数中发现它为了让值立刻起作用,所以它自己更新了中断’
	//想要从0开始,就需要把此时中断的标志值清空
	TIM_ClearFlag(TIM2,TIM_IT_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的TIM2通道
	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){
//		Num++;
//		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
//	}
//}
//由于我们打算在主文件上使用这个中断函数来改写Num的值,第一个方法:extern Num到Time.c中;第二个方法,把中断函数放到主文件里

定时器外部时钟

main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Time.h"

//我们想让定时器每秒自动帮我们加一下Num这个变量
uint16_t Num;
int main(void){
	OLED_Init();
	Time_Init();
	OLED_ShowString(1,3,"hello world!"); //如果超出16个字,会从头到尾重新输入
	OLED_ShowString(2,1 ,"Num:");
	OLED_ShowString(3,1 ,"Count:");
	while(1){
		OLED_ShowNum(2,7,Num,5);
		OLED_ShowNum(3,7,Timer_GetCount(),5);
	}
}
void TIM2_IRQHandler(void){
	//判断标志位是否存在
	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){
		Num++;
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}

Time.c
#include "stm32f10x.h"                  // Device header
	//1.RCC开启时钟
  //2.选择时基单元的时钟源,我们选择内部时钟源
	//3.配置时基单元,通过结构体配置
	//4.配置输出中断控制,允许更新中断输出到NVIC
	//5.配置NVIC,并打开定时器中断的通道,并分配一个优先级
	//6.运行控制,对定时器进行使能,配置中断函数
void Time_Init(void){
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);  //开启APB1的时钟函数,TIM2在APB1总线中
	//初始化GPIO引脚
	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);
	
	//选择Ext时基单元
	TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x00);
	//配置时基单元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //给输入的滤波器一个采样频率
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//计数方式
	//因为公式里CK_PSC / (PSC + 1) / (ARR + 1),所以两个值需要手动减1
	//不需要分频的原因是我们是使用的手动拨频,是有一定的限制的,很慢,不像机器
	TIM_TimeBaseInitStructure.TIM_Period=10-1;//ARR自动重装器的值
	TIM_TimeBaseInitStructure.TIM_Prescaler=1-1;//预分配器的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//重复计数器的值,我们用的通用寄存器,所以直接写0就好了
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);//我们发现当我们按下复原键时,定时器是从1开始的,就说明复原的时候,自己走了一次,在此函数中发现它为了让值立刻起作用,所以它自己更新了中断’
	//想要从0开始,就需要把此时中断的标志值清空
	TIM_ClearFlag(TIM2,TIM_IT_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的TIM2通道
	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){
//		Num++;
//		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
//	}
//}
//由于我们打算在主文件上使用这个中断函数来改写Num的值,第一个方法:extern Num到Time.c中;第二个方法,把中断函数放到主文件里

//实时查看CNT计数器的值
uint16_t Timer_GetCount(void){
	return TIM_GetCounter(TIM2);
}


网站公告

今日签到

点亮在社区的每一天
去签到