3. STM32CubeMX 项目配置:时钟树、I2C、I2S、DMA 与 GPIO

好,咱们正式开始动手配置。这一章是整个系统的骨架——时钟、外设、DMA、GPIO,全在这里定下来。我个人习惯是先把时钟树理清楚,因为所有外设的时序都依赖它。WM8960 这颗 codec 对 MCLK 的要求比较特殊,搞不好后面 I2S 就罢工了。

核心思路: 用 STM32CubeMX 生成初始化代码,后续在 HAL 库基础上做二次开发。配置顺序建议:时钟树 → I2C → I2S → DMA → GPIO。

3.1 时钟树配置:MCLK 生成

WM8960 需要外部提供 MCLK(主时钟),典型频率是 12.288 MHz 或 11.2896 MHz。我一般选 12.288 MHz,因为它能整除 48 kHz、96 kHz 这些常用采样率。你想想看,如果 MCLK 频率不对,codec 内部 PLL 锁不住,音频直接变调。

STM32F4 系列通常用 HSE(外部高速晶振)作为时钟源。我习惯用 8 MHz 晶振,然后通过 PLL 倍频到 168 MHz(系统主频),再通过 I2S 专用的 PLLI2S 分频出 12.288 MHz。

具体步骤:

  1. 选择 HSE:RCC 配置中选 Crystal/Ceramic Resonator。
  2. 配置 PLL:HSE 8 MHz → PLLM=8 → PLLN=336 → PLLP=2 → 得到 168 MHz。
  3. 使能 PLLI2S:PLLI2S 的 N=258,R=3 → 8 MHz / 8 * 258 / 3 = 86 MHz?不对,这里要算清楚。

嗯,这里要注意。PLLI2S 的输出频率公式是:VCO = HSE / PLLM * PLLI2SN,然后 I2S_CLK = VCO / PLLI2SR。我们要的是 12.288 MHz,所以:

VCO = 8 MHz / 8 * 258 = 258 MHz
I2S_CLK = 258 MHz / 21 = 12.2857 MHz ≈ 12.288 MHz

误差在 0.02% 以内,完全可用。我在项目中遇到过用 11.2896 MHz 的情况(针对 44.1 kHz 采样率),但 48 kHz 系更通用,所以这次就用 12.288 MHz。

小技巧: CubeMX 的 Clock Configuration 页面可以直接输入目标频率,它会自动帮你算分频系数。但建议手动验算一遍,防止工具选了不合理的参数。

配置完成后,I2S 外设的时钟源选 PLLI2S R,这样 MCLK 就能从 I2S_CKIN 引脚输出了。WM8960 的 MCLK 引脚接在 STM32 的 I2S_CKIN 上,频率就是 12.288 MHz。

3.2 I2C 外设配置:WM8960 寄存器控制

WM8960 的寄存器读写走 I2C 总线。它的 I2C 地址是 0x1A(7 位地址),数据格式是先发寄存器地址(7 位 + R/W),再发 9 位数据。嗯,这里有个坑——WM8960 的寄存器是 9 位数据,但 I2C 一次只能传 8 位,所以需要分两次发送。

CubeMX 里配置 I2C1:

  • Mode:I2C(标准模式或快速模式,我选 100 kHz 标准模式,稳定第一)
  • I2C Speed Mode:Standard Mode(100 kHz)
  • DMA:暂时不使能,寄存器配置量小,轮询就够了

引脚分配:PB6(SCL)、PB7(SDA)。记得外部上拉电阻,4.7 kΩ 就行。我曾经因为没加上拉,调试了一下午才发现波形全是乱的。

避坑指南: WM8960 的 I2C 时序要求 SCL 高电平期间 SDA 不能变化(除了起始和停止条件)。如果 STM32 的 I2C 时钟太快,可能触发 codec 的误动作。100 kHz 是安全选择。

3.3 I2S 外设配置:音频数据传输

I2S 是音频数据的通道。WM8960 支持 I2S、左对齐、右对齐、DSP 等多种格式。我习惯用标准 I2S 格式(飞利浦协议),数据位 16 位或 24 位。这次用 16 位,因为 48 kHz 16 位立体声已经能满足大部分场景。

