基于正点原子阿波罗stm32F767IGT6+SD+FATFS+SAI+FMC+WM8978 的音乐播放器(stm32cubemx)(1)
本文记录了基于STM32F767IGT6开发板的音频播放系统实现过程。系统通过SDMMC接口读取SD卡中的WAV音频文件,使用FATFS文件系统进行管理,通过SAI接口将音频数据传输至WM8978解码芯片输出。文章重点分析了硬件配置,包括SD卡的单线/四线模式选择、时钟分频设置,以及SAI接口的异步主模式配置方案。作者指出原开发板代码存在耦合度高、移植困难等问题,并分享了去耦合的优化思路。整体框架
概要
前言:这篇文章主要记录自己学习的过程
最近拿到一块STM32F767IGT6正点原子板子,叫阿波罗开发板,在家无事想着播放一下David的音乐
,自己玩一下,正点实现的挺简单的,效果还行。直到我上手之后我才觉得和吃了屎一样,感觉代码风格比较落后,而且代码耦合性太高了,很难移植到其他芯片的开发板上。最开始去b站搜发现阿波罗F7没讲这一节,后面去他M4视频里面听了一点关于I2S的原理,但是也讲的很差,但是看了一下最新版的视频讲的还挺不错的,什么时候把这清朝的老视频更新一下,让人学的真的是无语。
而且,它把内存分配,mcu屏幕打印,wav解码啥的,全放一起,我单单把代码移植跑通还是容易,但是这个耦合不去,我感觉我学不到东西,就自己开始一步一步去耦合的过程了。
还有就是学F7最难的不是一些代码的实现,而是一些底层的原理,我这里不过多的赘述原理,只求大家看了这篇文章能知道每个东西是干嘛的,就算我这篇博客的成功了。
大体框架
整个系统的框架如下:(注意:SDMMC虽然画在STM32外面,但是它是stm32外设,只是这样方便理解)
我大概概述一下:
SD卡里面存放了我们的音乐文件,与F7的SDMMC外设进行相连,连接在SD卡槽上,为了方便我们操作我们的音频文件,我们把SD卡挂载在FATFS上,这样就相当于用stm32操作以前我们学的C语言里面的文件操作,就可以让我们的操作变的很简单。然后就是我们通过FATFS读取到wav文件之后,将读到的buffer通过DMA双缓冲传输到SAI接口,WM8978意识到SAI有数据来了,它就会将音频数据输出出来,我们的I2C自然就是对WM8978的一些状态进行设置,比如说调节喇叭和耳机的音量啊,配置输出输入模式啊等等,本文只讲WM8978的输出。
硬件配合STM32CUBEMX进行说明
1. SD卡
驱动方式:
SD卡有两种驱动方式:
- SDIO接口:CLK/CMD/DAT0~3 (本文使用)
- SPI :CS/CLK/MOSI/MISO(之后有机会再使用)
了解过SPI的就知道,SPI分为软件和硬件两种方式,F7H7系列还有功能更加强大的QSPI,我们这里先不概述了。
原理图如下:
这里可以看到SD卡的接口是SDIO而不是SDMMC,这只是写法上的区别,SDMMC可以理解成SDIO的升级版,速度先不提,SDMMC不仅支持SD卡,还支持emmc。本文没有使用SDMMC的DMA,采用直接传输的方式。
SDMMC在stm32cubemx里支持5种模式:
| 选项 | 卡类型 | 数据线 | 常见用途 |
|---|---|---|---|
| Disable | 无 | 无 | 不用 SDMMC |
| SD 1 bit | SD 卡 | DAT0 | 兼容性最好、最慢 |
| SD 4 bits Wide bus | SD 卡 | DAT0–DAT3 | 最常用,速度快 |
| MMC 1 bit | MMC / eMMC | DAT0 | 初始化或低速 |
| MMC 4 bits Wide bus | MMC / eMMC | DAT0–DAT3 | 中速 |
| MMC 8 bits Wide bus | MMC / eMMC | DAT0–DAT7 | eMMC 专用,高速 |
我们这里先试用SD 1 bit模式,四位宽总线模式,以及配合DMA我们到后面有机会再说,我旨在先用最简单的方式驱动。注意:SD卡最开始初始化的时候我们最好用1bit模式,直接用4bit模式可能会出问题,因为上电状态下多线可能不同步,容易通信失败,也是CUBEMX没考虑周到的一个点。
SD卡参数配置:
| 参数项 | 当前设置 | 作用 | 配置原因 |
|---|---|---|---|
| Clock transition on which the bit capture is made | Rising transition | 在时钟上升沿采样数据 | SD 协议规定 |
| SDMMC Clock divider bypass | Disable | 不绕过分频器 | 需要精确控制 SD 时钟 |
| SDMMC Clock output enable when the bus is idle | Disable power save | 空闲时不关闭时钟 | 稳定性优先 |
| SDMMC hardware flow control | Disable | 关闭硬件流控 | SD 卡通常不需要 |
| SDMMCCLK clock divide factor | 0 | SDMMC 时钟不再分频 | 进入高速模式 |
第五个参数比较重要 它是SD卡时钟配置的一个依据,它就是下面文字中的CLKDIV,手册也有说明。
- SDMMC_CK=SDMMCCLK/(2+CLKDIV)

