2. 开发环境搭建:STM32CubeMX配置I2C与I2S外设

好,咱们正式开始动手。这一章我会带你走一遍STM32CubeMX的配置流程。说白了,就是先把硬件外设的“骨架”搭好,后面写驱动才能跑得起来。

我个人习惯,拿到一个新项目,第一件事不是写代码,而是先把时钟树和外设引脚理清楚。你想想看,如果时钟配错了,后面调I2S音频采样率,调死你都调不对。我在项目中就吃过这个亏,当时折腾了一整天,最后发现是PLL配置多了一个分频系数……嗯,从那以后我再也不敢跳过时钟树检查了。

2.1 新建工程与芯片选择

打开STM32CubeMX,点击“New Project”。在Board Selector里,我建议你直接按芯片型号搜索。咱们课程用的是STM32F407VET6,当然你用F4系列其他型号也行,只要带I2S外设就好。

  • Series: STM32F4
  • Lines: STM32F407
  • Package: LQFP100(或者你手头开发板的封装)
  • MCU: STM32F407VET6

选好后双击,工程就创建了。别急着点“Generate Code”,咱们先把外设配好。

小提示:如果你用的是STM32F4其他型号,比如STM32F407ZGT6,引脚数量不同,但I2C和I2S外设的配置逻辑完全一样。只需要根据数据手册调整一下引脚号就行。

2.2 I2C外设配置(控制通道)

WM8960有两个接口:控制接口用I2C,音频数据接口用I2S。先配I2C。

在Pinout & Configuration界面,找到Connectivity → I2C1。为什么选I2C1?因为我习惯把I2C1留给音频编解码器,I2C2留给其他传感器。当然你也可以用I2C2或I2C3,只要后面驱动里改一下句柄就行。

I2C1的模式选I2C,不要选SMBus。参数配置如下:

参数项 配置值 说明
I2C Speed Mode Standard Mode (100kHz) WM8960支持100kHz和400kHz,100kHz更稳定
I2C Clock Speed 100000 Hz 对应100kHz
I2C Peripheral Enabled 使能I2C外设
I2C Address 7-bit WM8960默认地址0x1A(7位)
注意:WM8960的I2C地址是7位0x1A,但HAL库读写函数需要左移一位后的8位地址。也就是说,写地址是0x34,读地址是0x35。这个坑我踩过,当时读寄存器一直返回0xFF,查了半天才发现是地址没移位。

引脚分配会自动生成。默认PB6是SCL,PB7是SDA。如果你的板子有硬件上拉电阻,那就不用额外加;如果没有,记得在PCB上加上4.7kΩ上拉电阻到3.3V。

2.3 I2S外设配置(音频数据通道)

接下来配I2S。WM8960支持I2S、左对齐、右对齐等多种格式。咱们用最通用的I2S标准格式,16位数据,立体声。

在Pinout & Configuration里,找到Connectivity → SPI2。为什么是SPI2?因为STM32F4的I2S功能是复用SPI引脚的。SPI2对应的I2S2就是咱们要用的。

模式选择I2S2,然后配置参数:

参数项 配置值 说明
Mode Master Transmit STM32做主设备,发送音频数据
Standard I2S Standard (Philips) 标准I2S格式
Data Format 16-bit 16位数据,WM8960也支持24位
Audio Frequency 48 kHz CD音质采样率
Clock Polarity Low CK信号低电平空闲
Master Clock Output Disabled MCLK由外部晶振提供

这里有个关键点:Audio Frequency选48kHz后,CubeMX会自动计算分频系数。但有时候它算出来的值不对,尤其是当你改了系统时钟之后。我建议你先用默认值,等时钟树配完再回来看这个参数。

I2S2的引脚分配:

  • CK (SCK): PB13
  • SD (MOSI): PB15
  • WS (NSS): PB12
  • MCK: PC6(如果使能了Master Clock Output)
核心要点:I2S的CK和WS信号由STM32产生,WM8960作为从设备接收。SD线是单向的,从STM32发到WM8960。如果你需要录音(双向通信),模式要选Master Transmit/Receive。