CubeMX 里配置 I2S3(全双工模式):

  • Mode:Master Transmit / Master Receive(全双工)
  • Standard:I2S Phillips
  • Data Format:16-bit
  • Audio Frequency:48 kHz
  • Clock Polarity:Low(数据在上升沿采样)
  • MCLK Output:Enable(输出 12.288 MHz 给 WM8960)

引脚分配:PC10(I2S3_CK)、PC12(I2S3_SD)、PA4(I2S3_WS)、PC7(I2S3_MCK)。注意 MCK 引脚必须使能,否则 WM8960 没有主时钟。

关键点: I2S 的主从模式要跟 WM8960 匹配。STM32 做主(Master),WM8960 做从(Slave)。这样时钟由 STM32 提供,codec 被动接收。

3.4 DMA 配置:音频数据搬运

音频数据量大,用 CPU 一个一个搬不现实。DMA 就是干这个的。我配置两个 DMA 通道:一个用于 I2S3 发送(TX),一个用于接收(RX)。

CubeMX 里在 DMA Settings 中添加:

  • I2S3_TX:DMA2 Stream 1,通道 0,优先级 High,方向 Memory-to-Peripheral
  • I2S3_RX:DMA2 Stream 0,通道 0,优先级 High,方向 Peripheral-to-Memory

DMA 参数:

  • Mode:Circular(循环模式,音频流是连续的)
  • Data Width:Half Word(16 位)
  • Burst:Single
  • FIFO:Disable(直接模式,减少延迟)

为什么用 Circular 模式?因为音频数据是环形缓冲,DMA 自动循环搬运,CPU 只需要在缓冲区满或空时处理。我曾经用 Normal 模式,结果每次传输完都要手动重配,音频流卡顿得没法听。

经验之谈: DMA 优先级设 High 是为了防止其他中断抢占导致音频断流。但别设最高(Very High),否则可能影响系统实时性。

3.5 GPIO 配置:复位与使能引脚

WM8960 有两个控制引脚:复位(RESET)和使能(EN)。复位引脚低电平有效,使能引脚高电平有效。我习惯把它们接到 STM32 的普通 GPIO 上,方便软件控制上电时序。

配置:

  • RESET:PA0,推挽输出,默认高电平(不复位)
  • EN:PA1,推挽输出,默认低电平(不使能)

上电时序很重要:先拉低 RESET 至少 10 ms,然后释放,再拉高 EN 使能 codec。我见过有人把顺序搞反,结果 codec 初始化失败,I2C 通信一直 NACK。

// 伪代码示例
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); // 复位
HAL_Delay(10);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);   // 释放复位
HAL_Delay(1);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);   // 使能 codec
HAL_Delay(5);
注意: WM8960 的 EN 引脚内部有上拉,但外部最好再加一个 10 kΩ 下拉,防止上电瞬间浮空导致误使能。

3.6 整体配置一览

为了方便你对照检查,我把所有配置整理成表格:

外设 功能 关键参数 引脚
RCC 时钟源 HSE 8 MHz → PLL 168 MHz PH0/PH1
PLLI2S MCLK 生成 N=258, R=21 → 12.288 MHz —
I2C1 寄存器控制 100 kHz, 7-bit 地址 0x1A PB6/PB7
I2S3 音频数据传输 Master, 16-bit, 48 kHz, MCLK 使能 PC10/PC12/PA4/PC7
DMA2 Stream 0 I2S3 RX Circular, Half Word, High —
DMA2 Stream 1 I2S3 TX Circular, Half Word, High —
GPIO PA0 WM8960 RESET 推挽输出, 默认高 PA0
GPIO PA1 WM8960 EN 推挽输出, 默认低 PA1

3.7 知识体系结构图

下面这张 SVG 图把本章的配置逻辑串起来了。你可以看到时钟怎么分叉到 I2S 和 I2C,DMA 怎么接在 I2S 后面,GPIO 怎么控制 codec。

STM32CubeMX 配置结构图 STM32CubeMX 配置结构图 HSE 8 MHz 晶振 PLL → 168 MHz (系统时钟) PLLI2S → 12.288 MHz (MCLK) I2S3 (Master, 48 kHz) I2C1 (100 kHz, 0x1A) DMA2 (Circular, 16-bit) GPIO (RESET/EN) WM8960 低功耗音频 Codec 说明 MCLK 由 PLLI2S 生成 I2S 和 I2C 独立控制 DMA 负责数据搬运