STM32输入捕获之快速构建频率计

        简介:配置好STM32 CUBE IDE后只需要额外7行代码就可以构建一个频率计,目前只计算测频,占空比测量需要加入下降沿捕获标记(暂时没做)。

 一、原理

  1. 频率:单位时间内完成周期性变化的次数,f = 1/T。
  2. 如何测量:
    1. 脉冲数测量法,单位时间内脉冲的数量来计算频率。适用条件:高频,因为高频的脉冲间隔时间很短,测量时间不容易达到准确状态。
    2. 测量时间间隔法,测量脉冲之前的时间间隔。适用条件:低、中频
    3. 高、中低频分类:暂时没有找到依据借鉴,这里取经验算,当单片机定时器的时钟为108MHz时,取脉冲为误差为1的条件下,计算频率误差为10%时的最高测量频率为1.08MHz,即1MHz以下的频率可以用测量时间间隔法,理论频率可以测到108MHz只是高于1MHz的情况下,误差大于10%。
    4. 其他减小误差的方法,多次平均值等。

二、时间间隔法测频

  1. 计算方法,f = 1/T,如T = 1mS时,f = 1KHz,所以当我测到两个脉冲的间隔T,就能得到频率。
  2. 采用定时器输入捕获得到时间间隔。例如,输入捕获有三种触发方式,上升沿、下降沿、上/下沿,当配置为上升沿时,当引脚有一个上升沿,就会触发一次捕获事件,此时记录定时器的计数值V1,第二次上升沿触发时,记录定时器的计数值V2,所以事件间隔为T = V2 – V1,从而得到频率。
  3. 这里需要考虑定时器溢出的情况,如16位定时器计数值为0-65535,两个上升沿捕获可能发生在当前计数周期最后或下一个计数周期、或跨越多个计数周期。
  4. 有些例子使用置计数值0的方法,这种方法限制挺多的,如一个定时器多个通道等。

三、实际应用

例:使用TIM4_CH2和TIM4_CH3测频,测量范围2Hz-200Hz,频率更新速度200mS

  1. STM32 CUBE IDE配置

        2.添加代码

包含头文件:文件里面可更改采集和计算次数

#include "user_tim_measure_frequency.h"

定义测量对象:

TIM_Capture_Def TIM4_CH2, TIM4_CH3;

初始化测量状态,主要在于输入定时器主频,用于后续计算频率

// 定时器主频

    user_tim_capture_init(&TIM4_CH2, (HAL_RCC_GetPCLK1Freq() * 2) / htim4.Init.Prescaler);

    user_tim_capture_init(&TIM4_CH3, (HAL_RCC_GetPCLK1Freq() * 2) / htim4.Init.Prescaler);

开启定时器捕获中断和溢出中断

HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_2); // 开启捕获

HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_3); // 开启捕获

__HAL_TIM_ENABLE_IT(&htim4, TIM_IT_UPDATE); // 更新中断用于溢出计数

捕获中断和溢出中断中加入标记函数

/******************************************************************************
 * 功能:定时器更新中断(计数溢出)中断处理回调函数, 该函数在HAL_TIM_IRQHandler中会被调用
 * TIM4:溢出计数
 *****************************************************************************/
void user_HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) // 更新中断(溢出)发生时执行
{
    if (htim->Instance == TIM4) // 捕获
    {
        user_tim_tick_overflow(&TIM4_CH2);
        user_tim_tick_overflow(&TIM4_CH3);
    }
}

/******************************************************************************
 * 功能:定时器捕获,测频
 *****************************************************************************/
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM4)
    {
        if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
        {
            user_tim_capture_mark(&TIM4_CH2, HAL_TIM_ReadCapturedValue(&htim4, TIM_CHANNEL_2));
        }
        if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3)
        {
            user_tim_capture_mark(&TIM4_CH3, HAL_TIM_ReadCapturedValue(&htim4, TIM_CHANNEL_3));
        }
    }
}

 200mS一次计算频率

printf("---> TIM4_CH2: %f Hz   TIM4_CH3: %f Hz \r\n", user_tim_calculate_frequency(&TIM4_CH2), user_tim_calculate_frequency(&TIM4_CH3));

四、测量结果

2Hz:

20Hz:

200Hz:

1KHz:

五、user_tim_measure_frequency.h文件

