2. STM32 I2S外设配置:时钟树、主从模式与协议差异
好,咱们进入第二章节。上一章我们把整个音频链路捋了一遍,这一章要动真格的了——配置STM32的I2S外设。
说实话,I2S这个外设看着简单,但坑不少。我最早做音频项目时,以为配个I2S就是点几个选项的事,结果出来的声音全是噪声。后来才发现,时钟树没搞对,主从模式选反了。嗯,咱们今天就把这些坑一个个填平。
2.1 I2S时钟树分析——音频的“心跳”
I2S通信的核心是什么?是时钟。没有精准的时钟,音频数据就是一堆乱码。STM32的I2S时钟源有好几个选项,选错了,采样率就对不上。
核心结论:I2S的位时钟(BCLK)和帧时钟(LRCK)都由主时钟(I2S_CK)分频得到。主时钟的来源可以是PLLI2S、系统时钟(SYSCLK)或外部时钟(I2S_CKIN)。
我个人习惯用PLLI2S。为什么?因为它可以独立于系统主频,专门为音频生成精准的时钟。你想想看,系统主频可能跑168MHz,但音频需要的是11.2896MHz(44.1kHz的256倍)或12.288MHz(48kHz的256倍),直接用系统时钟分频很难分得准。
下面这张图是我画的I2S时钟树结构,你看一眼就明白了:
从图上你能看到,I2S_CK经过分频器后,产生三个时钟:BCLK(位时钟)、LRCK(帧时钟)和可选的MCK(主时钟)。BCLK的频率 = 采样率 × 位深 × 通道数 × 2。举个例子,44.1kHz、16位、立体声:BCLK = 44100 × 16 × 2 = 1.4112MHz。
我的经验:如果你用WM8960,建议开启MCK输出。WM8960内部需要MCK来驱动其内部时钟树,没有MCK的话,有些模式会工作不正常。我曾经因为这个折腾了两天,最后发现是MCK没开。
2.2 主从模式选择——谁说了算?
I2S通信中,必须有一个设备产生时钟(主设备),另一个设备接收时钟(从设备)。这个选择直接影响你的系统设计。
| 模式 | 时钟产生者 | 适用场景 | 注意事项 |
|---|---|---|---|
| 主模式 | STM32产生BCLK和LRCK | STM32作为音频数据源(播放) | 时钟精度取决于PLL配置 |
| 从模式 | 外部设备(如CODEC)产生时钟 | STM32作为音频数据接收端(录音) | 时钟抖动取决于外部设备 |
在我们的课程场景中——用STM32播放音频给WM8960——STM32应该配置为主模式。WM8960作为从设备,接收STM32产生的BCLK和LRCK。
注意:主从模式不能搞反。如果你把STM32配成从模式,而WM8960又没有产生时钟,那I2S接口就死在那了。我见过有人把主从接反了,调试了三天没找到原因,最后发现是CubeMX里点错了一个选项。
还有一个细节:主模式下,STM32的I2S引脚配置也有讲究。BCLK和LRCK必须配置为输出模式,而SD(数据线)根据方向配置为输入或输出。在CubeMX中,这些引脚会自动配置,但你要确认一下。
2.3 标准模式与飞利浦模式——就差一个时钟沿
这两个模式的区别,说白了就是数据在时钟的哪个沿采样。很多初学者搞混,其实没那么复杂。
| 特性 | 飞利浦模式(I2S Philips) | 标准模式(I2S Standard/MSB Justified) |
|---|---|---|
| 数据与LRCK对齐 | 数据在LRCK变化后的第二个BCLK沿开始 | 数据在LRCK变化后的第一个BCLK沿开始 |
| 左声道数据 | LRCK为低时传输 | LRCK为高时传输 |
| 右声道数据 | LRCK为高时传输 | LRCK为低时传输 |
| 兼容性 | 最广泛,大多数CODEC支持 | 部分老设备使用 |
你可能会问:到底选哪个?我直接告诉你——选飞利浦模式。WM8960的数据手册里明确写着支持飞利浦模式,而且这是业界最通用的标准。标准模式(也叫左对齐模式)用得少,除非你遇到老古董设备。
关键点:飞利浦模式下,数据在LRCK切换后的第二个BCLK上升沿开始传输。这意味着第一个BCLK周期是“空闲”的。标准模式下,数据在第一个BCLK沿就开始,少了一个周期的延迟。
嗯,这里要注意:虽然只差一个时钟沿,但如果STM32和WM8960配置不一致,出来的声音就是全频段的噪声。我遇到过这种情况,当时用逻辑分析仪一看,发现数据偏移了一个位,改过来就好了。
2.4 STM32CubeMX配置步骤——手把手来一遍
理论说完了,咱们实际操作。打开STM32CubeMX,按下面的步骤来:
- 选择芯片:以STM32F407为例,打开Pinout & Configuration标签页。
- 配置I2S外设:在左侧列表找到Connectivity → SPIx(注意,I2S和SPI共用外设,选SPI后切换到I2S模式)。
- 设置模式:Mode选择“Master Transmit”(主模式发送),因为我们只播放音频。
- 配置参数:在Configuration面板中设置:
- Standard:I2S Philips
- Data Format:16-bit(根据你的音频数据位深)
- Audio Frequency:44.1 kHz(或48 kHz)
- Clock Polarity:Low(BCLK空闲时为低电平)
- Master Clock Output:Enable(开启MCK输出给WM8960)
- 时钟配置:切换到Clock Configuration标签页,确保PLLI2S的时钟源和分频系数正确。对于44.1kHz,PLLI2S需要输出11.2896MHz的倍数。
- DMA配置:添加DMA通道,选择I2S的TX请求,DMA模式选择Circular(循环模式),数据宽度Half Word(16位)。
- 生成代码:点击Project → Generate Code。
我的习惯:生成代码后,我会手动检查一下HAL_I2S_Init()函数中的参数。CubeMX有时候会生成一些奇怪的默认值,比如把AudioFrequency设成0。别问我怎么知道的。
下面是一个典型的初始化代码片段,你可以参考:
// I2S句柄定义
I2S_HandleTypeDef hi2s3;
// I2S初始化函数
void MX_I2S3_Init(void)
{
hi2s3.Instance = SPI3;
hi2s3.Init.Mode = I2S_MODE_MASTER_TX;
hi2s3.Init.Standard = I2S_STANDARD_PHILIPS;
hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;
hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;
hi2s3.Init.AudioFreq = I2S_AUDIOFREQ_44K;
hi2s3.Init.CPOL = I2S_CPOL_LOW;
hi2s3.Init.ClockSource = I2S_CLOCK_PLL;
hi2s3.Init.FirstBit = I2S_FIRSTBIT_MSB;
if (HAL_I2S_Init(&hi2s3) != HAL_OK)
{
Error_Handler();
}
}
避坑指南:我曾经在配置I2S时,把DataFormat设成了24位,但音频数据是16位的。结果WM8960接收到的数据全是错的,声音像卡带一样。记住:DataFormat必须和你的音频数据位深一致。
配置完成后,I2S外设就准备好了。下一件事就是让DMA把数据喂给I2S,然后I2S把数据送到WM8960。不过那是后面章节的内容了。
好,这一章就到这里。时钟树、主从模式、协议差异、CubeMX配置,这四个点你搞清楚了,I2S配置这块就稳了。