4. WM8960 寄存器概览与初始化流程

好,咱们进入 WM8960 驱动的核心环节——寄存器配置。说实话,很多朋友拿到数据手册一看几百页,头就大了。我刚开始做音频驱动时也一样,翻着翻着就迷失在寄存器海洋里。

但你别怕。WM8960 虽然寄存器多,真正初始化要用到的,也就二三十个。关键是理清脉络,知道哪些是必配的,哪些是默认值就能用的。

4.1 芯片内部框图解析

先看内部结构。我习惯把 WM8960 分成三大块:

  • 输入通路:麦克风(差分/单端)、LINE IN、AUX。经过 PGA 和混音器。
  • 输出通路:耳机输出(HPOUT_L/R)、扬声器输出(SPK_OUT)、LINE OUT。每个输出都有独立的音量控制和静音开关。
  • 数字核心:ADC、DAC、数字滤波器、时钟管理、I2S 接口。

你想想看,这三块之间靠什么连接?靠内部数字总线。所以初始化顺序很重要——必须先让数字核心跑起来,再配模拟通路。

核心思路:先数字,后模拟;先时钟,后数据通路。

下面这张图是我自己画的简化框图,帮你快速建立整体认知:

WM8960 内部功能框图(简化版) MIC输入 LINE IN AUX输入 输入混音器 PGA增益控制 ADC 数字核心 数字滤波器 I2S接口 时钟管理 ALC DAC 输出混音器 音量/静音控制 耳机输出 扬声器输出 LINE OUT 注意 输入/输出通路 可独立使能 节省功耗

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 输出全是爆音。

正确的顺序应该是:

  1. 软件复位:写入 0x0F 寄存器为 0x000,确保芯片回到已知状态。
  2. 配置时钟:设置 MCLK 分频、采样率、系统时钟源。
  3. 配置 I2S 接口:格式、字长、主从模式。
  4. 使能数字核心电源:开 ADC/DAC 数字部分。
  5. 配置模拟通路:开 MICBIAS、PGA、混音器。
  6. 使能输出级:开耳机或扬声器驱动。
  7. 设置音量:从静音状态逐步提升,避免 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 写错,查了整整两天——后来发现是数据手册的位定义看反了。