STM32频率计之路

前言

定时器的输入捕获和外部计数(外部输入时钟,脉冲数计数)均可以用来测频,但是输入捕获的测频范围较小,而使用外部计数则可以使测频范围可达35M。

外部计数的频率计

外部计数

外部计数,就是把外部的需要测频的信号作为时钟源输入到32的定时器中(如定时器5),用这个定时器来计外部信号源的上升沿或下降沿次数,同时通过另一个定时器计时(如定时器2 定时一秒),即每隔一秒产生一次中断来获取一秒内定时器5 的计数次数,来获取频率。

使用外部时钟

TIM的外部时钟的配置有三个库函数

在stm32f10x_tim的头文件中可以找到这三个函数:

void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource, uint16_t TIM_ICPolarity, uint16_t ICFilter);
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);

函数具体作用及参数源文件中可以找到。

在这里我们以第一个函数为例,第一个函数的作用其实与第二个函数一样,同样使用的是外部时钟源模式一 ,只是可能输入引脚有点不一样哦。而使用外部时钟源模式二也只要改一下时钟源配置函数和引脚即可。

这是第一个函数作用:@brief Configures the TIMx Trigger as External Clock(另外两个的函数作用同样可以在stm32f10x_tim.c中找到

 

外部时钟源

我们先来看一下32中文手册中的TIM外部时钟源,具体可参考中文手册,本文使用的是TIM5的外部时钟模式一的TI1FP1通道,即TIM5_CH1 通道,引脚的配置也是配置TIM5_CH1通道对应的引脚PA0.(引脚信息可在32芯片资料中寻找)

 

 

TIM5外部时钟模式配置

这里我们以TIM5的通道一为例:

void TIM5_external_count(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);    //使能TIM5时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  //使能GPIOA时钟
    
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;  //PA0 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING ; // 浮空输入  
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    
    //初始化定时器5 TIM5   
    TIM_TimeBaseStructure.TIM_Period = 0xFFFF; //设定计数器自动重装值 
    TIM_TimeBaseStructure.TIM_Prescaler =0;     //预分频器   
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
    TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
    
    
  // 设置外部时钟输入1通道  检测下降沿计数  无滤波 
  //这一步是配置外部时钟源的关键 当使用其他外部时钟源模式时 更改此库函数以及相应引脚即可
  TIM_TIxExternalClockConfig(TIM5,TIM_TIxExternalCLK1Source_TI1,TIM_ICPolarity_Falling,0);
    
    //中断分组初始化
    NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;  //TIM5中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; 
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
    NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 
    
    TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE);//允许更新中断 
    
    TIM_SetCounter(TIM5,0);
   TIM_Cmd(TIM5,ENABLE);    //使能定时器5
}

 

接下来是定时器5的中断函数(作用看代码注释,下同):

oid TIM5_IRQHandler(void)
{
    //定时器5发生更新中断,即寄存器CNT溢出,产生中断来计数溢出次数 
    if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
    {
        TIM_ClearITPendingBit(TIM5,TIM_IT_Update);
        temp+=1;
    }
    
}

 

配置完定时器5,我们还需要另一个定时器来定时,这里以定时器2为例,每次的中断就是获取信号的频率。

void TIM2_Int_Init(u16 arr,u16 psc)
{
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //时钟使能

    TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值    计数到5000为500ms
    TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值  10Khz的计数频率  
    TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
 
    
    TIM_ITConfig( TIM2, TIM_IT_Update,ENABLE );
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;  //TIM3中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
    NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器

    TIM_Cmd(TIM2, ENABLE);  //使能TIMx外设
                             
}

void TIM2_IRQHandler(void)   
{
    //达到规定时间,记录该时间内的定时器5的计数次数(CNT)以及溢出次数,以便计算定时器5的总计数次数,即信号源经历的周期数
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 
    {
        TIM5_count=TIM_GetCounter(TIM5);  //记录计数次数
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);  //清除TIMx的中断待处理位:TIM 中断源 
        temp1=temp;  //记录溢出次数
        temp=0;     //清空计数器
      TIM_SetCounter(TIM5,0);   //清空计数器
    }
}

 

需注意的是,上述的配置有三个全局变量,另外,其中两个还需要在main函数中作为外部变量引入,他们的作用主要是用于在main函数中计算频率。

u32 TIM5_count; //记录CNT中的值,即计数值
u16 temp; //用于计数溢出次数 相当于 CNT 寄存器
u16 temp1;//用于记录溢出次数  相当于 TIM5_count

最后是main函数模块,这边采用的是简单的串口输出显示频率:

extern u32  TIM5_count; //定时器5的CNT值 
extern u16 temp1;  //定时器5溢出次数
//65536*temp1+TIM5_count 即为定时器5在所规定时间内的实际计数次数

 int main(void)
 {      
    u32 f;
    delay_init();            //延时函数初始化    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
    uart_init(115200);   //串口初始化为115200
    TIM2_Int_Init(999,7199); //10kHz  0.1s计
    TIM5_external_count();  //  PA.0
     
    while(1)
    {
   f=(TIM5_count+temp1*65536)*10;
        printf("f: %d Hz\r\n",f);
        delay_ms(1000);
    }
 }

此外,可以自由改变参数来获取需要的精度,需要注意的是在测试时存在一个系统误差,可以通过加上一个修正参数来修正,比如乘上1.0000x或者0.9999x,具体由测试结果而看。

 

最后提醒一下,如果使用的是另外两个库函数配置的外部时钟源需注意的是对应引脚为相应通道的ETR引脚
下图表示外部时钟模式2的输入引脚为ETR引脚

 

end

本文的最后附上亲爱的百酱提供的参考链接:https://blog.csdn.net/liwuxin1/article

发表评论