stm32学习笔记---TIM输出比较(代码部分)定时器定时中断/定时器外部时钟

目录

第一个代码:定时器定时中断

Timer.c

初始化函数

初始化定时器的步骤

定时器的库函数

TIM_DeInit

TIM_TimeBaseInit

TIM_TimeBaseStructInit

TIM_Cmd

TIM_ITConfig

TIM_InternalClockConfig

TIM_ITRxExternalClockConfig

TIM_InputTriggerSource

TIM_TIxExternalClockConfig

TIM_ICPolarity和ICFilter

TIM_ETRClockMode1Config

TIM_ETRClockMode2Config

TIM_ETRConfig

TIM_PrescalerConfig

TIM_CounterModeConfig

TIM_SetCounter

TIM_SetAutoreload

TIM_GetCounterr

TIM_GetPrescaler

其他四个函数

第一步,RCC开启时钟

第二步,选择时基单元的时钟源

第三步,配置时基单元

第四步,配置输出中断控制

第五步,配置NVIC

第六步,运行控制

中断函数

Timer.h

main.c

第二个代码:定时器外部时钟

Timer.c

选择时钟源

配置GPIO

预分频和自动重装值

封装CNT计数器的值

Timer.h

main.c


声明:本专栏是本人跟着B站江科大的视频的学习过程中记录下来的笔记,我之所以记录下来是为了方便自己日后复习。如果你也是跟着江科大的视频学习的,可以配套本专栏食用,如有问题可以QQ交流群:963138186

本节我们来学习一下定时中断和内外时钟段选择的代码部分。

第一个代码:定时器定时中断

接线图:

复制OLED显示屏那一节的工程并改名

定时器不涉及外部的硬件,所以可以把它放到system文件夹里面。当然你也可以放在其他文件夹里,这个都没问题。

Timer.c

老规矩,上来就写初始化函数。

初始化函数

我们要初始化定时器的话,就看这张图

这个是定时中断的整个框架结构,我们只需要把这里面的每个模块都打通,就可以让定时器工作了。

初始化定时器的步骤

大体上的步骤就是:

第一步,RCC开启时钟这个基本上每个代码都是第一步。在这里打开时钟后,定时器的基准时钟和整个外设的工作时钟就都会同时打开了。

第二步选择时基单元的时钟源,对于定时中断,我们就选择内部时钟源。

第三步配置时基单元包括这里的预分频器、自动重装器、计数模式等等。这些参数用一个结构体就可以配置好了。

第四步配置输出中断控制,允许更新中断输出到NVIC。

第五步配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级。这部分在上节我们也用过,流程基本是一样的。

第六步运行控制整个模块配置完成后,我们还需要使能一下计数器。要不然计数器是不会运行的,当定时器使能后,计数器就会开始计数了。当计数器更新时触发中断。最后我们再写一个定时器的中断函数,这样这个中断函数每隔一段时间就能自动执行一次了。

这些就是我们初始化定时器的大体思路了。

定时器的库函数

打开tim.h文件,拖到最后,可以看到库函数的数量是非常多。但是我们先把我们本节需要用的函数挑出来学习,其他的之后再慢慢学。

TIM_DeInit

TIM_DeInit恢复缺省配置,这个不用多说。

TIM_TimeBaseInit

TIM_TimeBaseInit时基单元初始化,这函数比较重要,它就是用来配置这个图里这里的时基单元的。

这里面有两个参数,第一个TIMx选择某个定时器。第二个是结构体,里面包含了配置时基单元的一些参数。

我们在这里先把我们讲过的函数都做个标记,这样等会儿方便查找。我们可以把光标放在我们想做标记的行,然后点这个按钮。这样就可以在这行代码左边添加一个书签,如果再点一下,就会清除书签。这个对代码的运行没有任何影响。

TIM_TimeBaseStructInit

TIM_TimeBaseStructInit这个函数可以把结构体变量赋一个默认值。这个之前我们也都见过,没啥说的,我们也做个标记。

TIM_Cmd

TIM_Cmd这个是用来使能计数器的,对应的就是我们这个图里的这个位置:运行控制。

