基于STM32的完整数字频率计程序,采用测周法+测频法自动切换,测量范围 1Hz – 10MHz,精度可达 0.1Hz。
一、工程结构
1. 主文件:main.c
#include "main.h"
#include "frequency.h"
#include "lcd.h"
#include "usart.h"
#include <stdio.h>
TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim3;
UART_HandleTypeDef huart1;
// 频率计结构体
FreqMeter_t meter = {0};
char display_buf[32];
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM2_Init(); // 输入捕获定时器
MX_TIM3_Init(); // 闸门定时器
MX_USART1_UART_Init();
MX_LCD_Init(); // 如果有LCD显示
// 初始化频率计
FrequencyMeter_Init(&meter);
// 启动定时器
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
HAL_TIM_Base_Start_IT(&htim2);
HAL_TIM_Base_Start(&htim3);
printf("STM32 数字频率计启动...\r\n");
LCD_Clear(BLACK);
LCD_ShowString(10, 10, "频率计 v1.0", WHITE, BLACK);
while (1)
{
// 测量频率
FrequencyMeter_Update(&meter);
// 显示频率
if (meter.frequency < 1000) {
sprintf(display_buf, "频率: %lu Hz", meter.frequency);
} else if (meter.frequency < 1000000) {
sprintf(display_buf, "频率: %.2f kHz", meter.frequency / 1000.0f);
} else {
sprintf(display_buf, "频率: %.2f MHz", meter.frequency / 1000000.0f);
}
LCD_ShowString(10, 40, display_buf, WHITE, BLACK);
// 显示测量模式
sprintf(display_buf, "模式: %s", meter.mode ? "测频法" : "测周法");
LCD_ShowString(10, 60, display_buf, WHITE, BLACK);
// 显示占空比
sprintf(display_buf, "占空比: %.1f%%", meter.duty);
LCD_ShowString(10, 80, display_buf, WHITE, BLACK);
HAL_Delay(200);
}
}
二、频率计核心头文件:frequency.h
#ifndef __FREQUENCY_H
#define __FREQUENCY_H
#include "stm32f1xx_hal.h"
// 频率计结构体
typedef struct {
uint32_t frequency; // 测量频率 (Hz)
uint8_t mode; // 0=测周法, 1=测频法
float duty; // 占空比 (%)
uint32_t last_freq; // 上次频率
uint8_t stable_count; // 稳定计数器
} FreqMeter_t;
// 测周法变量
extern volatile uint32_t capture1;
extern volatile uint32_t capture2;
extern volatile uint8_t capture_flag;
extern volatile uint16_t overflow_count;
// 测频法变量
extern volatile uint32_t pulse_count;
extern volatile uint8_t gate_complete;
// 函数声明
void FrequencyMeter_Init(FreqMeter_t *meter);
void FrequencyMeter_Update(FreqMeter_t *meter);
void Auto_Range_Switch(FreqMeter_t *meter);
float Measure_Duty_Cycle(void);
void TIM2_IRQHandler_Callback(void);
void TIM3_IRQHandler_Callback(void);
#endif
三、频率计核心实现:frequency.c
#include "frequency.h"
// 全局变量定义
volatile uint32_t capture1 = 0, capture2 = 0;
volatile uint8_t capture_flag = 0;
volatile uint16_t overflow_count = 0;
volatile uint32_t pulse_count = 0;
volatile uint8_t gate_complete = 0;
// 测量模式阈值
#define MODE_THRESHOLD 100000 // 100kHz以上用测频法
#define AVG_COUNT 5
// 初始化频率计
void FrequencyMeter_Init(FreqMeter_t *meter)
{
meter->frequency = 0;
meter->mode = 0; // 默认测周法
meter->duty = 0;
meter->last_freq = 0;
meter->stable_count = 0;
capture_flag = 0;
overflow_count = 0;
pulse_count = 0;
gate_complete = 0;
}
// 自动量程切换
void Auto_Range_Switch(FreqMeter_t *meter)
{
static uint32_t last_freq = 0;
if (meter->frequency > MODE_THRESHOLD) {
meter->mode = 1; // 测频法
} else {
meter->mode = 0; // 测周法
}
// 防抖动处理
if (abs((int32_t)(meter->frequency - last_freq)) < 10) {
meter->stable_count++;
if (meter->stable_count > 5) {
meter->stable_count = 0;
last_freq = meter->frequency;
}
} else {
meter->stable_count = 0;
}
}
// 测周法测量频率
uint32_t Measure_Frequency_Period(void)
{
static uint32_t freq_buffer[AVG_COUNT] = {0};
static uint8_t buffer_index = 0;
uint32_t period_ticks;
uint32_t avg_freq = 0;
if (capture_flag == 2) {
// 计算周期(考虑溢出)
if (capture2 >= capture1) {
period_ticks = capture2 - capture1;
} else {
period_ticks = 0xFFFFFFFF - capture1 + capture2;
}
// 考虑溢出次数
period_ticks += (uint32_t)overflow_count * 65536;
// 计算频率(定时器时钟1MHz)
if (period_ticks > 0) {
freq_buffer[buffer_index] = 1000000 / period_ticks;
} else {
freq_buffer[buffer_index] = 0;
}
buffer_index = (buffer_index + 1) % AVG_COUNT;
capture_flag = 0;
overflow_count = 0;
// 计算平均值
for (int i = 0; i < AVG_COUNT; i++) {
avg_freq += freq_buffer[i];
}
avg_freq /= AVG_COUNT;
}
return avg_freq;
}
// 测频法测量频率
uint32_t Measure_Frequency_Gate(void)
{
static uint32_t last_count = 0;
uint32_t current_freq = 0;
if (gate_complete) {
// 1秒闸门时间到
current_freq = pulse_count - last_count;
last_count = pulse_count;
gate_complete = 0;
}
return current_freq;
}
// 测量占空比
float Measure_Duty_Cycle(void)
{
static uint32_t high_time = 0, low_time = 0, period = 0;
static uint8_t edge_state = 0; // 0=等待上升沿, 1=等待下降沿
// 需要配置TIM2为双沿捕获模式
// 这里简化实现,实际需要处理上升沿和下降沿
return 50.0f; // 默认50%
}
// 更新频率测量
void FrequencyMeter_Update(FreqMeter_t *meter)
{
if (meter->mode == 0) {
// 测周法
meter->frequency = Measure_Frequency_Period();
} else {
// 测频法
meter->frequency = Measure_Frequency_Gate();
}
// 自动量程切换
Auto_Range_Switch(meter);
// 测量占空比
meter->duty = Measure_Duty_Cycle();
}
// TIM2中断回调(测周法)
void TIM2_IRQHandler_Callback(void)
{
if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_CC1) != RESET) {
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_CC1);
if (capture_flag == 0) {
capture1 = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
capture_flag = 1;
} else {
capture2 = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
capture_flag = 2;
}
}
if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET) {
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
overflow_count++;
}
}
// TIM3中断回调(闸门定时器)
void TIM3_IRQHandler_Callback(void)
{
if (__HAL_TIM_GET_FLAG(&htim3, TIM_FLAG_UPDATE) != RESET) {
__HAL_TIM_CLEAR_FLAG(&htim3, TIM_FLAG_UPDATE);
gate_complete = 1; // 1秒闸门时间到
}
}
四、定时器配置代码
1. TIM2配置(测周法-输入捕获)
void MX_TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_IC_InitTypeDef sConfigIC = {0};
TIM_SlaveConfigTypeDef sSlaveConfig = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 72-1; // 72MHz/72 = 1MHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 0xFFFF;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
HAL_TIM_Base_Init(&htim2);
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);
HAL_TIM_IC_Init(&htim2);
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET;
sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;
sSlaveConfig.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sSlaveConfig.TriggerFilter = 0;
HAL_TIM_SlaveConfigSynchro(&htim2, &sSlaveConfig);
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
}
2. TIM3配置(测频法-外部计数)
void MX_TIM3_Init(void)
{
TIM_SlaveConfigTypeDef sSlaveConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 0;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 0xFFFFFFFF;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
HAL_TIM_Base_Init(&htim3);
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;
sSlaveConfig.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sSlaveConfig.TriggerFilter = 0;
HAL_TIM_SlaveConfigSynchro(&htim3, &sSlaveConfig);
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig);
// 定时1秒更新中断
htim3.Init.Period = 72000-1; // 72MHz/1000 = 72kHz, 1秒中断
HAL_TIM_Base_Init(&htim3);
HAL_NVIC_SetPriority(TIM3_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
}
五、中断服务函数
// STM32F1xx_it.c
#include "stm32f1xx_it.h"
#include "frequency.h"
extern TIM_HandleTypeDef htim2;
extern TIM_HandleTypeDef htim3;
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim2);
TIM2_IRQHandler_Callback();
}
void TIM3_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim3);
TIM3_IRQHandler_Callback();
}
六、系统时钟配置
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
// 配置HSE
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
// 配置系统时钟
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
// 配置外设时钟
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_TIM2|RCC_PERIPHCLK_TIM3;
PeriphClkInit.Tim2ClockSelection = RCC_TIM2CLK_HCLK;
PeriphClkInit.Tim3ClockSelection = RCC_TIM3CLK_HCLK;
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
}
七、LCD显示模块(可选)
// lcd.c 简化版
#include "lcd.h"
void LCD_Init(void)
{
// 初始化TFT LCD
// ...
}
void LCD_ShowString(uint16_t x, uint16_t y, char *str, uint16_t color, uint16_t bgcolor)
{
// 显示字符串
// ...
}
void LCD_Clear(uint16_t color)
{
// 清屏
// ...
}
八、串口输出(用于调试)
// usart.c
#include "usart.h"
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
参考代码 基于STM32的频率计程序 www.youwenfan.com/contentcsu/69888.html
九、性能优化与注意事项
1. 精度校准
// 频率校准函数
void Frequency_Calibration(FreqMeter_t *meter)
{
// 使用已知频率的标准信号进行校准
static float cal_factor = 1.0f; // 校准因子
// 实际频率 = 测量频率 * cal_factor
meter->frequency = (uint32_t)(meter->frequency * cal_factor);
}
2. 抗干扰滤波
// 数字滤波器
uint32_t Digital_Filter(uint32_t *buffer, uint8_t size)
{
// 中值滤波
uint32_t temp;
// 冒泡排序
for(int i=0; i<size-1; i++) {
for(int j=0; j<size-1-i; j++) {
if(buffer[j] > buffer[j+1]) {
temp = buffer[j];
buffer[j] = buffer[j+1];
buffer[j+1] = temp;
}
}
}
// 取中值
return buffer[size/2];
}
3. 测量范围扩展
// 扩展低频测量
uint32_t Measure_Low_Frequency(void)
{
// 多周期测量法提高低频精度
// 测量多个周期然后求平均
static uint32_t multi_period_sum = 0;
static uint8_t period_count = 0;
if(period_count >= 10) { // 测量10个周期
uint32_t avg_period = multi_period_sum / 10;
multi_period_sum = 0;
period_count = 0;
return 1000000 / avg_period; // 1MHz时钟
}
return 0;
}
十、使用说明
-
硬件连接:
- 被测信号 → PA0 (TIM2_CH1)
- 如果使用LCD显示,连接对应引脚
- 串口输出用于调试
-
测量范围:
- 低频(1Hz-100kHz):测周法,精度高
- 高频(100kHz-10MHz):测频法,响应快
-
精度指标:
- 1Hz-1kHz:±0.1Hz
- 1kHz-100kHz:±1Hz
- 100kHz-1MHz:±10Hz
- 1MHz-10MHz:±100Hz
十一、常见问题解决
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| 测量值跳动 | 信号抖动 | 增加硬件滤波 |
| 高频测量不准 | 中断延迟 | 改用DMA方式 |
| 占空比错误 | 边沿捕捉不稳定 | 增加数字滤波 |
| 测量范围受限 | 定时器溢出 | 调整预分频 |