简介:配置好STM32 CUBE IDE后只需要额外7行代码就可以构建一个频率计,目前只计算测频,占空比测量需要加入下降沿捕获标记(暂时没做)。
一、原理
- 频率:单位时间内完成周期性变化的次数,f = 1/T。
- 如何测量:
- 脉冲数测量法,单位时间内脉冲的数量来计算频率。适用条件:高频,因为高频的脉冲间隔时间很短,测量时间不容易达到准确状态。
- 测量时间间隔法,测量脉冲之前的时间间隔。适用条件:低、中频
- 高、中低频分类:暂时没有找到依据借鉴,这里取经验算,当单片机定时器的时钟为108MHz时,取脉冲为误差为1的条件下,计算频率误差为10%时的最高测量频率为1.08MHz,即1MHz以下的频率可以用测量时间间隔法,理论频率可以测到108MHz只是高于1MHz的情况下,误差大于10%。
- 其他减小误差的方法,多次平均值等。
二、时间间隔法测频
- 计算方法,f = 1/T,如T = 1mS时,f = 1KHz,所以当我测到两个脉冲的间隔T,就能得到频率。
- 采用定时器输入捕获得到时间间隔。例如,输入捕获有三种触发方式,上升沿、下降沿、上/下沿,当配置为上升沿时,当引脚有一个上升沿,就会触发一次捕获事件,此时记录定时器的计数值V1,第二次上升沿触发时,记录定时器的计数值V2,所以事件间隔为T = V2 – V1,从而得到频率。
- 这里需要考虑定时器溢出的情况,如16位定时器计数值为0-65535,两个上升沿捕获可能发生在当前计数周期最后或下一个计数周期、或跨越多个计数周期。
- 有些例子使用置计数值0的方法,这种方法限制挺多的,如一个定时器多个通道等。
三、实际应用
例:使用TIM4_CH2和TIM4_CH3测频,测量范围2Hz-200Hz,频率更新速度200mS
- 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);