4. WM8960 初始化流程:芯片复位、电源管理寄存器配置、时钟使能、音频接口配置
好,咱们进入正题。WM8960 这芯片,说复杂也复杂,说简单也简单。但初始化这一步要是搞错了,后面全是白搭。我见过不少朋友,焊好板子一上电,没声音,就开始怀疑硬件。其实多半是初始化顺序没搞对。
WM8960 的初始化,我习惯把它拆成四个步骤:芯片复位 → 电源管理 → 时钟使能 → 音频接口配置。这个顺序不能乱,尤其是电源和时钟,必须先让芯片“吃饱喝足”,它才肯干活。
核心原则:WM8960 的寄存器是掉电易失的,每次上电都必须完整走一遍初始化流程。别指望它像 STM32 那样能记住上次的状态。
4.1 芯片复位——让一切归零
WM8960 的复位有两种方式:硬件复位(拉低 RST 引脚)和软件复位(写寄存器)。我个人更推荐软件复位,省一个 GPIO 引脚。
软件复位很简单,往 R15 (Software Reset) 寄存器写 0x0000 就行。芯片内部会自动加载默认值。
/* WM8960 软件复位 */
uint8_t ret = WM8960_WriteReg(WM8960_R15_SOFTWARE_RESET, 0x0000);
if (ret != HAL_OK) {
Error_Handler(); // 复位失败,基本可以判断 I2C 不通
}
HAL_Delay(10); // 等内部复位完成
我曾经踩过的坑:复位后不要立刻配置其他寄存器。WM8960 内部有多个稳压器和偏置电路,复位后需要几毫秒才能稳定。我早期做项目时没加这个延时,结果 10 片板子有 3 片初始化失败,查了两天才发现是时序问题。
4.2 电源管理寄存器配置——先让芯片“吃饱”
复位之后,WM8960 大部分模块是关着的。你得手动打开需要用到的电源域。相关的寄存器有三个:
| 寄存器 | 地址 | 作用 | 典型值 |
|---|---|---|---|
| R25 (PWR1) | 0x19 | MIC、ADC、DAC 电源 | 0x01C0 |
| R26 (PWR2) | 0x1A | 输出混音器、HP 功放 | 0x01FD |
| R27 (PWR3) | 0x1B | 输入混音器、LINEOUT | 0x01EF |
如果你只是做音频播放(不录音),可以只开 DAC 和耳机输出相关的电源域。我一般这样配:
/* 电源管理配置 - 仅播放模式 */
WM8960_WriteReg(WM8960_R25_PWR1, 0x01C0); // 开启 DACL/DACR, VMID
WM8960_WriteReg(WM8960_R26_PWR2, 0x01FD); // 开启 HP_OUT 左右声道
WM8960_WriteReg(WM8960_R27_PWR3, 0x01EF); // 开启输入混音器备用
HAL_Delay(5); // 等电源稳定
小技巧:VMID 是内部参考电压,必须开启。如果你发现输出有咔咔声,多半是 VMID 没开或者电容没焊好。我遇到过两次,都是 VMID 引脚对地电容虚焊导致的。
4.3 时钟使能——给芯片一个“心跳”
WM8960 的时钟系统有点绕,但说白了就三件事:选时钟源、配分频比、决定采样率。
相关的寄存器是 R4 (CLOCK1)、R5 (CLOCK2) 和 R6 (CLOCK3)。其中 R4 最重要,它决定了 MCLK 和 SYSCLK 的关系。
我常用的配置是:MCLK = 12.288MHz,采样率 48kHz。为什么选这个频率?因为 12.288MHz 是 48kHz 的整数倍(256 倍),分频最简单,抖动最小。
/* 时钟配置 - MCLK=12.288MHz, 采样率=48kHz */
WM8960_WriteReg(WM8960_R4_CLOCK1, 0x0160); // SYSCLK = MCLK, 不分频
WM8960_WriteReg(WM8960_R5_CLOCK2, 0x0000); // 不使用 PLL
WM8960_WriteReg(WM8960_R6_CLOCK3, 0x0000); // 默认
如果你用的是其他 MCLK 频率(比如 11.2896MHz 对应 44.1kHz),需要调整分频系数。具体怎么算?看数据手册 Table 36,里面有详细的对照表。
注意:MCLK 和采样率的比例必须是整数倍。常见的有 256x、384x、512x。如果比例不对,WM8960 内部锁相环会失锁,出来的声音就是变调的。我刚开始用 16kHz 采样率时,忘了改 MCLK,结果放出来的声音像磁带卡住了一样。
4.4 音频接口配置——约定通信“语言”
这一步是让 WM8960 和 STM32 的 I²S 接口“对上话”。说白了,两边得用同一种格式说话。
主要配置 R7 (IFACE1) 和 R8 (IFACE2)。R7 决定数据格式、字长、位时钟极性;R8 决定左右声道时钟的极性。
| 参数 | 选项 | 推荐值 | 说明 |
|---|---|---|---|
| 数据格式 | I²S / Left-justified / DSP | I²S | 最通用,STM32 默认支持 |
| 字长 | 16 / 20 / 24 / 32 bit | 16 bit | 兼容性好,省带宽 |
| BCLK 极性 | 上升沿/下降沿采样 | 上升沿 | 与 STM32 默认一致 |
| LRCLK 极性 | 高电平左声道/右声道 | 低电平左声道 | 标准 I²S 协议 |
/* 音频接口配置 - I²S, 16bit, 标准模式 */
WM8960_WriteReg(WM8960_R7_IFACE1, 0x0002); // I²S 格式, 16bit
WM8960_WriteReg(WM8960_R8_IFACE2, 0x0000); // 默认 LRCLK 极性
关键检查点:配置完 R7 后,务必确认 STM32 的 I²S 初始化参数与 WM8960 完全一致。尤其是数据格式和字长,两边差一点都不行。我习惯在代码里用宏定义统一管理这些参数,避免手写不一致。
4.5 完整的初始化函数
把上面四步串起来,就是一个完整的初始化函数。我一般这样写:
/**
* @brief WM8960 完整初始化
* @retval HAL_OK 成功,其他 失败
*/
HAL_StatusTypeDef WM8960_Init(void)
{
HAL_StatusTypeDef ret;
/* 第一步:软件复位 */
ret = WM8960_WriteReg(WM8960_R15_SOFTWARE_RESET, 0x0000);
if (ret != HAL_OK) return ret;
HAL_Delay(10);
/* 第二步:电源管理 */
ret = WM8960_WriteReg(WM8960_R25_PWR1, 0x01C0);
if (ret != HAL_OK) return ret;
ret = WM8960_WriteReg(WM8960_R26_PWR2, 0x01FD);
if (ret != HAL_OK) return ret;
ret = WM8960_WriteReg(WM8960_R27_PWR3, 0x01EF);
if (ret != HAL_OK) return ret;
HAL_Delay(5);
/* 第三步:时钟使能 */
ret = WM8960_WriteReg(WM8960_R4_CLOCK1, 0x0160);
if (ret != HAL_OK) return ret;
/* 第四步:音频接口 */
ret = WM8960_WriteReg(WM8960_R7_IFACE1, 0x0002);
if (ret != HAL_OK) return ret;
ret = WM8960_WriteReg(WM8960_R8_IFACE2, 0x0000);
if (ret != HAL_OK) return ret;
return HAL_OK;
}
我的习惯:每个寄存器写完后都检查返回值。虽然 I²C 通信出错的概率不高,但一旦出错,你至少能立刻定位到是哪一步出了问题。别问我为什么这么谨慎——有一次 I²C 上拉电阻焊错了,就是靠这个检查才快速找到问题的。