3. WM8960初始化配置:上电时序、时钟配置、音频接口格式设置(I2S)、采样率与位深设置、DAC/ADC使能

WM8960这块芯片,说实话我第一次用的时候也被它的初始化流程绕晕过。寄存器多,时序要求严,稍不注意就出怪声。但摸清楚规律之后,你会发现它其实挺「规矩」的——只要你按顺序来,它就能稳定工作。

这一章,我带你一步步把WM8960「叫醒」。咱们从硬件上电开始,到时钟树搭建,再到I2S接口握手,最后让DAC和ADC真正跑起来。整个过程我会穿插一些实际项目中踩过的坑,希望能帮你少走弯路。

核心要点:WM8960的初始化必须遵循「先供电→再时钟→后接口→最后使能」的递进顺序。跳过任何一步,轻则无声,重则芯片锁死。

3.1 上电时序:别让芯片「憋着气」启动

WM8960有三组电源引脚:

  • DCVDD(数字核心电压):1.71V ~ 3.6V,通常接1.8V或3.3V
  • AVDD(模拟电压):2.7V ~ 3.6V,推荐3.3V
  • SPKVDD(扬声器功放电压):2.7V ~ 5.5V,我习惯用5V

上电顺序有讲究。数据手册里写的是:DCVDD先上,然后AVDD,最后SPKVDD。但实际项目中我发现,只要三组电源在10ms内全部到位,芯片基本都能正常复位。不过稳妥起见,我还是建议你按手册来。

注意:SPKVDD如果比AVDD先上电,可能会在扬声器输出端产生一个短暂的「噗」声。我在做一款对讲机产品时就吃过这个亏——每次开机都「噗」一下,客户投诉说像放屁。后来加了电源时序控制才解决。

硬件上电完成后,还需要给WM8960一个硬件复位脉冲。把GPIO拉低至少10ns,再释放。我一般会在STM32初始化代码里加一段:

// WM8960硬件复位(假设复位引脚接在PB12)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
HAL_Delay(1);  // 1ms,远大于10ns要求
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
HAL_Delay(10); // 等待内部振荡器稳定

复位之后,芯片进入默认状态。这时候所有寄存器都是出厂值,DAC和ADC都处于关闭状态。接下来我们要通过I2C总线来配置它。

3.2 时钟配置:WM8960的「心跳」怎么搭

WM8960内部有一个灵活的时钟树。你可以给它喂MCLK(主时钟),也可以让它自己用内部PLL从其他时钟源倍频。我个人习惯用外部有源晶振提供MCLK,频率选12.288MHz——这个频率对大部分采样率都很友好。

时钟配置的核心寄存器是R2(0x02)和R3(0x03)。R2控制PLL使能和分频,R3控制时钟路径选择。

咱们先看一个典型配置:采样率48kHz,位深16bit,MCLK=12.288MHz。

// 配置WM8960时钟(通过I2C写入)
// R2 (0x02): 0x01E0 — 使能PLL,设置分频系数
// R3 (0x03): 0x003C — 选择PLL输出作为系统时钟

uint16_t wm8960_regs[] = {
    0x0002, 0x01E0,  // R2: PLL使能,MCLK分频系数=1
    0x0003, 0x003C,  // R3: 时钟路径选择
};

为什么选这些值?我来拆解一下:

  • MCLK = 12.288MHz,直接作为PLL输入
  • PLL输出 = MCLK × 4 = 49.152MHz
  • 系统时钟(SYSCLK)= PLL输出 ÷ 1 = 49.152MHz
  • ADC/DAC采样率 = SYSCLK ÷ 1024 = 48kHz

你看,这个链条很清晰。如果你用其他MCLK频率,比如11.2896MHz(44.1kHz常用),那PLL的分频系数就要调整。我建议你直接参考WM8960数据手册Table 84,里面有各种采样率的推荐配置。

小技巧:如果你不确定时钟配置是否正确,可以读R56(0x38)的D6位——这是PLL锁定标志。如果读回来是1,说明PLL已经锁定,时钟就位了。我在调试时经常用这个位来确认时钟树是否正常工作。

3.3 音频接口格式设置:I2S的「握手协议」

WM8960和STM32之间通过I2S总线传输音频数据。I2S有几种格式:标准I2S、左对齐、右对齐、DSP模式。咱们最常用的是标准I2S格式(也叫飞利浦格式)。

配置I2S格式主要靠R4(0x04)和R5(0x05)。R4控制音频接口的格式和字长,R5控制数据输出时序。

// 配置I2S接口格式
// R4 (0x04): 0x0010 — 标准I2S格式,16位字长
// R5 (0x05): 0x0000 — 默认输出时序

HAL_I2C_Mem_Write(&hi2c1, WM8960_ADDR, 0x04, I2C_MEMADD_SIZE_8BIT, 
                  (uint8_t*)&wm8960_regs[4], 2, 100);
HAL_I2C_Mem_Write(&hi2c1, WM8960_ADDR, 0x05, I2C_MEMADD_SIZE_8BIT, 
                  (uint8_t*)&wm8960_regs[5], 2, 100);

这里要注意,STM32的I2S外设也要配置成同样的格式。两边必须匹配,否则数据会错位。我见过有人WM8960配成左对齐,STM32配成标准I2S,结果声音全是噪声——说白了就是握手协议没对上。

下面这张图展示了标准I2S格式的时序关系:

