文章目录
-
- 一、项目概述
- 二、硬件准备
-
- [2.1 硬件清单](#2.1 硬件清单)
- [2.2 硬件接线](#2.2 硬件接线)
- 三、开发环境搭建
- 四、STM32CubeMX工程配置
-
- [4.1 新建工程](#4.1 新建工程)
- [4.2 系统时钟配置](#4.2 系统时钟配置)
- [4.3 ADC配置(音频信号采集)](#4.3 ADC配置(音频信号采集))
- [4.4 串口配置(数据打印)](#4.4 串口配置(数据打印))
- [4.5 启用CMSIS-DSP库(FFT核心)](#4.5 启用CMSIS-DSP库(FFT核心))
- [4.6 工程生成](#4.6 工程生成)
- 五、工程流程图
- 六、代码实现
-
- [6.1 代码文件创建说明](#6.1 代码文件创建说明)
- [6.2 文件1:main.c](#6.2 文件1:main.c)
- [6.3 文件2:adc.c(ADC驱动文件)](#6.3 文件2:adc.c(ADC驱动文件))
- [6.4 文件3:usart.c(串口驱动文件)](#6.4 文件3:usart.c(串口驱动文件))
- [6.5 文件4:dsp.h(DSP库头文件)](#6.5 文件4:dsp.h(DSP库头文件))
- [七、Keil MDK编译配置(关键步骤)](#七、Keil MDK编译配置(关键步骤))
-
- [7.1 开启浮点运算单元](#7.1 开启浮点运算单元)
- [7.2 添加DSP库路径](#7.2 添加DSP库路径)
- [7.3 编译工程](#7.3 编译工程)
- 八、程序下载与调试
-
- [8.1 下载程序](#8.1 下载程序)
- [8.2 串口调试](#8.2 串口调试)
- 九、原理讲解
-
- [9.1 音频信号采集](#9.1 音频信号采集)
- [9.2 FFT快速傅里叶变换](#9.2 FFT快速傅里叶变换)
- [9.3 频谱数据含义](#9.3 频谱数据含义)
- 十、项目扩展(进阶优化)
- 十一、常见问题解决
一、项目概述
本项目基于STM32F407ZGT6开发板,实现音频信号的实时采集、FFT(快速傅里叶变换)频谱分析,并通过串口/液晶屏输出频谱数据,完整落地嵌入式音频信号处理实战。
- 核心硬件:STM32F407、驻极体麦克风(音频采集)、ADC外设
- 核心算法:CMSIS-DSP库FFT(STM32官方优化,速度远超手写FFT)
- 输出方式:串口打印频谱数据、可扩展OLED/TFT液晶屏显示
- 适用人群:零基础嵌入式开发者、电子竞赛、音频处理入门
二、硬件准备
2.1 硬件清单
- STM32F407ZGT6开发板(任意F407核心板均可)
- 驻极体麦克风模块(带放大电路,输出模拟信号)
- 杜邦线若干
- USB转TTL模块(串口调试)
- 5V电源(供电)
2.2 硬件接线
| 麦克风模块 | STM32F407 |
|---|---|
| VCC | 3.3V |
| GND | GND |
| OUT | PA0 |
说明:PA0对应STM32F407的ADC1_IN0通道,用于采集音频模拟信号。
三、开发环境搭建
本项目使用STM32CubeMX+Keil MDK5开发,零基础也能快速配置。
- 安装STM32CubeMX(6.0以上版本)
- 安装Keil MDK5,添加STM32F4固件库
- 安装USB转TTL驱动,准备串口调试助手
四、STM32CubeMX工程配置
4.1 新建工程
- 打开STM32CubeMX,点击
ACCESS TO MCU SELECTOR - 搜索
STM32F407ZG,选择芯片,点击Start Project
4.2 系统时钟配置
- 点击
RCC,选择HSE为Crystal/Ceramic Resonator(外部晶振) - 点击
Clock Configuration,配置系统时钟:- HSE:8MHz
- PLL锁相环:336MHz
- System Clock:168MHz(STM32F407最大主频)
- APB1 Prescaler:4,APB2 Prescaler:2
4.3 ADC配置(音频信号采集)
- 点击
Analog->ADC1 - 勾选
IN0(对应PA0) - 参数配置:
- 分辨率:12位(0~4095)
- 转换模式:单次转换
- 对齐方式:右对齐
- 不开启DMA(基础版先使用阻塞式采集)
4.4 串口配置(数据打印)
- 点击
USART1,模式选择Asynchronous(异步通信) - 波特率:115200,数据位8,停止位1,无校验
- 串口引脚:TX-PA9,RX-PA10(自动分配)
4.5 启用CMSIS-DSP库(FFT核心)
- 点击
Software Packs->Add - 选择
CMSIS->DSP,勾选Library - 回到主界面,点击
Project Manager
4.6 工程生成
- 工程名称:STM32_FFT_Audio
- 存储路径:英文路径(禁止中文)
- 编译器:MDK-ARM V5
- 点击
GENERATE CODE,生成工程文件
五、工程流程图
系统初始化
时钟初始化
ADC1初始化
USART1初始化
CMSIS-DSP FFT初始化
循环采集音频
ADC采集音频原始数据
数据存入FFT输入数组
执行FFT变换
计算幅值频谱
串口输出频谱数据
六、代码实现
6.1 代码文件创建说明
所有代码均在STM32CubeMX生成的工程中修改,无需新建工程,严格按照以下文件名和位置添加代码。
6.2 文件1:main.c
路径 :Core/Src/main.c
功能:系统初始化、主循环、ADC采集、FFT调用、串口打印
c
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "dsp.h"
#include "usart.h"
#include "gpio.h"
#include <stdio.h>
#include <math.h>
/* Private defines -----------------------------------------------------------*/
// FFT点数 必须是2的N次方 1024点
#define FFT_N 1024
/* Private variables ---------------------------------------------------------*/
// FFT输入输出数组 CMSIS-DSP要求32位浮点型
float32_t fft_input[FFT_N];
float32_t fft_output[FFT_N];
// FFT实例结构体
arm_cfft_instance_f32 fft_instance;
// ADC采集缓存
uint16_t adc_value;
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void FFT_Init(void);
static void ADC_GetData(float32_t *buf, uint16_t len);
static void Calculate_FFT_Magnitude(void);
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
// 1. 系统初始化
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
// 2. FFT初始化
FFT_Init();
// 3. 主循环
while (1)
{
// 采集1024个点音频数据
ADC_GetData(fft_input, FFT_N);
// 执行FFT变换并计算幅值
Calculate_FFT_Magnitude();
// 串口打印前100个频谱点
for(int i=0; i<100; i++)
{
printf("FreqPoint[%d]=%.2f\r\n", i, fft_output[i]);
}
printf("-------------------------\r\n");
HAL_Delay(500);
}
}
/**
* @brief FFT初始化函数
* @param None
* @retval None
*/
static void FFT_Init(void)
{
// 初始化1024点浮点FFT
arm_cfft_init_f32(&fft_instance, FFT_N);
}
/**
* @brief ADC采集音频数据 存入FFT输入数组
* @param buf: 数据存储数组
* @param len: 采集长度
* @retval None
*/
static void ADC_GetData(float32_t *buf, uint16_t len)
{
for(uint16_t i=0; i<len; i++)
{
// 启动ADC转换
HAL_ADC_Start(&hadc1);
// 等待转换完成
HAL_ADC_PollForConversion(&hadc1, 10);
// 获取ADC值
adc_value = HAL_ADC_GetValue(&hadc1);
// 转换为浮点型 并去除直流偏置
buf[i] = (float32_t)adc_value - 2048.0f;
}
}
/**
* @brief 执行FFT 计算幅值频谱
* @param None
* @retval None
*/
static void Calculate_FFT_Magnitude(void)
{
// 执行复数FFT变换
arm_cfft_f32(&fft_instance, fft_input, 0, 1);
// 计算复数幅值
arm_cmplx_mag_f32(fft_input, fft_output, FFT_N);
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 7;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
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_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief 重定向printf到串口1
*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 100);
return ch;
}
/**
* @brief 错误处理函数
* @retval None
*/
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif /* USE_FULL_ASSERT */
6.3 文件2:adc.c(ADC驱动文件)
路径 :Core/Src/adc.c
功能:STM32CubeMX自动生成,无需修改,仅确认配置正确
6.4 文件3:usart.c(串口驱动文件)
路径 :Core/Src/usart.c
功能:STM32CubeMX自动生成,无需修改
6.5 文件4:dsp.h(DSP库头文件)
路径 :Drivers/CMSIS/DSP/Include/dsp.h
功能:官方库文件,无需修改,工程已自动引用
七、Keil MDK编译配置(关键步骤)
7.1 开启浮点运算单元
- 打开Keil工程,点击
魔法棒图标 - 选择
Target,勾选Use FPU(F407硬件浮点单元)
7.2 添加DSP库路径
- 点击
C/C++ - 在
Include Paths中添加DSP库头文件路径:
Drivers/CMSIS/DSP/Include
7.3 编译工程
- 点击
Build(编译) - 无报错、无警告即为配置成功
八、程序下载与调试
8.1 下载程序
- 用ST-Link/V2连接开发板
- Keil中点击
Load,下载程序到STM32F407
8.2 串口调试
- 打开串口调试助手
- 配置参数:波特率115200,数据位8,停止位1,无校验
- 打开串口,即可看到实时打印的频谱数据
九、原理讲解
9.1 音频信号采集
驻极体麦克风将声音转换为模拟电压信号,STM32的ADC将模拟信号转换为数字信号(0~4095),我们将数据减去2048,去除直流偏置,得到正负交替的音频波形数据。
9.2 FFT快速傅里叶变换
FFT是一种快速计算傅里叶变换的算法,能把时域信号(波形)转换为频域信号(频谱)。
- 时域:信号随时间的变化
- 频域:信号包含哪些频率、幅值多大
9.3 频谱数据含义
串口打印的FreqPoint[x]代表第x个频率点的幅值,幅值越大,说明该频率的声音信号越强。
十、项目扩展(进阶优化)
- DMA采集:使用DMA代替阻塞式ADC采集,提升系统实时性
- 液晶屏显示:将频谱数据输出到OLED/TFT屏,实现可视化频谱
- 滤波处理:添加数字滤波,去除噪声
- 频率计算:根据采样率计算实际频率值
- 音乐频谱灯:结合PWM控制LED,实现音乐频谱律动
十一、常见问题解决
- 串口无数据
- 检查串口接线、波特率是否匹配
- 确认printf重定向代码添加正确
- FFT数据全为0
- 检查ADC接线,确认麦克风正常工作
- 检查ADC初始化配置
- 编译报错
- 确认DSP库已添加
- 开启FPU浮点单元