任务介绍
本项目实现了基于 MAX32690EVKIT 开发板的实时音频频谱可视化器,通过板载麦克风输入接口采集音频信号,使用 MAX9867 编解码芯片完成模数转换,主控通过 I2C 接口配置编解码芯片参数,通过 I2S 数字音频接口以 DMA 方式连续获取音频数据,并在 MCU 端运行 CMSIS-DSP 的 CFFT 算法完成频谱分析,最终基于 FreeRTOS + LVGL 框架在 128×128 彩色 TFT 屏幕上实时显示时域波形图与频域频谱柱状图,刷新率满足 10fps 以上的实时性要求。
硬件平台
本次使用 Analog Devices(前 Maxim Integrated)推出的 MAX32690EVKIT 评估板,该开发板搭载Arm® Cortex®-M4F CPU,用于高效计算复杂函数和算法,并集成最新一代蓝牙5低功耗(Bluetooth LE)无线电,适用于可穿戴与听戴式健身设备、便携式与可穿戴无线医疗设备、工业传感器/网络及物联网等场景。板上通过 I2C2 接口外接 MAX9867 立体声音频编解码芯片,用于采集 Line-In / 麦克风信号;同时板载一块基于 ST7735 驱动的 128×128 彩色 TFT 屏幕,用于实时图形显示。

主控设备:MAX32690EVKIT 评估板
- 搭载 MAX32690 MCU(ARM Cortex-M4F,硬件 FPU)
- 板载 I2C2、I2S、DMA 等丰富外设资源
- 支持 100MHz IPO 主频运行
- 支持多线程实时操作系统(FreeRTOS)+ 图形库(LVGL)
音频前端:MAX9867 立体声编解码芯片
- I2C 接口配置,工作频率 100kHz
- MCLK = 12.288MHz,Controller 模式下 LRCLK 实测输出 24kHz
- 仅使能录音(ADC/Line-In)通路,禁用播放/耳机回环输出
- ADC 电平 -12dB,Line-In 增益可调
显示终端:128×128 ST7735 彩色 TFT 屏幕
- 通过 SPI 驱动,
tft_st7735.c完成底层适配 - 结合 LVGL 的
lv_port_disp.c显示端口完成图形渲染
任务分析与实现
本系统实现了基于 FreeRTOS + LVGL 的嵌入式音频频谱分析平台,主要功能包括:
三级数据处理链路:
- 音频采集
- I2S RX DMA 双缓冲(ping-pong),单次传输 64 个 uint32_t
- 采样率:24kHz(由 MAX9867 输出 LRCLK 决定)
- 累积 1024 点左声道样本触发一次处理
- 信号处理
- Hann 窗 + CMSIS-DSP CFFT(1024 点)
- 频谱分辨率 ≈23.4Hz/bin,0~8kHz 范围内分 16 柱显示
- 全 Nyquist 范围(0~12kHz)扫描主频率峰值
- 显示渲染
- 时域折线图:128 点原始采样,1:1 映射,时间窗口≈5.33ms
- 频域柱状图:16 根柱子,0~8kHz,dB 映射到 [0,100]
- 刷新周期 10ms(LVGL 任务),满足 ≥10fps 要求
任务与优先级划分:
任务名 | 优先级 | 栈大小 | 职责 |
fft_task | 4 | 2048 字 | 等待 DMA ISR 信号量,加窗+CFFT+分组+主频检测 |
lvgl_task | 3 | 2048 字 | 持锁周期调用 lv_task_handler() |
ui_task | 2 | 1024 字 | 等待 fft_task 信号量,持锁刷新文字/波形/频谱 |
idle/timer | 0/5 | 128/256 字 | FreeRTOS 内置任务 |
方案框图(音频链路):
音频与显示链路的搭建过程
本项目基于 MAX32690 MSDK 完成了 MAX9867 音频采集链路、CMSIS-DSP FFT 引擎与 FreeRTOS + LVGL 图形界面的整合。以下详细描述关键流程与核心要点。
一、开发前的准备
1. 硬件环境
- 主控芯片:MAX32690(ARM Cortex-M4F 内核,硬件浮点单元)
- 开发板:MAX32690EVKIT
- 外设资源:
- I2C2 接口用于配置 MAX9867 编解码芯片
- I2S 接口(Slave 模式)用于接收音频数字信号
- DMA0/DMA1 通道用于 I2S RX 数据搬运
- SPI + ST7735 TFT 屏幕用于图形显示
2. 软件环境
- MSDK 库:Analog Devices MAX32690 Micro SDK
- RTOS:FreeRTOS(LIB_FREERTOS=1)
- 图形库:LVGL(LIB_LVGL=1)
- DSP 库:CMSIS-DSP(LIB_CMSIS_DSP=1),硬件浮点 ABI(MFLOAT_ABI=hard)
- 编译工具链:GCC ARM Embedded
二、核心搭建步骤
1. 时钟与缓存初始化
系统上电后先切换到 100MHz IPO 主时钟并使能指令缓存,为 FFT 运算与图形渲染提供足够算力:
MXC_SYS_Clock_Select(MXC_SYS_CLOCK_IPO);
SystemCoreClockUpdate();
MXC_ICC_Enable(MXC_ICC0);
2. I2C 与 MAX9867 编解码芯片配置
通过 MXC_I2C_Init 初始化 I2C2 总线并设置 100kHz 通信频率,随后调用 max9867_init 以 Controller 模式初始化编解码芯片,仅使能录音通路:
if (max9867_init(CODEC_I2C, CODEC_MCLOCK, 1) != E_NO_ERROR)
blink_halt("Error initializing MAX9867 CODEC");
if (max9867_enable_record(1) != E_NO_ERROR)
blink_halt("Error enabling record path");
if (max9867_adc_level(-12, -12) != E_NO_ERROR)
blink_halt("Error setting ADC level");
3. I2S 数字音频接口配置
MAX32690 侧 I2S 配置为 Slave 模式(MXC_I2S_EXTERNAL_SCK_EXTERNAL_WS),采样率完全由 MAX9867 输出的 LRCLK 决定(实测 24kHz),24bit 立体声、MSB 左对齐:
req.wordSize = MXC_I2S_WSIZE_WORD;
req.sampleSize = MXC_I2S_SAMPLESIZE_TWENTYFOUR;
req.channelMode = MXC_I2S_EXTERNAL_SCK_EXTERNAL_WS;
req.stereoMode = MXC_I2S_STEREO;
4. DMA 双缓冲与样本累积
DMA 完成回调运行在 ISR 上下文,从偶数索引(左声道)提取样本累积进 1024 点缓冲区,ping-pong 切换缓冲后立即重新挂载 DMA,样本满帧后释放二值信号量唤醒 fft_task:
for (int i = 0; i < I2S_DMA_BUFFER_SIZE; i += 2) {
if (fft_sample_idx < FFT_SIZE)
fft_sample_buf[fft_sample_idx++] = (int32_t)rxBufPtr[i];
}
if (fft_sample_idx >= FFT_SIZE)
xSemaphoreGiveFromISR(xSampleFullSem, &xHigherPriorityTaskWoken);
5. RTOS 同步原语与任务创建
在使能 DMA/ISR 之前预先创建二值信号量与互斥锁,保证生产者-消费者模型正确同步:
xSampleFullSem = xSemaphoreCreateBinary(); // DMA ISR -> fft_task
xUiUpdateSem = xSemaphoreCreateBinary(); // fft_task -> ui_task
xGuiDataMutex = xSemaphoreCreateMutex(); // 保护显示数据
代码详解
整体软件流程
一、硬件初始化序列
系统上电后按 I2C -> Codec -> I2S -> DMA 的顺序完成音频前端初始化:
dma_init();
i2c_init();
codec_init();
i2s_init();
MXC_I2S_RegisterDMACallback(dma_callback);
dma_ch_rx = MXC_I2S_RXDMAConfig(rxBufPtr, I2S_DMA_BUFFER_SIZE * sizeof(i2s_rx_buffer[0]));
二、FFT 信号处理任务
fft_task 阻塞等待样本满帧信号量,临界区内快照并复位共享缓冲区,随后完成加窗、CFFT、频谱分组与全频段主频检测:
arm_cfft_init_f32(&cfft_inst, FFT_SIZE);
arm_hanning_f32(hann_window, FFT_SIZE);
...
arm_cfft_f32(&cfft_inst, fft_input, 0, 1);
arm_cmplx_mag_f32(fft_input, fft_output, FFT_SIZE);
频谱只分析显示 0~8kHz(SPEC_USABLE_BINS≈341),但主频率检测特意扫描全部 1~511 号 bin,覆盖完整 Nyquist 范围,避免用户被截断的柱状图误导。
三、UI 显示任务
ui_task 创建顶部文字标签、时域折线图与频域柱状图三个 LVGL 组件,阻塞等待 fft_task 发布的新一帧数据后持锁刷新:
lv_chart_set_range(chart_wave, LV_CHART_AXIS_PRIMARY_Y, -500, 500);
lv_chart_set_type(chart_spec, LV_CHART_TYPE_BAR);
lv_chart_set_range(chart_spec, LV_CHART_AXIS_PRIMARY_Y, 0, 100);
由于 LVGL 内置精简 snprintf 不支持 %f,主频率与幅度在显示前手动转换为整数部分与一位小数拼接后格式化输出。
效果展示
音频生成Web页面

