STM32-定时器定时中断&定时器外部时钟(十一)

定时器定时中断&定时器外部时钟

main删除代码编译

在这里面我们来初始化定时器。那怎么来初始化定时器呢?我们可以看一下 PPT 的这个图。这个是定时中断的整个框架结构哈。我们只需要把这里面的每个模块都打通,就可以让定时器工作了。

那大体上的步骤就是,第一步 RCC 开启时钟啊,这个基本上每个代码都是第一步,不用多想。在这里打开时钟后定时器的基准时钟和整个外设的工作时钟就都会同时打开了。第二步呢,选择时基单元的时钟源。对于定时中断,我们就选择内部时钟源啊。第三步,配置时基单员,包括这里的预分频器,自动重装器,计数模式等等。这些参数用一个结构体就可以配置好了。第四步,配置输出中断控制,允许更新中断输出到 NVIC。 第五步呢,配置 NVIC, 在 NVIC 中打开定时器中断的通道,并分配一个优先级啊。这部分在上节我们也用过。流程基本是一样的。第六步就是运行控制了。整个模块配置完成后,我们还需要使能一下计数器啊,要不然计数器是不会运行的。当定时器使能后,计数器就会开始计数了。当计数器更新时,触发中断。最后我们再写一个定时器的中断函数,这样这个中断函数每隔一段时间就能自动执行一次了。好好,那这些就是我们初始化定时器的大体思路了。

先看一下定时器的库函数都有哪些哈。那我们找一下定时器 TIM 的库函数。

看一下,第一个 TIM_ DeInit, 恢复缺省配置

第二个 TIM_ TimeBaseInit, 时基单元初始化,这函数比较重要啊,它就是用来配置这个图里这里的时基单元的。这里面有两个参数,第一个 TIMX 选择某个定时器,第二个是结构体,里面包含了配置时基单元的一些参数

然后下面这几个函数我们暂时用不到啊,之后再说。

TIM_ TimeBaseStructInit 这个函数可以把结构体变量赋一个默认值

然后是 TIM_ Cmd, 这个是用来使能计数器的啊,对应的就是我们这个图里的这个位置,运行控制。它有两个参数,第一个 TIMx 选择定时器,第二个 NewState 新的状态啊,也就是使能还是失能,使能计数器就可以运行,失能计数器就不运行

然后下一个是 TIM_ IT_ Config 这个是用来使能中断输出信号的,对应的就是这个位置,中断输出控制。它的参数看一下啊,第一个 TIMx 选择定时器,第二个 TIM_ IT 选择要配置哪个中断输出,第三个 new state 新的状态,使能还是失能。这种 IT config 函数之后还会经常遇到哈,就是使能外设的中断输出

接下来看一下这下面的 6 个函数,先打上标记哈。那这 6 个函数对应的就是这里哈,时基单元的时钟选择部分,可以选择 RCC 内部时钟 ETR 外部时钟 ITRx 其他定时器 TIX 补货通道这些。

我们来看一下,第一个 TIM Internal Clock config 选择内部时钟,参数值有一个 TIMX, 调用一下,这里的连接就是这样的了。

第二个 TIM_InternalClockConfig 选择 ITRX 其他定时器的时钟,参数是 TIMX 选择要配置的定时器和 Input Trigger Source 选择要接入哪个其他的定时器,调用一下,这里的连接就是这样的了。

接着第三个 TIM_ITRxExternalClockConfig 选择 TIX 补货通道的时钟,参数第一个 TIMX 不用说了,第二个 TIX External clock source 选择 TIX 具体的某个引脚。接着还有两个参数, IC polarity 和 IC filter,输入的极性和滤波器啊。对外部引脚的波形一般都会有极性选择和滤波器,这样更灵活一些啊。调用一下这个函数,这个图里就是这样连接的。

那再下面, TIM_ETRClockMode1Config,选择 ETR 通过外部时钟模式一输入的时钟,也就是这一路哈,从下面这里走的。那它的参数 EXTTRG prescaler 外部触发预分频器啊,这里可以对 ETR 的外部时钟再提前做一个分频哈。然后接下来 polarity 和 filter 也是一样,极性和滤波器。

