3. WM8960 寄存器配置:I2C 控制接口、时钟树分析、PLL 配置、音频路径设置
好,这一章我们正式进入 WM8960 的配置环节。说实话,很多朋友拿到 codec 芯片的第一反应就是「寄存器好多,怎么配?」。别急,我带你捋一遍,其实核心就四个模块:怎么跟它说话(I2C)、怎么给它喂时钟(时钟树 & PLL)、怎么走信号(音频路径)。搞明白这四块,WM8960 基本就玩转了。
3.1 I2C 控制接口:怎么跟 WM8960 打招呼
WM8960 的寄存器读写走的是 I2C 总线,地址是固定的 0x1A(7 位地址,左移一位后是 0x34 用于写操作)。我个人习惯在初始化时先发一个软复位(寄存器 0x0F 写 0x00),确保芯片处于已知状态。
下面是我常用的 I2C 写寄存器函数,直接贴出来:
// WM8960 I2C 写寄存器
// reg: 7 位寄存器地址 (0x00 - 0x7F)
// val: 9 位数据值 (0x000 - 0x1FF)
HAL_StatusTypeDef WM8960_WriteReg(uint8_t reg, uint16_t val)
{
uint8_t buf[2];
buf[0] = (reg << 1) | ((val >> 8) & 0x01); // 高字节:寄存器地址 + 数据 bit8
buf[1] = val & 0xFF; // 低字节:数据 bit7-bit0
return HAL_I2C_Master_Transmit(&hi2c1, WM8960_ADDR, buf, 2, 100);
}
嗯,这里要注意:WM8960 的寄存器地址是 7 位,但数据是 9 位。所以发送的两个字节里,第一个字节的高 7 位是寄存器地址,最低位是数据的第 8 位(bit8);第二个字节是数据的低 8 位(bit7-bit0)。我第一次用的时候没仔细看 datasheet,直接把 16 位数据当两个字节发,结果读回来的数据全是乱的……后来对着逻辑分析仪才找到问题。
3.2 时钟树分析:WM8960 的「心跳」怎么搭
WM8960 的时钟系统比想象中灵活,但也容易踩坑。它的核心时钟源有三个:MCLK(主时钟)、BCLK(位时钟)、内部 PLL。我一般用 MCLK 作为主时钟源,然后通过 PLL 倍频到需要的内部时钟。
先看一张时钟树的结构图,帮你建立整体认知:
从图里可以看到,MCLK 进来后先经过一个可编程分频器(CLK_DIV),然后可以选择直通或者进 PLL 倍频。我个人习惯用 12.288MHz 的 MCLK,配合 PLL 倍频到 256×Fs(比如 48kHz 采样率时,内部时钟 = 12.288MHz)。
3.3 PLL 配置:算好倍频比,别让时钟跑偏
WM8960 的 PLL 配置是很多人的噩梦,其实公式很简单:
PLL 输出频率 = MCLK × (R × N) / (D × 4)
其中:
- R:参考分频系数(寄存器 0x36 的 PLL_R[3:0])
- N:倍频系数(寄存器 0x36 的 PLL_N[3:0])
- D:后分频系数(寄存器 0x37 的 PLL_D[3:0])
举个例子,MCLK = 12.288MHz,目标内部时钟 = 256 × 48kHz = 12.288MHz。那 PLL 其实不需要倍频,直接旁路就行(寄存器 0x34 的 PLL_EN = 0)。但如果 MCLK 是 11.2896MHz(常见于 44.1kHz 系统),要得到 256 × 44.1kHz = 11.2896MHz,同样可以旁路。
PLL 配置的寄存器如下:
| 寄存器地址 | 寄存器名称 | 位域 | 说明 |
|---|---|---|---|
| 0x34 | PLL Control 1 | PLL_EN (bit0) | 0 = 旁路 PLL,1 = 启用 PLL |
| 0x36 | PLL Control 2 | PLL_R[3:0] (bit7:4), PLL_N[3:0] (bit3:0) | R 和 N 系数 |
| 0x37 | PLL Control 3 | PLL_D[3:0] (bit3:0) | D 系数 |
3.4 音频路径设置:ADC/DAC 的信号怎么走
WM8960 的音频路径非常灵活,但配置不当容易产生噪声或信号串扰。我一般把路径分为两条:录音路径(ADC)和播放路径(DAC)。
3.4.1 播放路径(DAC → 耳机输出)
播放路径的典型配置是:
- DAC 使能:寄存器 0x05 的 DACL_EN (bit7) 和 DACR_EN (bit6) 置 1。
- 数字音量控制:寄存器 0x0A(左声道)和 0x0B(右声道),范围 0x00(静音)到 0xFF(最大)。
- 模拟混音:寄存器 0x02 的 DACL_TO_HPOUT1L (bit7) 和 DACR_TO_HPOUT1R (bit6) 置 1。
- 耳机输出使能:寄存器 0x04 的 HPOUT1L_EN (bit7) 和 HPOUT1R_EN (bit6) 置 1。
3.4.2 录音路径(麦克风输入 → ADC)
录音路径的典型配置是:
- 麦克风偏置使能:寄存器 0x01 的 MICB_EN (bit0) 置 1。
- 输入 PGA 配置:寄存器 0x00 的 LINPUT1 (bit7:4) 和 RINPUT1 (bit3:0) 设置输入增益。
- ADC 使能:寄存器 0x05 的 ADCL_EN (bit3) 和 ADCR_EN (bit2) 置 1。
- 数字音量控制:寄存器 0x0C(左声道)和 0x0D(右声道)。
下面是一个完整的初始化序列示例,包含了时钟、PLL 和路径配置:
void WM8960_Init(void)
{
// 1. 软复位
WM8960_WriteReg(0x0F, 0x00);
HAL_Delay(10);
// 2. 时钟配置:MCLK = 12.288MHz,旁路 PLL
WM8960_WriteReg(0x34, 0x00); // PLL 旁路
WM8960_WriteReg(0x35, 0x00); // 时钟分频器默认
// 3. 接口格式:I2S 16 位,主模式
WM8960_WriteReg(0x07, 0x02); // I2S 格式,16 位数据
// 4. 播放路径使能
WM8960_WriteReg(0x05, 0xC0); // DACL_EN + DACR_EN
WM8960_WriteReg(0x02, 0xC0); // DACL_TO_HPOUT1L + DACR_TO_HPOUT1R
WM8960_WriteReg(0x04, 0xC0); // HPOUT1L_EN + HPOUT1R_EN
// 5. 设置音量(0dB 左右)
WM8960_WriteReg(0x0A, 0xE0); // 左声道 DAC 音量
WM8960_WriteReg(0x0B, 0xE0); // 右声道 DAC 音量
// 6. 解除静音
WM8960_WriteReg(0x02, 0xC8); // 取消 DAC 静音
}
3.5 避坑指南:我踩过的几个坑
- I2C 地址搞错:WM8960 的 7 位地址是 0x1A,但有些库要求左移一位后的 8 位地址(0x34)。我刚开始用 HAL 库时直接传 0x1A,结果一直 NACK……后来才发现 HAL 库内部会左移一位。
- PLL 锁定时间不够:启用 PLL 后,需要等待至少 5ms 让 PLL 锁定。我习惯在 PLL_EN 置 1 后加一个 HAL_Delay(10)。
- 静音位没清除:WM8960 上电后默认所有输出都是静音的。寄存器 0x02 的 bit3(DAC_MUTE)和 bit2(OUTPUT_MUTE)必须清零才能听到声音。
- 时钟极性搞反:I2S 的时钟极性(CKP)如果配反,数据会错位。我一般用标准 I2S 格式(CKP = 0),跟 STM32 的 I2S 外设默认配置一致。