开发板运行状态

屏幕顶部实时刷新“主频率 Hz | 幅度”文字,中部为绿色时域波形折线,底部为橙色 0~8kHz 频谱柱状图,播放不同频率的单音/复合音时可清晰观察到对应柱子的高度变化与主频文字的联动更新
遇到的难题与解决办法
问题一:波形显示时正弦波在幅度接近满量程时被裁剪成方波。
解法:降低编解码器增益,同时将波形数值裁剪边界(±500)与 LVGL Chart 的 Y 轴量程(-500~500)保持严格一致,避免裁剪范围与显示量程不匹配导致的视觉失真。
问题二:DMA ISR、FFT 任务与 UI 任务之间存在多生产者-消费者关系,需要避免数据竞争。
解法:引入两个二值信号量(xSampleFullSem、xUiUpdateSem)分别衔接"DMA→FFT"与"FFT→UI"两级流水线,并用互斥锁 xGuiDataMutex 保护共享显示数据结构,确保跨任务访问的一致性。
活动感想
通过本项目实践,深入掌握了在 MAX32690 平台上完成音频采集、DSP 信号处理与图形界面渲染三者协同工作的完整流程,理解了 I2C 编解码芯片配置、I2S DMA 双缓冲采集、CMSIS-DSP CFFT 运算以及 FreeRTOS 多任务同步机制的具体实现细节。整个开发过程中,需重点关注:
- DMA 双缓冲切换与样本累积的临界区保护
- 频谱显示窗口与主频检测范围的取舍,避免视觉误导
- 波形裁剪边界与图表量程的一致性,防止波形失真
MAX32690EVKIT 良好的外设集成度与 MSDK 完善的驱动支持,加上 CMSIS-DSP 库对浮点 FFT 的硬件加速,使得在资源有限的 MCU 上实现流畅的实时频谱可视化成为可能。感谢本次实践机会,让我对嵌入式音频信号处理与实时图形显示系统的软硬件协同设计有了更深刻的理解。