那下面这个 TIM_ ETRClockMode2Config 选择 ETR 通过外部时钟模式 2 输入的时钟,对应的就是这一路从上面这里走的。

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

然后最后一个函数, TIM_ ETR_ Config 这个不是用来选择时钟的啊,就是单独用来配置 ETR 引脚的预分频器啊、极性啊、滤波器这些参数的。

好到这里,这个图里关键部分的函数基本就讲完了啊,

时钟源选择用这里的 6 个函数。

时基单元用 TIM_ Base_ Init 函数

中断输出控制用 IT_ Config 函数,

NVIC 用上节讲过的 NVIC_ Init 函数啊

运行控制用 TIM_ Cmd 函数。

这样初始化基本上就 OK 了。接下来我们再看几个函数啊,因为在初始化结构体里有很多关键的参数,比如自动重装值和预分频值等等,这些参数可能会在初始化之后还需要更改,如果为了改某个参数还要再调用一次初始化函数,那太麻烦了是吧?所以这里有一些单独的函数,可以方便的更改这些关键参数。

比如这里的 TIM_PrescalerConfig, 就是用来单独写预分频值的,看下参数啊, prescaler 就是要写入的预分频值,后面还有个参数, psc reload mode,写入的模式哈。我们上一小节说了,预分频器有个缓冲器,写入的值是在更新事件发生后才有效的,所以这里有个写入的模式,可以选择是听从安排,在更新事件生效,或者是在写入后手动产生一个更新事件,让这个值立刻生效。不过这些都是细节问题哈,影响不大,你就知道这个是写预分频值的函数就行了。

然后下一个,TIM_CounterModeConfig,用来改变计数器的计数模式啊,参数 counter mode 选择新的计数器模式。

TIM_ARRPreloadConfig自动重装器预装功能配置啊,我们之前 PPT 的后面哈,介绍了这个计数器的预装功能,有预装还是无预装是可以自己选择的,怎么选择?调用一下这个函数,给个参数,使能还是失能就行了。这就是这个函数的用途。

然后再往下。 TIM_SetCounter 给计数器写入一个值,如果你想手动给一个计数值,就可以用这个函数。

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

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

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

最后再看一下后面的这四个函数啊,这四个函数熟悉吧?上节我们也见过。这些就是用来获取标志位和清除标志位的

定时器定时中断

回到这里,按照我们之前说的步骤来初始化定时器。在这里我准备初始化的是 TIM2 啊,也就是通用定时器。首先第一步是开启时钟, RCC_ APB1 外设时钟控制。这里注意哈,要使用 APB1 的开启时钟函数,因为 TIM2 是 APB1 总线的外设。那参数的第一个就是 RCC_ APB1 外设 TIM2,然后以 ENABLE,这样时钟就开启了。

接下来第二步,选择时基单元的时钟,我们到这个 TIM 点 h 文件里来,复制一下这个TIM_InternalClockConfig函数啊,我们想选择为内部时钟,然后放到这里,参数只有一个 TIMX,那我们写 TIM2,这样 TIM2 的时基单元就由内部时钟来驱动了。

不过这个选择时钟的函数啊,我看其他很多人的代码都没有写,因为定时器上电后默认就是使用内部时钟,如果不调用这个函数,那也也是使用的内部时钟,所以不写这一行也行啊。

接下来我们就到了第三步配置时基单元。我们到 TIM .h具体找一下哈。在这里我们要使用的是这个 TIM TimeBaseInit 函数来初始化时基单元。那我们复制一下,放到这里。参数第一个 TIMX, 我们写 TIM2。第二个是结构体,我们转到函数定义看一下哈。在上面这个注释找一下结构体的类型名,复制一下

放到这里。然后起个变量名啊,叫 TIM TimeBaseInit Structure。 然后先把结构体成员都引出来,先放在这里。最后把这个结构体的地址放到 Init 函数里,这样就行了。

接下来我们来一一的看一下参数啊,第一个TIM_ClockDivision, 我们跳转一下定义。然后看一下这个参数的注释啊,这里写的是指定时钟分频,这个参数可以是这里面的一个值啊。

