STM32cubemx定时外部模式测量10M以上频率

STM32cubemx定时外部模式测量10M以上频率

本文讲解利用定时器的外部时钟功能,巧妙测量高频外部信号频率。范围可以到高达30M以上。

所需工具:

  • 开发板:STM32F103RCT6
  • STM32CubeMX
  • IDE: Keil-MDK

原理讲解

定时器的时钟

我们在正常使用TIM定时器的时候,在cubemx里面的时钟树里,随便点击配置,就可以选择好定时器的时钟。比如下面这个情况:

img

通过时钟树,给挂在APB2时钟上的定时器,提供了72M的时钟。内部是如何产生时钟的呢,我们这里不用关心,不在本文讨论范围,只要知道有一个72M的时钟输入给了定时器TIM作为时钟源。

形象一点说,是一个72M、占空比50%、高电平3.3V、低电平0V的PWM输入给了定时器。每当定时器检测到PWM的上升沿时,就会在CNT计数值寄存器中加一。

下面我们举例说明,有一个1k的内部时钟,输入给定时器。如下图

image-20230603165825032

当TIM检测到时钟的上升沿的时候,CNT寄存器就会加一。我们知道,当CNT计算到ARR寄存器里面存放的自动重装载值的时候,就会归零。如果ARR为1000。那么CNT的计数值成如下关系:

image-20230603170109275

因为内部时钟是1khz,CNT寄存器每1ms增加1,当计数到1000时,满足ARR的值,就会重新开始装载。

很好,现在开动我们的小脑筋。假如我们并不知道内部时钟是多少。我们可以如何通过CNT得知它的频率呢?

读者可以在这打住,想一下这个问题应该如何解决。后面会揭晓答案。

我们可以先将ARR值,设置的足够大,比如65536吧,避免计数值达到ARR后,重新装载从0计数。我们可以每1秒去读取CNT的值,读取出当前的值后,把CNT寄存器清零,重新开始计数。可以想象,每隔1秒读出来的CNT就是1秒内,定时器收到的内部时钟上升沿,上升沿的个数不就是时钟频率嘛!(呕吼~~狂喜)当前例子下,可以想象每次读取出来都是1000。

有了这个思路,我们不就可以测量定时器时钟源的频率吗?要是这个时钟源可以选择外部信号就好了,通过IO口引脚输入给定时器作为时钟源。欸!巧了,定时器还真有外部时钟引脚!

定时器的外部时钟

下图是从参考手册中,截取的定时器框图:

QQ图片20230603171436

TIMX_EXR就是外部时钟引脚,如果你看到这个框图犯嘀咕,好麻烦啊,其他都是什么乱七八糟的方框、旁边的ITR0、TRC是干什么的?不用担心,我们来对图进行一下化简:

914C47CEEC0B42B77177D684639B7C75

定时器引出一个引脚,这个引脚对应着一个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 ARRCOUNT+CNT=500002000+1000=10.001M
需要注意的是,每次读取后,不光要清空CNT寄存器,还需要清空COUNT变量。

工程建立

时钟树

image-20230603190613506

image-20230603190625608

定时器配置

本例程使用到两个定时器。首先是定时器2,时钟源选择外部时钟。

image-20230603190513559

可以看到PA0便是定时器2对应的外部时钟引脚,我们后面只需要把外部PWM信号,连接到PA0,就可以测量频率了。

image-20230603190914960

开启定时器2的中断,用来在计数值超过65536时,产生更新中断,在中断中对变量进行加一,表明此时计数了一个周期。

image-20230603191028836

定时器3用来产生1s的中断,每隔一秒在中断中读取当前频率。

image-20230603190653410

image-20230603191955032

串口配置

image-20230603192104481

代码生成

QQ图片20230603192419

QQ图片20230603192436

代码编写

串口重定向

在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;
}

QQ图片20230603192340

在main.c中,包含头文件:

#include "stdio.h"

QQ图片20230603192910

QQ图片20230603194008

脉冲计数

首先定义用来计数的变量,变量含义在注释中说明。

uint32_t FQ; // 用来存放频率

uint16_t CNT_2 = 0; // 超出65536位后 用这个记录超过的次数
uint16_t CNT_1 = 0; // 0-65536内用这个记录

QQ图片20230603192836

接着开启定时器2和3。

HAL_TIM_Base_Start_IT(&htim3); // 启动定时器3进行1S定时

HAL_TIM_Base_Start_IT(&htim2); // 启动定时器2进行外部脉冲计数

QQ图片20230603192839

因为定时器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,标志多了一个满量程数
    }
}

image-20230603193436367

此处的频率计算公式如下:
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串口助手。

IMG_20230603_195910

运行结果

信号发生器产生12M的方波信号,0-3.3V。串口信息如下:

image-20230603200553300

可以看到,cnt成功测量出来了外接信号的频率,误差0.004%。

实际上,因为定时器内部的边沿检测部分存在,定时不光可以测量PWM信号的频率,还可以直接测量正弦、三角波等频率,幅度也不一定要0-3.3V!

QQ图片20230605160228

比如我测量一个1M,1V-2V的三角波:

IMG_20230603_202058

image-20230603202037889

正如前面所提到的,刚开始运行时,前两个数据是不能用的,需要扔掉,后续测量出来信号频率是999957hz,误差0.0043%。

如下是我测试出来的,能测量频率的不同波形、不同幅度的信号。

  • 方波(8M以内的极限值,再往上,高电平尽量高,低电平尽量低)
    • 高电平1.6——3V(3.3是上限,建议用3V以内)
    • 低电平-200mv——1.3v。
  • 正弦(1M以内)
    • 高电平1.7V——3V
    • 低电平-200mv——1.3v
  • 三角(1k以内)
    • 高电平1.7V——3V
    • 低电平-200mv——1.3v

下表是我用H750测试出来的表单,H7的测试精度略高于F1

频率 误差
1k 0
10k 0
1M 22
10M 212
15M 317
我的信号发生器发不出更高的 ————

练习

  1. 复现本文例程,测量不同幅值频率下,方波、正弦等波形的信号频率。
  2. 该方法的测量范围高,但是测量时间长,有没有办法略微牺牲精度,换取更快的频率测量?
  3. 外部计数本质是测量外部的上升沿个数,CNT达到ARR值时,会产生更新中断,这个功能能否在其他场景下起作用呢?ARR是可以动态更改的。

后记

本文章收录于:

唐承乾的电赛小站

本文为系列文章中的冰山一角,欢迎进入小站查看。

配套程序:

STM32cubemx定时外部模式测量10M以上频率例程 gitee

QQ图片20230603203816

猜你喜欢

转载自blog.csdn.net/qq_34022877/article/details/131025473
今日推荐