3. STM32与WM8960的I2C通信:I2C协议回顾、WM8960寄存器映射、初始化序列

好,我们正式开始动手连接WM8960。第一步,就是通过I2C总线跟它“对上话”。

WM8960是一颗很聪明的芯片,它的控制接口只认I2C。你给它发一串寄存器地址+数据,它就乖乖配置好内部模块。说白了,你写代码就是在填一张寄存器表。

我个人习惯先把I2C底层调通,再去看寄存器。不然你代码写了一大堆,逻辑对不对根本不知道。

3.1 I2C协议快速回顾

I2C只有两根线:SCL(时钟)和SDA(数据)。STM32作为主机,WM8960作为从机。通信流程其实就三步:

  1. 起始条件:SCL高电平时,SDA从高拉低。
  2. 发送地址+读写位:WM8960的7位地址是0x1A(左对齐),加上写位0就是0x34,加读位1就是0x35。
  3. 数据收发:每字节后跟一个ACK应答位。结束条件是SCL高电平时SDA从低拉高。

小提示:WM8960的I2C地址引脚ADDR如果接地,地址就是0x1A;如果接VDD,地址变成0x1B。我建议你原理图上直接接地,省得后面软件还要判断。

STM32的I2C外设支持标准模式(100kHz)和快速模式(400kHz)。WM8960的datasheet说它支持400kHz,但我建议你初期先用100kHz。为什么?

我曾经在一个项目里上来就开400kHz,结果波形上升沿太缓,从机偶尔不响应。排查了半天,最后降速到100kHz就稳了。嗯,高速不一定好,稳定才是王道。

3.2 WM8960寄存器映射

WM8960内部有56个寄存器,每个16位宽。但I2C传输时,地址是7位,数据是9位(实际有效是9位,但STM32的I2C是按字节传的)。

这里有个坑:WM8960的寄存器地址是7位,但I2C协议里地址字节是7位地址+1位读写位。所以你要把寄存器地址左移1位,再拼上读写位。很多新手在这里搞混。

我整理了一张常用寄存器表,你写初始化序列时肯定用得上:

寄存器地址(7位) 寄存器名称 默认值(16位) 功能简述
0x00 Reset 0x0000 写0x0000复位芯片
0x02 Power Management (1) 0x0000 开启/关闭各模块电源
0x04 Audio Interface (1) 0x0050 配置I2S格式、字长
0x06 Clocking (1) 0x0000 配置MCLK分频、采样率
0x08 Audio Interface (2) 0x0000 配置左右声道、BCLK极性
0x0A Left Input Volume 0x0000 左声道输入增益
0x0C Right Input Volume 0x0000 右声道输入增益
0x0E Left Output Volume 0x0000 左声道耳机输出音量
0x10 Right Output Volume 0x0000 右声道耳机输出音量
0x12 Power Management (2) 0x0000 开启/关闭输出级电源
0x14 Power Management (3) 0x0000 开启/关闭输入级电源
0x16 Additional Control (1) 0x0000 DAC/ADC采样率控制
0x18 Additional Control (2) 0x0000 GPIO、抖动控制
0x1A Additional Control (3) 0x0000 3D、混音控制
0x1C Additional Control (4) 0x0000 自动电平控制(ALC)

注意:WM8960的寄存器是16位的,但I2C传输时数据是9位有效位(bit8:0)。高7位(bit15:9)是保留位,必须写0。我见过有人把整个16位都写进去,结果芯片不工作——因为高7位被当成保留位处理了,导致寄存器地址错乱。

3.3 初始化序列

初始化WM8960,说白了就是按顺序写寄存器。顺序很重要,不能乱。我总结了一个标准流程:

  1. 复位芯片:写0x00到寄存器0x00。
  2. 配置时钟:设置MCLK分频、采样率。
  3. 配置音频接口:设置I2S格式、字长、BCLK极性。
  4. 开启电源:按顺序打开各模块电源。
  5. 设置音量:配置输入/输出增益。
  6. 解除静音:最后才解除DAC/ADC的静音。