2.4 时钟树配置

好,重头戏来了。时钟树配不好,I2S采样率全是错的。咱们的目标是让I2S外设得到精确的时钟源,这样才能生成48kHz的采样率。

切换到Clock Configuration标签页。我习惯的配置路径:

  1. HSE:8MHz外部晶振(根据你板子实际晶振频率设置)
  2. PLL:HSE作为PLL输入,PLLM=8,PLLN=336,PLLP=2,得到168MHz主频
  3. SYSCLK:168MHz
  4. AHB Prescaler:/1,HCLK=168MHz
  5. APB1 Prescaler:/4,APB1=42MHz(I2S2挂在APB1上)
  6. APB2 Prescaler:/2,APB2=84MHz

为什么要这么配?因为I2S2的时钟源来自APB1(42MHz),而42MHz经过I2S分频器后,正好能精确得到48kHz的采样率。具体分频公式:

I2S采样率 = I2S时钟源 / (分频系数 * 8 * 2)
48kHz = 42MHz / (54.6875 * 8 * 2)
// 实际分频系数取整后为55,得到47.7kHz,误差约0.6%
// 这个误差人耳基本听不出来

如果你用其他主频,比如180MHz,APB1可能是45MHz,分频后误差会更大。所以我个人建议,做音频项目就用168MHz这个经典配置,省心。

经验之谈:配完时钟树后,回到I2S配置页面,确认一下Audio Frequency显示的是48kHz。如果显示47.7kHz或48.2kHz,说明分频系数有舍入误差,问题不大。但如果显示24kHz或96kHz,那肯定是时钟树配错了。

2.5 生成工程与代码结构

配完所有外设,点Project → Generate Code。生成前检查一下:

  • Project Name: wm8960_audio_driver
  • Project Location: 你自己选
  • Toolchain / IDE: MDK-ARM V5(或者你用的IDE)
  • Generate peripheral initialization as a pair of .c/.h files: 勾选

生成后,打开工程,你会看到HAL库自动生成了以下文件:

文件 内容
main.c 主函数,外设初始化调用
i2c.c / i2c.h I2C1初始化函数MX_I2C1_Init()
spi.c / spi.h I2S2初始化函数MX_I2S2_Init()
stm32f4xx_hal_msp.c 引脚GPIO配置、时钟使能、DMA配置(如果用了)

打开main.c,你会看到类似这样的初始化顺序:

int main(void)
{
  HAL_Init();
  SystemClock_Config();    // 时钟树配置
  MX_GPIO_Init();          // GPIO初始化
  MX_I2C1_Init();          // I2C初始化
  MX_I2S2_Init();          // I2S初始化

  // 接下来就是咱们的WM8960驱动初始化
  while (1)
  {
    // 主循环
  }
}

嗯,到这里,硬件外设的“骨架”就搭好了。I2C负责控制,I2S负责传音频数据,时钟树保证采样率准确。下一章咱们就开始写WM8960的寄存器读写函数,让这个骨架真正动起来。

哦对了,差点忘了。生成工程后,记得检查一下GPIO引脚配置。有时候CubeMX会把I2C的PB6、PB7配置成开漏输出,但忘了使能内部上拉。如果你板子上没有外部上拉电阻,记得在MX_GPIO_Init()里加上:

GPIO_InitStruct.Pull = GPIO_PULLUP;

这个细节,我曾经在调试I2C通信时卡了半小时,最后发现是上拉没配。你注意一下就好。

开发环境搭建核心逻辑 STM32CubeMX 配置入口 I2C 控制通道 PB6(SCL) PB7(SDA) 100kHz I2S 音频数据通道 PB13(CK) PB15(SD) PB12(WS) 时钟树配置 HSE 8MHz → 168MHz Generate Code → HAL库工程 main.c / i2c.c / spi.c stm32f4xx_hal_msp.c 系统时钟配置函数 I2C控制WM8960寄存器 → I2S传输音频数据 → 时钟树保证采样率精确