TIM 输入捕获-stm32入门

1. 输入捕获(IC)简介

1.1 基本概念

IC(Input Capture)输入捕获

  • 输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔(和频率差不多)、电平持续时间(和占空比差不多)等参数

指定的电平跳变就是上升沿或者下降沿,可以通过程序配置。
锁存:把当前 CNT 的值读出来,存在 CCR 中。

类似于外部中断,都是检测电平跳变,然后执行动作;只不过外部中断的动作是向 CPU 申请中断,而这里电路执行的动作就是,控制后续电路,让当前 CNT 的值,锁存到 CCR 寄存器中。

  • 与输出比较区别:

    • 输出比较,引脚是输出端口,是根据 CNT 和 CCR 的大小关系来执行输出操作的。
    • 输入捕获,引脚是输入端口,是接收到输入信号,执行 CNT 锁存到 CCR 的动作。
  • 每个高级定时器和通用定时器都拥有4个输入捕获通道(高级定时器和通用定时器没有区别,都是一样的,基本定时器没有输入捕获功能)

4 个输入捕获和输出比较通道,共用 4 个 CCR 寄存器;另外它们的 CH1 到 CH4,4 个通道的引脚,也是共用的。所以对于同一个定时器,输入捕获和输出比较,只能使用其中一个,不能同时使用。

  • 可配置为PWMI模式,同时测量频率和占空比

PWMI 模式是 PWM 的输入模式,是专门为测量 PWM 频率和占空比设计的。

  • 可配合主从触发模式,实现硬件全自动测量

把这两个功能结合起来,测量频率和占空比就是硬件全自动执行,软件不需要进行任何干涉,也不需要进入中断,需要测量的时候,直接读取 CCR 寄存器就行了,使用非常方便,而且极大的减轻了软件的压力。

1.2 频率测量

频率的定义:1s 内出现了多少个重复的周期。
在这里插入图片描述
一个频率逐渐降低的方波波形,越往左,频率越高,越往右,频率越低。这里信号都是只有高低电平的数字信号,对于 STM32 测频率而言,它也是只能测量数字信号的。

如果你需要测量一个正弦波,那还需要搭建一个信号预处理电路,最简单的就是用运放搭一个比较器,把正弦波转换为数字信号,再输入给 STM32 就行了,如果你测量的信号电压非常高,那还要考虑一下隔离问题。比如用一些隔离放大器,电压互感器等元件,隔离高压端和低压端,保证电路的安全。总之,经过处理最终输入给 STM32 的信号,要是这样的高低电平信号,高电平 3.3V,低电平 0V。

  • 测频法:在闸门时间T(通常设置为 1s)内,对上升沿(或者下降沿)计次,得到N,则频率
    𝑓𝑥 = 𝑁 / 𝑇
  • 之后为了方便,我们统一以上升沿为一个周期的开始进行描述。每来一个上升沿,其实就是来了一个周期的信号。
  • 所以在 1s 的时间内,来了多少个周期,频率就是多少 Hz。这种直接按照频率定义来进行测量的方法就叫测频法。
  • 测周法:两个上升沿内,以标准频率fc计次,得到N ,则频率
    𝑓𝑥 = 𝑓𝑐 / 𝑁

测周法的基本原理:周期的倒数就是频率,测出一个周期的时间,再取个倒数,就是频率。
捕获信号的两个上升沿,测量之间持续的时候,但是实际上,我们并没有一个精度无穷大的秒表来测量时间,测量时间的方法,实际上也是定时器计次,我们使用一个已知的标准频率 fc 的计次时钟,来驱动计数器,从一个上升沿开始计,计数器从 0 开始,一直记到下一个上升沿,停止,计一个数的时间是 1 / fc,计 N 个数,时间就是 N / fc,N / fc就是周期,再取个倒数,就得到了频率计算公式。、

  • 测频法与测周法区别:

    • 测频法适合测量高频信号,测频法在闸门时间内,最好多出现一些上升沿,计次数量多一些,这样有助于减小误差。比如你定了 1s 的闸门时间,结果信号频率非常低,1s 的时间才只有寥寥无几的几个上升沿,甚至一个上升沿都没有,那总不能认为频率是 0 吧,在计次 N 很少时,误差会非常大。所以测频法要求待测信号频率高一些。
    • 测周法适合测量低频信号,低频信号周期比较长,计次就会比较多,有助于减小误差。否则比如标准频率 1MHz,待测信号频率太高,比如待测信号 500 KHz,那在一个周期内只能计一两个数,如果待测信号再高一些,甚至一个数也记不到,那总不能认为频率无穷大吧。所以测周法要求待测信号频率低一些。
    • 测频法测量的结果更新的慢一些,数值相对稳定。测频法测量的是在闸门时间内的多个周期,所以它自带一个均值滤波,如果在闸门时间内波形频率有变化,那得到的其实是这一段时间的平均频率。如果闸门时间选择 1s,那么每隔 1s 才能得到一次结果,所以测频法结果更新慢,测量结果是一段时间平均值,值比较平滑。
    • 测周法更新的快,数值跳变也非常快。测周法只测量一个周期,就能出一次结果,所以出结果的速度取决于待测信号的频率,一般而言,待测信号都是 几百几千 Hz,所以一般情况下,测周法结果更新更快;但是由于它只测量一个周期,所以结果值会受噪声的影响,波动比较大。
  • 中界频率:测频法与测周法误差相等的频率点

