4. WM8960驱动初始化:I2C控制接口配置、寄存器映射表、PLL配置与时钟生成、音频路径配置(ADC/DAC)
好,到了这一章,我们真正开始跟WM8960这个芯片“对话”了。前面几章我们把DMA和I2S的硬件通道打通了,但WM8960本身还是个“哑巴”——它不知道你要用多大的采样率、走哪条音频路径、要不要开PLL。所有这些配置,都得通过I2C接口写进它的寄存器。
我个人习惯把WM8960的初始化分成四大块:I2C控制接口配置、寄存器映射表理解、PLL与时钟生成、音频路径配置。这四块搞定了,芯片才能正常发声。咱们一块一块来拆。
4.1 I2C控制接口配置
WM8960的I2C地址是固定的:0x1A(7位地址),写操作时地址字节是0x34,读操作是0x35。STM32作为主机,通过I2C1或I2C2连接WM8960的SCL和SDA引脚。
配置I2C时,我建议把速率设在100kHz左右,别贪快。WM8960的I2C接口最高支持400kHz,但实际项目中线缆寄生电容、上拉电阻值都会影响信号质量。我曾经在一个项目里把I2C速率设到400kHz,结果偶尔出现寄存器写失败,音频断断续续,查了两天才发现是I2C时序问题。降到100kHz后一切正常。
I2C初始化关键参数:
- I2C模式:标准模式(100kHz)或快速模式(400kHz)
- 从机地址:0x1A(7位)
- 时钟使能:I2C外设时钟和GPIO时钟都要开
- GPIO配置:SCL/SDA设为开漏输出,带上拉
下面是我常用的I2C初始化代码片段,基于STM32 HAL库:
// I2C句柄定义
I2C_HandleTypeDef hi2c1;
// I2C初始化函数
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寄存器的封装函数
void WM8960_WriteReg(uint8_t reg, uint16_t val)
{
uint8_t data[2];
data[0] = (reg << 1) | ((val >> 8) & 0x01); // 高9位:寄存器地址+数据高位
data[1] = val & 0xFF; // 低8位:数据低位
HAL_I2C_Master_Transmit(&hi2c1, WM8960_ADDR, data, 2, 100);
}
小技巧:WM8960的寄存器写入格式是16位数据,但I2C传输时拆成两个字节。第一个字节的高7位是寄存器地址,bit0是数据bit8;第二个字节是数据bit7~bit0。写驱动时一定要按这个格式拼装,否则寄存器写不进去。
4.2 寄存器映射表理解
WM8960总共有56个寄存器(地址0x00~0x37),每个寄存器16位。但实际用到的没那么多,很多是保留位或工厂测试位。我整理了一份核心寄存器清单,初始化时重点关注这些:
| 地址 | 名称 | 功能描述 | 初始化值 |
|---|---|---|---|
| 0x00 | Reset | 软件复位,写入0x00FF复位芯片 | 0x00FF |
| 0x02 | Power Management 1 | 控制VMID、MICBIAS、ADC/DAC电源 | 0x01E0 |
| 0x03 | Power Management 2 | 控制输入输出通道电源 | 0x003C |
| 0x04 | Power Management 3 | 控制PLL、时钟等电源 | 0x0001 |
| 0x05 | Audio Interface | 配置I2S格式、字长、主从模式 | 0x0050 |
| 0x06 | Clock Management 1 | PLL分频系数、时钟源选择 | 0x0000 |
| 0x07 | Clock Management 2 | PLL倍频系数N、K | 0x0000 |
| 0x08 | Audio Interface 2 | ADC/DAC采样率控制 | 0x0000 |
| 0x0A | DAC Control | DAC去加重、静音控制 | 0x0000 |
| 0x0C | Left Input Volume | 左声道输入增益 | 0x0017 |
| 0x0D | Right Input Volume | 右声道输入增益 | 0x0017 |
| 0x10 | DAC Volume | DAC数字音量控制 | 0x00FF |
| 0x1A | Additional Control 1 | TSHUT、VMID等额外控制 | 0x0000 |
嗯,这里要注意:寄存器地址是7位,但WM8960的I2C写操作中,第一个字节的高7位才是地址,bit0是数据bit8。所以你在代码里写寄存器地址时,要左移1位。我刚开始写驱动时没注意这个细节,结果写0x02寄存器,实际写到了0x04,折腾了半天。
4.3 PLL配置与时钟生成
WM8960内部有一个可编程锁相环(PLL),可以把外部低频时钟(比如12.288MHz、24.576MHz)倍频到芯片内部需要的频率。为什么要用PLL?说白了,就是让WM8960能适应各种外部时钟源。
PLL的输出频率计算公式:
Fvco = Fref × (N + K/2^24) / R
其中:
- Fref:外部参考时钟频率(通常来自MCLK引脚)
- R:R分频系数(寄存器0x06的R[3:0]位)
- N:整数倍频系数(寄存器0x07的N[5:0]位)
- K:小数倍频系数(寄存器0x07的K[23:0]位)
Fvco必须在90MHz~100MHz范围内,这是WM8960内部PLL的锁定范围。然后Fvco再经过内部分频,生成ADC/DAC需要的采样时钟。
实际项目中的PLL配置示例:
假设外部MCLK = 12.288MHz,目标采样率 = 48kHz,I2S字长 = 16bit,声道数 = 2。
需要的BCLK = 48k × 16 × 2 = 1.536MHz
PLL输出Fvco = 12.288MHz × (4 + 0) / 1 = 49.152MHz(在90~100MHz范围外,不行)
调整:取R=2,Fvco = 12.288MHz × (8 + 0) / 1 = 98.304MHz(OK)
然后内部再分频得到BCLK和LRCLK。
配置PLL的代码示例:
// 配置PLL:MCLK=12.288MHz,目标Fs=48kHz
void WM8960_ConfigPLL(void)
{
// 1. 设置时钟管理1:选择MCLK作为PLL输入,R=2
WM8960_WriteReg(0x06, 0x0001); // R=2,PLL输入来自MCLK
// 2. 设置时钟管理2:N=8,K=0
WM8960_WriteReg(0x07, 0x0008); // N=8,K=0
// 3. 使能PLL
WM8960_WriteReg(0x04, 0x0003); // Power Management 3,使能PLL
// 4. 等待PLL锁定(至少10ms)
HAL_Delay(20);
}
注意:PLL锁定需要时间,一般5~10ms。锁定期间不要操作音频接口,否则可能出现时钟抖动。我遇到过PLL还没锁定就开始播放音频,结果前几百毫秒全是噪声。加个延时就好了。
4.4 音频路径配置(ADC/DAC)
音频路径配置,说白了就是告诉WM8960:声音从哪里来,到哪里去。WM8960内部有两条主要路径:
- ADC路径:模拟输入 → 输入放大器 → ADC → 数字接口(I2S发送)
- DAC路径:数字接口(I2S接收) → DAC → 输出放大器 → 耳机/喇叭输出
我画了一张路径图,方便你理解:
配置音频路径时,需要按顺序操作:
- 先开电源:使能VMID、MICBIAS、ADC/DAC电源
- 再配接口:设置I2S格式、字长、主从模式
- 然后设音量:输入增益、DAC数字音量
- 最后开通路:使能ADC/DAC路径上的模拟开关
完整的初始化序列代码:
void WM8960_Init(void)
{
// 1. 软件复位
WM8960_WriteReg(0x00, 0x00FF);
HAL_Delay(10);
// 2. 电源管理:使能VMID、MICBIAS、ADC、DAC
WM8960_WriteReg(0x02, 0x01E0); // Power Management 1
WM8960_WriteReg(0x03, 0x003C); // Power Management 2
WM8960_WriteReg(0x04, 0x0001); // Power Management 3
// 3. 配置音频接口:I2S格式,16bit,从模式
WM8960_WriteReg(0x05, 0x0050); // Audio Interface
// 4. 配置PLL(如果使用外部MCLK)
WM8960_ConfigPLL();
// 5. 配置采样率:48kHz
WM8960_WriteReg(0x08, 0x0000); // Audio Interface 2
// 6. 配置DAC:关闭去加重,取消静音
WM8960_WriteReg(0x0A, 0x0000); // DAC Control
// 7. 设置输入音量:左声道+右声道
WM8960_WriteReg(0x0C, 0x0017); // Left Input Volume
WM8960_WriteReg(0x0D, 0x0017); // Right Input Volume
// 8. 设置DAC音量:最大
WM8960_WriteReg(0x10, 0x00FF); // DAC Volume
// 9. 使能输出通路
WM8960_WriteReg(0x1A, 0x0000); // Additional Control 1
// 10. 取消DAC静音
WM8960_WriteReg(0x02, 0x01E8); // 重新设置Power Management 1,取消DAC静音
}
经验之谈:初始化顺序很重要。我试过先开DAC音量再开电源,结果DAC输出瞬间有个爆音。正确的做法是:先开电源,等稳定后再配音量。另外,取消静音要放在最后一步,否则会听到上电噪声。