4. WM8960 寄存器概览与初始化流程
好,咱们进入 WM8960 驱动的核心环节——寄存器配置。说实话,很多朋友拿到数据手册一看几百页,头就大了。我刚开始做音频驱动时也一样,翻着翻着就迷失在寄存器海洋里。
但你别怕。WM8960 虽然寄存器多,真正初始化要用到的,也就二三十个。关键是理清脉络,知道哪些是必配的,哪些是默认值就能用的。
4.1 芯片内部框图解析
先看内部结构。我习惯把 WM8960 分成三大块:
- 输入通路:麦克风(差分/单端)、LINE IN、AUX。经过 PGA 和混音器。
- 输出通路:耳机输出(HPOUT_L/R)、扬声器输出(SPK_OUT)、LINE OUT。每个输出都有独立的音量控制和静音开关。
- 数字核心:ADC、DAC、数字滤波器、时钟管理、I2S 接口。
你想想看,这三块之间靠什么连接?靠内部数字总线。所以初始化顺序很重要——必须先让数字核心跑起来,再配模拟通路。
核心思路:先数字,后模拟;先时钟,后数据通路。
下面这张图是我自己画的简化框图,帮你快速建立整体认知:
4.2 关键寄存器说明
WM8960 的寄存器地址是 7 位,数据是 9 位。I2C 通信时,先发 7 位地址 + 读写位,再发 8 位寄存器地址,最后发 9 位数据(分两个字节)。
下面这几个寄存器,是初始化必须打交道的:
| 寄存器名 | 地址 | 作用 | 我的建议 |
|---|---|---|---|
| Reset | 0x0F | 软件复位,写入 0x000 恢复默认值 | 初始化第一步必须做 |
| Power 1 (0x19) | 0x19 | 控制 MICBIAS、VMID、ADC/DAC 电源 | 按需使能,别全开 |
| Power 2 (0x1A) | 0x1A | 控制输出级电源(耳机、扬声器) | 用哪个开哪个 |
| Clock 1 (0x04) | 0x04 | 配置 MCLK 分频、采样率 | 必须与 I2S 主时钟匹配 |
| Interface 1 (0x05) | 0x05 | 配置 I2S 格式、字长、主从模式 | 与 STM32 侧严格一致 |
| Interface 2 (0x06) | 0x06 | 配置音频数据字长、BCLK/LRC 极性 | 默认值通常够用 |
小技巧:我习惯把常用寄存器地址定义成宏,比如 WM8960_REG_RESET、WM8960_REG_POWER1,这样代码可读性高很多。
4.3 标准初始化序列设计
初始化顺序搞错了,芯片可能不工作,或者有奇怪噪声。我踩过这个坑——有一次先开了模拟电源,再配时钟,结果 DAC 输出全是爆音。
正确的顺序应该是:
- 软件复位:写入 0x0F 寄存器为 0x000,确保芯片回到已知状态。
- 配置时钟:设置 MCLK 分频、采样率、系统时钟源。
- 配置 I2S 接口:格式、字长、主从模式。
- 使能数字核心电源:开 ADC/DAC 数字部分。
- 配置模拟通路:开 MICBIAS、PGA、混音器。
- 使能输出级:开耳机或扬声器驱动。
- 设置音量:从静音状态逐步提升,避免 POP 声。
注意:第 6 步和第 7 步之间,建议加 100ms 延时。让电源稳定后再输出音频,否则容易听到“噗”的一声。
4.4 编写初始化函数
好,理论讲完了,咱们直接上代码。下面是我在项目里用的一套初始化函数,经过多个板子验证,稳定可靠。
/* WM8960 寄存器地址宏定义 */
#define WM8960_REG_RESET 0x0F
#define WM8960_REG_POWER1 0x19
#define WM8960_REG_POWER2 0x1A
#define WM8960_REG_CLOCK1 0x04
#define WM8960_REG_IFACE1 0x05
#define WM8960_REG_IFACE2 0x06
/* I2C 写寄存器函数 */
static HAL_StatusTypeDef wm8960_write_reg(uint8_t reg, uint16_t data)
{
uint8_t buf[2];
buf[0] = (data >> 8) & 0x01; /* 9 位数据的高 1 位 */
buf[1] = data & 0xFF; /* 低 8 位 */
return HAL_I2C_Master_Transmit(&hi2c1, WM8960_ADDR << 1, buf, 2, 100);
}
/* WM8960 初始化函数 */
HAL_StatusTypeDef wm8960_init(void)
{
HAL_StatusTypeDef ret;
/* 1. 软件复位 */
ret = wm8960_write_reg(WM8960_REG_RESET, 0x000);
if (ret != HAL_OK) return ret;
HAL_Delay(10);
/* 2. 配置时钟:MCLK = 12.288MHz,采样率 48kHz */
ret = wm8960_write_reg(WM8960_REG_CLOCK1, 0x000); /* 不分频,默认 */
if (ret != HAL_OK) return ret;
/* 3. 配置 I2S 接口:I2S 格式,24 位,从模式 */
ret = wm8960_write_reg(WM8960_REG_IFACE1, 0x0040); /* 格式:I2S,字长:24 */
if (ret != HAL_OK) return ret;
ret = wm8960_write_reg(WM8960_REG_IFACE2, 0x0000); /* 默认极性 */
if (ret != HAL_OK) return ret;
/* 4. 使能数字核心电源 */
ret = wm8960_write_reg(WM8960_REG_POWER1, 0x01C0); /* 开 ADC、DAC、VMID */
if (ret != HAL_OK) return ret;
HAL_Delay(50);
/* 5. 配置模拟通路:这里以耳机输出为例 */
/* 使能左/右声道耳机输出 */
ret = wm8960_write_reg(WM8960_REG_POWER2, 0x01E0);
if (ret != HAL_OK) return ret;
/* 6. 设置音量:从静音状态解除,逐步提升 */
/* 左声道耳机音量设为 0dB */
ret = wm8960_write_reg(0x02, 0x007F);
if (ret != HAL_OK) return ret;
/* 右声道耳机音量设为 0dB */
ret = wm8960_write_reg(0x03, 0x007F);
if (ret != HAL_OK) return ret;
return HAL_OK;
}
关键点:WM8960_ADDR 取决于芯片的 CS 引脚电平。如果 CS 接地,地址是 0x1A;如果接高,是 0x1B。我建议你在硬件设计时固定接地,少一个变量。
这段代码看起来简单,但每个寄存器值都有讲究。比如 POWER1 的 0x01C0,bit6 是 VMID 使能,bit7 是 ADC 使能,bit8 是 DAC 使能。我习惯对照数据手册的寄存器位图,一个一个确认。
嗯,这里要注意:如果你用的是扬声器输出,POWER2 的配置会不一样,需要额外开 Class D 功放电源。这个我们后面章节会细讲。
最后说一句:初始化函数写完后,建议用逻辑分析仪抓一下 I2C 波形,确认每个寄存器的写入值对不对。我曾经因为一个 bit 写错,查了整整两天——后来发现是数据手册的位定义看反了。