𝑓𝑚= ( f   c   / T ) \sqrt{(f~c~/T)} (f c /T)

测频法计次和测周法计次,这个计次数量 N,要尽量大一些,N 越大,相对误差越小,因为在这些方法中,计次可能会存在正负 1 误差。比如测频法,在闸门时间内并不是每个周期信号都是完整的,在最后的时间里,可能有一个周期刚出现一半,闸门时间就到了,那这只有半个周期,只能舍弃掉或者当作一整个周期来看,因为计次只有整数,不可能计次 0.5 个数,那在这个过程,就会出现多计一次,或者少计一次的情况,这就叫做 正负 1 误差。在测周法中,标准频率 fc 计次,在最后时刻,有可能一个数刚数到一半,计时就结束了,那这半个数也只能舍弃或者按一整个数来算了;这里也会出现 正负 1 误差。正负 1 误差是这两种方法都固有的误差,要想减小 正负 1 误差 的影响,就只能尽量多计一些数,当计次 N 比较大时,正负 1 误差对 N 的影响就会很小。总结就是 N 越大,正负 1 误差对 N 的影响越小。
那当有一个频率,测频法和测周法计次的 N 相同时,就说明误差相同,这就是中界频率。把测频法和测周法的 N 提出来,令这两个方法 N 相等,把 fx 解出来,就得到了中界频率。
当待测信号频率 小于 中界频率时,测周法误差更小,选用测周法更合适;当待测信号频 大于 中界频率时,测频法误差更小,选用测频法更合适。

1.3 STM32 实现两种频率测量方法

1.3.1 测频法实现

我们之前讲的对射式红外传感器计次,定时器外部时钟,这些代码,稍加改进就是 测频法。比如对射式红外传感器计次,每来一个上升沿计次 +1,那我们再用一个定时器,定一个 1s 的定时中断,在中断里,每隔 1s 取一下计次值,同时清 0 计次,为下一次做准备,这样每次读取的计次值就直接是频率。对于定时器外部时钟的代码也是如此,每隔 1s 取一下计次值,就能实现测频率测量频率的功能了。

1.3.2 测周法实现

硬件电路图:
在这里插入图片描述
最左边是四个通道的引脚,参考引脚定义表,就能知道这个引脚是复用在了哪个位置。
然后引脚进来,这里有一个三输入的异或门,这个异或门的输入接在了通道 1,2,3 端口,异或门执行逻辑:当三个输入引脚的任何一个有电平翻转时,输出引脚就产生一次电平翻转,
之后输出通过数据选择器,到达输入捕获通道 1,数据选择器如果选择上面一个,那输入捕获通道 1 的输入,就是 3 个引脚的异或值,如果选择下面一个,那异或门就没有用,4 个通道各用各的引脚,设计这个异或门,其实还是为三相无刷电机服务的。无刷电机有 3 个霍尔传感器检测转子的位置,可以根据转子的位置进行换相,有了这个异或门,就可以在前三个通道接上无刷电机的霍尔传感器,然后这个定时器就作为无刷电机的接口定时器,去驱动换相电路工作。
输入信号进来,来到了输入滤波器和边沿检测器,输入滤波器可以对信号进行滤波,避免一些高频的毛刺信号误触发,然后边沿检测器,这就和外部中断那里是一样的,可以选择高电平触发,或者低电平触发,当出现指定的电平时,边沿检测电路就会触发后续电路执行动作,另外这里,它其实是设计了两套滤波和边沿检测电路,第一套电路,经过滤波和极性选择,得到 TI1FP1(TI1 Filter Polarity 1),输入给通道 1 的后续电路;第二套电路,经过另一个滤波和极性选择,得到 TI1FP2(TI1 Filter Polarity 2),输入给下面通道 2 的后续电路。同理,下面 TI2 信号进来,也经过两套滤波和极性选择,得到 TI2FP1 和 TI2FP2,其中 TI2FP1 输入给上面,TI2FP2 输入给下面。在这里,两个信号进来,可以选各走各的,也可以选择进行一个交叉,让 CH2 引脚输入给通道 1,或者 CH1 引脚输入给通道 2。下面通道 3 和通道 4 也是一样的结构,可以选各自独立连接,也可以选择进行一个交叉。另外,这里还有一个 TRC 信号,也可以选择作为捕获部分的输入,TRC 信号来源于上面,这样设计,也是为了无刷电机的驱动。

那为什么要进行一个交叉连接呢?第一个目的:可以灵活切换后续捕获电路的输入,比如你一会想以 CH1 作为输入,一会想以 CH2 作为输入,这样可以通过数据选择器,灵活地进行选择。第二个目的(主要目的):可以把一个引脚的输入,同时映射到两个捕获单元,这也是 PWMI 模式的经典结构。(第一个捕获通道,使用上升沿触发,用来捕获周期;第二个捕获通道,使用下降沿触发,用来捕获占空比,两个通道同时对一个引脚进行捕获,就可以同时测量频率和占空比,这就是 PWMI 模式)一个通道灵活切换两个引脚,两个通道同时捕获一个引脚,这就是交叉的作用和目的。