它有两个参数,第一个TIMx选择定时器,第二个NewState新的状态,也就是使能还是失能,使能计数器就可以运行,失能计数器就不运行,我们也标记一下。

TIM_ITConfig

TIM_ITConfig这个是用来使能中断输出信号的,对应的就是这个位置:中断输出控制。

它的参数看一下,第一个TIMx选择定时器。第二个TIM_IT选择要配置哪个中断输出,第三个,NewState新的状态,失能还是失能。这种TIM_ITConfig函数之后,还会经常遇到,就是使能外设的中断输出。

我们继续接下来看一下这下面的六个函数。

这六个函数对应的就是这里时基单元的时钟选择部分

可以选择RCC内部时钟,ETR外部时钟,ITRx其他定时器,TIx捕获通道这些。

TIM_InternalClockConfig

选择内部时钟,参数只有一个TIMx,调用一下这里的连接就是这样的

TIM_ITRxExternalClockConfig

选择ITRx其他定时器的时钟。参数是TIMx,选择要配置的定时器和

TIM_InputTriggerSource

选择要接入哪个其他的定时器,调用一下,这里的连接就是这样的

TIM_TIxExternalClockConfig

选择TIx捕获通道的时钟。参数第一个TIMx不用说了,第二个TIM_TIxExternalCLKSource选择TIx具体的某个引脚。

TIM_ICPolarity和ICFilter

输入的极性和滤波器。对于外部引脚的波形,一般都会有极限选择和滤波器,这样更灵活一些,调用一下这个函数,这个图里就是这样连接的

TIM_ETRClockMode1Config

选择ETR通过外部时钟模式1输入的时钟,也就是这一路

它的参数TIM_ExtTRGPrescaler外部触发预分频器,这里可以对ETR的外部时钟再提前做一个分频。

TIM_ETRClockMode2Config

选择ETR通过外部时钟模式2输入的时钟,对应的就是这一路

它的参数和上面一个函数一模一样,对于ETR输入的外部时钟而言,这两个函数是等效的,参数也是一样的,如果不需要触发输入的功能,两个函数可以互换。

TIM_ETRConfig

这个不是用来选择时钟的,用单独用来配置ETR引脚的预分频器,极性,滤波器这些函数的。

这个图里关键部分的函数基本就讲完了,时钟源选择就用这里的六个函数

时基单元用TIM_TimeBaseInit的函数;

中断输出控制,用TIM_ITConfig函数;

NVIC用上节讲过的函数;

运行控制用TIM_Cmd函数。

这样初始化基本上就ok了。

接下来我们再看几个函数。

因为在初始化结构体里有很多关键的参数,比如自动重装值和预分频值等等。这些参数可能会在初始化之后还需要更改。如果未来改某个参数,还要再调用一次初始化函数,太麻烦了,所以这里有一些单独的函数,可以方便的更改这些关键参数。

TIM_PrescalerConfig

比如这里的TIM_PrescalerConfig就是用来单独写预分频值的

参数Prescaler就是要写入的预分并值,TIM_PSCReloadMode写入的模式。我们上节说了,预分频器有个缓冲器写入的值是在更新事件发生后才有效的,所以这里有个写入的模式,可以选择是听从安排,在更新事件生效。或者是在写入后手动产生一个更新事件,让这个值立刻生效。不过这些都是细节问题,影响不大,只要知道这个是写预分频值的函数就行了。

TIM_CounterModeConfig

用来改变计数器的计数模式,参数TIM_CounterMode选择新的计数器模式。TIM_ARRPreloadConfig自动重装器预装功能配置,前面介绍了这个计数器的预装功能,有预装还是无预装是可以自己选择的。调用一下这个函数给个参数,使能还是失能就行了。

TIM_SetCounter

给计数器写入一个值。如果你想手动给一个计数值,就可以用这个函数。

TIM_SetAutoreload

给自动重装器写一个值。如果你想手动给一个自动重装值,就可以用这个函数。

TIM_GetCounterr

获取当前计数器的值。如果你想看当前计数器计到哪里了,就可以调用一下这个函数。返回值就是当前的计数器的值。

TIM_GetPrescaler

获取当前的预分频器的值。如果想看预分频值,就调一下这个函数。

其他四个函数

