3. STM32与WM8960的I2C通信:I2C协议回顾、WM8960寄存器映射、初始化序列
好,我们正式开始动手连接WM8960。第一步,就是通过I2C总线跟它“对上话”。
WM8960是一颗很聪明的芯片,它的控制接口只认I2C。你给它发一串寄存器地址+数据,它就乖乖配置好内部模块。说白了,你写代码就是在填一张寄存器表。
我个人习惯先把I2C底层调通,再去看寄存器。不然你代码写了一大堆,逻辑对不对根本不知道。
3.1 I2C协议快速回顾
I2C只有两根线:SCL(时钟)和SDA(数据)。STM32作为主机,WM8960作为从机。通信流程其实就三步:
- 起始条件:SCL高电平时,SDA从高拉低。
- 发送地址+读写位:WM8960的7位地址是0x1A(左对齐),加上写位0就是0x34,加读位1就是0x35。
- 数据收发:每字节后跟一个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,说白了就是按顺序写寄存器。顺序很重要,不能乱。我总结了一个标准流程:
- 复位芯片:写0x00到寄存器0x00。
- 配置时钟:设置MCLK分频、采样率。
- 配置音频接口:设置I2S格式、字长、BCLK极性。
- 开启电源:按顺序打开各模块电源。
- 设置音量:配置输入/输出增益。
- 解除静音:最后才解除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通信与寄存器配置的流程:
这张图把I2C通信的每个步骤都拆开了。你写代码时,就按这个流程来。每发完一帧数据,检查一下ACK。如果芯片没回ACK,说明它没收到或者地址不对。