输入信号经过滤波和极性选择后,就来到了预分频器,预分频器,每个通道各有一个,可以选择对前面的信号进行分频,分频之后的触发信号,就可以触发捕获电路进行工作了;每来一个触发信号,CNT 的值,就会向 CCR 转运一次,转运的同时,会发送一个捕获事件,这个事件会在状态寄存器置标志位,同时也可以产生中断。如果需要在捕获的瞬间,处理一些事情的话,就可以开启这个捕获中断,这就是整个电路的工作流程。

比如我们可以配置上升沿触发捕获,每来一个上升沿,CNT 转运到 CCR 一次,又因为这个 CNT 计数器是有内部的标准时钟(fc)驱动的,所以 CNT 的数值,可以用来记录两个上升沿之间的时间间隔,这个时间间隔,就是周期,再取个倒数,就是测周法测量的频率。(一个小细节:每次捕获之后,我们都要把 CNT 清 0 一下,这样下次上升沿再捕获的时候,取出的 CNT 才是两个上升沿的时间间隔,这个在一次捕获后自动将 CNT 清零的步骤,我们可以用主从触发模式,自动来完成)

输入捕获通道的详细框图
在这里插入图片描述
引脚进来,还是先经过一个滤波器,滤波器的输入是 TI1,就是 CH1 的引脚,输出的 TI1F,就是滤波后的信号;fDTS 是滤波器的采样时钟来源,下面 CCMR1 寄存器里的 IC1F 位可以控制滤波器的参数(定义了 TI1 输入的采样频率及数字滤波器长度,数字滤波器是由一个事件计数器组成,它记录到 N 个事件后会产生一个输出的跳变,简单理解滤波器的工作原理是:以采样频率对输入信号进行采样,当连续 N 个值都为高电平,输出才为高电平;连续 N 个值都为低电平,输出才为低电平;如果信号出现高频抖动,导致连续采样 N 个值不全都一样,那输出就不会变化,这样就可以达到滤波的效果,采样频率越低,采样个数 N 越大,滤波效果就越好。参数对应了采样频率和采样个数,在实际应用中,如果波形噪声比较大,就可以把这个参数设置大一些,就可以过滤噪声了。

滤波之后的信号通过边沿检测器,捕获上升沿或者下降沿,用 CCER 寄存器里的 CCIP 位,就可以选择极性了,最终得到 TI1FP1 触发信号,通过数据选择器,进入通道 1 后续的捕获电路,当然这里实际应该还有一套一样的电路,得到 TI1FP2 触发信号,连通到通道 2 的后续电路,这里并没有画出来,同样,通道 2 有 TI2FP1,连通到通道 1 的后续,通道 2 有 TI2FP2,连通到通道 2 的后续。

然后经过这里的数据选择器,进入后续捕获部分电路,CC1S 位可以对数据选择器进行选择,之后 ICPS 位,可以配置这里的分频器,可以选择不分频、2 分频、4 分频、8 分频,最后 CC1E 位,控制输出使能或失能,如果使能了输出,输入端产生指定边沿信号,经过层层电路到达这里,就可以让这里 CNT 的值,转运到 CCR 里面来,另外我们刚才说了,每捕获一次 CNT 的值,都要把 CNT 清零一下,以便于下一次的捕获,在这里硬件电路就可以把捕获之后自动完成 CNT 的清零工作。如何自动清零 CNT?TI1FP1 信号 TI1 的边沿信号(TIF_ED)都可以通向从模式控制器,比如 TI1FP1 信号的上升沿触发捕获,那通过这里,TI1FP1 还可以同时触发从模式,这个从模式里面,就有电路,可以自动完成 CNT 的清零,所以可以看出,这个从模式就是完成自动化操作的利器。

1.4 主从触发模式

在这里插入图片描述
主从触发模式就是主模式、从模式和触发源选择这三个功能的简称。

  • 其中主模式可以将定时器内部的信号,映射到 TRGO 引脚,用于触发别的外设,所以这部分叫做主模式。
  • 从模式就是接受其他外设或者自身外设的一些信号,用于控制自身定时器的运行,也就是被别的信号控制,所以这部分叫从模式。
  • 触发源选择就是选择从模式的触发信号源的,可以认为是从模式的一部分,触发源选择,选择指定的一个信号,得到 TRGI,TRGI 去触发从模式,从模式可以在这个列表里,选择一项操作来自动执行。

如果想完成我们刚才说的任务,想让 TI1FP1 信号自动触发 CNT 清零,那触发源选择,就可以选中这里的 TI1FP1,从模式执行的操作,就可以选择执行 Reset 的操作,这样 TI1FP1 的信号就可以自动触发从模式,从模式自动清零 CNT,实现硬件全自动测量,这就是主从触发模式的用途。

信号的具体解释参考手册。

  • 主模式还可以选择复位、使能、比较脉冲和 4 个 OCREF 信号作为 TRGO 的输出。(比如你想实现定时器的级联,就可以选择一个定时器主模式输出更新信号到 TRGO,另一个定时器选择上一个定时器的触发从模式,从模式选择执行外部时钟模式 1 的操作,这样就能实现定时器的级联了,还要其他高级的功能,如一个定时器使能另一个定时器,一个定时器启动另一个定时器,可以使用主从触发模式实现,使用非常灵活)
  • 从模式触发源可选信号,可以选择信号去触发从模式。
  • 从模式可以选择复位模式、门控、触发、3 个编码器模式 、外部时钟模式1 和关闭从模式。

复位模式执行的操作就是:选中触发输入的上升沿重新初始化计数器(就是清零 CNT 的意思)

在库函数中也非常简单,这三块对应三个函数,调用函数,给个参数就行了。

1.5 基本结构图

1.5.1 输入捕获基本结构

在这里插入图片描述
只是用了一个通道,目前只能测量频率。

  1. 右上角是时基单元,我们把时基单元配置好,启动定时器,CNT 就会在预分频之后的这个时钟驱动下,不断自增,这个 CNT 就是测周法用来计数计时的东西,经过预分频之后这个位置的时钟频率,就是驱动 CNT 的标准频率 fc,不难看出:标准频率 = 72M / 预分频系数
  2. 下面输入捕获通道 1 的 GPIO 口,输入一个这样的方波信号经过滤波器和边沿检测,选择 TI1FP1 为上升沿触发,之后输入选择直连的通道,分频器选择不分频,当 TI1FP1 出现上升沿之后,CNT 的当前计数值转运到 CCR1 里。
  3. 同时触发源选择,选中 TI1FP1 为触发信号,从模式选择复位操作,这样 TI1FP1 的上升沿,也会通过上面这一路,去触发 CNT 清零,当然这里会有个先后顺序,肯定得是先转运 CNT 的值到 CCR 里去,再触发从模式给 CNT 清零;或者非阻塞的同时转移,CNT 的值转移到 CCR,同时 0 转移到 CNT 里面去。总之,肯定不会是先清零,再捕获,要不然捕获值肯定都是 0 了。

如左上角图所示:信号出现一个上升沿,CCR1 = CNT,就是把 CNT 的值转运到 CCR1 里面去,这是输入捕获自动执行的,然后 CNT = 0,清零计数器,这是从模式自动执行的。然后在一个周期之内,CNT 在标准时钟的驱动下,不断自增,并且由于之前清零过了,所以 CNT 就是从上升沿开始,从 0 开始计数,一直 ++,直到下一次上升沿来临,然后执行相同的操作,CCR1 = CNT, CNT = 0。注意,第二次捕获的时候,CNT 就是两个上升沿之间的计数值,所以当这个电路工作的时候,CCR1 的值,始终保持为最新一个周期的计数值,这个计数值就是公式中的 N,然后 fc/N 就是信号的频率,所以当我们想要读取信号的频率时,只需要读取 CCR1 得到 N,再计算 fc/N,就行了,当我们不需要读取的时候,整个电路全自动测量,不需要占用任何软件资源。

注意事项:

  1. CNT 的值是有上限的,ARR 一般设置为最大 65535,那 CNT 最大也只能计 65535 个数,如果信号频率太低,CNT 计数值可能会溢出。
  2. 从模式的触发源选择,在这里看到只有 TI1FP1 和 TI2FP2,没有 TI3 和 TI4 信号,所以这里如果想使用从模式自动清零 CNT,就只能用通道 1 和通道 2;对于通道 3 和 通道 4,就只能开启捕获中断,在中断里手动清零了,不过这样,程序就会处于频繁中断的状态,比较消耗软件资源,这个注意一下。

1.5.2 PWMI 基本结构

在这里插入图片描述
使用两个通道同时捕获一个引脚,可以同时测量周期和占空比。
上面整体结构和输入捕获基本结构一样,下面这里多了一个通道;首先 TI1FP1 配置上升沿触发,触发捕获和清零 CNT,正常的捕获周期,这时我们再来一个 TI1FP2,配置为下降沿触发,通过交叉通道,去触发通道 2 的捕获单元。
如左上角图所示,最开始上升沿,CCR1 捕获,同时清零 CNT,之后 CNT 一直 ++,然后,在下降沿这个时刻,触发 CCR2 捕获,所以这时 CCR2 的值,就是 CNT 从上升沿到下降沿的计数值,就是高电平期间的计数值,CCR2 捕获,并不触发 CNT 清零,所以 CNT 继续 ++,直到下一次上升沿,CCR1 捕获周期,CNT 清零。这样执行之后,CCR1 就是一整个周期的计数值,CCR2 就是高电平期间的计数值,我们用 CCR2/CCR1 就是占空比。这就是 PWMI 模式,使用两个通道来捕获频率和占空比的思路。另外通过两个通道同时捕获第一个引脚的输入,这样通道 2 的前面这一部分就没有用到;当然也可以配置两个通道同时捕获第二个引脚的输入,这样我们就是使用 TI2FP1 和 TI2FP2 两个引脚了,这两个输入可以灵活切换。

2. 两个定时器输入捕获功能案例

2.1 输入捕获模式测频率

2.1.1 硬件电路图

在这里插入图片描述
测量信号的输入引脚是 PA6,信号从 PA6 进来;待测的 PWM 信号也是 STM32 自己生成的,输出引脚是 PA0,所以接线这里,直接用一根线把 PA0 引到 PA6 就行了。如果有信号发生器的话,也可以设置成方波信号输出,高电平 3.3V,低电平 0V,然后直接接到 PA6,另外也别忘了共地,这样接线也行。

2.1.2 定时器输入捕获模块初始化

  • 定时器输入捕获模块的库函数
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);//用结构体配置输入捕获单元的模块(选择定时器,包含各个配置的结构体)
//OC 和 IC 都有 4 个通道,OCInit 每个通道单独占一个函数,而 ICInit,4个通道共用一个函数,在结构体里额外有一个参数,可以用来选择具体是配置哪个通道的,因为可能有交叉通道的配置,所以通道合在一起比较方便
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);//用于初始化输入捕获单元,但是上一个函数只是单一的配置一个通道,而这个函数,可以快速配置两个通道,把外设电路结构配置成 PWMI 模式
void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);//给输入捕获结构体赋一个初始值
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);//选择输入触发源 TRGI,对应从模式的触发源选择,调用这个函数,就能选择从模式的触发源了,比如本节要用的 TI1FP1
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);//选择输出触发源 TRGO,对应主模式输出的触发源
void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);//选择从模式,对应的就是从模式选择的部分(下面四个从模式),三个函数对应三个需要选择的部分
//上面 3 个 Encoder 从模式,是给编码器接口用的,还会另外有函数进行配置,所以这个函数的参数没有给出
void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);//分别单独配置1~4通道的分频器,这个参数结构体里也可以配置,是一样的效果
void TIM_SetIC2Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC3Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC4Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);//分别读取 4 个通道的 CCR,这 4 个函数和上面的 SetCompare1~4 是对应的,读写的都是 CCR 寄存器,输出比较模式下,CCR 是只写的,要用 SetCompare 写入;输入捕获模式下,CCR 是只读的,要用 GetCompare 读出
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);

