1. 课程导学与硬件准备

各位同学,欢迎来到《STM32 HAL库实战:移植WM8960音频驱动到自定义开发板》的第一章。在正式开始敲代码之前,咱们得先把家底儿摸清楚。说白了,做嵌入式驱动开发,七分靠硬件准备,三分靠代码。硬件没搞明白,后面调驱动就是瞎子摸象。

我个人习惯,拿到一个新项目,第一件事不是看数据手册,而是先看原理图和PCB。你得知道你的MCU和音频Codec之间是怎么连的,供电够不够,时钟干不干净。这一章,我们就来把这些准备工作做扎实。

1.1 WM8960芯片特性速览

WM8960是Cirrus Logic(原Wolfson)出品的一款低功耗立体声音频Codec。我在项目中用过不少音频芯片,WM8960算是比较“皮实”的一款,适合做便携式音频设备。

它的核心特性,我整理了一张表:

参数项 具体规格 我的备注
音频通道 立体声DAC + 立体声ADC 支持同时录音和放音
采样率 8kHz ~ 96kHz 常见48kHz足够用
位深 16/20/24/32 bit I2S标准接口
控制接口 I2C(从机模式) 地址0x1A或0x1B
音频接口 I2S / Left-Justified / DSP 我们只用I2S
输出功率 1W @ 8Ω (单声道BTL) 直推小喇叭没问题
工作电压 DCVDD 2.7~3.6V / DBVDD 1.71~3.6V 注意模拟和数字分开供电

重点记住:WM8960的控制接口是I2C,音频数据接口是I2S。这两个接口的时序,是我们后面移植工作的核心。

为什么会强调这个?因为我曾经在一个项目里,把I2C地址搞错了,结果调了两天I2C通信都调不通,最后发现是原理图上ADDR引脚的电平没拉对。嗯,这种低级错误,犯过一次就记住了。

1.2 自定义开发板硬件架构

我们这次用的开发板,是我自己设计的一块STM32F407VGT6核心板,外挂了WM8960。你想想看,如果直接用官方开发板,那移植工作就太“顺滑”了,学不到真东西。自定义板子才有挑战性。

先看整体架构图:

自定义开发板音频子系统架构 STM32F407VGT6 Cortex-M4 @168MHz I2C1 + I2S3 DMA + 中断 WM8960 立体声Codec I2C从机地址: 0x1A I2S从机模式 电源管理 3.3V / 1.8V LDO 音频输出 3.5mm耳机 / 喇叭 外部晶振 12.288MHz MCLK输入 I2C (SCL/SDA) I2S (BCLK/WS/SD) 供电 供电 MCLK 音频输出 关键连接 I2C: PB6/PB7 I2S: PC10~PC12 MCLK: PC6(TIM8)

从图上可以看到,STM32和WM8960之间主要有三条“路”:

  • I2C控制通路:用来配置WM8960的寄存器,比如音量、采样率、输入输出路由等。
  • I2S音频数据通路:用来传输实际的PCM音频数据,这是音频流的“高速公路”。
  • MCLK时钟通路:WM8960需要主时钟,我们用一个12.288MHz的晶振提供。这个频率是48kHz采样率的整数倍,很标准。

注意:MCLK必须精确。如果MCLK不准,WM8960内部PLL锁不住,出来的声音就会变调。我遇到过用内部RC振荡器当MCLK的情况,结果声音像磁带卡带了一样。

1.3 开发工具准备

工欲善其事,必先利其器。我们这次用到的工具链很标准:

1.3.1 STM32CubeMX

版本我用的是6.10.0,其实6.x系列都行。CubeMX的作用是帮我们生成HAL库的初始化代码,省去手动配置时钟树和GPIO的麻烦。我个人习惯,先用CubeMX把外设引脚和时钟配好,生成工程,再往里面加自己的驱动代码。

1.3.2 Keil MDK

版本5.38,ARMCC V6编译器。Keil的调试器很好用,尤其是硬件断点和实时变量查看。如果你用其他IDE,比如IAR或者STM32CubeIDE,也没问题,代码是通用的。

1.3.3 调试工具

  • 逻辑分析仪:至少8通道,用来抓I2C和I2S时序。我推荐Saleae的逻辑分析仪,便宜好用。
  • 万用表:测电压、测通断。别小看这个,很多问题都是虚焊或者供电不足引起的。
  • 示波器:如果有条件,用示波器看MCLK和BCLK的波形,能发现时钟抖动和幅值问题。

我的小建议:刚开始调音频驱动时,别急着上音乐数据。先用逻辑分析仪抓I2C的寄存器读写,确认通信正常了,再放一个1kHz的正弦波测试音。这样能快速定位问题是出在控制通路还是数据通路。

1.4 搭建最小系统测试环境

所谓最小系统,就是让STM32能跑起来,并且能和WM8960建立基本的I2C通信。这一步成功了,后面的事情就顺了。

具体步骤:

  1. 焊接检查:用万用表测STM32和WM8960的电源对地电阻,确保没有短路。上电后测各点电压是否正常。
  2. 下载LED闪烁程序:先不管音频,让STM32的LED灯闪烁,证明MCU最小系统工作正常。
  3. I2C扫描:写一个I2C扫描程序,让STM32去扫描0x00~0x7F地址,看能不能找到WM8960(地址0x1A或0x1B)。
  4. 读取WM8960芯片ID:WM8960的寄存器0x00是芯片ID,复位值是0x004F。如果能正确读到这个值,说明I2C通信完全OK。

下面是一个简单的I2C扫描代码片段,我用HAL库写的:

// I2C扫描函数,检测WM8960是否存在
void I2C_Scan(void)
{
    HAL_StatusTypeDef ret;
    uint8_t i;
    uint8_t pData = 0;

    printf("I2C Scanning...\r\n");
    for (i = 0x00; i < 0x7F; i++)
    {
        ret = HAL_I2C_IsDeviceReady(&hi2c1, (uint16_t)(i << 1), 1, 10);
        if (ret == HAL_OK)
        {
            printf("Found device at 0x%02X\r\n", i);
        }
    }
    printf("Scan done.\r\n");
}

// 读取WM8960芯片ID
uint16_t WM8960_ReadID(void)
{
    uint8_t reg = 0x00;
    uint8_t data[2] = {0};
    HAL_I2C_Master_Transmit(&hi2c1, 0x34, ®, 1, 100);
    HAL_I2C_Master_Receive(&hi2c1, 0x34, data, 2, 100);
    return ((uint16_t)data[0] << 8) | data[1];
}

关键点:WM8960的I2C地址是7位地址0x1A,左移一位后变成0x34(写操作)和0x35(读操作)。HAL库的地址参数需要传入左移后的8位地址。

嗯,这里要注意。如果你扫描不到设备,别急着怀疑代码。先检查:

  • I2C的上拉电阻有没有焊?一般4.7kΩ到10kΩ。
  • WM8960的供电引脚电压对不对?DVDD和AVDD都要测。
  • 原理图上ADDR引脚的电平,决定了I2C地址是0x1A还是0x1B。

我曾经在一个板子上,I2C死活扫不到设备,折腾了半天,最后发现是原理图上SCL和SDA画反了。嗯,这种低级错误,画PCB的时候一定要仔细核对。