这个时钟分频的参数之前在讲原理的时候一直都没出现过啊,那它是用来干啥的呢?我们可以看一下 PPT 里的这个图啊,我们之前说了,在这个定时器的外部信号输入引脚一般都会有一个滤波器啊,比如这里,还下面这里。这个滤波器可以滤掉信号的抖动干扰,那它是怎么工作的呢?其实也是非常简单的哈,就是在一个固定的时钟频率 F 下进行采样。如果连续 N 个采样点都为相同的电平,那就代表输入信号稳定了,就把这个采样值输出出去。如果这 N 个采样值不全都相同,那就说明信号有抖动,这时就保持上一次的输出,或者直接输出低电平也行啊,这样就能保证输出信号在一定程度上的滤波。这里的采样频率 F 和采样点数 N 都是滤波器的参数,频率越低,采样点数越多。那滤波效果就越好。不过相应的信号延迟就越大啊。这就是这个滤波器的工作原理。

现在关键的地方来了,这个采样频率 F 从哪来?那手册里写的是,它可以是由内部时钟直接而来,也可以是由内部时钟加一个时钟分频而来。

那分频多少就是由我们这个参数 clock divide 决定的。可见这个参数其实跟实际单元关系并不大啊。在这里我们随便配一个就行了啊。那我们 ctrl F 搜索一下。可以看到这个参数可以取这三个值啊

第一个是一分频,也就是不分频,第二个是二分频,第三个是四分频。那我们选个一分频吧,复制,放到这里,这样第一个参数就完成了。

接着第二个TIM_CounterMode计数器模式那我们就直接手动找一下吧,在这个 TIM 点 h 的最上面。就是这个地方,可以看到这个 counter mode 的参数

这个参数可以是这里的一个值,搜索一下哈。这里可以看到有这些种模式,

分别是向上计数,向下计数和三种中央对齐的模式。那我们选择向上计数。放到这里,这样第二个参数就完成了。

接着下面三个参数, period 的周期就是 ARR 自动重装载的值哈, prescaler 就是 PSC 预分频器的值, repetition counter 就是重复计数器的值。这些参数就是实际单元里面每个关键寄存器的参数了。不过这里并没有 CNT 计数器的参数啊,这个如果我们之后需要的话,可以用之前说的 set counter 和 get counter 这两个函数来操作计数器。那这里这个 repetition counter 是重复计数器哈,是高级定器才有的。这里不需要用,我们直接给 0 就好了。

接下来决定定时时间的参数就是这两个了。如果我们想定一个一秒的时间,那就可以参考一下 PPT 里的哈,这个公式。

定时频率等于 72 兆除以 PSC 加一,再除 ARR 加一。定时一秒,也就是定时频率为一赫兹

那我们就可以 PSC 给一个 7200 啊, ARR 给一个一万,然后两个参数都再减一个一。这样就完成了。因为预分频器和计数器都有一个数的偏差,所以这里要再减个一哈。然后注意这个 PSC 和 ARR 的取值都要在 0~6535 之间,不要超范围了。这个 PSC 和 ARR 的取值不是唯一的哈,你可以预分频给少点,自动重装给多点,这样就是以一个比较高的频率记比较多的数,也可以预分频给多点,自动重装给少点,这样就是以一个比较低的频率记比较少的数。两种方法都可以达到目标的定时时间哈。在这里我们预分频是对七十二兆进行 7200 分频,得到的就是 10K 的计数频率。在 10K 的频率下记一万个数,那不就是一秒的时间吗?这样理解也是没问题的哈。关于定时时间的计算,要么记公式,要么理解工作流程哈,这都是可以的。

好,到这里,实际单元就配置好了。

接下来我们就需要使能更新中断了,我们可以找一下。这个 TIM IT Config 就是用来使能中断的,我们复制一下,放到这里。参数第一个写 TIM2,第二个选择中断哈,我们跳转一下函数定义看一下。这个参数可以是下面这些值的任意组合,其中第一个就是 Update 更新中断,我们复制这个参数。

然后放到这里,最后第三个参数直接以ENABLE就行了。这样就开启了更新中断到 NVIC 的通路,

那下一步自然就是 NVIC 了对吧。这个