#ifndef __USER_TIM_MEASURE_FREQUENCY_H
#define __USER_TIM_MEASURE_FREQUENCY_H

/******************************************************************************
 *参数:设置最大采集和计算次数,
 *****************************************************************************/
#define capture_MAX_times 16 // 最大捕获数据次数

/******************************************************************************
 * 参数:测量相关的数据结构体
 *****************************************************************************/
typedef struct
{
    uint32_t basic_frequency; // 定时器的主频 = 时钟主频/分频

    _Bool capture_state;           // 输入捕获状态
    uint16_t capture_last_value;   // 当前捕获值
    uint16_t capture_now_value;    // 上一次的捕获值
    uint8_t capture_count;         // 一次计算的捕获次数
    uint8_t capture_overflow_time; // 溢出次数

    uint32_t capture_total_times; // 捕获总次数

    uint8_t data_point;                              // 数据存储位置
    uint16_t pulse_interval_time[capture_MAX_times]; // 脉冲间隔缓冲数组

    float frequency; // 计算的频率
} TIM_Capture_Def;

/******************************************************************************
 * 功能:置零计算数据
 *****************************************************************************/
void user_reset_tim_calculate_data(TIM_Capture_Def *_capture_channel)
{
    _capture_channel->capture_state = 0;
    _capture_channel->data_point = 0;
    _capture_channel->capture_overflow_time = 0;

    if (_capture_channel->capture_count >= capture_MAX_times)
    {
        memset(_capture_channel->pulse_interval_time, 0, capture_MAX_times * sizeof(uint16_t));
    }
    else
    {
        memset(_capture_channel->pulse_interval_time, 0, _capture_channel->capture_count * sizeof(uint16_t));
    }
    _capture_channel->capture_count = 0;
}

/******************************************************************************
 * 功能:测频初始化
 * 需要输入定时器的主频
 * 1、输入定时器主频,用于后续计算频率
 * 2、置零数据
 *****************************************************************************/
void user_tim_capture_init(TIM_Capture_Def *_capture_channel, uint32_t _timer_basic_frequency)
{
    _capture_channel->basic_frequency = _timer_basic_frequency;
    user_reset_tim_calculate_data(_capture_channel);
}

/******************************************************************************
 * 功能:溢出状态
 *****************************************************************************/
void user_tim_tick_overflow(TIM_Capture_Def *_capture_channel)
{
    _capture_channel->capture_overflow_time++;
}

/******************************************************************************
 * 功能:定时器产生捕获,标记时间同时计算时间间隔 T
 * _capture_now_value: 当前捕获值
 *****************************************************************************/
void user_tim_capture_mark(TIM_Capture_Def *_capture_channel, uint32_t _capture_now_value)
{
    // 0、捕获总次数记录,捕获状态更新
    if (_capture_channel->capture_state == 0)
    {
        _capture_channel->capture_state = 1;
    }
    _capture_channel->capture_total_times++;

    // 1、计算时间间隔
    if (_capture_channel->capture_overflow_time == 0) // 无溢出
    {
        _capture_channel->pulse_interval_time[_capture_channel->data_point] = _capture_now_value - _capture_channel->capture_last_value;
    }
    else // 有溢出
    {
        if (_capture_channel->capture_overflow_time == 1) // 溢出一次
        {
            _capture_channel->pulse_interval_time[_capture_channel->data_point] = _capture_now_value + 65535 - _capture_channel->capture_last_value;
        }
        else // 溢出多次,这里没做计算,最大取65535
        {
            _capture_channel->pulse_interval_time[_capture_channel->data_point] = 65535;
        }
        _capture_channel->capture_overflow_time = 0; // 溢出状态清零
    }

    // 2、保存上一次状态
    _capture_channel->capture_last_value = _capture_now_value;

    // 3、指向下一个存储地址,当前次数+1
    _capture_channel->data_point++;
    _capture_channel->capture_count++;

    // 4、位置溢出的情况下,调整为第一个位置,覆盖原有数据
    if (_capture_channel->data_point >= capture_MAX_times)
    {
        _capture_channel->data_point = 0;
    }
}

/******************************************************************************
 * 功能:计算频率
 *****************************************************************************/