注意滤波器和分频器的区别:虽然都是计次,但是滤波器计次,并不会改变信号的原有频率,一般滤波器的采样频率都会远高于信号频率,所以它只会滤除高频噪声,使信号更平滑,1KHz 滤波后仍然是 1KHz,信号频率不会变化。而分频器就是对信号本身进行计次了,会改变频率,1KHz 二分频之后就是 500 Hz,4 分频之后就是 250 Hz。

配置定时器(TIM3 通用定时器)

  1. RCC开启时钟,把 GPIO 和 TIM 时钟打开
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//需要 TIM2 输出 PWM,所以输入捕获定时器要换一个
  1. GPIO 初始化,把 GPIO 配置成输入模式(一般选择上拉输入/浮空输入模式)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);//GPIO 配置好默认低电平
  1. 配置时基单元,让 CNT 计数器在内部时钟的驱动下自增运行
TIM_InternalClockConfig(TIM3);//选择内部时钟
	
TIM_TimeBaseInitTypeDef TIMTimeBaseInitStructure;
TIMTimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频,决定滤波器的采样频率
TIMTimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式,向上计数
//标准频率 = 72M / 65536 / 72 = 1 MHz
TIMTimeBaseInitStructure.TIM_Period = 65536 - 1;//最好设置大一些,防止计数溢出,这里给最大值,也就是 16 位的计数器可以满量程计数
TIMTimeBaseInitStructure.TIM_Prescaler = 72 - 1;//决定测周法的标准频率,需要根据信号频率的分布范围来调整
TIMTimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值(高级定时器才有),不需要用赋 0
TIM_TimeBaseInit(TIM3, &TIMTimeBaseInitStructure);
  1. 配置输入捕获单元(包括滤波器、极性、直连通道还是交叉通道、分频器这些参数),用一个结构体统一配置
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//选择通道,因为 IC_Init 函数只有一个,所以要靠结构体这个参数来指定配置哪个通道
TIM_ICInitStructure.TIM_ICFilter = 0xF;//用来配置输入捕获的滤波器,如果信号有毛刺和噪声,可以增大滤波器参数,可以有效地避免干扰,数越大,滤波效果就越好
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//极性,对应边沿检测与极性选择部分,选择是上升沿触发、下降沿触发还是双边触发
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//触发信号分频器:不分频就是每次触发都有效,二分频就是每隔一次有效一次,以此类推,不过分频值不能任意指定,由参数决定
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//选择触发信号从哪个引脚输入,用来配置数据选择器,可以选择直连通道的输入,或者是交叉通道的输入,或者是 TRC 引脚
TIM_ICInit(TIM3, &TIM_ICInitStructure);
  1. 选择从模式的触发源,触发源选择 TI1FP1,这里调用一个库函数,给一个参数就行
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
  1. 选择触发之后执行的操作,执行 Reset 操作,这里也是调用一个库函数就行
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
  1. 开启定时器,CNT 自增运行
