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 → 输出放大器 → 耳机/喇叭输出

我画了一张路径图,方便你理解:

WM8960音频路径框图 WM8960音频路径框图 模拟输入 输入放大器 ADC I2S发送 I2S接收 DAC 输出放大器 耳机/喇叭 ADC路径 DAC路径 说明: ADC路径:模拟信号 → 放大 → 数字转换 → I2S输出到STM32 DAC路径:I2S从STM32接收数字音频 → 数模转换 → 放大 → 驱动耳机/喇叭

配置音频路径时,需要按顺序操作:

  1. 先开电源:使能VMID、MICBIAS、ADC/DAC电源
  2. 再配接口:设置I2S格式、字长、主从模式
  3. 然后设音量:输入增益、DAC数字音量
  4. 最后开通路:使能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输出瞬间有个爆音。正确的做法是:先开电源,等稳定后再配音量。另外,取消静音要放在最后一步,否则会听到上电噪声。