其中的SDMMC_CK就是我们SD卡的CLK线,SDMMCCLK就是SDMMC 外设的输入时钟。在STM32CUBEMX中如下图所示。
我们设置的CLKDIV=0,所以我们最后得出来SD卡SCL的时钟频率是48/(2+0)Mhz=24Mhz。
2.WM8978(SAI)
WM8978原理图如下:
从原理图能清楚的看到SAI接口和IIC接口。
SAI:
SAI(Serial Audio Interface)可以把它理解为 “升级版、超集的 I2S”,是 STM32 在中高端型号里提供的一套更灵活、更强的音频串行接口。那什么是I2S呢,I2S 是 Philips 提出的标准音频总线协议。你可以把他理解为SPI或者I2C一样的通信协议,只不过I2S更多应用于音频处理而已。SAI更底层的原理,我就不概述了,我只说一下我的参数为什么这么配置,如果想更深入了解这个协议,可以看下面的这篇文章,讲的很好。
https://www.taterli.com/3120
SAI配置如下图所示:

 | A-law/u-law 不用(压缩编码) |
| SD Line Output Mode | Driven | 主机必须驱动数据线 | 否则总线悬空 (TRI-state包括高组态) |
| --------------- | ---------------------------- | ------------------------------------- | --------------- |
| First Bit | MSB First | 音频标准 (I2S协议) | WM8978 只认 MSB |
| Frame Sync Definition | Channel Identification | LRCLK=0/1 区分左右声道 | WM8978 默认就是这种 |
| Frame Sync Polarity | Active Low | 符合 I2S 标准 | 大多数 Codec 默认 |
| Frame Sync Offset | Before First Bit | I2S 标准时序 | 数据在 LRCLK 翻转后 1bit 输出 |
| Frame Sync Active Level Length | 32 | FS 占一个 slot 长度 | 一个声道 32bit |
| ------------------------------ | ---------------------- | ---------------- | --------------------- |
| First Bit Offset | 0 | 数据紧贴 slot 起始 | 不会移位 |
| Number of Slots | 2 | 左 + 右 | 标准立体声 |
| Slot 0 Active | ✔ | 左声道 | |
| Slot 1 Active | ✔ | 右声道 | |
| Slot Active Final Value | 0x00000003 | bit0+bit1 | CubeMX 自动生成 |
| -------------------- | ------------ | ---------------- | ----------- |
| Master Clock Divider | Enabled | 给 WM8978 提供 MCLK | 很多 IC 必须 |
| Audio Frequency | 44.1 kHz | 和 WAV 完全一致 | 设错会变调 |
| Clock Strobing | Falling Edge | 符合 I2S 采样习惯 | 边沿错会噪声 |
| Real Audio Frequency | 41.868 kHz(按道理越解决44.1khz越好) | PLL 精度限制(但是这样设置声音也还是会变调,cubmx配置的参数有5%的误差,后面需要我们手动调整) | 属于不可接受范围 |
| -------------------- | ------------ | ---------------- | ----------- |
| FIFO Threshold | One Quarter Full | DMA 触发及时 | DMA负载适中 |
| Output Drive | Enabled | 推动外部 Codec | 信号完整性更好,提高PCB驱动能力 |
*Frame Length与Slot Size的关系(图片有点点误差别介意):
WAV文件的一般信息:
开启SAI1_A的DMA,采用DMA双缓冲的方式,减少CPU的负担,cubemx配置如下(wav文件是16bit,所以这里DMA选择Half word):
I2C:
WM8978的I2C我们直接用软件I2C,这里只要简单移植一下驱动就好了,很简单,后面讲代码的时候讲一下。
3.FATFS