TIM_Cmd(TIM3, ENABLE);

2.1.3 示例代码

PWM.h 文件

#ifndef __PWM_H
#define __PWM_H

void PWM_Init(void);
void PWM_SetCompare1(uint16_t compare);
void PWM_SetPrescaler(uint16_t Prescaler);

#endif

PWM.c 文件

#include "stm32f10x.h"                  // Device header

void PWM_Init(void) {
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//TIM2 是 APB1 总线的外设

	TIM_InternalClockConfig(TIM2);//选择内部时钟
	
	TIM_TimeBaseInitTypeDef TIMTimeBaseInitStructure;
	TIMTimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频,决定滤波器的采样频率
	TIMTimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式,向上计数
	//定时 1 s(记公式/理解工作流程)
	TIMTimeBaseInitStructure.TIM_Period = 100 - 1;//ARR 的值(取值范围0~65535)
	TIMTimeBaseInitStructure.TIM_Prescaler = 720 - 1;//PSC 的值(取值范围0~65535),0````````:不需要分频
	TIMTimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值(高级定时器才有),不需要用赋 0
	TIM_TimeBaseInit(TIM2, &TIMTimeBaseInitStructure);

	TIM_OCInitTypeDef TIM_OCInitStructure;//有些参数高级定时器才会用到,只列出需要用到的
	TIM_OCStructInit(&TIM_OCInitStructure);//给结构体变量赋初始值,函数内部也是手动赋初始值
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//输出比较模式,强制输出模式不允许初始化时使用
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出比较极性,高极性,电平不翻转
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//输出状态
	TIM_OCInitStructure.TIM_Pulse = 0;//设置 CCR 寄存器值
	TIM_OC1Init(TIM2, &TIM_OCInitStructure); 

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出:输出数据寄存器将被断开,输出控制权转移给片上外设,这里片上外设引脚连接的就是 TIM2 的 CH1 通道,PWM 波形通过引脚输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);//GPIO 配置好默认低电平

	TIM_Cmd(TIM2, ENABLE);
}