最后再看一下后面的这四个函数,

这四个函数熟悉,上节我们也见过。这些就是用来获取标志位和清除标志位的。

本小节我们就暂时介绍这么多。

在这里我准备初始化的是TM2,也就是通用定时器。

第一步,RCC开启时钟

这里注意要使用APB1开启时钟函数,因为TIM2APB1总线的外设

第二步选择时基单元的时钟源

打开tim.h文件,调用一下这个函数,选择内部时钟源

这样TM2的时基单元就由内部时钟来驱动了,其实不写这行代码也行,因为会默认是内部时钟

第三步配置时基单元

打开tim.h文件,调用一下这个函数。

第二个参数是个结构体就需要转到它的定义找到它的结构体类型,然后取个结构体变量名,再引出成员,并赋值,最后调用这个初始化时基单元的函数,将结构体的地址传过去。

TIM_ClockDivision这个成员的作用是什么呢?

我们之前说了,在这个定时器的外部信号输入引脚一般都会有一个滤波器,它可以滤掉信号的抖动干扰。

它工作的原理是在一个固定的时钟频率f下进行采样。如果连续n个采样点都为相同的电平,就代表输入信号稳定了,就把这个采样值输出出去。如果这n个采样值不全都相同,就说明信号有抖动,这时就保持上一次的输出或者直接输出低电平也行,这样就能保证输出信号在一定程度上的滤波。这里的采样频率f和采样点数n都是滤波器的参数,频率越低,采样点数越多,滤波效果就越好,不过相应的信号延迟就越大。这就是这个滤波器的工作原理。

f和n的关系在手册的这里也有说明,需要可以去看一下

现在关键的地方来了,这个采样频率f从哪来?

手册里写的是,它可以是由内部时钟加一个时钟直接而来,也可以是由内部时钟加一个时钟分频而来。

分频多少就是由我们这个参数可TIM_ClockDivision决定的。我们在理论部分并没有提到这个参数,可见这个参数其实跟时基单元关系并不大,它的取值随便配一个就行了。我们可以选择1分频。

TIM_CounterMode这个成员的取值分别是向上计数,向下计数和三种中央对齐的模式。我们选择向上计数。

这里并没有CNT计数器的成员,这个如果我们之后需要的话,可以用之前说的set counter和get counter这两个函数来操作计数器。

TIM_Period的取值可以参照这个式子,比如定时1s,也就是定时频率为1Hz,那么PSC可以是10000-1,ARR可以是7200-1,这样预分频是对72M进行7200分频,得到的就是10k的计数频率,在10k的频率下计10000个数,不就是一秒的时间吗?

数值不是唯一的,可以预分频给少点,自动重装给多点,这样就是以一个比较高的频率,记比较多的数。也可以预分频给多点,自动重装给少点,这样就是以一个比较低的频率,记比较少的数。

到这里,时基单元就配置好了,接下来我们就需要使能更新中断了。

第四步配置输出中断控制

这个函数就是用来使能中断的,

这样就开启了更新中断到NVIC的通路。

第五步配置NVIC

下一步自然就是NVIC

第六步运行控制

最后一步启动定时器,到tim.h文件中找一下这个函数使能TIM

这样定时器就可以开始工作了。当产生更新时,就可以触发中断。

到这里整个定时中断的初始化代码就完成了。

中断函数

接下来我们就可以写中断函数了,我们打开启动文件,找到这个TIM2的中断函数名字

这样我们的代码就已经基本完事了。

#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的值
	//因为预分频器和计数器都有1个数的偏差,所以这里都要再减个1
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到,我们现在不需要,直接给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_IT_Update
	{
		
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}
*/

Timer.h

#ifndef __TIMER_H
#define __TIMER_H

void Timer_Init(void);

#endif

main.c

我们想让定时器每秒自动帮我们加一下number这个变量。所以我先定义一个变量.

然后这个number要在中断函数里执行加加,但是这个中断函数是在timer模块里的。如果直接在这里写number加加,number就是跨越不同点c文件的变量了,这样编译就会报错。解决方法有两种。