我自然就是 NVIC 了对吧。这个我们上节讲过,这里就直接写了。首先, NVIC 优先级分组, NVIC priority group configure, 分组写 NVIC priority group 2,选择分组 2。

接着, NVIC ITEM define, NVIC ITEM structure。 复制粘贴一下,把参数列出来。最后, NVIC_ Edit 取地址,粘贴结构体,这样就好了。

参数第一个中断通道,我们跳转一下定义。右边看一下啊,选中这个 IRQn Type 搜索一下,下面这个范围当前工程啊,搜索。

然后在这个列表里找到 TIM2 IRQn,这就是定时器 2 在 NVIC 里的通道,我们复制,放到这里。然后第二个参数,写 enable。下面是抢占优先级和响应优先级,我们就给个 2 和 1 吧,这样中断通道就打通了。

然后还需要最后一步,启动定时器就完事了。我们到 TIM 点 h 里找一下,复制这个 TIM cmd 函数,放到这里。然后里面的参数,第一个给 TIM2,第二个那个给 enable 这样定时器就可以开始工作了,当产生更新时就可以触发中断。

到这里整个定时中断的数字化代码就完成了。接下来我们就可以写中断函数了,我们找一下启动文件,打开。这里可以找到这个 TIM2 IRQ Handler

这个就是定时器 2 的中断函数了,我们复制一下,回到这里啊,写 void,粘贴 void。当定时器产生更新中断时,这个函数就会自动被执行。在定函数之后,检查一下中断标志位。 If TIM Get IT Status 获取中断标志位,参数第一个是 TIM2,第二个是想看哪个中断的标志位,我们跳转一下定义。那这里就选择 TIM_ IT_ UPDATE 更新中断,放到这里。如果更新中断标志位等于等于 SET, 那我们就可以执行相应的用户代码了。最后别忘了清除标志位啊, TIM_ ClearITPendingBit, TIM2,第二个参数也是这个哈。

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

想让定时器每秒自动帮我们加一下 number 这个变量,所以我先定义一个变量啊,在这里, UINT16 杠 7 number 定义一个 16 位的全局变量 number。然后这个 number 要在中断函数里执行加加,但是这个中断函数是在 TIM2 模块里的,如果直接在这里写 number 加加,那 number 就是跨越不同点 c 文件的变量了。这样编译试一下。它就会报错啊。

那解决方法有两种,第一种是,如果你想跨文件使用变量,那可以在使用变量的那个文件的上面用 extern 声明一下要用的变量。比如在这里写 extern uint16_ t number,这样就行了。

extern 声明变量就是告诉编译器我现在有 number 这个变量,它在别的文件里定义了,至于在哪里你自己去找吧。然后编译器就会勤勤恳恳的去找它,最终它发现在 main 点 c 里确实有这个变量。那编译器就知道了,它就会把这个 extern 声明的变量当做 main 点 c 变量的一个引用。注意这个过程并没有定义新的变量哈,它操作的还是 main 点 c 里的这个 number。

其实我们头文件里的函数声明也是用 extern 实现的,在这个函数的前面哈,是有一个 extern 的,只不过这个 extern 可以省略,所以我们一般不写。

那回到这里啊,当我们用 extern 声明了主函数的 number 变量时,就可以在这里直接使用主函数的 number 变量了,这样编译就不会有问题了。这是第一种解决方案,就是使用跨文件的变量,用 extern 声明即可。

那第二种方案呢,就是直接把这个中断函数复制一下,放到主函数后面,这样它们就在一个文件里了。

这里 extern 声明就不需要了,下面这个中断函数我们就注释掉。这里也删掉哈。对于定时中断而言,这个中断函数就是为别的文件服务的,所以中断函数可以放在使用它的地方,这样更方便一些。在这里我们把这个函数注释掉,当成一个中断函数的模板,如果哪个文件想用的话,就复制过去就行了。

那这里我们就暂时采用第二种解决方案了。在 main 点 c 里,中断函数每秒自动把 number 加加,然后主循环里我们显示一下就行了。这里显示字符串改一下哈,在一行一列显示 number 冒号,然后主循环里第二用 OLED 修 number,一行五列,显示 number,长度为五,这样循环刷新 number 的值就行了。我们编译看一下,没有问题啊,下载。