//PWM 频率取决于 PSC 和 ARR。占空比取决于 CCR 和 ARR。
//通过调节 PSC 来改变 PWM 频率,ARR 固定为 100-1,CCR 的数值直接就是占空比,用起来比较直观
//使用技巧:一般我们可以根据分辨率的要求,先确定好 ARR,这样 PSC 决定频率,CCR 决定占空比

void PWM_SetCompare1(uint16_t compare) {
	TIM_SetCompare1(TIM2, compare);//单独写入 CCR 的函数:
}

void PWM_SetPrescaler(uint16_t Prescaler) {
	TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate);//单独写入 PSC 的函数:
	//因为这个函数有一个重装模式(预分频器在更新事件重装/预分频器立即重装)的参数,说白了还是影子寄存器,预装载问题,就是你写入的值,是立刻生效,还是在更新事件生效。
	//立刻生效,可能会在值改变时产生切断波形的现象,比如 PWM 一个周期刚过去一半,立刻生效了,那就立刻切断当前波形,开始新的一个周期,在频率变化时
	//会出现一个不完整的周期,那在更新事件生效,就是会有一个缓存器,延迟参数的写入时间,等一个周期结束了,在更新事件时,再统一改变参数,保证每个周期的完整
	//所以不叫 TIM_SetPrescaler,这是这个库的命名规范,但其实都是一个意思,就是写入 PSC
}

void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode) 单独写入 PSC 的函数。

IC.h 文件

#ifndef __IC_H
#define __IC_H

void IC_Init(void);
uint32_t IC_GetFreq(void);

#endif

IC.c 文件

#include "stm32f10x.h"                  // Device header

void IC_Init(void) {
//1.RCC开启时钟,把 GPIO 和 TIM 时钟打开
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//需要 TIM2 输出 PWM,所以输入捕获定时器要换一个
//2.GPIO 初始化,把 GPIO 配置成输入模式(一般选择上拉输入/浮空输入模式)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);//GPIO 配置好默认低电平
//3.配置时基单元,让 CNT 计数器在内部时钟的驱动下自增运行
	TIM_InternalClockConfig(TIM3);//选择内部时钟
	
	TIM_TimeBaseInitTypeDef TIMTimeBaseInitStructure;
	TIMTimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频,决定滤波器的采样频率
	TIMTimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式,向上计数
	//标准频率 = 72M / 65536 / 72 = 1 MHz
	TIMTimeBaseInitStructure.TIM_Period = 65536 - 1;//最好设置大一些,防止计数溢出,这里给最大值,也就是 16 位的计数器可以满量程计数
	TIMTimeBaseInitStructure.TIM_Prescaler = 72 - 1;//决定测周法的标准频率,需要根据信号频率的分布范围来调整
	TIMTimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值(高级定时器才有),不需要用赋 0
	TIM_TimeBaseInit(TIM3, &TIMTimeBaseInitStructure);
//4.配置输入捕获单元(包括滤波器、极性、直连通道还是交叉通道、分频器这些参数),用一个结构体统一配置
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//选择通道 TIM3 的 CH1 通道,即通道 1
	TIM_ICInitStructure.TIM_ICFilter = 0xF;//用来配置输入捕获的滤波器,如果信号有毛刺和噪声,可以增大滤波器参数,可以有效地避免干扰,数越大,滤波效果就越好
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//极性,选择上升沿触发
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//触发信号分频器:选择不分频
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//选择触发信号从哪个引脚输入,选择直连通道的输入
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
//5.选择从模式的触发源,触发源选择 TI1FP1,这里调用一个库函数,给一个参数就行
	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
//6.选择触发之后执行的操作,执行 Reset 操作,这里也是调用一个库函数就行
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
//7.开启定时器,CNT 自增运行
	TIM_Cmd(TIM3, ENABLE);
}

