1. 项目概述与环境搭建
各位同学,欢迎来到这门实战课。我是你们的讲师,一个在嵌入式音频领域摸爬滚打了十来年的老工程师。今天咱们要聊的,是搭建一个完整的音频系统——用 STM32 控制 WM8960 这颗音频编解码器,实现录音和播放。
说实话,音频项目在嵌入式里算是个「硬骨头」。它不像点个灯、转个电机那么简单。它涉及模拟信号、数字信号、时钟同步、DMA 传输……任何一个环节出问题,出来的声音要么是噪声,要么干脆没声。我当年第一次调 WM8960 时,折腾了整整三天,最后发现是 I2C 地址写错了。嗯,这种坑,咱们今天就得提前填上。
1.1 WM8960 芯片特性速览
WM8960 是 Wolfson(现属 Cirrus Logic)推出的一款低功耗立体声音频编解码器。它为什么适合咱们的 STM32 项目?说白了,它把音频系统里最麻烦的模拟部分都集成好了。
核心特性一览:
- 双通道 DAC 与 ADC:支持立体声播放和立体声录音,采样率最高 48kHz
- 内置耳机放大器:可以直接驱动 16Ω/32Ω 耳机,省掉外部功放芯片
- 数字麦克风接口:支持 PDM 数字麦克风,也支持模拟麦克风
- 可编程增益控制:ADC 输入增益、DAC 输出音量均可通过寄存器调节
- 低功耗模式:待机电流仅 1μA,适合电池供电设备
我个人习惯把 WM8960 看作一个「音频瑞士军刀」。它内部有 32 个寄存器,通过 I2C 接口配置。你想想看,一个芯片能搞定音频输入、输出、增益、滤波、混音……这在十年前得用三四颗芯片才能实现。
| 参数 | 典型值 | 备注 |
|---|---|---|
| 电源电压 | 2.7V - 3.6V | 数字核 1.8V 内部 LDO 生成 |
| 信噪比 (DAC) | 98dB | A 加权 |
| 信噪比 (ADC) | 94dB | A 加权 |
| 采样率范围 | 8kHz - 48kHz | 支持 16/20/24/32 位数据 |
| I2C 地址 | 0x1A (7位) | ADDR 引脚接地时 |
避坑提示:WM8960 的 I2C 地址是 7 位 0x1A,但 HAL 库的 I2C 地址函数需要左移一位,变成 0x34。我当年就是在这里栽了跟头——写 0x1A 进去,芯片死活没反应。
1.2 STM32 与 WM8960 的硬件连接
咱们以 STM32F407 为例,讲解硬件连接。为什么选 F4?因为它有 I2S 外设,而且主频够高,能处理音频数据流。你如果用 F1 系列,也不是不行,但 I2S 需要软件模拟,稳定性差一些。
连接方式其实不复杂。我画了一张框图,你看一眼就明白了。
从图上你能看到,STM32 和 WM8960 之间有两类通信:
- I2C 总线:负责控制。STM32 通过 I2C 写 WM8960 的寄存器,设置采样率、音量、输入输出路由等。
- I2S 总线:负责数据。音频数据通过 I2S 接口双向传输。播放时,STM32 把 PCM 数据发给 WM8960 的 DAC;录音时,WM8960 的 ADC 把数据发给 STM32。
硬件连接注意事项:
- WM8960 的 MCLK 必须由 STM32 提供。F4 的 I2S 外设可以输出 MCLK,但需要配置 PLL 分频。
- 复位引脚 RST 建议用 GPIO 控制,不要直接接 VCC。上电后先拉低再拉高,确保芯片正确复位。
- 模拟电源 AVDD 和数字电源 DVDD 要分开走线,避免数字噪声串入模拟电路。
1.3 CubeMX 工程配置
好,硬件连接清楚了,咱们开始配置软件。我用的是 STM32CubeMX,版本 6.x。配置过程我一步步说,你跟着做就行。
1.3.1 新建工程与时钟配置
打开 CubeMX,选择 STM32F407VGT6。先配时钟——音频系统对时钟精度要求很高,我建议直接用外部晶振。
- HSE:8MHz 外部晶振
- PLL 倍频:8MHz * 9 = 72MHz (SYSCLK)
- APB1 时钟:36MHz (I2S 外设挂在这个总线上)
- I2S 时钟源:选择 PLLI2S,配置为 256 * 采样率 * 通道数
举个例子,如果采样率是 44.1kHz,16 位立体声,那么 I2S 时钟频率 = 44.1k * 32 * 2 = 2.8224MHz。PLLI2S 需要输出 256 * 44.1k = 11.2896MHz。这个计算我建议你记下来,后面调试时会反复用到。
1.3.2 I2C 外设配置
I2C 用于配置 WM8960 寄存器。配置参数如下:
| 参数 | 设置值 | 说明 |
|---|---|---|
| I2C 速度模式 | Standard Mode | 100kHz,兼容性好 |
| I2C 地址长度 | 7 位 | WM8960 支持 7 位地址 |
| GPIO 引脚 | PB6 (SCL), PB7 (SDA) | 复用开漏输出 |
| 上拉电阻 | 内部上拉使能 | 外部最好再加 4.7kΩ 上拉 |
我的经验:I2C 速度别设太高。WM8960 的 I2C 接口虽然支持 400kHz,但实际项目中我遇到过线长超过 10cm 时通信不稳定的情况。保守起见,100kHz 最稳妥。
1.3.3 I2S 外设配置
I2S 是音频数据的传输通道。配置时要注意主从模式的选择——WM8960 可以配置为主模式或从模式。我个人习惯让 STM32 做主设备,WM8960 做从设备。这样时钟由 STM32 产生,时序更容易控制。
| 参数 | 设置值 | 说明 |
|---|---|---|
| 模式 | Master Transmit / Master Receive | STM32 做主设备 |
| 标准 | I2S Philips | 左对齐,MSB 先传 |
| 数据格式 | 16 位 | WM8960 支持 16/20/24/32 位 |
| 采样率 | 44.1 kHz | CD 音质标准 |
| MCLK 输出 | 使能 | PC6 输出 256 * fs |
| DMA | 使能 (TX 和 RX) | 循环模式,数据宽度半字 |
这里有个关键点:I2S 的 DMA 必须配成循环模式。为什么?因为音频数据是连续流,不是一次传输完就结束。你想想看,播放一首歌,数据是一帧一帧往外送的,DMA 循环模式能自动把缓冲区填满,CPU 只需要在缓冲区半满或全满时处理一下就行。
1.4 I2C 与 I2S 外设初始化代码
配置完 CubeMX,生成代码。HAL 库会自动生成外设初始化函数。但有几个地方需要手动调整,我直接贴代码说明。
1.4.1 I2C 初始化
// I2C 句柄定义
I2C_HandleTypeDef hi2c1;
// I2C 初始化函数(CubeMX 生成,无需修改)
void MX_I2C1_Init(void)
{
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000; // 100kHz
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK)
{
Error_Handler();
}
}
写 WM8960 寄存器时,我封装了一个函数:
// 写 WM8960 寄存器
// reg: 寄存器地址 (7位)
// val: 寄存器值 (9位)
HAL_StatusTypeDef WM8960_WriteReg(uint8_t reg, uint16_t val)
{
uint8_t data[2];
data[0] = (reg << 1) | ((val >> 8) & 0x01); // 高字节:寄存器地址 + 数据高位
data[1] = val & 0xFF; // 低字节:数据低位
return HAL_I2C_Master_Transmit(&hi2c1, WM8960_ADDR, data, 2, 100);
}
注意:WM8960 的寄存器是 9 位数据,但 I2C 传输时拆成两个字节。第一个字节的高 7 位是寄存器地址,最低位是数据第 8 位;第二个字节是数据低 8 位。这个格式容易搞错,我建议你对照数据手册第 42 页的时序图确认。
1.4.2 I2S 初始化
// I2S 句柄定义
I2S_HandleTypeDef hi2s3;
DMA_HandleTypeDef hdma_spi3_tx;
DMA_HandleTypeDef hdma_spi3_rx;
// I2S 初始化函数(CubeMX 生成)
void MX_I2S3_Init(void)
{
hi2s3.Instance = SPI3;
hi2s3.Init.Mode = I2S_MODE_MASTER_TX; // 主模式发送
hi2s3.Init.Standard = I2S_STANDARD_PHILIPS;
hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;
hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;
hi2s3.Init.AudioFreq = I2S_AUDIOFREQ_44K;
hi2s3.Init.CPOL = I2S_CPOL_LOW;
hi2s3.Init.ClockSource = I2S_CLOCK_PLL;
hi2s3.Init.FirstBit = I2S_FIRSTBIT_MSB;
if (HAL_I2S_Init(&hi2s3) != HAL_OK)
{
Error_Handler();
}
}
DMA 配置部分,CubeMX 会生成,但需要手动开启 DMA 中断:
// 启动 I2S 发送 DMA(循环模式)
HAL_I2S_Transmit_DMA(&hi2s3, (uint16_t*)audio_buffer, AUDIO_BUFFER_SIZE);
// 启动 I2S 接收 DMA(循环模式)
HAL_I2S_Receive_DMA(&hi2s3, (uint16_t*)record_buffer, AUDIO_BUFFER_SIZE);
避坑指南:我曾经在 DMA 中断优先级上吃过亏。I2S 的 DMA 中断优先级要设得比 I2C 高,否则音频数据流会被 I2C 配置操作打断,导致声音出现「咔咔」的爆音。建议 DMA 中断优先级设为 1,I2C 设为 3。
1.5 本章小结
好了,这一章的内容就到这里。咱们把 WM8960 的特性、硬件连接、CubeMX 配置、I2C 和 I2S 初始化都过了一遍。这些东西是后面所有实验的基础。你如果现在觉得有点晕,没关系——音频系统涉及的知识点确实多。但只要你把硬件连接和时钟配置搞对了,后面写代码就是水到渠成的事。