你想想看,为什么电源要在最后才开?因为如果你先开了电源,但时钟和接口还没配好,芯片内部状态会乱。我踩过这个坑——先开了电源,结果I2S数据进来后芯片不识别,波形全乱套。

下面是我实际项目里用过的初始化代码片段(基于STM32 HAL库):

// WM8960寄存器地址(7位)左移1位,加上写位0
#define WM8960_ADDR_WRITE 0x34

// 写WM8960寄存器函数
HAL_StatusTypeDef WM8960_WriteReg(uint8_t reg_addr, uint16_t data)
{
    uint8_t buf[2];
    // 数据只有9位有效,高7位必须为0
    buf[0] = (data >> 8) & 0x01;  // 只取bit8
    buf[1] = data & 0xFF;          // 低8位
    return HAL_I2C_Mem_Write(&hi2c1, WM8960_ADDR_WRITE, reg_addr, 
                             I2C_MEMADD_SIZE_8BIT, buf, 2, 100);
}

// 初始化序列
void WM8960_Init(void)
{
    // 1. 复位
    WM8960_WriteReg(0x00, 0x0000);
    HAL_Delay(10);  // 等待复位完成

    // 2. 配置时钟:MCLK=12.288MHz,采样率48kHz
    WM8960_WriteReg(0x06, 0x0000);  // 不分频,MCLK直接作为内部时钟

    // 3. 配置音频接口:I2S格式,24位数据,左对齐
    WM8960_WriteReg(0x04, 0x0050);  // I2S格式,24位
    WM8960_WriteReg(0x08, 0x0000);  // 左右声道正常,BCLK低电平有效

    // 4. 开启电源:先开数字部分,再开模拟部分
    WM8960_WriteReg(0x02, 0x01E0);  // 开启DAC、ADC、输入输出接口电源
    WM8960_WriteReg(0x12, 0x01F0);  // 开启耳机输出级、线路输出级电源
    WM8960_WriteReg(0x14, 0x01C0);  // 开启麦克风输入级、线路输入级电源

    // 5. 设置音量:耳机输出音量设为0dB(无衰减)
    WM8960_WriteReg(0x0E, 0x01FF);  // 左声道耳机音量最大
    WM8960_WriteReg(0x10, 0x01FF);  // 右声道耳机音量最大

    // 6. 解除静音:DAC和ADC都取消静音
    WM8960_WriteReg(0x02, 0x01E8);  // 在原来基础上,解除DAC静音
    // 注意:ADC静音在寄存器0x0A和0x0C中控制,后面再配
}

关键点:初始化序列里,电源管理寄存器(0x02、0x12、0x14)的配置顺序是固定的。先开数字电源,再开模拟电源。如果反过来,模拟部分上电时没有数字时钟,可能会产生爆音。我建议你在每个写寄存器操作后加一个短延时(至少1ms),让芯片内部状态稳定。

嗯,这里要注意:上面的代码只是演示了基本流程。实际项目中,你还需要根据你的硬件设计调整音量、增益、采样率等参数。比如你的麦克风是单端输入还是差分输入,增益设置就不一样。

3.4 通信验证小技巧

初始化写完后,怎么知道芯片有没有正确响应?我教你一个土办法:读回寄存器0x00(复位寄存器),如果读到的值是0x0000,说明I2C通信正常。如果读到0xFFFF或者一直超时,那就要检查硬件连接了。

我曾经在一个项目里,I2C死活不通。查了半天,发现是WM8960的ADDR引脚悬空了。嗯,这种低级错误,犯过一次就再也不会犯了。

下面这张图展示了整个I2C通信与寄存器配置的流程:

WM8960 I2C通信与初始化流程 STM32 I2C主机启动 发送从机地址 0x34(写) 发送寄存器地址(7位) 发送16位数据(高7位=0) 发送停止条件 重复以上步骤,按顺序写入所有寄存器 注意 1. 每字节后等待ACK 2. 数据高7位必须为0 3. 寄存器地址左移1位

这张图把I2C通信的每个步骤都拆开了。你写代码时,就按这个流程来。每发完一帧数据,检查一下ACK。如果芯片没回ACK,说明它没收到或者地址不对。