4. WM8960 寄存器配置:I2C 通信与关键寄存器初始化
好,这一章我们正式进入 WM8960 的配置环节。说实话,很多初学者拿到芯片手册就头大——几百页的文档,上百个寄存器,到底该配哪些?
我个人习惯是:先搭好通信链路,再按「时钟→接口→电源」的顺序逐个击破。你想想看,芯片没通电、时钟没跑起来,你配啥都没用,对吧?
4.1 I2C 通信初始化
WM8960 支持 I2C 和 SPI 两种控制接口。我一般用 I2C,因为它只需要两根线,省引脚。STM32 这边我们用 HAL 库的 I2C 外设来驱动。
4.1.1 硬件连接
| STM32 引脚 | WM8960 引脚 | 说明 |
|---|---|---|
| PB6 (I2C1_SCL) | SCLK | 时钟线 |
| PB7 (I2C1_SDA) | SDIN | 数据线 |
| 3.3V | VDD | 数字电源 |
| GND | DGND | 数字地 |
4.1.2 I2C 初始化代码
我用 CubeMX 生成 I2C1 的初始化代码,然后自己封装一个写寄存器函数。嗯,这里要注意:WM8960 的寄存器是 7 位地址 + 9 位数据,总共 16 位,所以发送时要先发高 8 位,再发低 8 位。
// 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;
HAL_I2C_Init(&hi2c1);
}
// 写 WM8960 寄存器封装函数
// reg_addr: 7 位寄存器地址(0x00-0x7F)
// reg_val: 9 位寄存器数据
HAL_StatusTypeDef WM8960_WriteReg(uint8_t reg_addr, uint16_t reg_val)
{
uint8_t buf[2];
buf[0] = (reg_addr << 1) | ((reg_val >> 8) & 0x01); // 高 8 位:7 位地址 + 数据 bit8
buf[1] = reg_val & 0xFF; // 低 8 位:数据 bit7-bit0
return HAL_I2C_Master_Transmit(&hi2c1, WM8960_I2C_ADDR, buf, 2, 100);
}
4.2 时钟配置——让芯片先跑起来
WM8960 内部有 PLL,可以对外部时钟进行倍频。我个人建议:如果板子上有 12.288MHz 或 24.576MHz 晶振,直接用它做 MCLK,省去 PLL 配置的麻烦。但很多开发板只给了 12MHz 晶振,那就得用 PLL 了。
4.2.1 时钟树分析
WM8960 的时钟路径是这样的:
- MCLK:主时钟输入,可以是外部晶振或来自 STM32 的 MCO 输出
- PLL:内部锁相环,将 MCLK 倍频到需要的频率
- BCLK:位时钟,由 MCLK 分频得到,决定采样率
- LRCLK:帧时钟,等于采样率(如 48kHz、44.1kHz)
举个例子:我们要实现 48kHz 采样率,16 位数据,双声道。那么 BCLK = 48k × 16 × 2 = 1.536MHz。如果 MCLK 是 12MHz,就需要配置分频器让 MCLK 分频得到 BCLK。
4.2.2 时钟寄存器配置
关键寄存器有两个:R2(0x02) 和 R3(0x03)。
// 配置时钟寄存器
// R2 (0x02): 时钟管理 1
// bit[5:4] = 00: MCLK 作为时钟源
// bit[3:2] = 00: 不使用 PLL
// bit[1:0] = 00: 无分频
WM8960_WriteReg(0x02, 0x0000);
// R3 (0x03): 时钟管理 2
// bit[6] = 0: 禁用 PLL
// bit[5:1] = 00000: 无额外分频
// bit[0] = 0: 正常模式
WM8960_WriteReg(0x03, 0x0000);
4.3 音频接口配置——告诉芯片怎么传数据
WM8960 支持多种音频接口格式:I2S、左对齐、右对齐、DSP 模式。我们最常用的是 I2S 标准格式,也就是飞利浦格式。
4.3.1 接口格式寄存器
R4(0x04) 是音频接口控制寄存器,这里面的配置决定了数据怎么在 STM32 和 WM8960 之间传输。
// R4 (0x04): 音频接口控制
// bit[7:6] = 10: I2S 标准格式(飞利浦格式)
// bit[5:4] = 00: 16 位数据长度
// bit[3] = 0: 左声道在 LRCLK 低电平时有效
// bit[2] = 0: 主模式(WM8960 产生 BCLK 和 LRCLK)
// bit[1:0] = 00: 无字长调整
WM8960_WriteReg(0x04, 0x0080);
这里有个坑:我刚开始做项目时,把主从模式搞反了。STM32 作为 I2S 主机,WM8960 作为从机,结果 WM8960 的 BCLK 和 LRCLK 引脚一直没信号。后来查手册才发现,WM8960 默认是从机模式,需要把 bit[2] 置 1 才能让它输出时钟。
4.3.2 数据路径配置
接口配好了,还得告诉芯片数据往哪走。R5(0x05) 和 R6(0x06) 控制左右声道的输入输出路径。
// R5 (0x05): 左声道输入路径
// bit[8] = 1: 使能左声道 PGA
// bit[7:4] = 0000: 选择 LINPUT1 作为输入
// bit[3:0] = 0000: PGA 增益 0dB
WM8960_WriteReg(0x05, 0x0100);
// R6 (0x06): 右声道输入路径
// bit[8] = 1: 使能右声道 PGA
// bit[7:4] = 0000: 选择 RINPUT1 作为输入
// bit[3:0] = 0000: PGA 增益 0dB
WM8960_WriteReg(0x06, 0x0100);
4.4 电源管理——别让芯片饿着
WM8960 的电源管理很灵活,可以单独开关各个模块。录音模式下,我们需要开启:
- 模拟电源(VMID)
- 输入 PGA(可编程增益放大器)
- ADC 模块
- 参考电压
4.4.1 电源管理寄存器
R1(0x01) 是电源管理寄存器 1,控制模拟部分的供电。
// R1 (0x01): 电源管理 1
// bit[7] = 1: 使能 VMID(模拟电源中值)
// bit[6] = 1: 使能参考电压
// bit[5] = 1: 使能左声道输入 PGA
// bit[4] = 1: 使能右声道输入 PGA
// bit[3] = 1: 使能左声道 ADC
// bit[2] = 1: 使能右声道 ADC
// bit[1] = 0: 禁用左声道输出
// bit[0] = 0: 禁用右声道输出
WM8960_WriteReg(0x01, 0x00FC);
4.4.2 上电时序
我总结了一个安全的电源上电顺序:
- 使能 VMID 和参考电压(R1 bit[7:6])
- 延时 10ms
- 使能输入 PGA(R1 bit[5:4])
- 使能 ADC 模块(R1 bit[3:2])
- 配置音频接口(R4)
- 配置时钟(R2, R3)
4.5 完整初始化流程
好了,把上面这些串起来,就是一个完整的初始化函数。我个人习惯把初始化分成两步:先配电源和时钟,再配接口和路径。
void WM8960_Init(void)
{
// 第一步:电源管理
// 先开 VMID 和参考电压
WM8960_WriteReg(0x01, 0x00C0); // 只开 VMID 和参考
HAL_Delay(10); // 等待稳定
// 再开 PGA 和 ADC
WM8960_WriteReg(0x01, 0x00FC); // 开全部录音所需模块
// 第二步:时钟配置
// 使用外部 MCLK,不分频
WM8960_WriteReg(0x02, 0x0000);
WM8960_WriteReg(0x03, 0x0000);
// 第三步:音频接口配置
// I2S 格式,16 位,主模式
WM8960_WriteReg(0x04, 0x0080);
// 第四步:输入路径配置
// 选择 LINPUT1/RINPUT1,PGA 增益 0dB
WM8960_WriteReg(0x05, 0x0100);
WM8960_WriteReg(0x06, 0x0100);
// 第五步:取消静音
// R47 (0x2F) bit[7:6] = 0: 左/右声道 ADC 取消静音
WM8960_WriteReg(0x2F, 0x0000);
}
4.6 本章知识体系
下面这张图总结了 WM8960 初始化的核心流程和寄存器依赖关系,你可以对照着看: