You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32F411 ADC输入频率识别结果异常问题求助

问题:STM32F411结合libopencm3与CMSIS DSP识别正弦波频率错误

我正尝试用STM32F411搭配libopencm3和CMSIS DSP库识别函数信号发生器输出的正弦波频率,但未成功。已知FFT算法无问题(用代码生成正弦波时能得到正确结果),但输入100Hz信号时,识别结果为3.91Hz而非随机值。已检查原始ADC缓冲区,数值范围为0-4095符合预期。

代码实现

#include "arm_math.h"
#include "arm_const_structs.h"

#include <math.h>

#define SAMPLE_RATE 4000
#define BUFFER_SIZE 1024


arm_rfft_fast_instance_f32 fft_instance;


float  adc_buffer[BUFFER_SIZE];
float input_buffer[BUFFER_SIZE];
float output_buffer[BUFFER_SIZE];
float magnitude_buffer[BUFFER_SIZE / 2]; 

void adc_setup(void) {
    rcc_periph_clock_enable(RCC_GPIOA);
    rcc_periph_clock_enable(RCC_ADC1);

    // 配置PA1为模拟输入,PA5为输出
    gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO1);
    gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO5);

    adc_power_off(ADC1);

    adc_disable_scan_mode(ADC1);
    adc_set_resolution(ADC1, ADC_CR1_RES_12BIT); // 12位分辨率
    adc_set_right_aligned(ADC1); // 右对齐

    adc_disable_external_trigger_regular(ADC1); // 禁用外部触发,软件启动转换
    adc_set_sample_time(ADC1, ADC_CHANNEL1, ADC_SMPR_SMP_84CYC); // 采样时间84周期

    adc_set_regular_sequence(ADC1, 1, (uint8_t[]){ADC_CHANNEL1}); // 规则通道序列仅包含通道1

    adc_power_on(ADC1);

    // ADC上电等待
    for (int i = 0; i < 100000; i++) {
        __asm__("nop");
    }
}

// 生成正弦波(测试用)
void generate_sine_wave(void) {
    for (int i = 0; i < BUFFER_SIZE; i++) {
        float time = (float)i / SAMPLE_RATE; // 采样时间
        input_buffer[i] = arm_sin_f32(2.0f * PI * TEST_FREQUENCY * time);
    }
}

void usart_setup(void) {
    rcc_periph_clock_enable(RCC_GPIOA);
    rcc_periph_clock_enable(RCC_USART2);

    // 配置PA2、PA3为复用功能
    gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO2 | GPIO3);
    gpio_set_af(GPIOA, GPIO_AF7, GPIO2 | GPIO3); // 复用为USART2

    usart_set_baudrate(USART2, 115200);
    usart_set_databits(USART2, 8);
    usart_set_stopbits(USART2, USART_STOPBITS_1);
    usart_set_mode(USART2, USART_MODE_TX_RX);
    usart_set_parity(USART2, USART_PARITY_NONE);
    usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);

    usart_enable(USART2);
}

void usart_send_string(const char *str) {
    while (*str) {
        usart_send_blocking(USART2, *str++);
    }
}

// ADC采样函数
void adc_sample(void) {
    for (int i = 0; i < BUFFER_SIZE; i++) {
        adc_start_conversion_regular(ADC1); // 启动规则转换
        while (!adc_eoc(ADC1)); // 等待转换完成
        adc_buffer[i] = adc_read_regular(ADC1); // 读取转换结果
        input_buffer[i] = ((float)adc_buffer[i] / 4095); // 转换为0-1范围的浮点数

        // generate_sine_wave(); // 测试用,注释后采集真实信号
    }
}

// FFT计算函数
void fft_compute(float *inBuffer, float *outBuffer, int sizeBuffer) {
    arm_rfft_fast_init_f32(&fft_instance, sizeBuffer); // 初始化FFT实例
    
    arm_rfft_fast_f32(&fft_instance, inBuffer, outBuffer, 0); // 执行FFT(0表示正向变换)

    arm_cmplx_mag_f32(outBuffer, magnitude_buffer, sizeBuffer / 2); // 计算复数幅度
}

// 查找基频
float find_fundamental_frequency(float *fft_output, int buffer_size) {
    float max_value = 0.0f;
    int max_index = 0;
    for (int i = 1; i < buffer_size / 2; i++) { // 忽略DC分量(索引0)
        if (fft_output[i] > max_value) {
            max_value = fft_output[i];
            max_index = i;
        }
    }
    return (float)max_index * SAMPLE_RATE / BUFFER_SIZE; // 根据索引计算频率
}


int main(){
    rcc_clock_setup_pll(&rcc_hse_25mhz_3v3[RCC_CLOCK_3V3_84MHZ]); // 配置系统时钟为84MHz
    usart_setup();
    adc_setup();

    char bufferMsg[50];

    while(1){
        adc_sample();

        fft_compute(input_buffer, output_buffer, BUFFER_SIZE);

        float frequency = find_fundamental_frequency(magnitude_buffer, BUFFER_SIZE);
        snprintf(bufferMsg, sizeof(bufferMsg), "频率: %.2f \r\n", frequency);
        usart_send_string(bufferMsg);
        
        // 简单延时
        for(int b =0; b < 500000; b++){
            __asm__("nop");
        }
    }    
    return 0;
}
问题分析与解决方法

核心问题1:ADC信号未去除直流偏置

当前代码将ADC采集的0-4095直接转换为0-1的浮点数,而函数信号发生器输出的正弦波通常是双极性交流信号(如峰峰值2V、中心0V),直接接入单电源ADC(0-3.3V)会导致信号被钳位,或采集到的信号带有固定直流偏置(若加了偏置)。而测试用的generate_sine_wave生成的是-1到1的无偏置信号,这是FFT能正确识别的原因。

修复方法:
修改adc_sample函数中的输入转换代码,去除直流偏置:

input_buffer[i] = ((float)adc_buffer[i] / 4095) - 0.5f;

转换后信号范围变为-0.5到0.5,与测试用正弦波的格式一致,FFT能正确识别交流分量。

核心问题2:采样率不精确

当前采用软件触发ADC采样,每次采样的间隔由代码执行时间决定,实际采样率远高于定义的SAMPLE_RATE 4000,导致频率计算时使用错误的采样率参数,结果偏差。

修复方法:
使用定时器触发ADC采样,保证精确的采样间隔:

  1. 配置定时器(如TIM2),设置定时周期为1/SAMPLE_RATE = 250us,对应84MHz时钟下的自动重装载值为(84000000 / 1000000) * 250 - 1 = 20999。
  2. 配置ADC为外部触发模式,触发源为定时器更新事件。
  3. 可选使用DMA传输ADC数据,避免CPU阻塞,进一步保证采样间隔稳定。

其他排查点

  • 信号连接检查:确保函数信号发生器的输出幅度适配ADC范围(建议峰峰值2V左右,偏置1.65V,使信号落在0-3.3V区间内),避免信号被钳位或幅度太小导致交流分量被噪声掩盖。
  • 峰值查找优化:在find_fundamental_frequency中增加幅度阈值,忽略低于阈值的峰值,避免将噪声误判为信号:
float threshold = 0.1f; // 根据实际情况调整
for (int i = 1; i < buffer_size / 2; i++) {
    if (fft_output[i] > max_value && fft_output[i] > threshold) {
        max_value = fft_output[i];
        max_index = i;
    }
}

内容的提问来源于stack exchange,提问作者Rafael Chuede

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 14:30:56