赞
踩
本文讲解利用定时器的外部时钟功能,巧妙测量高频外部信号频率。范围可以到高达30M以上。
所需工具:
我们在正常使用TIM定时器的时候,在cubemx里面的时钟树里,随便点击配置,就可以选择好定时器的时钟。比如下面这个情况:
通过时钟树,给挂在APB2时钟上的定时器,提供了72M的时钟。内部是如何产生时钟的呢,我们这里不用关心,不在本文讨论范围,只要知道有一个72M的时钟输入给了定时器TIM作为时钟源。
形象一点说,是一个72M、占空比50%、高电平3.3V、低电平0V的PWM输入给了定时器。每当定时器检测到PWM的上升沿时,就会在CNT计数值寄存器中加一。
下面我们举例说明,有一个1k的内部时钟,输入给定时器。如下图
当TIM检测到时钟的上升沿的时候,CNT寄存器就会加一。我们知道,当CNT计算到ARR寄存器里面存放的自动重装载值的时候,就会归零。如果ARR为1000。那么CNT的计数值成如下关系:
因为内部时钟是1khz,CNT寄存器每1ms增加1,当计数到1000时,满足ARR的值,就会重新开始装载。
很好,现在开动我们的小脑筋。假如我们并不知道内部时钟是多少。我们可以如何通过CNT得知它的频率呢?
读者可以在这打住,想一下这个问题应该如何解决。后面会揭晓答案。
我们可以先将ARR值,设置的足够大,比如65536吧,避免计数值达到ARR后,重新装载从0计数。我们可以每1秒去读取CNT的值,读取出当前的值后,把CNT寄存器清零,重新开始计数。可以想象,每隔1秒读出来的CNT就是1秒内,定时器收到的内部时钟上升沿,上升沿的个数不就是时钟频率嘛!(呕吼~~狂喜)当前例子下,可以想象每次读取出来都是1000。
有了这个思路,我们不就可以测量定时器时钟源的频率吗?要是这个时钟源可以选择外部信号就好了,通过IO口引脚输入给定时器作为时钟源。欸!巧了,定时器还真有外部时钟引脚!
下图是从参考手册中,截取的定时器框图:
TIMX_EXR就是外部时钟引脚,如果你看到这个框图犯嘀咕,好麻烦啊,其他都是什么乱七八糟的方框、旁边的ITR0、TRC是干什么的?不用担心,我们来对图进行一下化简:
定时器引出一个引脚,这个引脚对应着一个IO口,我们把外部的PWM信号,输入给这个引脚:TIMx_EXR。pwm信号输入到上升沿检测中后,再连接CNT寄存器,对每个上升沿进行计数加一。
现在我们把ARR设置为65536,ETR引脚外接60k的PWM信号。每1秒去读取CNT寄存器的计数数值,读取完成后清空。可以想象,每次读取出来CNT都是60000,对应着这一秒内,CNT检测到60k个上升沿,PWM频率为60k。
可是!如果频率超过65536hz,CNT计数值打到ARR后自动清零,我们不就读不到数据了嘛?欲知后事如何,且听下节分说。
CNT达到ARR后,确实会更新,从零计数。不过,会产生定时器中断嘛。我们可以定义一个变量COUNT,当中断产生时,变量就加一,这个变量便表明,记录了多少个轮回,有多少个ARR。
假设:ARR为50k,外部输入PWM频率为10.001M。我们每1秒读取时,会读取到COUNT为2000,CNT寄存器值为1000。那么可以推算出来PWM频率为:
A
R
R
∗
C
O
U
N
T
+
C
N
T
=
50000
∗
2000
+
1000
=
10.001
M
ARR*COUNT+CNT=50000*2000+1000=10.001M
ARR∗COUNT+CNT=50000∗2000+1000=10.001M
需要注意的是,每次读取后,不光要清空CNT寄存器,还需要清空COUNT变量。
本例程使用到两个定时器。首先是定时器2,时钟源选择外部时钟。
可以看到PA0便是定时器2对应的外部时钟引脚,我们后面只需要把外部PWM信号,连接到PA0,就可以测量频率了。
开启定时器2的中断,用来在计数值超过65536时,产生更新中断,在中断中对变量进行加一,表明此时计数了一个周期。
定时器3用来产生1s的中断,每隔一秒在中断中读取当前频率。
在usart.c靠末尾的地方,编写下面函数
#include <stdio.h>
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
return ch;
}
在main.c中,包含头文件:
#include "stdio.h"
首先定义用来计数的变量,变量含义在注释中说明。
uint32_t FQ; // 用来存放频率
uint16_t CNT_2 = 0; // 超出65536位后 用这个记录超过的次数
uint16_t CNT_1 = 0; // 0-65536内用这个记录
接着开启定时器2和3。
HAL_TIM_Base_Start_IT(&htim3); // 启动定时器3进行1S定时
HAL_TIM_Base_Start_IT(&htim2); // 启动定时器2进行外部脉冲计数
因为定时器2、3开启有先后顺序,所以第一次测量到的频率不能使用
接下来便是本文的重要部分,读取计数值:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim == (&htim3)) // 定时器中断1S触发一次 { CNT_1 = __HAL_TIM_GET_COUNTER(&htim2); // 读取CNT计数值 // CNT_1 = TIM2->CNT;// 读取CNT寄存器这样也可以 FQ = CNT_1 + CNT_2 * (__HAL_TIM_GET_AUTORELOAD(&htim2) + 1); // 获取频率值 __HAL_TIM_SET_COUNTER(&htim2, 0);// 设置CNT为0 printf("cnt:%u\n", FQ); // 打印频率值 1S打印一次 CNT_1 = 0; CNT_2 = 0; } if (htim == (&htim2)) // 计数值计满,产生更新中断 { CNT_2++; // 记满后,+1,标志多了一个满量程数 } }
此处的频率计算公式如下:
F
Q
=
C
N
T
1
+
C
N
T
2
∗
(
65535
+
1
)
FQ=CNT_1+CNT_2*(65535+1)
FQ=CNT1+CNT2∗(65535+1)
__HAL_TIM_GET_AUTORELOAD(&htim2)读取的是TIM2的ARR寄存器值,因为计数都是从0开始,所以要加一。比如ARR为999,从0计算到999,实际计算了999+1次。
引脚 | 连接对象 | 释义 |
---|---|---|
PA0 | 信号发生器的正极 | TIM2的外部时钟引脚 |
GND | 信号发生器的负极 | 供地 |
因为我的开发板板载ch340,这里并不需要外接ch340串口助手。
信号发生器产生12M的方波信号,0-3.3V。串口信息如下:
可以看到,cnt成功测量出来了外接信号的频率,误差0.004%。
实际上,因为定时器内部的边沿检测部分存在,定时不光可以测量PWM信号的频率,还可以直接测量正弦、三角波等频率,幅度也不一定要0-3.3V!
比如我测量一个1M,1V-2V的三角波:
正如前面所提到的,刚开始运行时,前两个数据是不能用的,需要扔掉,后续测量出来信号频率是999957hz,误差0.0043%。
如下是我测试出来的,能测量频率的不同波形、不同幅度的信号。
下表是我用H750测试出来的表单,H7的测试精度略高于F1
频率 | 误差 |
---|---|
1k | 0 |
10k | 0 |
1M | 22 |
10M | 212 |
15M | 317 |
我的信号发生器发不出更高的 | ———— |
本文章收录于:
本文为系列文章中的冰山一角,欢迎进入小站查看。
配套程序:
公众号回复:32定时器测频
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。