float user_tim_calculate_frequency(TIM_Capture_Def *_capture_channel)
{
    if (_capture_channel->capture_state) // 捕获到数据
    {
        // 1、取平均值,先取和,再取平均
        uint32_t capture_sum_time = 0;
        float _average;

        if (_capture_channel->capture_count >= capture_MAX_times) // 一次间隔捕获时间大于 capture_MAX_times 次
        {
            for (size_t i = 0; i < capture_MAX_times; i++)
            {
                capture_sum_time += _capture_channel->pulse_interval_time[i];
            }
            _average = (float)capture_sum_time / capture_MAX_times;
        }
        else
        {
            for (size_t i = 0; i < _capture_channel->capture_count; i++)
            {
                capture_sum_time += _capture_channel->pulse_interval_time[i];
            }
            _average = (float)capture_sum_time / _capture_channel->capture_count;
        }

        // printf("---> capture_sum_time: %ld  count:%d    _average:%f\r\n", capture_sum_time, _capture_channel->capture_count, _average);

        // 2、计算频率,f = 主频/(分频*重装值)
        _capture_channel->frequency = _capture_channel->basic_frequency / _average;

        // 3、归零状态
        user_reset_tim_calculate_data(_capture_channel);

        // 4、返回计算频率
        return _capture_channel->frequency;
    }
    else // 没有捕获到数据
    {
        return 0;
    }
}

#endif /* __USER_TIM_MEASURE_FREQUENCY_H */

六、使用步骤和参数解释

 /***********************************************************************************************************************************************************/

步骤1:定义测量对象。

    如:TIM_Capture_Def TIM4_CH2, TIM4_CH3;

步骤2:初始化对象。

    针对对象,传入定时器主频(用于计算频率)、置零状态和数据。
    user_tim_capture_init(&TIM4_CH2, (HAL_RCC_GetPCLK1Freq() * 2) / htim4.Init.Prescaler);

    通过系统时钟树得到TIM4 对应的时钟为 PLCK1 x 2

    计算频率为 f = 1/T 
    定时器主频 = APBx外设时钟 / 预分频
    T = 定时器主频 / 重装值
    f = 1/T = 定时器主频 / 重装值

    预分频为根据需求自己设定,这个影响到测频范围。
    例如,tim4时钟为108M、预分频为1080、16位重装计数器的条件下,
    测量频率 fmax = 0.1M / 1 = 100 KHz
            fmin = 0.1M / 65535 = 1.53Hz
    根据以上配置,理论可测量 1.53Hz —— 100KHz

    因为捕获计时是有抖动误差的,即重装值误差,所以在保证精度的前提下,需要限值最大测量频率。
    例,同样相差10个重装值的情况下,在低频时, f = 0.1M / 65530 = 1.52601 Hz    、  f = 0.1M / 65520 = 1.52625 Hz
    而在高频时f = 0.1M / 100 = 1000 Hz    、  f = 0.1M / 90 = 1111.11 Hz

    减小误差的方法有很多,如平均值、滤波等,需要平衡计算时间等,测量时间越长,结果就越精确。

    例如,因为自己需要的测频条件为:范围2Hz —— 100Hz,200mS计算一次频率。所以可以采集到200组数据,取最后16组数据做平均值,重装值误差为1,取1Hz测量误差的条件下,最高可测得1.2KHz
    所以在这种情况下,测量范围为1.53Hz —— 1.2KHz

步骤3: 捕获中断中添加标记函数,每捕获到一个上升沿,记录重装值,同时计算上升沿的间隔时间
       溢出中断加入溢出标记函数,用于标记重装是否产生溢出

    void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
    {
        if (htim->Instance == TIM4)
        {
            if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
            {
                user_tim_capture_mark(&TIM4_CH2, HAL_TIM_ReadCapturedValue(&htim4, TIM_CHANNEL_2));
            }
            else if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3)
            {
                user_tim_capture_mark(&TIM4_CH3, HAL_TIM_ReadCapturedValue(&htim4, TIM_CHANNEL_3));
            }
        }
    }

    void user_HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) // 更新中断(溢出)发生时执行
    {
        if (htim->Instance == TIM4) // 捕获
        {
            user_tim_tick_overflow(&TIM4_CH2);
            user_tim_tick_overflow(&TIM4_CH3);
        }
    }

步骤4:计算频率 f = 1/T 

    T为上升沿间隔时间的平均值,可减小误差,这里取16组数据做计算,因为我是测量100Hz内的低频,如果需要根据自己需求提高采集计算次数。

    user_tim_calculate_frequency(&TIM4_CH2);

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值