目录
第一个代码:定时器定时中断
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开启时钟函数,因为TIM2是APB1总线的外设。
第二步,选择时基单元的时钟源
打开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个数的偏差,所以这里都要再减个1TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到,我们现在不需要,直接给0TIM_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线路的抢占优先级为2NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1NVIC_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_Hvoid 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线路的抢占优先级为2NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1NVIC_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_Hvoid 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
本篇就到这里,下篇继续!欢迎点击下方订阅本专栏↓↓↓