第一种是如果你想跨文件使用变量可以在使用变量的那个文件的上面用extern声明一下要用的变量。就是告诉编译器,现在有number这个变量,它在别的文件里定义了,至于在哪里,编译器要自己去找。注意这个过程并没有定义新的变量,它操作的还是main.c里的这个number。

我们在Timer.c里面声明了这个变量之后,就可以在该文件下方的中断函数中使用这个变量了

第二种方式就是直接把这个中断函数挪到main.c文件下,就不需要声明了。

我们一般都是直接把中断函数放到使用它的文件下,所以还是挪到main.c下

最后在屏幕中显示一下Number的值

但是这样运行的结果是每次复位后都从1开始计数,看不到0这个数值,这是为什么呢?

我们打开tim.h,找到TIM_TimeBaseInit函数,然后跳转到它的定义,可以看到它的定义里有这一句注释:

意思是生成一个更新事件来立刻重新装载预分频器和重复计数器的值,为什么要加这一句?我们知道这个预分频器是有一个缓冲寄存器的,我们写的值只有在更新事件时才会真正其作用。所以这里为了让值立刻起作用,就在这最后手动生成了一个更新时件,这样预分频器的值就有效了。但同时它的副作用就是更新事件和更新中断是同时发生的,更新中断会置更新中断标志位。一旦初始化晚了,更新中断就会立刻进入。

这就是我们刚一上电就立刻进中断的原因。

解决方案也非常简单,就是在TIM_TimeBaseInit后面,和开启中断的前面,在这里调用一下这个函数,这样再手动把更新中断,标志位清除一下,就能避免刚初始化完就进中断的问题了。

这样就没问题了,上电后数字从零开始增加。

#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变量
		OLED_ShowNum(2,5,TIM_GetCounter(TIM2),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-定时器定时中断

在这里我们还可以看一下CNT计数器值的变化情况

这时可以看到CNT的值在飞速变化,变化范围应该就是从0一直到自动重装值。我们的自动重装值刚刚写的是10000-1,所以这个值就是从0一直自增到9999,总共是一万个数,记一万次就是1秒。如果我们把自动重装值改成1000,就是由原来记一万个数变成了记一千个数,这时,这个值就是从零加到了999了,对应的就是0.1秒,那就可以看到上面这个数字(也就是刚刚Num的值)加加的速度也是比原来快了十倍。

如果我们把这里改成这样:

就是以原来十倍的计数频率记10000个数,这时number加加的速度也是原来的10倍和刚才是一样的。这就是预分频值和自动重装值对中断频率的影响。

接下来我们就来开始写第二个代码。

第二个代码:定时器外部时钟

接线图:

右下角是一个OLED显示屏,上面接了一个对射式红外传感器

复制上一个工程改名:

现在我们的基本任务仍然是定时中断,但是这个时钟部分我们就不使用内部时钟了

Timer.c

其他都不变,只需要修改这些就可以了:

选择时钟源

我们到tim.h里找一下选择时钟的一个函数

我们要通过ETR引脚的外部时钟模式2配置,第二个参数是时外部触发预分频器,我们选择不需要分频。第三个参数是外部触发的极性(触发极性不是指触发信号本身的正负,而是指由它的上升沿或下降沿触发或者其他),我们可以选择不反向。第四个参数是外部触发滤波器,我们这里就暂时不用滤波器了,所以这个位置写0x00就行了。

这样通过ETR的外部时钟模式2就配置好了。

配置GPIO

这个GPIO_Mode这个可以看一下手册的配置表,手册中推荐配置是浮空输入。但是浮空输入一旦悬空,电平就会跳个没完。

所以我们可以配置为上拉输入。

什么时候需要用浮空输入?

如果外部的输入信号功率很小,内部的这个上拉电阻可能会影响到这个输入信号,这时就可以用一下浮空输入,防止影响外部输入的电平。

预分频和自动重装值

下面这个预分频和自动重装值也改小点,我们手动模拟的没那么快。预分频就给1,不需要分频。自动重装值给10,从0寄到9就行了。

封装CNT计数器的值

我们想实时看一下CNT计数器的值,我们也把它的函数也封装一下:

完整代码:

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);
	}
}
*/

Timer.h

#ifndef __TIMER_H
#define __TIMER_H

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

