基于STM32的完整数字频率计程序

基于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;
}

十、使用说明

  1. 硬件连接

    • 被测信号 → PA0 (TIM2_CH1)
    • 如果使用LCD显示,连接对应引脚
    • 串口输出用于调试
  2. 测量范围

    • 低频(1Hz-100kHz):测周法,精度高
    • 高频(100kHz-10MHz):测频法,响应快
  3. 精度指标

    • 1Hz-1kHz:±0.1Hz
    • 1kHz-100kHz:±1Hz
    • 100kHz-1MHz:±10Hz
    • 1MHz-10MHz:±100Hz

十一、常见问题解决

问题 可能原因 解决方法
测量值跳动 信号抖动 增加硬件滤波
高频测量不准 中断延迟 改用DMA方式
占空比错误 边沿捕捉不稳定 增加数字滤波
测量范围受限 定时器溢出 调整预分频

专注于matlab/simulink,电子电路,编程