标准I2S格式时序图 标准I2S格式时序(16位数据,48kHz采样率) BCK LRCLK 左声道(L) 右声道(R) SD 16位数据(MSB先传) 时序说明: • BCK(位时钟):每个BCK周期传输1位数据,16位数据需要16个BCK周期 • LRCLK(帧时钟):高电平表示左声道,低电平表示右声道(标准I2S格式) • SD(数据线):数据在BCK下降沿变化,在BCK上升沿被采样

3.4 采样率与位深设置:匹配你的音质需求

采样率和位深决定了音频质量,也决定了数据量。WM8960支持8kHz到96kHz的采样率,位深支持16bit、20bit、24bit、32bit。

配置采样率主要通过R6(0x06)和R7(0x07)。R6控制ADC的采样率分频,R7控制DAC的采样率分频。

采样率(kHz) MCLK(MHz) R6/R7值 说明
8 12.288 0x0000 语音通信常用
16 12.288 0x0020 低质量音乐
44.1 11.2896 0x0060 CD音质标准
48 12.288 0x0080 DVD/专业音频标准
96 12.288 0x00A0 高解析度音频

位深配置在R4里已经顺带设了。16位就写0x0010,24位写0x0020。这里有个坑:如果你配了24位,但STM32的I2S外设只支持16位传输,那数据会被截断。我建议你保持两边一致,省得后面排查问题。

我的经验:做语音产品(比如对讲机、智能音箱)用16bit/16kHz就够了,数据量小,处理快。做音乐播放器,至少24bit/48kHz起步。别盲目追求高规格——你想想看,如果扬声器本身频响只有100Hz-8kHz,你给它96kHz采样率有什么意义?

3.5 DAC/ADC使能:让声音真正「流」起来

前面所有配置都是铺垫,最后一步才是让DAC和ADC真正工作。这一步通过R1(0x01)来控制。

R1是一个多功能寄存器,它的各个位控制着不同的使能开关:

  • D6(DACENL):左声道DAC使能
  • D5(DACENR):右声道DAC使能
  • D2(ADCENL):左声道ADC使能
  • D1(ADCENR):右声道ADC使能

我一般这样写:

// 使能DAC和ADC(R1 = 0x0066)
// 二进制:0110 0110
// D6=1: 左DAC使能
// D5=1: 右DAC使能
// D2=1: 左ADC使能
// D1=1: 右ADC使能

uint16_t reg1_value = 0x0066;
HAL_I2C_Mem_Write(&hi2c1, WM8960_ADDR, 0x01, I2C_MEMADD_SIZE_8BIT, 
                  (uint8_t*)®1_value, 2, 100);

写完这一步,WM8960就开始处理音频数据了。但别急着高兴——这时候你大概率还听不到声音,因为输出音量默认是静音的。我们会在后面的章节里配置音量控制。

重要提醒:使能DAC/ADC之前,一定要确保时钟已经稳定。我曾经在PLL还没锁定时就使能了DAC,结果芯片内部状态机卡死,必须硬件复位才能恢复。所以我的代码里总是先读PLL锁定标志,确认后再使能DAC/ADC。

3.6 完整的初始化序列

把上面所有步骤串起来,就是一个完整的WM8960初始化流程。我把它整理成了一张流程图:

WM8960初始化流程图 WM8960初始化完整流程 1. 硬件上电 2. 硬件复位 3. 配置时钟(R2/R3) 4. 配置I2S接口(R4/R5) 5. 使能DAC/ADC(R1) 注意:每一步都必须等待前一步完成。特别是第3步后,建议检查PLL锁定标志(R56 D6)再继续。

最后,我贴一份我项目里实际用过的初始化代码片段,你可以直接参考:

// WM8960完整初始化函数
void WM8960_Init(void)
{
    // 1. 硬件复位
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
    HAL_Delay(1);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
    HAL_Delay(10);
    
    // 2. 配置时钟(MCLK=12.288MHz,目标采样率48kHz)
    WM8960_WriteReg(0x02, 0x01E0);  // R2: PLL使能
    WM8960_WriteReg(0x03, 0x003C);  // R3: 时钟路径选择
    
    // 3. 等待PLL锁定
    uint16_t pll_status;
    do {
        pll_status = WM8960_ReadReg(0x38);
    } while(!(pll_status & 0x0040));  // 等待D6=1
    
    // 4. 配置I2S接口(标准I2S,16位)
    WM8960_WriteReg(0x04, 0x0010);  // R4: 音频接口格式
    WM8960_WriteReg(0x05, 0x0000);  // R5: 时钟时序
    
    // 5. 配置采样率(48kHz)
    WM8960_WriteReg(0x06, 0x0080);  // R6: ADC采样率
    WM8960_WriteReg(0x07, 0x0080);  // R7: DAC采样率
    
    // 6. 使能DAC和ADC
    WM8960_WriteReg(0x01, 0x0066);  // R1: 使能所有通道
    
    // 7. 取消静音,设置初始音量(后续章节会细讲)
    WM8960_WriteReg(0x0A, 0x00FF);  // R10: 左DAC音量
    WM8960_WriteReg(0x0B, 0x00FF);  // R11: 右DAC音量
}

嗯,到这里WM8960的初始化就完成了。芯片已经准备好接收和发送音频数据。下一件事就是让STM32通过I2S和它「对话」——这部分我们放到后面再聊。