#endif

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-定时器外部时钟

用挡块片挡一下CNT加1,因为现在时基单元没有预分频,所以每次遮挡CNT都会加1。如果有预分频,就是遮挡几次才能加1次。

然后加到9后自动清零,同时申请中断,Num就加1。

本节的代码部分到这里就结束了,下节继续。

QQ交流群:963138186

本篇就到这里,下篇继续!欢迎点击下方订阅本专栏↓↓↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/742439.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一文读懂客户管理系统平台:概念、功能与应用场景介绍!

在当今竞争激烈的市场环境下,企业想要保持竞争力,不仅要有优质的产品和服务,更要有高效的客户管理手段。客户管理系统平台作为一种重要的工具,正在被越来越多的企业所青睐。那么,客户管理系统平台有什么用呢&#xff1…

fidder自动测试cookie脚本

前言 工作在使用fidder抓包时,经常需要找到一个请求携带的cookie中,真正校验了那些cookie,从而在代码中实现写入这些cookie的请求。这个过程除了根据经验快速过滤,就只能一个一个删除测试了。 所以我写了这个脚本,自动…

Telnet远程登录(Cisco)

Telnet 基于TCP/IP协议族 远程终端协议 在Internet上远程登录 VTY(Virtual Teletype) 通过IP连接物理上的终端 实现在Internet上 登陆和配置远程目标终端 A Router>enable Router#config Router(config)#hostname A A(config)#interface gigabitEthernet 0/0 A(confi…

ArkUI开发学习随机——得物卡片,京东登录界面