不过在这里会有个问题哈,就是我们复位一下,可以看到这个 number 是从一开始计数的,多复位几次啊,可以看到每次程序刚一开始, number 就立刻变成一了。按理说 number 的初始值是 0,应该从 0 开始计数的,但是 number 一上电就立刻变为一了,这说明中断函数在初始化之后就立刻进入了一次,这是怎么回事呢?

那我之前研究了一下哈,这个问题就出在这里,我们打开这个 TimeBaseInit 函数,

在最后可以看到这一句哈,上面的注释写的是生成一个更新事件来重新装载预分频器和重复计数器的值,立刻。为什么要加这一句呢?

我们知道这个预分频器是有一个缓冲寄存器的,我们写的值只有在更新事件时才会真正起作用,所以这里为了让值立刻起作用,就在这最后手动生成了一个更新事件,这样预分频器的值就有效了。但同时它的副作用就是更新事件和更新中断是同时发生的,更新中断会置更新中断标志位,当我们之后一旦初始化完了,更新中断就会立刻进入,这就是我们刚一上电就立刻进中断的原因。

那解决方案也非常简单哈,就是在 TimeBase 一例的后面,开启中断的前面,可以在这里哈,在手动调用一下 TIM Clear Flag。 TIM2 转到第一行,复制一下这个 TIM_ FLAG_ UPDATE 放到这里,这样再手动把更新中断标志位清除一下,就能避免刚初始化完就进中断的问题了。那我们再看一下。

这里直接写 OLED_ ShowNumber 二行五列显示 TIM_ GetCounter (TIM2) 长度为 5 啊。编译看一下。这时可以看到下面这个计数器的值在飞速变化啊,变化范围应该就是从 0 一直到自动重装载值。我们的自动重装载值写的是 1 万减 1,所以这个值就是从 0 一直自增到 9999。总共是一万个数,记一万次就是一秒对吧?

如果我们把自动重装载值改成一千,那就是由原来记一万个数变成了记一千个数了,

再看一下。这时这个值就是从 0 加到了 999 了,对应的就是 0.1 秒。可以看到上面这个数字加加的速度也是比原来快了 10 倍啊。如果我们把这里改回 1 万**,把下面预分频值**去掉一个 0

那就是以原来 10 倍的计数频率记 1 万个数,编译看一下。这时 number 加加的速度也是原来的 10 倍和刚才是一样的哈。这就是预分频值和自动重装值对中断频率的影响,大家可以多玩一玩这部分哈,我们之后还会经常和它们打交道的。好,这就是第一

定时器外部时钟

右下角是一个 OLED 显示屏,上面接了一个对射式红外传感器,DO 数字输出接到 PA0 引脚,这个 PA0 引脚就是 TIM2 的 ETR 引脚哈,我们就在这个引脚输入一个外部时钟,然后看下面包板,我们拿出对射式红外传感器,接三根杜邦线,然后 VCC 接正极,GND 接负极,DO 接 PA0 引脚。

的基础上更改一下啊。现在我们的基本任务仍然是定时中断,但是这个时钟部分呢,我们就不使用内部时钟了,我们把这一行删掉。

然后到 TIM 点 h 里找一下选择时钟的几个函数。在这里我们复制一下这个 TIM_ ETR_ ClockMode2Config,这个就是我们想要的啊,通过 ETR 引脚的外部时钟模式 2 配置,然后放到这里。看一下参数,第一个给 TIM2,然后剩下的参数我们转到定义看一下。

第二个参数是外部触发预分频器可以是下面的这些字,那我们不需要分频啊,就选择第一个,复制,放到这里。

接着继续,第三个参数是外部触发的极性,第一个是反向,就是低电平或下降沿有效,第二个是不反向,就是高电平或上升沿有效,这个根据需求来哈,我就选择不反向了。

