STM32通用定时器的详细使用指南

如果不想看的可以直接使用git把我的代码下载出来,里面工程挺全的,后期会慢慢的补注释之类的

码云地址:stm32学习笔记: stm32学习笔记源码

如果不会使用git快速下载可以选择直接下载压缩包或者去看看git的使用

git的使用(下载及上传_git如何下载文件_八月风贼冷的博客-CSDN博客

目录

一、理论部分

1、什么是定时器

2、定时器类型以及功能

 3、定时中断图

​4、通用定时器框图

 二、代码部分

1、开启时钟

2、配置为使用内部的时钟源

 3、配置时基单元

4、配置中断输出控制:TIM_ITConfig

5、配置NVIC

6、配置完成后一定记得开启计数器,启动定时器。

7、中断函数

三、外部时钟(这里我会使用按键的高低电平来模拟时钟)


本章内容为基本的定时和使用外部时钟源定时,部分框图和代码分析,不涉及全部的时钟树和定时器框图分析,代码使用通用定时器2来写(TIM2)。

实验现象、让一个数每一秒加1,

一、理论部分

1、什么是定时器

2、定时器类型以及功能

 3、定时中断图

总共分为6个部分:

1、RCC的内部时钟打开 2、内部时钟模式的配置  3、配置时基单元  4、配置中断输出控制

5、NVIC配置   6、运行控制(计数器使能) 

4、通用定时器框图

本次实验只涉及到了定时中断与外部时钟源选择,所以没有涉及到下面一大部分的输入模式。

 二、代码部分

1、开启时钟

根据框图的部分、首先开启时钟(基本所以配置代码都需要开启时钟,不然后面的配置代码没有时钟就是无效的,时钟是一段代码的心脏)查询一下TIM2挂在在哪个时钟下(数据手册和参考手册都有图)。第一张的是第一张总线结构,第二章是数据手册12页的性能线框图。

注意!!!!:虽然TIM2挂在在APB1下但是他的时钟频率并不是36Mhz,具体原因看第三张图所示。

 

 这里有说明,如果APB1的分频系数=1,这里会倍频率1,否则会乘以2,因为APB1是二分频,所以这里为36×2=72Mhz。

开启时钟:在rcc.c中找到函数填入参数即可。

void TimerInit()
{
	 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	
	 
}

2、配置为使用内部的时钟源

选择内部时钟源TIM2为时钟、这个不配置也没关系,时钟初始化之后使用的本来就是内部时钟。

void TimerInit()
{
	 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	
	 TIM_InternalClockConfig(TIM2);
}

 3、配置时基单元

配置图中的时基单元:由ARR(自动重装器)、PSC(预分频器)、CNT(计数器组成)。这一部分可以直接配置结构体完成,写一下配置逻辑。

PSC:一个预分频器对输入的时钟进行分频:如PSC写入71则是对输入的72Mhz进行71+1为72的分频,分频因子都会加1(计数器计数频率:CK_CNT = CK_PSC / (PSC + 1))

CNT:计数值:可以计数,配合自动充装器完成计数以及归零

ARR:自动重载器:计时器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1) 当计数值达到预定的装在值,则归0,

假设想要一个一次一秒的计时:则psc设置为7200   此时:72000000/7200=10000 

                                                   arr设置为100000   10000/10000=1 即1hz  设定不唯一取的是我觉得方便算的值。 

这里不用自己往寄存器写入数据,调用结构体配置好就会帮你写入,找Tim.h里面的结构体。初始化时基单元。TIM_TimeBaseInitTypeDef自己声明一个结构体将成员全部点出来。代码如下:

	 TIM_TimeBaseInitTypeDef  TIM_TimeBaseInitStruct;
		TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
		TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
		TIM_TimeBaseInitStruct.TIM_Period=9999;
		TIM_TimeBaseInitStruct.TIM_Prescaler=7199;
		TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
	 TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);

参数:Clockdividion:这个东西并不是定时器的时钟分频,而是滤波器的分频,滤波器的时钟可以由内部的时钟产生,也能通过内部时钟分频产生,滤波原理就是通过采集n个点来观测这一段的频率是不是都一致,如果不一致则不输出或者直接输出低电平,f的频率越低,采样点点数越多,滤波的效果就好。本次不使用这个东西参数随便选一个。

TIM_CounterMode:为计数模式:这里有几种模式可以选择,基本使用向上计数模式,后面的折中模式基本使用在三相无刷电机、以后可能会写这个东西。

TIM_Prescaler:分频:输入为72M,对他进行7200分频让他只有10k。 