案例一:得物卡片 代码: Column(){Column(){Image($r("app.media.mihoyo")).width(200).height(200)Row(){Text("今晚玩这个 | 每日游戏打卡").fontWeight(700).fontSize(16).padding(4)}.width(200)Text("No.12").fontWe…

服务器数据恢复—raid故障导致部分分区无法识别/不可用的数据恢复案例

服务器数据恢复环境: 一台某品牌DL380服务器中3块SAS硬盘组建了一组raid。 服务器故障: RAID中多块磁盘出现故障离线导致RAID瘫痪,其中一块硬盘状态指示灯显示红色。服务器上运行的数据库在D分区,备份文件存放在E分区。由于RAID瘫…

游戏AI的创造思路-技术基础-深度学习(2)

感觉坑越挖越大,慢慢填~~~~ 继续上篇进行填坑,这一篇我们介绍下循环神经网络 目录 3.2. 循环神经网络(RNN) 3.2.1. 算法形成过程 3.2.2. 运行原理 3.2.3. RNN有哪些优缺点 3.2.4. RNN参数 3.2.5. 如何选择RNN模型参数 3.2…

【Playwright+Python】—— 环境搭建及脚本录制!

前言 看到这个文章,有的同学会说: 静姐,你为啥不早早就写完python系列的文章。 因为有徒弟需要吧,如果你也想学自学,那这篇文章,可以说是我们结缘一起学习的开始吧! 如果对你有用&#xff0…

Qt开发 | Qt界面布局 | 水平布局 | 竖直布局 | 栅格布局 | 分裂器布局 | setLayout使用 | 添加右键菜单 | 布局切换与布局删除重构

文章目录 一、Qt界面布局二、Qt水平布局--QHBoxLayout三、Qt竖直布局四、Qt栅格布局五、分裂器布局代码实现六、setLayout使用说明七、布局切换与布局删除重构1.如何添加右键菜单2.布局切换与布局删除重构 一、Qt界面布局 Qt的界面布局类型可分为如下几种 水平布局(…

Python+Pytest+Allure+Yaml接口自动化测试框架详解

PythonPytestAllureYaml接口自动化测试框架详解 编撰人:CesareCheung 更新时间:2024.06.20 一、技术栈 PythonPytestAllureYaml 版本要求:Python3.7.0,Pytest7.4.4,Allure2.18.1,PyYaml6.0 二、环境配置 1、安装python3.7,并配置…

解析分子筛自动填充高原制氧机的工作原理及优势

在高原地区,由于空气稀薄,氧气含量相对较低,这给人们的生活、工作和学习带来了诸多不便。为了解决这个问题,高原制氧机应运而生,其中分子筛自动填充高原制氧机以其高效、稳定、安全的特点受到了广泛的关注和应用。 一、…

CRMEB 多门店后台登录入口地址修改(默认admin)

一、>2.4版本 1、修改后端 config/admin.php 配置文件,为自定义的后缀 2、修改 平台后台前端源码中 view/admin/src/settings.js 文件,修改为和上面一样的配置 3、修改后重新打包前端代码,并且覆盖到后端的 public 目录下&#xff1a;打包方法 4、重启swoole 二、<2.4版…

蒙特卡洛树搜索

蒙特卡洛树搜索入门---强化学习 - 知乎蒙特卡洛树搜索&#xff08;Monte Carlo tree search&#xff09;简称MCTS&#xff0c;和一般的蒙特卡洛方法不是一个概念。通俗的理解&#xff0c;蒙特卡洛方法是随机现象中用频率来近似概率&#xff0c;模拟次数越多&#xff0c;结果越准…

从 Hadoop 迁移,无需淘汰和替换

我们仍然惊讶于有如此多的客户来找我们&#xff0c;希望从HDFS迁移到现代对象存储&#xff0c;如MinIO。我们现在以为每个人都已经完成了过渡&#xff0c;但每周&#xff0c;我们都会与一个决定进行过渡的主要、高技术性组织交谈。 很多时候&#xff0c;在这些讨论中&#xff…

项目实训-vue(十一)

项目实训-vue&#xff08;十一&#xff09; 文章目录 项目实训-vue&#xff08;十一&#xff09;1.概述2.页顶导航栏3.导航信息4.总结 1.概述 本篇博客将记录我在图片上传页面中的工作。 2.页顶导航栏 <divstyle"display: flex;justify-content: space-between;alig…

打造智能家居:用ESP32轻松实现无线控制与环境监测

ESP32是一款集成了Wi-Fi和蓝牙功能的微控制器&#xff0c;广泛应用于物联网项目。它由Espressif Systems公司开发&#xff0c;具有强大的处理能力和丰富的外设接口。下面我们将详细介绍ESP32的基础功能和引脚功能&#xff0c;并通过具体的实例项目展示其应用。 主要功能 双核处…

网络安全协议

1. 概述 1.1 网络安全需求 五种需求&#xff1a; 机密性&#xff1a;防止数据未授权公开&#xff0c;让消息对无关听众保密 完整性&#xff1a;防止数据被篡改 可控性&#xff1a;限制对网络资源&#xff08;硬件和软件&#xff09;和数据&#xff08;存储和通信&#xff0…

「2024中国数据要素产业图谱1.0版」重磅发布,景联文科技凭借高质量数据采集服务入选!

近日&#xff0c;景联文科技入选数据猿和上海大数据联盟发布的《2024中国数据要素产业图谱1.0版》数据采集服务板块。 景联文科技是专业数据服务公司&#xff0c;提供从数据采集、清洗、标注的全流程数据解决方案&#xff0c;协助人工智能企业解决整个AI链条中数据采集和数据标…

Kendryte K210 固件烧录

本章将为读者介绍 Kendryte K210 的固件烧录&#xff0c;以及 Kendryte K210 外部 NOR Flash 的空间 分布。 本章分为如下几个小节&#xff1a; 6.1 外部 NOR Flash 的空间分布 6.2 Ubuntu 下的固件烧录 6.3 Windows 下的固件烧录 外部 NOR Flash 的空间分布 Kendryte K210 的…

如何以管理员身份运行CMD?

好久没更新博客了&#xff0c;今天在日常使用中遇到了一个问题&#xff0c;顺便记录下来。 据说国内的谷歌浏览器 Chrome 可以自动升级了&#xff0c;终于不用每次都自己跑去官网下载最新版本&#xff0c;然后安装迁移&#xff0c;重复劳动。下一篇讲如何讲迁移 Chrome&#x…

【Python】已解决:Python读取字典查询键报错“KeyError: ‘d‘”

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;Python读取字典查询键报错“KeyError: ‘d’” 一、分析问题背景 在Python编程中&#xff0c;字典&#xff08;dictionary&#xff09;是一种非常重要的数据结构…