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采样,保证精确的采样间隔:
- 配置定时器(如TIM2),设置定时周期为
1/SAMPLE_RATE = 250us,对应84MHz时钟下的自动重装载值为(84000000 / 1000000) * 250 - 1 = 20999。 - 配置ADC为外部触发模式,触发源为定时器更新事件。
- 可选使用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
相关产品推荐
相关产品推荐