TIM_Period:周期:也就是ARR的值嘛,记到多少让他归0,这里手动输入9999,因为公式会+1,所以我们自己输入的时候要-1;(因为现在时钟是10k嘛,代表1ms内可以处理10k个数据,所以这里直接给了他10k数据,就是一秒完成了)

TIM_RepetitionCounter:重复计数器(高级定时器才有写了也没用)就是这个东西,他可以让你几个周期才产生一次中断(很牛逼)假设之前能定时1分钟,这个16位的寄存器能让这个值扩大55636倍,省时省力直接延时几万倍。

4、配置中断输出控制:TIM_ITConfig

需要配置的参数为:tim,TIM_IT,NewState

其中第二个参数可以有以下选择,1、更新中断源   2、输入比较1中断源  3差不多后面写吧、

代码如下

TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);

5、配置NVIC

这个在前面的外部中断学习笔记已经写过了,首先配置分组,并且整个工程只能有一个分组,然后初始化NVIC代码如下,其中参数为通道、使能、抢占优先级和优先级具体讲解看这一章。https://blog.csdn.net/qq_51426845/article/details/130034250?spm=1001.2014.3001.5501

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel=TIM2_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
	NVIC_Init(&NVIC_InitStruct);

6、配置完成后一定记得开启计数器,启动定时器。

	TIM_Cmd(TIM2,ENABLE);

7、中断函数

在main.c下定义一个全局变量Timer 所以要调用只能用extern声明一下变量,也可以在这个文件下面直接写个函数返回值。

//Timer.c
extern u8 Timer;

void TIM2_IRQHandler(void)
{
	 if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
	 {
		  Timer++;
		  TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	 }
}
u8 Timer=0;
/**
  * @brief  主函数
  * @param  无
  * @retval 无
  */
int main(void)
{	
  /*初始化USART 配置模式为 115200 8-N-1,中断接收*/
  USART_Config();
	delay_init();
	TimerInit();
	/* 发送一个字符串 */
	Usart_SendString( DEBUG_USARTx,"这是一个串口中断接收回显实验\n");
	printf("欢迎使用野火STM32开发板\n\n\n\n");
	
  while(1)
	{	

		 printf("%d\n",Timer);
				 delay_ms(1000);
	}	
}

ok这里我们看见复位之后直接打印了1,并没有从0开始计

 这个是因为在TIM_TimeBaseInit函数里面写了一个操作。他在代码的最后直接更新了一个事件,所以代码会默认标志位已经有值了,这里我们需要手动清除一下,直接加在中断之前就行。

 这样操作,写一个Clear来清除标志位。

 看一下效果,这里就已经是从0开始计数了。

 ok写完了内部时钟,开始写外部时钟。

三、外部时钟(这里我会使用按键的高低电平来模拟时钟)

这一章学习的就是内外时钟的选择了。其他代码不变,我们得将刚刚选择得内部时钟改为外部,外部时钟ETR。找一下tim里面得函数。

 

 这里加了一个我的按键gpio得初始化,以及屏蔽掉了内部时钟,打开了ETR的模式2,然后因为是手动模拟时钟,所以按一下就是1hz,这里我们就不分频了,重载值也就设置个3吧= =方便实验嘛。试了一下TIM_ETRClockMode1Config不管是模式1还是模式2好像都是有用的。

void TimerInit(void)
{
	 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	 //TIM_InternalClockConfig(TIM2);
	TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);
	
   GPIO_InitTypeDef  GPIO_InitStruct;
	 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;
	 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0;
	 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	 GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	 
	 TIM_TimeBaseInitTypeDef  TIM_TimeBaseInitStruct;
		TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
		TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
		TIM_TimeBaseInitStruct.TIM_Period=5;
		TIM_TimeBaseInitStruct.TIM_Prescaler=2;
		TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
	 TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
	
	TIM_ClearFlag(TIM2,TIM_IT_Update);
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel=TIM2_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
	NVIC_Init(&NVIC_InitStruct);
	
	TIM_Cmd(TIM2,ENABLE);
}

实验是成功了= =,之后会优化一下代码的,让他不变动的时候不打印,这个实验拿OLED屏幕就能很好的打印,拿串口反而麻烦。这是主函数代码。和结果图。

	
  while(1)
	{	

		 printf("num=%d\n",Timer);
		 printf("count=%d\n",TIM_GetCounter(TIM2));
				 delay_ms(500);
	}	
}

物联沃分享整理
物联沃-IOTWORD物联网 » STM32通用定时器的详细使用指南

发表评论