这是一个基于 STM32F103C8T6 微控制器的麦克风音频信号 FFT(快速傅里叶变换)分析项目。该项目通过 ADC 采集麦克风音频信号,使用 ARM Cortex-M3 的 DSP 库进行 FFT 运算,并在 OLED 显示屏上实时显示时域波形(谐波模式)和频域频谱(频谱模式)。
- 微控制器: STM32F103C8T6 (Cortex-M3, 72MHz)
- 开发环境: STM32CubeMX + Keil MDK-ARM V5
- HAL 库: STM32F1xx HAL Driver
- DSP 库: STM32 官方 DSP 库(汇编优化的 FFT 实现)
- 外设接口:
- ADC1(PA1 引脚):麦克风信号采集
- DMA1_Channel1:ADC 数据自动传输
- I2C1(PB6-SCL, PB7-SDA):OLED 显示屏通信
- TIM3:ADC 触发定时器
- TIM4:按键消抖定时器
- GPIO:按键输入(PB1)和 LED 指示(PC13)
- 系统时钟: 72MHz(HSE 外部晶振 + PLL ×9)
- ADC 配置:
- 采样时间:1.5 周期(谐波模式)/ 239.5 周期(频谱模式)
- 触发方式:软件触发(谐波模式)/ TIM3_TRGO 触发(频谱模式)
- 分辨率:12 位(0-4095)
- 定时器配置:
- TIM3:1632 周期,用于触发 ADC 采样
- TIM4:10000 周期(预分频 8),用于按键消抖
MIC_FFT/
├── Core/ # STM32CubeMX 生成的核心代码
│ ├── Inc/ # 头文件
│ │ ├── adc.h # ADC 配置头文件
│ │ ├── dma.h # DMA 配置头文件
│ │ ├── gpio.h # GPIO 配置头文件
│ │ ├── i2c.h # I2C 配置头文件
│ │ ├── main.h # 主程序头文件
│ │ ├── stm32f1xx_hal_conf.h # HAL 库配置
│ │ ├── stm32f1xx_it.h # 中断服务程序头文件
│ │ └── tim.h # 定时器配置头文件
│ └── Src/ # 源文件
│ ├── adc.c # ADC 初始化和配置
│ ├── dma.c # DMA 初始化和配置
│ ├── gpio.c # GPIO 初始化和配置
│ ├── i2c.c # I2C 初始化和配置
│ ├── main.c # 主程序逻辑
│ ├── stm32f1xx_hal_msp.c # HAL MSP 初始化
│ ├── stm32f1xx_it.c # 中断服务程序
│ ├── system_stm32f1xx.c # 系统初始化
│ └── tim.c # 定时器初始化和配置
├── Drivers/ # STM32 HAL 和 CMSIS 驱动
│ ├── CMSIS/ # ARM CMSIS 核心
│ └── STM32F1xx_HAL_Driver/ # STM32F1xx HAL 驱动
├── MDK-ARM/ # Keil 项目文件
│ ├── MIC.uvprojx # Keil 项目文件
│ ├── MIC.uvoptx # Keil 选项文件
│ ├── DSP/ # DSP 库文件
│ │ ├── cr4_fft_256_stm32.s # 256 点 FFT 汇编实现
│ │ ├── cr4_fft_1024_stm32.s # 1024 点 FFT 汇编实现
│ │ ├── stm32_dsp.h # DSP 函数声明
│ │ └── table_fft.h # FFT 查找表
│ └── MyDrivers/ # 自定义驱动
│ ├── OLED.c/h # OLED 显示屏驱动
│ ├── OLED_Data.c/h # OLED 字库和显示数据
│ └── key.c/h # 按键驱动
├── MIC.ioc # STM32CubeMX 项目配置文件
├── .mxproject # STM32CubeMX 元数据
└── 硬件电路.png # 硬件电路图
本项目使用 Keil MDK-ARM 开发环境进行编译。请在 Keil IDE 中打开 MDK-ARM/MIC.uvprojx 项目文件。
编译步骤:
- 打开 Keil MDK-ARM
- 打开项目文件
MDK-ARM/MIC.uvprojx - 选择目标:
MIC - 点击 "Project" → "Build Target" 或按 F7 编译项目
编译产物:
MDK-ARM/MIC/MIC.axf- 调试可执行文件MDK-ARM/MIC/MIC.hex- Intel HEX 格式固件(用于烧录)
可以使用以下方式烧录生成的 HEX 文件:
- ST-Link Utility:使用 ST-LINK 调试器
- Keil MDK Flash Download:在 Keil IDE 中直接烧录
- J-Link:使用 SEGGER J-Link 调试器
- USB 串口:使用 USB 串口和 ISP 模式烧录(需要 Bootloader)
系统支持两种显示模式,通过按键(PB1)切换:
- 使用 ADC 连续转换模式
- 软件触发采样
- 最小采样时间(1.5 周期)以获得最高采样率
- 在 OLED 上实时显示音频信号的时域波形
- 波形范围:X 轴 0-127,Y 轴 0-63(对应 ADC 值 0-4095)
- 使用 ADC 单次转换模式
- TIM3 定时器触发采样(固定采样率)
- 最大采样时间(239.5 周期)以确保采样精度
- 使用 DMA 自动采集 256 个采样点
- 对采集的数据进行 256 点 FFT 运算
- 计算各频率分量的幅值
- 在 OLED 上显示 64 个频率点的频谱图
使用 STM32 优化的 Radix-4 复数 FFT 算法:
- 函数:
cr4_fft_256_stm32(lBufOutArray, AD_Value_buf[data_select], 256) - 输入:256 个 int16_t 类型的采样点(实数)
- 输出:256 个 uint32_t 类型的复数结果(高 16 位为虚部,低 16 位为实部)
FFT 输出是复数形式,需要计算其模(幅值):
real = (lBufOutArray[i] << 16) >> 16; // 提取实部
imag = (lBufOutArray[i] >> 16); // 提取虚部
magnitude[i] = sqrt(real * real + imag * imag) >> 3; // 计算幅值并缩放使用两个 256 点的缓冲区交替工作:
AD_Value_buf[0]和AD_Value_buf[1]- ADC 完成一次采集后,通过 DMA 中断切换到另一个缓冲区
- 主循环从已完成的缓冲区读取数据进行 FFT 运算
- 使用 STM32CubeMX 生成的基础框架
- 用户代码放在
/* USER CODE BEGIN */和/* USER CODE END */标记之间 - 使用 HAL 库函数进行外设操作
- 变量命名采用驼峰命名法
- ADC 转换完成中断:
HAL_ADC_ConvCpltCallback - 定时器中断:
HAL_TIM_PeriodElapsedCallback - 按键消抖使用 TIM4 中断处理
- 堆大小:0x200
- 栈大小:0x400
- RAM 使用:从 0x20000000 到 0x20004FFF(20KB)
- 时序配置:谐波模式追求高采样率,频谱模式追求高精度
- FFT 输入:必须是 2 的幂次方长度(项目使用 256 点)
- 显示刷新:OLED 使用缓冲区机制,需要调用
OLED_Update()刷新显示 - 按键防抖:使用定时器中断实现硬件防抖,避免误触发
在 main.c 的主循环中添加新的模式分支,配置相应的 ADC 参数和显示逻辑。
- 在
MDK-ARM/DSP/目录下使用相应的 FFT 汇编文件 - 修改缓冲区大小
- 调整采样点数和显示分辨率
使用 MyDrivers/OLED.c/h 中提供的绘图函数:
OLED_DrawPoint()- 画点OLED_DrawLine()- 画线OLED_ShowString()- 显示字符串OLED_ShowNum()- 显示数字
- 确保已安装 STM32F1xx_DFP 包
- 检查 DSP 库文件是否正确添加到项目中
- 检查包含路径设置
- 确认系统时钟配置正确(72MHz)
- 检查 ADC 和 DMA 初始化顺序
- 验证 I2C 连接和 OLED 地址
- 检查 OLED 供电和 I2C 连接
- 确认 OLED_Init() 初始化成功
- 验证 OLED_Update() 是否被调用
本项目使用 STMicroelectronics HAL 库,遵循相应的许可协议。