//当我们需要读取最新一个周期的频率时,直接读取 CCR 寄存器,然后按照 fc/N 计算一下就行了
uint32_t IC_GetFreq(void) {
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

main.c 文件

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"

int main(void) {
	OLED_Init();
	PWM_Init();
	IC_Init();
	
	OLED_ShowString(1, 1, "Freq:00000Hz");
	
	PWM_SetPrescaler(720 - 1);//Freq = 72M /(PSC + 1)/ 100
	PWM_SetCompare1(50);	//Duty = CCR / 100
	
	while(1){
		OLED_ShowNum(1, 6, IC_GetFreq(), 5);//计数刚到 1000 Hz 的那个数时,信号也刚好跳变,因为电路结构或者其他什么原因,导致这一个数刚好没记到,才会有一点误差,
		//不过这个也属于 正负1 误差的范畴,目前这个误差也是符合要求的。
	} 
}

2.2 PWMI 模式测频率占空比

2.2.1 硬件电路图

在这里插入图片描述
目前这两个代码的接线一样。

2.2.2 配置两个通道同时捕获同一个引脚的方法

方法 1:

TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//选择通道,因为 IC_Init 函数只有一个,所以要靠结构体这个参数来指定配置哪个通道
TIM_ICInitStructure.TIM_ICFilter = 0xF;//用来配置输入捕获的滤波器,如果信号有毛刺和噪声,可以增大滤波器参数,可以有效地避免干扰,数越大,滤波效果就越好
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//极性,对应边沿检测与极性选择部分,选择是上升沿触发、下降沿触发还是双边触发
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//触发信号分频器:不分频就是每次触发都有效,二分频就是每隔一次有效一次,以此类推,不过分频值不能任意指定,由参数决定
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//选择触发信号从哪个引脚输入,用来配置数据选择器,可以选择直连通道的输入,或者是交叉通道的输入,或者是 TRC 引脚
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;
TIM_ICInit(TIM3, &TIM_ICInitStructure);*/

方法 2:

TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);//只需要传入一个通道的参数就行了,在函数里会自动把剩下的一个通道初始化成相反的配置。

只需要传入一个通道的参数就行了,在函数里会自动把剩下的一个通道初始化成相反的配置。比如传入通道 1,直连,上升沿;函数里面就会顺带配置通道 2,交叉,下降沿。可以快捷的把电路配置成 PWMI 模式的标准结构。

注意:函数只支持通道 1 和 通道 2 的配置,不要传入通道 3 和 通道 4 的配置

2.2.3 示例代码

PWM.h

#ifndef __PWM_H
#define __PWM_H

void PWM_Init(void);
void PWM_SetCompare1(uint16_t compare);
void PWM_SetPrescaler(uint16_t Prescaler);

#endif

PWM.c

#include "stm32f10x.h"                  // Device header

void PWM_Init(void) {
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//TIM2 是 APB1 总线的外设

	TIM_InternalClockConfig(TIM2);//选择内部时钟
	
	TIM_TimeBaseInitTypeDef TIMTimeBaseInitStructure;
	TIMTimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频,决定滤波器的采样频率
	TIMTimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式,向上计数
	//定时 1 s(记公式/理解工作流程)
	TIMTimeBaseInitStructure.TIM_Period = 100 - 1;//ARR 的值(取值范围0~65535)
	TIMTimeBaseInitStructure.TIM_Prescaler = 720 - 1;//PSC 的值(取值范围0~65535),0````````:不需要分频
	TIMTimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值(高级定时器才有),不需要用赋 0
	TIM_TimeBaseInit(TIM2, &TIMTimeBaseInitStructure);

	TIM_OCInitTypeDef TIM_OCInitStructure;//有些参数高级定时器才会用到,只列出需要用到的
	TIM_OCStructInit(&TIM_OCInitStructure);//给结构体变量赋初始值,函数内部也是手动赋初始值
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//输出比较模式,强制输出模式不允许初始化时使用
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出比较极性,高极性,电平不翻转
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//输出状态
	TIM_OCInitStructure.TIM_Pulse = 0;//设置 CCR 寄存器值
	TIM_OC1Init(TIM2, &TIM_OCInitStructure); 

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出:输出数据寄存器将被断开,输出控制权转移给片上外设,这里片上外设引脚连接的就是 TIM2 的 CH1 通道,PWM 波形通过引脚输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);//GPIO 配置好默认低电平

	TIM_Cmd(TIM2, ENABLE);
}

void PWM_SetCompare1(uint16_t compare) {
	TIM_SetCompare1(TIM2, compare);//单独写入 CCR 的函数:
}

void PWM_SetPrescaler(uint16_t Prescaler) {
	TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate);//单独写入 PSC 的函数:
}

PWM 频率取决于 PSC 和 ARR。占空比取决于 CCR 和 ARR。通过调节 PSC 来改变 PWM 频率,ARR 固定为 100-1,CCR 的数值直接就是占空比,用起来比较直观
使用技巧:一般我们可以根据分辨率的要求,先确定好 ARR,这样 PSC 决定频率,CCR 决定占空比

因为 TIM_PrescalerConfig 函数有一个重装模式(预分频器在更新事件重装 / 预分频器立即重装)的参数,说白了还是影子寄存器,预装载问题,就是你写入的值,是立刻生效,还是在更新事件生效。立刻生效,可能会在值改变时产生切断波形的现象,比如 PWM 一个周期刚过去一半,立刻生效了,那就立刻切断当前波形,开始新的一个周期,在频率变化时会出现一个不完整的周期;那在更新事件生效,就是会有一个缓存器,延迟参数的写入时间,等一个周期结束了,在更新事件时,再统一改变参数,保证每个周期的完整。所以这个函数不叫 TIM_SetPrescaler,这是这个库的命名规范,但其实都是一个意思,就是写入 PSC。