SD卡挂载FATFS的路径,我的SD卡的文件结构如下:
MUSIC文件夹下有很多子文件夹,他们分别是陶喆对应发的专辑名称,每个子文件夹里面都是wav文件,本来里面全是中文命名的M4A文件,我用python转了一下,把文件夹以及wav文件名都翻译成了英文,把M4a转成了wav文件,方便我们解码使用。


4.FMC:MCU+LCD配置
我们这里利用FMC模拟8080时序来配置LCD,LCD驱动依旧用的正点原子的。
注意!!!:F7和H7等高系列M7核,都带有MPU内存保护,配置完FMC之后我们还需要配置MPU!,低系列不需要
原理图如下:

这里是什么东西呢,请打开我们STM32F7的中文手册对应的FMC的那节。
其实这每个选项都对应了我们FMC的存储区域,我们是用SRAM(LCD Interface),所以我们需要用到前四个选项,我们选择存储区域1就好了(Bank1),记住这个Bank1的首地址,我们后面会用(0x6000000)。
F7将FMC分成6个Bank区,前四个bank是NOR Flash/PSRAM/SRAM/ROM,后两个Bank是与SDRAM相关的。
其中每个Bank又分成四个小bank,对应的就是我们的NE1-NE4,我们选择NE1。
我们这里选择的是LCD Interface,实际上底层代码也是在把这个选项当SRAM使用,但是可能会有人问为什么不直接选SRAM,其实这是cubemx想好了的,它觉得大部分人都会用LCD屏幕,所以从SRAM里面分出一个选项作为LCD Interface,这样也不用开启FMC_AX(0-17)地址线的使用,因为我们只使用A18这个地址线,使能SRAM的话,会开启FMC_A0到FMC_A17的GPIO,当然我们之间选择SRAM也是没问题的,时序以及功能配置都与LCD Interface一样,配置如下图所示:
FMC的配置我们按照原理图上的网络标签去配置就行了。
- FMC_NE1对应NE1
- FMC_A18对应A18
- D0-D15对应16bits
- 注意:配置完FMC之后,它给的GPIO是CUBEMX默认的,我对应F7正点原子的开发板是NE1需要修改,其他的不用修改,你们配置的时候注意修改就好了。


这里的一些时序的设置我就不赘述了,网上有很多人讲了如何看手册配置,具体可以参照这位博主的文章,已经很详细了:
https://blog.51cto.com/u_13640625/3021879
5.定时器与串口设置
其中定时器我采用的一个基本定时器TIM6,用于按键状态机消抖,至于我们怎么设置它的预分频器值和自动重装载值,我们看看手册上描述TIM6挂载在哪条总线上。



一般按键消抖时间为10ms,我们TIM6总线频率为108Mhz,所以108000000hz/108/10000=100hz,推出时间是10ms。
串口设置也没什么多说的,注意F767板子上的引脚是那两个就行,很有可能不是cubemx默认的引脚。

6.代码:
代码太多了,不好讲解,大家自己看吧,我主要讲解一些配置的原理。
https://gitee.com/zhhdfadfadsdsf/stm32-f767-igt6–music-player
更多推荐


所有评论(0)