放到这里。继续,第四个参数是外部触发滤波器,这个值必须是 0x00~0x0F 之间的一个值。这个滤波器我刚才介绍过了啊,就是以一个采样频率 F 采样 N 个点,如果 N 个点都一样才会有效输出,那这个值就是来决定 F 和 N 的,具体是怎样的对应关系呢?手册里有说明啊。在这个位置,大家可以自己看一下啊。

那我们这里就暂时不用滤波器了,所以这个位置写 0X00 就行了。好这样通过 ETR 的外部时钟模式 2 就配置好了。

那引脚要用到 GPIO,所以还是别忘了啊,在这之前还要先配置 GPIO,我们快速写一下,先 RCC_ APB2 外设时钟控制, RCC_ APB2 外设 GPIOA ,ENABLE,开启 GPIOA 的时钟。

接着 GPIO 类型定义,GPIO_InitTypeDef GPIO_InitStructure,然后列一下结构体成员。最后 GPIOE GPIOA 取地址, GPIO_InitStructure

那这个 GPIO mode 这个可以看一下手册的配置表啊,我们找一下。在这里啊,它这里给的推荐配置是浮空输入啊,但是我一般不太喜欢浮空输入,因为一旦悬空电平就会跳个没完,所以我准备给上拉输入哈,这也是可以的。那什么时候需要用浮空输入呢?就是如果你外部的输入信号功率很小,内部的这个上拉电阻可能会影响到这个输入信号,这时就可以用一下浮空输入,防止影响外部输入的电平。

那这里我就还用上拉输入吧, GPIO mode IPU。 然后这里 GPIOPin 给 GPIOPin 0。最后一个 GPIOSpeed 给 GPIOSpeed 50 兆赫兹,这样就完事了。

当然下面这个预分频和自动重装载值也改小点哈

我们手动模拟的没那么快。这里预分频就给 1,不需要分频哈。自动重装载值给 10,从零记到 9 就行了。

最后我们想实时看一下 CNT 计数器的值,我们把它的函数也封装一下吧,这样规范一些。我们在下面写 unit 16-7 Type GetCounter void。然后里面直接写 return TIM GetCounter TM2,这样就行了。

然后这个函数也放在头文件里声明一下。

编一下,没有问题啊。那在主函数里,我们先复制一下这个啊,在二行一列显示个 CNT 冒号,然后下面复制一下,在二行五列显示 TIM GetCounter 长度为 5,这样就完成了。

我们编译下载试一下。看一下啊,这时上面这里是 number,下面是 CNT 计数器的值。

我们用挡光片挡一下, CNT 加一。

因为现在实际单元没有预分频哈,所以每次遮挡 CNT 都会加一。如果有预分频呢,就是遮挡几次才能加一次。然后加到 9 后自动清 0,同时申请中断, number 加加。

这就是第二个程序的现象了。这些预分频器极性等参数啊,大家都可以自己换一换,看看现象。然后 TIM 点 h 这里,其他的一些时钟输入啊,大家也都可以自己试一试,这里我就不再一一演示了哈。好那本小节的代码部分都

相关推荐
披着假发的程序唐1 小时前
STM32 H743 MPU的配置使用方法
linux·c语言·c++·驱动开发·stm32·单片机·mcu
张健11564096481 小时前
MSP主堆栈指针
单片机
wild-civil2 小时前
解决Keil 生成的文件在 VSCode 乱码问题(自动识别,不用手动改编码)
ide·vscode·stm32·编辑器
qxl_7999154 小时前
Windows 显卡掉线无报警|模型推理全套防呆方案(实操完整版)
windows·stm32·单片机·推理显卡掉线误报警防呆
hhb_6184 小时前
Armbian 的 root 密码“总被修改”
stm32·单片机·嵌入式硬件
项目題供诗10 小时前
STM32-TIM定时中断(十)
stm32·单片机·嵌入式硬件
普中科技10 小时前
【普中 51-Ai8051 开发攻略】-- 第 24 章 RTC 时钟实验
单片机·嵌入式硬件·rtc·实时时钟·普中科技·ai8051u·aicube
-liming-11 小时前
单片机设计_串口调试工具
数据库·单片机·mongodb
潜创微科技12 小时前
IT9201+IT66021:便携 KVM 一站式方案,音视控三合一免驱即插即用
嵌入式硬件·音视频