IC.h

#ifndef __IC_H
#define __IC_H

void IC_Init(void);
uint32_t IC_GetFreq(void);
uint32_t IC_GetDuty(void);

#endif

IC.c

#include "stm32f10x.h"                  // Device header

void IC_Init(void) {
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//需要 TIM2 输出 PWM,所以输入捕获定时器要换一个

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);//GPIO 配置好默认低电平

	TIM_InternalClockConfig(TIM3);//选择内部时钟
	
	TIM_TimeBaseInitTypeDef TIMTimeBaseInitStructure;
	TIMTimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频,决定滤波器的采样频率
	TIMTimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式,向上计数
	//标准频率 = 72M / 65536 / 72 = 1 MHz
	TIMTimeBaseInitStructure.TIM_Period = 65536 - 1;//最好设置大一些,防止计数溢出,这里给最大值,也就是 16 位的计数器可以满量程计数
	TIMTimeBaseInitStructure.TIM_Prescaler = 72 - 1;//决定测周法的标准频率,需要根据信号频率的分布范围来调整
	TIMTimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值(高级定时器才有),不需要用赋 0
	TIM_TimeBaseInit(TIM3, &TIMTimeBaseInitStructure);
//配置成两个通道同时捕获同一个引脚的模式
	//方式 2:
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
	TIM_ICInitStructure.TIM_ICFilter = 0xF;
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);//只需要传入一个通道的参数就行了,在函数里会自动把剩下的一个通道初始化成相反的配置
//5.选择从模式的触发源,触发源选择 TI1FP1,这里调用一个库函数,给一个参数就行
	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
//6.选择触发之后执行的操作,执行 Reset 操作,这里也是调用一个库函数就行
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
//7.开启定时器,CNT 自增运行
	TIM_Cmd(TIM3, ENABLE);
}

//当我们需要读取最新一个周期的频率时,直接读取 CCR 寄存器,然后按照 fc/N 计算一下就行了
uint32_t IC_GetFreq(void) {
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);// +1 用于纠正 正负 1 误差
}

uint32_t IC_GetDuty(void) {
	return  (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);//0~100,对应占空比 0%~100%
}

当我们需要读取最新一个周期的频率时,直接读取 CCR 寄存器,然后按照 fc / N 计算一下就行了

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"

int main(void) {
	OLED_Init();
	PWM_Init();
	IC_Init();
	
	OLED_ShowString(1, 1, "Freq:00000Hz");
	OLED_ShowString(2, 1, "Duty:00%");
	
	PWM_SetPrescaler(7200 - 1);//Freq = 72M /(PSC + 1)/ 100
	PWM_SetCompare1(99);	//Duty = CCR / 100
	
	while(1){
		OLED_ShowNum(1, 6, IC_GetFreq(), 5);//01001 Hz
		OLED_ShowNum(2, 6, IC_GetDuty(), 2);
	} 
}

计数刚到 1000 Hz 的那个数时,信号也刚好跳变,因为电路结构或者其他什么原因,导致这一个数刚好没记到,才会有一点误差,不过这个也属于 正负1 误差 的范畴,目前这个误差也是符合要求的。

2.2.4 输入捕获测频率的性能评估

  1. 测频率的范围:目前我们给的标准频率是 1 MHz,计数器最大只能计到 65535,所以所测量的最低频率是 1M/65535,这个值算一下大概是 15 Hz,如果信号频率再低,计数器就要溢出了,所以最低频率就是 15 Hz 左右。那如果想要再降低一些最低频率的限制,我们可以把这个预分频再加大点,这样标准频率就更低,所支持测量的最低频率也就更低,这是测量频率的下限。然后测量的上限,就是支持的最大频率,这个最大频率并没有一个明显的界限,因为随着待测频率的增大,误差也会逐渐增大,如果非要找一个频率上限,那应该就是标准频率 1MHz,超过 1MHz,信号频率比标准频率还高,那肯定测不了了。但是这个 1MHz 的上限并没有意义,因为信号已经接近 1MHz 时,误差已经非常大了,所以最大频率要看你对误差的要求。上一小节我们说到了 正负1误差,计 100 个数,误差一个,相对误差就是 1%;计 1000 个数,误差一个,相对误差就是 0.1%,所以正负 1误差可以认为是 1/计数值。在这里,如果要求误差等于千分之一时,频率为上限,那这个上限就是 1M / 1000 = 1 KHz,如果要求误差可以到 1%,那频率上限就是 1M / 100 = 10KHz。如果想提高频率的上限,那我们在这里,就要把 PSC 给降低一些,提高标准频率,上限就会提高。除此之外,如果频率还要更高,那我们就需要考虑一下测频法了。测频法适合高频,测周法适合低频,我们这里是测周法,所以对于非常高的频率,还是交给测频法来解决吧。
  2. 然后呢,还有一个就是误差分析,除了我们之前说的 正负 1 误差外,在实际测量的时候还会有晶振误差,比如我们 STM32 的晶振不是那么准,在计次几百几万次之后,误差累积起来,也会造成一些影响,当然目前我们是自己测量自己,不存在晶振误差,所以数值还是非常稳定的,如果你要测量别的信号,那数值可能就会有些抖动了,后期可以再做一些滤波处理。
  3. 其他更深入的内容,就需要大家在实践中不断研究总结了。
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值