发 帖  
原厂入驻New
「ALIENTEK 阿波罗 STM32F767 开发板资料连载」第五十四章 SPDIF(光纤音频)实验
4 天前  148 STM32
分享
1)实验平台:alientek 阿波罗 STM32F767 开发板
2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子




第五十四章 SPDIF(光纤音频)实验
前面,我们介绍了 STM32F7 的 SAI 接口,实现了音乐播放器、录音机等功能,本章,我
们将介绍 STM32F7 的 SPDIF 接口,结合 SAI 接口和 wm8978 解码器,实现对光纤音频信号的
解码。本章分为如下几个部:
54.1 SPDIF 简介
54.2 硬件设计
54.3 软件设计
54.4 下载验证
54.1 SPDIF 简介
SPDIF 是 Sony/Philip Digital InteRFace Format 的缩写,是由索尼和飞利浦公司联合开发的数
字音频接口简称,分为 SPDIF 输入(IN)和 SPDIF 输出(OUT)两种,STM32F7 的 SPDIF 接
口,仅支持 SPDIF IN,称之为 SPDIF RX。
STM32F7 的 SPDIF 接口的主要特点有:
 提供多达 4 路输入
 自动符号率检测
 最大符号率:12.288 MHz
 支持 8 kHz 到 192 kHz 的立体声
 支持 IEC-60958 和 IEC-61937 音频标准,消费类应用
 奇偶校验位管理
 使用 DMA 通信进行音频采样
 支持控制和用户信息 DMA 传输
STM32F7 的 SPDIF RX 接口支持符合 IEC-60958 和 EC-61937 标准的 SPDIF 数据流。支持
采样率的简单立体声以及压缩的多通道环绕声(Dolby 或 DTS 音频)。SPDIF RX 接口框图
如图 54.1.1 所示:



图 54.1.1 SPDIF RX 接口框图
图中 SPDIFRX_DC 模块负责解码从 SPDIFRX_IN[4:1] 输入接收的 SPDIF 数据流。该模块
重新采样传入的信号、解码曼彻斯特数据流、并识别帧、子帧和块元素。它传送到 REG_IF 部
分、解码数据和相关的状态标志。关于 SPDIFRX_DC 模块的详细介绍,请参考《STM32F7 中
文参考手册》34.3.2 节。
SPDIF RX 接口通过 APB1 总线完全控制并且能够处理两个 DMA 通道:
1,专用于传输音频采样的 DMA 通道
2,专用于传输 IEC60958 通道状态和用户信息的 DMA 通道
此外,还提供了中断服务,既可用作 DMA 的复用功能,也可用来指示外设的错误或关键
状态。接下来,我们简单介绍一下 SPDIF 协议。
1)SPDIF 块
一个 SPDIF 块由 192 个 SPDIF 帧组成,如图 54.1.2 所示:



图 54.1.2 SPDIF 块格式
每个 SPDIF 帧又由 2 个子帧组成,如图 54.1.3 所示:



图 54.1.3 SPDIF 子帧格式
每个子帧包含 32 个位,他们的组成如下:
1,位 0 到 3 包含同步报头之一(B/M/W);
2,位 4 到 27 包含以线性 2 的补码表示的音频采样字。最高有效位(MSB)为位 27;
3,使用 20 位编码范围时,位 8 到 27 包含音频采样字,其中位 8 为 LSB。
4,位 28(有效性位“V”)表示数据是否有效(例如转换为模拟数据)。
5,位 29(用户数据位“U”)包含用户数据信息,如光盘的音轨编号。
6,位 30(通道状态位“C”)包含通道状态信息,如采样率和复制保护。
7,位 31(奇偶检验位“P”)包含奇偶校验位,位 4 到 31 将包含偶数个 1 和 0(偶校验)。
对于线性编码音频应用,第一个子帧(立体声操作中的左声道或“A”通道以及单声道操
作中的主通道)通常以报头“M”开始。但是,报头每 192 帧切换为报头“B”一次,以识别
用于组织通道状态和用户信息的块结构的开始。第二个子帧(立体声操作中的右声道或“B”
通道以及单声道操作中的辅助通道)始终以报头“W”开始。
2)同步报头
SPDIF 协议规定,总共有三种同步报头:B、M、W(也可以称为 Z、X、Y)。同步报头
总是以和前半个位相反的电平开始的。使能第一个帧的第一个“B”报头的传输前,此前半位
值为线路的电平。对于其它报头,此前半位值为之前子帧的奇偶校验位的第二个半位。
SPDIF 三种同步报头的编码方式如图 54.1.4 所示:


图 54.1.4 SPDIF 同步报头
3)位编码
为最大程度减小传输线路上的直流分量值,并加快时钟从数据流中恢复的速度,从第 4 位
开始,到第 31 位,全部采用双相符号编码。
双向符号编码原理:要传输的各个位通过由两个连续二进制状态构成的符号表示。符号的
第一个状态始终不同于前一个符号的第二个状态。如果要传输的位为逻辑 0,则符号的第二个
状态与第一个状态相同。但如果该位为逻辑 1,则两个状态不同。在 IEC-60958 规范中,这些
状态称为“UI”(单位间隔)。
SPDIF 的位编码原理如图 54.1.5 所示:



图 54.1.5 通道编码示例
图中比特流实际上就是通道解码时钟,每 2 个时钟解码一个位,在两个时钟内,通道编码
状态有变化的(10/01),解码为逻辑 1;在两个解码时钟内,通道编码状态没有变化的(00/11),
解码为逻辑 0;
结合通道位编码原理,以及图 54.1.4 所示的同步报头编码方式,我们可以看出,前面 4 个
位是不能用这个位编码原理来编码的(因为有 3 个 UI 的状态!!)。
SPDIF 协议我们就介绍到这里,接下来,我们看看 STM32F7 SPDIFRX 的状态流程。如图
54.1.6 所示:


图 54.1.6 STM32F7 SPDIFRX 状态流程
由图可知,STM32F7 的 SPDIFRX 接口总共有四种状态:
1,STATE_IDLE
该状态下,禁止 SPDIF 外设,SPDIFRX_CLK 域复位,PCLK1 域功能正常。
2,STATE_SYNC
该状态下,SPDIF 将与数据流同步(同步过程请参考《STM32F7 中文参考手册》34.3.4
节),阈值定期更新,可通过中断或 DMA 读取用户和通道状态。
3,STATE_RCV:
该状态下,SPDIF 将与数据流同步,阈值定期更新,可通过中断或 DMA 通道读取用
户、通道状态和音频采样。当检测到“B”报头后,开始保存音频数据。
4,STOP_STATE:
该状态下,SPDIF 将不再同步,用户、通道状态和音频数据的接收都将停止
我们可以通过 SPDIFRX_CR 寄存器的 SPDIFRXEN 字段,控制 SPDIFRX 的状态。
当 SPDIFRX 处于 STATE_IDLE 时:
通过将 SPDIFRXEN 设置为 01 或 11,将切换到 STATE_SYNC 状态。
当 SPDIFRX 处于 STATE_SYNC 时:
如果同步失败或者接收的数据未正确解码且无法在不进行再同步的情况下恢复(FERR
或 SERR 或 TERR=1),则 SPDIFRX 将进入 STATE_STOP 状态并且等待软件应答。
当同步阶段完成时,如果 SPDIFRXEN= 01,将保持该状态。
将 SPDIFRXEN 设置为 0,SPDIFRX 将立刻返回至 STATE_IDLE 状态。
当 SPDIFRXEN=11,且 SYNCD=1,则 SPDIFRX 进入 STATE_RCV 状态。
当 SPDIFRX 处于 STATE_RCV 时:
如果接收的数据未正确解码且无法在不进行再同步的情况下恢复(FERR 或 SERR 或
TERR=1),则 SPDIFRX 将进入 STATE_STOP 状态并且等待软件应答。
将 SPDIFRXEN 设置为 0,SPDIFRX 将立刻返回至 STATE_IDLE 状态。
在此状态下,如果数据接收正确,我们将其通过解码器播放出来,就能实现解码了。
当 SPDIFRX 处于 STATE_STOP 时:
SPDIFRX 停止接收和同步,并等待软件将 SPDIFRXEN 位设置为 0,以清零错误标志。
当 SPDIFRXEN 设置为 0 时,IP 被禁止,这意味着所有状态机被复位,并且 RX_BUF
被刷新。还要注意,标志 FERR、SERR 和 TERR 也会复位。
数据接收
SPDIFRX 为音频采样接收提供了两个 32 位双缓冲区:RX_BUF 和 SPDIFRX_DR。如果
SPDIFRX_DR 为空,则包含在 RX_BUF 中的有效数据将立刻传输至 SPDIFRX_DR。
SPDIFRX可以使用DMA或中断将音频采样传输到存储器中,我们一般使用DMA来传输,
这样效率比较高。
SPDIFRX 提供多种处理接收数据的方法,用户可以分别处理控制信息流和音频采样流,或
将二者一起处理。对于各子帧,数据接收寄存器 SPDIFRX_DR 包含 24 个数据位和可选的 V、
U、C、PE 状态位以及 PT,这些可选的位,通过 SPDIFRX_CR 寄存器的 VMSK、CUMSK、
PMSK 和 PTMSK 等位控制,我们将在寄存器介绍的时候,给大家详细讲解。
PE 位表示奇偶校验错误位,该位将在解码的子帧中检测到奇偶校验错误时置 1;PT 字段
包含报头类型(B、M 或 W);V、U 和 C 则是从 SPDIF 接口接收的值的直接副本。
通过 SPDIFRX_CR 寄存器的 DRFMT 位,我们可以选择三种音频格式,如图 54.1.7 所示:


图 54.1.7 SPDIFRX_DR 寄存器格式
将 DRFMT 设置为 00 或 01,可以使数据在 SPDIFRX_DR 寄存器中右对齐或左对齐,此时
我们可以根据软件所需的处理方式启用状态信息(V/C/U/PE/PT 等)或将其强制设置为零。
将 DRFMT 设置为 10 得出的格式在非线性模式下相关,因为每个子帧仅使用 16 位。使用
此格式,两个连续子帧的数据存储到 SPDIFRX_DR 中,存储器占用量将减半。
本章我们将 DRFMT 设置为 00,使用右对齐格式,且可以支持 24 位音频数据传输。
时钟策略
SPDIFRX 块需要两个不同的时钟:
1,APB1 时钟 (PCLK1),用于寄存器接口,其频率必须至少大于符号率;
2,SPDIFRX_CLK,主要由 SPDIFRX_DC 部分使用;
为正确解码传入的 SPDIF 数据流,SPDIFRX_DC 应以至少比最大符号率(符号率=采样率
*32*2)高 11 倍或者比音频采样率高 704 倍的时钟重新采样接收的数据。
例如,如果用户预期接收到最高 12.288 MHz 的符号率(对应音频采样率为 192Khz),则
采样率至少为 135.2MHz。
接下来,我们看看本章需要用到的一些相关寄存器。
首先,是 SPDIFRX 控制寄存器:SPDIFRX_CR,该寄存器各位描述如图 54.1.8 所示:



图 54.1.8 SPDIFRX_CR 寄存器各位描述
该寄存器我们只介绍本章需要用到的一些位(下同):
INSEL:这三个位,用于选择 SPDIFRX 的输入通道,STM32F7 总共有 4 个输入通道(0~3),
我们硬件上连接在通道 1 上面,所以设置 INSEL=001 即可。
NBTR:这两个位用于设置在同步阶段允许的最大重试次数:00,表示不允许重试;01,
表示允许重试 3 次;10,表示允许重试 15 次;11,表示允许重试 63 次。本章我们设置为 10,
允许重试 15 次。
CHSEL:用于选择通道状态获取路径。我们设置为 0,选择从通道 A 获取通道状态。
CBDMAEN:用于设置通道状态和用户数据是否使能 DMA 接收。我们设置为 0,禁止 DMA
方式接收通道状态和用户数据。
PTMSK:用于设置报头类型屏蔽位。我们设置为 1,禁止将报头数据写入 SPDIFRX_DR。
CUMSK:用于设置通道状态和用户数据屏蔽位。我们设置为 1,禁止将通道状态和用户数
据写入 SPDIFRX_DR。
VMSK:用于设置有效性位屏蔽位。我们设置为 1,禁止将有效性位写入 SPDIFRX_DR。
PMSK:用于设置奇偶校验屏蔽位。我们设置为 1,禁止将奇偶校验写入 SPDIFRX_DR。
DRFMT:用于设置 SPDIFRX_DR 的数据格式。我们设置为 00,选择右对齐格式(LSB)。
RXSTEO:用于设置是否使能立体声模式。我没设置为 1,使能立体声模式。
RXDMAEN:用于设置是否使能数据流 DMA 接收。我们设置为 1,使能 DMA 接收。
SPDIFRXEN:用于设置 SPDIFRX 的使能。实际上就是控制 SPDIFRX 的工作状态:00,
禁止 SPDIFRX;01,使能 SPDIFRX 同步;10,保留;11,使能 SPDIFRX 接收器。关于该寄
存器的设置对 SPDIFRX 工作状态的影响,请参考图 54.1.6。
接下来,我们介绍 SPDIFRX 中断屏蔽寄存器:SPDIFRX_IMR,该寄存器的各位描述如图
54.1.9 所示:



图 54.1.9 SPDIFRX_IMR 寄存器各位描述
IFEIE:串行接口错误中断使能屏蔽位。我们设置为1,当SPDIFRX_SR 寄存器中的SERR=1、
TERR=1 或者 FERR=1 时,将产生 SPDIFRX 中断。
PERRIE:奇偶校验错误中断屏蔽位。我们设置为 1,当 SPDIFRX_SR 寄存器中的 PERR=1
时,将产生 SPDIFRX 中断。
接下来,我们介绍 SPDIFRX 状态寄存器:SPDIFRX_SR,该寄存器的各位描述如图 54.1.10
所示:



图 54.1.10 SPDIFRX_SR 寄存器各位描述
WIDTH5:使用 SPDIFRX_CLK 计数 5 个符号的持续时间。它表示 5 个连续符号的时间内
包含的 SPDIFRX_CLK 时钟周期数。该值可用于估算 SPDIFRX 的音频采样率。其精度受
SPDIFRX_CLK 的频率限制。其估算公式为:
Fs = 5 * SPDIFRX_CLK / (WIDTH5 * 64)
Fs 为估算的音频采样率。假定 SPDIFRX_CLK 为 84Mhz,WIDTH5 为 147。计算可得 Fs
为 44.6Khz,最接近的标准采样率为 44.1Khz,所以,我们基本可以确定采样率为 44.1Khz。
注意,需要在同步完成以后(SYNCD=1),才可以读取 WIDTH5 的值,判断采样率。
TERR:超时错误标志。当该位为 1 时,表示检测到序列错误。
SERR:同步错误标志。当该位为 1 时,表示检测到同步错误。
FERR:帧错误标志。当该位为 1 时,表示检测到曼彻斯特编码错误(帧错误)。
SYNCD:同步完成标志。当该位为 1 时,表示同步完成。
OVR:上溢错误标志。当该位为 1 时,表示检测到上溢错误。
PERR:奇偶校验错误标志。当该位为 1 时,表示检测到奇偶校验错误。
接下来,我们介绍 SPDIFRX 中断标志清零寄存器:SPDIFRX_IFCR,该寄存器的各位描述
如图 54.1.11 所示:


图 54.1.11 SPDIFRX_IFCR 寄存器各位描述
OVRCF:清零上溢错误标志。向该位写 1,可以清除上溢错误标志。
PERRCF:清零奇偶校验错误标志。向该位写 1,可以清除奇偶校验错误标志。
最后,我们介绍 SPDIFRX 数据寄存器:SPDIFRX_DR,该寄存器的各位描述如图 54.1.12
所示:



图 54.1.12 SPDIFRX_DR 寄存器各位描述
该寄存器,有三种数据格式可选(见图 54.1.7),此图为 DRFMT=00 时的格式,使用右对
齐(LSB)格式的 SPDIFRX_DR 寄存器各位描述。该寄存器里面的 PT/C/U/V/PE 等位,我们都
不用,我们只用低 24 位(DR[23:0]),用于读取音频数据。当进入接收模式以后(SPDIFRXEN=11),
我们不停的读取 SPDIFRX_DR 的数据,并传送给 SAI 接口,再由 SAI 传输给 WM8978,就可
以播放来自 SPDIFRX 接收到的音乐了。不过,我们采用 DMA 来传输,所以直接设置 DMA 的
外设地址为 SPDIFRX_DR 即可。
SPDIFRX 的相关寄存器,就给大家介绍到这里,更详细的介绍,请大家参考《STM32F7
中文参考手册.pdf》第 34.5 节。
最后,我们看看要通过 STM32F767 的 SPDIFRX 接口接收光纤音频数据,并通过 SAI 驱动
WM8978 播放音乐的简要步骤。HAL 库中 SPDIFRX 相关的库函数定义和声明分布在源文件
stm32f7xx_hal_spdifrx.c 和头文件 stm32fxx_hal_spdifrx.h 中。具体欧配置步骤如下:
1)初始化 WM8978
最终要通过 WM8978 来输出音乐,我们需要先对其进行配置,详细的配置过程,请参考第
52 章。
2)初始化 SPDIF
此步需要初始化 SPDIFRX 对应的 IO 口、开启 SPDIFRX 时钟、设置 SPDIFRX 的模式和相
关配置,主要通过对 SPDIFRX_CR 寄存器的配置来实现。
在 4.3 小节讲解时钟系统的时候讲解过,SPDIF 时钟由 PLLI2SP 提供,HAL 库中配置
PLLI2SP 时钟是通过函数 HAL_RCCEx_PeriphCLKConfig 来实现的,该函数我们在讲解时钟系
统的时候已经给大家讲解,这里我们列出配置 SPDIF 时钟详细源码:
SPDIFCLK_Sture.PeriphclockSelection=RCC_PERIPHCLK_SPDIFRX;//SPDIF RX 时钟
SPDIFCLK_Sture.PLLI2S.PLLI2SN=316; //设置 PLLI2SN
SPDIFCLK_Sture.PLLI2S.PLLI2SP= RCC_PLLI2SP_DIv2;//设置 PLLI2SP:2 分频
HAL_RCCEx_PeriphCLKConfig(&SPDIFCLK_Sture);//设置时钟
SPDIF RX 时钟计算公式为:
SPDIF RX CLK 的频率=(HSE/pllm)*PLLI2SN/PLLI2SP
这里 HSE=25MHz,pllm 为系统初始化的时候设置为 25,当我们设置 PLLI2SN=316,,
RLLI2SP 为 2 分频值 RCC_PLLI2SP_DIV2 之后我们可以得出,SPDIF RX 时钟频率为:25/25
*316/2=158MHz。
HAL 库中初始化 SPDIF 函数为 HAL_SPDIFRX_Init,该函数声明如下:
HAL_StatusTypeDef HAL_SPDIFRX_Init(SPDIFRX_HandleTypeDef *hspdif);
该函数只有一个 SPDIFRX_HandleTypeDef 结构体类型指针类型入口参数 hspdif,接下
来我们看看结构体 SPDIFRX_HandleTypeDef 定义,如下:
typedef struct
{
SPDIFRX_TypeDef *Instance;
SPDIFRX_InitTypeDef Init;
uint32_t *pRxBuffPtr;
uint32_t *pCsBuffPtr;
__IO uint16_t RxXferSize;
__IO uint16_t RxXferCount;
__IO uint16_t CsXferSize;
__IO uint16_t CsXferCount;
DMA_HandleTypeDef *hdmaCsRx;
DMA_HandleTypeDef *hdmaDrRx;
__IO HAL_LockTypeDef Lock;
__IO HAL_SPDIFRX_StateTypeDef State;
__IO uint32_t ErrorCode;
}SPDIFRX_HandleTypeDef;        对于 SPDIFRX 初始化,这里我们主要讲解 Init 成员变量,该成员变量用来设置 SPDIFRX
的初始化参数,为 SPDIFRX_InitTypeDef 结构体类型,该结构体定义为:
typedef struct
{
uint32_t InputSelection; //选择 SPDIFRX 的输入通道
uint32_t Retries; //设置在同步阶段允许的最大重试次数
uint32_t WaitForActivity; //执行同步前是否等待 SPDIFRX_IN 线路上的活动
uint32_t ChannelSelection; //选择通道状态获取路径
uint32_t DataFormat; //设置 SPDIFRX_DR 的数据格式
uint32_t StereoMode; //设置是否使能立体声模式
uint32_t PreambleTypeMask; //设置报头类型屏蔽位
uint32_t ChannelStatusMask; //设置通道状态和用户数据屏蔽位
uint32_t ValidityBitMask; //设置有效性位屏蔽位
uint32_t ParityErrorMask; //设置奇偶校验屏蔽位
}SPDIFRX_InitTypeDef;该结构体各个成员变量的含义都在上面注释了,实际上配置的是 SPDIFRX_CR 寄存器各个
位,有不理解的地方可以对照中文参考手册中寄存器位定义描述来理解。
和其他外设接口一样,HAL 库同样提供了 SPDIFRX 初始化回调函数:
void HAL_SPDIFRX_MspInit(SPDIFRX_HandleTypeDef *hspdif);
3)设置 SPDIFRX 的 DMA
我们通过 DMA 双缓冲模式来接收 SPDIF RX 接收到的数据,从而提高效率。每当一个缓
冲区数据接收满以后,硬件自动切换为下一个缓冲区,同时可以将满的缓冲区数据通过 SAI 接
口,传输给 WM8978,从而实现音乐播放。SPDIFRX 使用 DMA 双缓冲接收数据的过程如图
54.1.13 所示:



图 54.1.13 DMA 双缓冲发送音频数据流框图
4)配置 SAI
在 SPDIFRX 接口同步完成,且与 SAI 接口完成时钟同步(后续介绍),并成功获取音频
数据流的采样率以后,就可以配置 SAI 接口,然后将 SPDIFRX 接收到的音频数据流,传输给
WM8978,实现音频播放。此过程主要配置 SAI 的采样率、工作模式、协议、时钟电平特性、
slot 相关参数和 DMA 等。我们同样通过 DMA 双缓冲模式将数据传输给 WM8978。SAI 接口的
DMA 处理流程,请参考第五十二章。
5)编写 SPDIFRX 中断服务函数
当 SPDIFRX 传输出现错误的时候(比如突然断开光纤线或采样率发生了变化),需要在
SPDIFRX 中断服务函数里面对其进行处理,当发生不可恢复的错误时,需要重新设置 SPDIFRX
进入 IDLE 状态,以便重新同步。SPDIFRX 中断服务函数为 SPDIF_RX_IRQHandler。
6)开启 DMA 传输
最后,我们就只需要开启 SPDIFRX 和 SAI 的 DMA 传输,就可以实现 SPDIF 接收光纤音
频数据,并通过 WM8978 播放出来。此时,就可以在 WM8978 的耳机和喇叭通道听到光纤传
输过来的音乐了。
54.2 硬件设计
本章实验功能简介:开机后,先初始化各外设,然后检测字库是否存在,如果检测无问题,
则初始化 WM8978 的 DAC工作,并开启 DAC 输出,随后设置 SPDIFRX 的 DMA 和回调函数.
然后进入死循环等待,不停的检测 SPDIF 的连接状态,当 SPDIF RX 同步完成,且与 SAI 时钟
同步完成后,开启 SPDIFRX 和 SAI 的 DMA 数据传输。此时,在屏幕上会显示当前的音频信
号采样率,同时在喇叭/耳机,可以听到音乐。另外,我们可以通过 KEY_UP 和 KEY1 来调节音
量,KEY_UP 用于增加音量,KEY1 用于减少音量。
本实验用到的硬件资源如下:
1) 指示灯 DS0
2) 两个按键(KEY_UP/KEY1)
3) 串口
4) LCD 模块
5) SPI FLASH
6) WM8978
7) 光纤接口(SPDIFRX)
8) SAI
其中,除了光纤接口,其他资源在之前的学习中都已经介绍过了。光纤接口我们使用的是
DLR1150 光纤座,该接口和 STM32F767 的连接,如图 54.2.1 所示:



图 54.2.1 光纤接口与 STM32F767 连接原理图
图中 DLR1150 就是我们所使用的光纤座(即光纤接口),它连接在 STM32F767 的 PG12 脚
上,是 SPDIFRX 的输入通道 1。
注意:SPDIF_RX 和 NRF_CE 共用 PG12,他们不可以同时使用!
另外,本实验还需要用到一个音频光纤信号发送设备和一条光纤线,这些都需要大家自备。
光纤音频发送设备推荐购买 ALIENTEK 的贝斯(BASE)蓝牙音频接收器,支持光纤输出,购
买地址:http://www.openedv.com/thread-86409-1-1.html
54.3 软件设计
打开本章实验工程可以看到,我们在 HARDWARE 分组下面添加了 spdif.c 文件,同时包含
了头文件 spdif.h。打开 spdif.c,内容如下:
#define audioDATA_SIZE 200

u32 spdif_audiobuff[2][AUDIODATA_SIZE]; //音频数据双缓冲区,200*4=800 字节

u32 spdif_controlbuff[10]; //SPDIF 传输通道状态和用户信息

spdif_struct spdif_dev; //SPDIF 控制结构体

SPDIFRX_HandleTypeDef SPDIFIN1_Handle; //SPDIF IN1 句柄

DMA_HandleTypeDef SPDIF_DTDMA_Handler; //SPDIF 音频数据 DMA

//初始化 SPDIF

void SPDIFRX_Init(void)

{

spdif_dev.spdif_clk=1580000; //默认为 158M,单位为 100HZ

SPDIFCLK_Config(); //配置 SPDIF 时钟

SPDIFIN1_Handle.Instance=SPDIFRX;

SPDIFIN1_Handle.Init.InputSelection=SPDIFRX_INPUT_IN1; //SPDIF 输入 1
SPDIFIN1_Handle.Init.Retries=SPDIFRX_MAXRETRIES_15; //同步阶段允许重试次数
SPDIFIN1_Handle.Init.WaitForActivity=SPDIFRX_WAITFORACTIVITY_ON;//等待同步
SPDIFIN1_Handle.Init.ChannelSelection=SPDIFRX_CHANNEL_A;
//控制流从通道 A 获取通道状态
SPDIFIN1_Handle.Init.DataFormat=SPDIFRX_DATAFORMAT_32BITS; //右对齐
SPDIFIN1_Handle.Init.StereoMode=SPDIFRX_STEREOMODE_ENABLE;//使能立体声模式
SPDIFIN1_Handle.Init.PreambleTypeMask=SPDIFRX_PREAMBLETYPEMASK_OFF;
//报头类型不复制到 SPDIFRX_DR 中
SPDIFIN1_Handle.Init.ChannelStatusMask=SPDIFRX_CHANNELSTATUS_OFF;
//通道状态和用户位不复制到 SPDIFRX_DR 中
SPDIFIN1_Handle.Init.ValidityBitMask=SPDIFRX_VALIDITYMASK_ON;
//有效性位不复制到 SPDIFRX_DR 中
SPDIFIN1_Handle.Init.ParityErrorMask=SPDIFRX_PARITYERRORMASK_ON;
//奇偶校验错误位不复制到 SPDIFRX_DR 中
HAL_SPDIFRX_Init(&SPDIFIN1_Handle);
SPDIFIN1_Handle.Instance->CR|=SPDIFRX_CR_RXDMAEN;
//SPDIF 音频数据使用 DMA 来接收
SPDIFIN1_Handle.Instance->CR|=SPDIFRX_CR_CBDMAEN;
//SPDIF 传输通道状态和用户信息使用 DMA 来接收
//使能 SPDIF 的串行接口错误中断、上溢错误和奇偶校验错误
__HAL_SPDIFRX_ENABLE_IT(&SPDIFIN1_Handle,SPDIFRX_IT_IFEIE| \
SPDIFRX_IT_PERRIE);
SPDIF_AUDIODATA_DMA_Init((u32*)&spdif_audiobuff[0],(u32*)& \
spdif_audiobuff[1],AUDIODATA_SIZE,2);
}
//SPDIF 时钟配置,设置为 158MHZ
void SPDIFCLK_Config(void)
{
RCC_PeriphCLKInitTypeDef SPDIFCLK_Sture;

SPDIFCLK_Sture.PeriphClockSelection=RCC_PERIPHCLK_SPDIFRX; //SPDIF RX时钟
SPDIFCLK_Sture.PLLI2S.PLLI2SN=316; //设置 PLLI2SN
SPDIFCLK_Sture.PLLI2S.PLLI2SP=RCC_PLLI2SP_DIV2; //设置 PLLI2SP:2 分频
HAL_RCCEx_PeriphCLKConfig(&SPDIFCLK_Sture); //设置时钟
}
//SPDIF 音频数据接收 DMA 配置
//设置为双缓冲模式,并开启 DMA 传输完成中断
//buf0:M0AR 地址. buf1:M1AR 地址.
//num:每次传输数据量 width:位宽(存储器和外设,同时设置),0,8 位;1,16 位;2,32 位;
void SPDIF_AUDIODATA_DMA_Init(u32* buf0,u32 *buf1,u16 num,u8 width)
{
  u32 memwidth=0,perwidth=0; //外设和存储器位宽
switch(width)
{
case 0: //8 位
memwidth=DMA_MDATAALIGN_BYTE;
perwidth=DMA_PDATAALIGN_BYTE;
break;
case 1: //16 位
memwidth=DMA_MDATAALIGN_HALFWORD;
perwidth=DMA_PDATAALIGN_HALFWORD;
break;
case 2: //32 位
memwidth=DMA_MDATAALIGN_WORD;
perwidth=DMA_PDATAALIGN_WORD;
break;

}
__HAL_RCC_DMA1_CLK_ENABLE(); //使能 DMA1 时钟
__HAL_LINKDMA(&SPDIFIN1_Handle,hdmaDrRx,SPDIF_DTDMA_Handler);
//将 DMA 与 SPDIF 联系起来
SPDIF_DTDMA_Handler.Instance=DMA1_Stream1; //DMA1 数据流 1
SPDIF_DTDMA_Handler.Init.Channel=DMA_CHANNEL_0; //通道 0
SPDIF_DTDMA_Handler.Init.Direction=DMA_PERIPH_TO_MEMORY; //外设到存储器
SPDIF_DTDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE; //外设非增量模式
SPDIF_DTDMA_Handler.Init.MemInc=DMA_MINC_ENABLE; //存储器增量模式
SPDIF_DTDMA_Handler.Init.PeriphDataAlignment=perwidth; //外设数据长度:16/32 位
SPDIF_DTDMA_Handler.Init.MemDataAlignment=memwidth;//存储器数据长度 16/32位
SPDIF_DTDMA_Handler.Init.Mode=DMA_CIRCULAR; //使用循环模式
SPDIF_DTDMA_Handler.Init.Priority=DMA_PRIORITY_HIGH; //最高优先级
SPDIF_DTDMA_Handler.Init.FIFOMode=DMA_FIFOMODE_DISABLE;//不使用 FIFO
SPDIF_DTDMA_Handler.Init.Memburst=DMA_MBURST_SINGLE;//单次突发传输
SPDIF_DTDMA_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE;//突发单次传输
HAL_DMA_DeInit(&SPDIF_DTDMA_Handler); //先清除以前的设置
HAL_DMA_Init(&SPDIF_DTDMA_Handler); //初始化 DMA

HAL_DMAEx_MultiBufferStart(&SPDIF_DTDMA_Handler,(u32)&SPDIFRX->DR,
(u32)buf0,(u32)buf1,num);//开启双缓冲
}
//进入同步状态,同步完成以后进入接收状态
//返回值:0 未同步;1 同步
u8 WaitSync_TORecv(void)
{
   u8 flag=0;
u8 timeout=0;
__HAL_SPDIFRX_SYNC(&SPDIFIN1_Handle);
while(__HAL_SPDIFRX_GET_FLAG(&SPDIFIN1_Handle,
SPDIFRX_FLAG_SYNCD)==0)//等待同步完成
{
timeout++;
delay_ms(5);
if (timeout>100) break;//超时,跳出
}
if(timeout>100) flag=0;//未同步
else //同步完成
{
flag=1;
__HAL_SPDIFRX_RCV(&SPDIFIN1_Handle);//同步完成,进入接收阶段
}
return flag;
}
//获取 SPDIF 收到的音频采样率
void SPDIF_GetRate(void)
{
u16 spdif_w5;
u32 spdif_rate;

spdif_w5=(SPDIFIN1_Handle.Instance->SR)>>16;
spdif_rate=(spdif_dev.spdif_clk*5)/(spdif_w5&0X7FFF);
spdif_rate>>=6; //除以 64
spdif_rate*=100; //乘以 100,得到最终的实际采样率
if((44100-1500<=spdif_rate)&&(spdif_rate<=44100+1500))
spdif_dev.spdifrate=44100; //44.1K 的采样率
else if((48000-1500<=spdif_rate)&&(spdif_rate<=48000+1500))
spdif_dev.spdifrate=48000; //48K 的采样率
else if((88200-1500<=spdif_rate)&&(spdif_rate<=88200+1500))
spdif_dev.spdifrate=88200; //88.2K 的采样率
else if((96000-1500<=spdif_rate)&&(spdif_rate<=96000+1500))
spdif_dev.spdifrate=96000; //96K 的采样率
else if((176400-1500<=spdif_rate)&&(spdif_rate<=176400+1500))
spdif_dev.spdifrate=176400; //176.4K 的采
else if((192000-1500<=spdif_rate)&&(spdif_rate<=192000+1500))
spdif_dev.spdifrate=192000; //192K 的采
else spdif_dev.spdifrate=0;   
}
//SPDIF 底层 IO 初始化和时钟使能
//此函数会被 HAL_SPDIF_Init()调用
//hltdc:SPDIF 句柄
void HAL_SPDIFRX_MspInit(SPDIFRX_HandleTypeDef *hspdif)
{
GPIO_InitTypeDef GPIO_Initure;

__HAL_RCC_SPDIFRX_CLK_ENABLE(); //使能 SPDIF RX 时钟
__HAL_RCC_GPIOG_CLK_ENABLE(); //使能 GPIOG 时钟
//初始化 PG12,SPDIF IN 引脚
GPIO_Initure.Pin=GPIO_PIN_12; //PG12,SPDIF IN 引脚
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用
GPIO_Initure.Pull=GPIO_NOPULL; //无上下拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速
GPIO_Initure.Alternate=GPIO_AF7_SPDIFRX; //复用为 SPDIF RX
HAL_GPIO_Init(GPIOG,&GPIO_Initure);
HAL_NVIC_SetPriority(SPDIF_RX_IRQn,1,0); //SPDIF 中断
HAL_NVIC_EnableIRQ(SPDIF_RX_IRQn);
}
//SPDIF 接收中断服务函数
void SPDIF_RX_IRQHandler(void)
{
//发生超时、同步和帧错误中断,这三个中断一定要处理!
if( __HAL_SPDIFRX_GET_FLAG(&SPDIFIN1_Handle,SPDIFRX_FLAG_FERR)||\
__HAL_SPDIFRX_GET_FLAG(&SPDIFIN1_Handle,SPDIFRX_FLAG_SERR)||\
__HAL_SPDIFRX_GET_FLAG(&SPDIFIN1_Handle,SPDIFRX_FLAG_TERR))
{
SPDIF_Play_Stop();//发生错误,关闭 SPDIF 播放\
//当发生超时、同步和帧错误的时候要将 SPDIFRXEN 写 0 来清除中断
__HAL_SPDIFRX_IDLE(&SPDIFIN1_Handle);
//当清除中断以后需要重新将 SPDIF 设置为接收模式
__HAL_SPDIFRX_RCV(&SPDIFIN1_Handle);
}else if(__HAL_SPDIFRX_GET_FLAG(&SPDIFIN1_Handle,SPDIFRX_FLAG_OVR))
{
__HAL_SPDIFRX_CLEAR_IT(&SPDIFIN1_Handle, SPDIFRX_FLAG_OVR);
}else if(__HAL_SPDIFRX_GET_FLAG(&SPDIFIN1_Handle,SPDIFRX_FLAG_PERR))
{
__HAL_SPDIFRX_CLEAR_IT(&SPDIFIN1_Handle, SPDIFRX_FLAG_PERR);
    } }
//配置音频接口
//AudioFreq:音频采样率
uint32_t SPDIF_AUDIO_Init(uint32_t AudioFreq)
{
SAIA_Init(SAI_modemASTER_TX,SAI_CLOCKSTROBING_RISINGEDGE,
SAI_DATASIZE_16);//设置 SAI,主发送,16 位数据
SAIA_SampleRate_Set(AudioFreq); //设置采样率
SAIA_TX_DMA_Init((u8*)&spdif_audiobuff[0],(u8*)&spdif_audiobuff[1],
AUDIODATA_SIZE*2,1); //配置 TX DMA,16 位
SAI_Play_Start(); //开启 DMA
return 0;
}
//SPDIF 开始播放
void SPDIF_Play_Start(void)
{
spdif_dev.connsta=1; //标记已经打开 SPDIF
__HAL_DMA_ENABLE(&SPDIF_DTDMA_Handler); //开启 SPDIF DMA 传输
}
//SPDIF 关闭
void SPDIF_Play_Stop(void)
{
spdif_dev.connsta=0; //标记已经关闭 SPDIF
__HAL_DMA_DISABLE(&SPDIF_DTDMA_Handler);//关闭 SPDIF DMA 传输
//两个缓冲区一定要清零,否则在断开的时候会有很大杂音!!!
memset((u8*)&spdif_audiobuff[0],0,AUDIODATA_SIZE*4);
memset((u8*)&spdif_audiobuff[1],0,AUDIODATA_SIZE*4);
}这里总共 10 个函数,接下来,我们分别介绍。
1,SPDIFRX_Init 函数
该函数用于初始化 SPDIFRX 接口,包括设置 SPDIFRX 时钟、设置 SPDIFRX 相关参数等,
最后开启了奇偶校验错误中断和接口错误中断,用于处理当接收数据出现错误时(包括光纤断
开、采样率切换等),重新同步。
2,HAL_SPDIFRX_MspInit 函数
该函数是 SPDIFRX 的初始化回调函数,主要用来配置时钟使能,IO 口模式和中断分组等。
3,SPDIFCLK_Config 函数
该函数用于设置 SPDIFRX 接口的时钟频率,SPDIFRX 的时钟频率来自 PLLI2S 的 N 分频,
我们默认设置 N 分频为 2,所以 SPDIFRX_CLK 的计算公式就是:
SPDIFRX_CLK=PLLI2SN/2
单位为 Mhz,另外,SPDIFRX 接口的时钟频率必须大于音频采样率的 704 倍,对于 192Khz
的音频采样率来说,SPDIFRX_CLK 的频率必须大于 135.168Mhz,才可以识别,所以我们在
SPDIF_RX_Init 函数里面,通过 SPDIFCLK_Config 函数,设置 SPDIFRX_CLK 为 158Mhz,以
支持最高 192Khz 的音频采样率。
4,WaitSync_TORecv 函数
该函数用于等待 SPDIFRX 同步完成,SPDIF 必须在等待同步完成以后,才可以获取音频
采样率,然后进入接收状态,接收音频数据。
5,SPDIF_AUDIODATA_DMA_Init 函数
该函数用于配置 SPDIFRX 接口的数据接收 DMA,使用双缓冲模式,将 SPDIFRX_DR 的
数据,存储到内存里面,实现音频数据接收。
6,SPDIF_GetRate 函数
该函数用于获取 SPDIFRX 识别到的音频采样率。该函数首先通过 SPDIFRX_SR 寄存器的
WIDTH5 位,获取大概的音频采样率,然后标准化为最接近的音频采样率。该函数必须在 SPDIF
同步完成以后才可以调用。
7,SPDIF_RX_IRQHandler 函数
该函数用于处理 SPDIFRX 接口的错误中断,当发生超时错误、同步错误和帧错误的时候,
必须停止 SPDIFRX 接口,然后重新进入 IDLE 状态,以便重新同步。
8,SPDIF_AUDIO_Init 函数
该函数用来配置音频接口采样率。
9,SPDIF_Play_Start 和 SPDIF_Play_Stop 函数
这两个函数,前者用于启动 SPDIFRX DMA 传输,在所有配置和状态都正常的情况下,用
于启动 SPDIF 音频播放。后者用于关闭 SPDIFRX DMA 传输,停止 SPDIF 播放。其中 spdif_dev
结构体用于控制 SPDIF 接收状态,结构体定义(见 spdif.h)如下:
//SPDIF 控制结构体
typedef struct
{
u8 connsta; //连接状态,0 未连接;1 连接上
u32 spdifrate; //SPDIF 采样率
u32 spdif_clk; //SPDIF 时钟,默认为 158M
}spdif_struct;
connsta 表示 SPDIF 连接状态,当 SPDIF 开启 DMA 传输的时候,设置为 1,表示 SPDIF
正在播放。当其为 0 的时候,表示 SPDIF 停止播放。
spdifrate 表示 SPDIF 识别到的音频采样率,单位为 Hz。
spdif_clk 表示 SPDIFRX_CLK 的时钟频率,单位为 Hz。
最后,我们看看 main.c 文件代码:
//显示采样率

//samplerate:音频采样率(单位:Hz)

void spdif_show_samplerate(u32 samplerate)

{

……//此处省略部分显示代码

}

int main(void)

{
         u8 i,strbuff[20];
u8 volume=45,key=0;

Cache_Enable(); //打开 L1-Cache
HAL_Init(); //初始化 HAL 库
Stm32_Clock_Init(432,25,2,9); //设置时钟,216Mhz
delay_init(216); //延时初始化
uart_init(115200); //串口初始化
usmart_dev.init(108); //初始化 USMART
LED_Init(); //初始化 LED
KEY_Init(); //初始化按键
SDRAM_Init(); //初始化 SDRAM
LCD_Init(); //初始化 LCD
W25QXX_Init(); //初始化 W25Q256
WM8978_Init(); //初始化 WM8978
WM8978_HPvol_Set(volume,volume); //耳机音量设置
WM8978_SPKvol_Set(volume); //喇叭音量设置
SPDIFRX_Init(); //SPDIF 初始化
my_mem_init(SRAMIN); //初始化内部内存池
my_mem_init(SRAMEX); //初始化外部 SDRAM 内存池
my_mem_init(SRAMDTCM); //初始化内部 DTCM 内存池
exfuns_init(); //为 fatfs 相关变量申请内存
f_mount(fs[0],"0:",1); //挂载 SD 卡
POINT_COLOR=RED;
while(font_init()) //检查字库
{
LCD_ShowString(30,50,200,16,16,"Font Error!");
delay_ms(200);
LCD_Fill(30,50,240,66,WHITE);//清除显示
delay_ms(200);
}
POINT_COLOR=RED;
Show_Str(30,40,200,16,"阿波罗 STM32F4/F7 开发板",16,0);
Show_Str(30,60,200,16,"SPDIF(光纤音频)实验",16,0);
Show_Str(30,80,200,16,"正点原子@ALIENTEK",16,0);
Show_Str(30,100,200,16,"2016 年 8 月 2 日",16,0);
Show_Str(30,120,200,16,"KEY_UP:VOL+ KEY1:VOL-",16,0);
Show_Str(30,150,200,16,"音量:",16,0);
Show_Str(30,170,200,16,"采样率:",16,0);
POINT_COLOR=BLUE;
u8 i,strbuff[20];
u8 volume=45,key=0;

Cache_Enable(); //打开 L1-Cache
HAL_Init(); //初始化 HAL 库
Stm32_Clock_Init(432,25,2,9); //设置时钟,216Mhz
delay_init(216); //延时初始化
uart_init(115200); //串口初始化
usmart_dev.init(108); //初始化 USMART
LED_Init(); //初始化 LED
KEY_Init(); //初始化按键
SDRAM_Init(); //初始化 SDRAM
LCD_Init(); //初始化 LCD
W25QXX_Init(); //初始化 W25Q256
WM8978_Init(); //初始化 WM8978
WM8978_HPvol_Set(volume,volume); //耳机音量设置
WM8978_SPKvol_Set(volume); //喇叭音量设置
SPDIFRX_Init(); //SPDIF 初始化
my_mem_init(SRAMIN); //初始化内部内存池
my_mem_init(SRAMEX); //初始化外部 SDRAM 内存池
my_mem_init(SRAMDTCM); //初始化内部 DTCM 内存池
exfuns_init(); //为 fatfs 相关变量申请内存
f_mount(fs[0],"0:",1); //挂载 SD 卡
POINT_COLOR=RED;
while(font_init()) //检查字库
{
LCD_ShowString(30,50,200,16,16,"Font Error!");
delay_ms(200);
LCD_Fill(30,50,240,66,WHITE);//清除显示
delay_ms(200);
}
POINT_COLOR=RED;
Show_Str(30,40,200,16,"阿波罗 STM32F4/F7 开发板",16,0);
Show_Str(30,60,200,16,"SPDIF(光纤音频)实验",16,0);
Show_Str(30,80,200,16,"正点原子@ALIENTEK",16,0);
Show_Str(30,100,200,16,"2016 年 8 月 2 日",16,0);
Show_Str(30,120,200,16,"KEY_UP:VOL+ KEY1:VOL-",16,0);
Show_Str(30,150,200,16,"音量:",16,0);
Show_Str(30,170,200,16,"采样率:",16,0);
POINT_COLOR=BLUE;
}

}
在 main 函数里面我们初始化 WM8978 和 SPDIFRX 等外设,随后在死循环里面等待 SPDIF
同步完成,在首次同步完成以后,获取音频采样率,然后设置 SAI 接口,设置 SAI 的音频采样
率,标记 SAI 与 SPDIF 时钟同步完成,然后重新同步(SPDIF 和光纤信号同步),重新同步完
成以后,开启 SPDIF 和 SAI 的 DMA 传输,启动音频播放,此时就可以从喇叭或者耳机听到光
纤传输过来的音频信号了。在主循环里面我们还加入了音量调节的代码,可以通过 KEY_UP 和
KEY1 调节音量的大小。
至此,本实验的软件设计部分结束。
54.4 下载验证
在代码编译成功之后,我们下载代码到 ALIENTEK 阿波罗 STM32 开发板上,程序先检测
字库,然后进入主循环,等待 SPDIF 信号(光纤信号)的输入,屏幕提示:正在识别…。如图
54.4.1 所示:



图 54.4.1 等待 SPDIF 信号输入(光纤音频信号)
此时,我们利用光纤线连接开发板的光纤座和光纤音频输出设备(比如贝斯蓝牙音频接收
器),然后播放歌曲,就可以看到屏幕显示了音频信号的采样率,然后,通过耳机和板载喇叭就
可以欣赏所播放的歌曲了。如图 54.4.2 所示:



图 54.4.2 识别采样率,播放音乐
此时,我们按下 KEY_UP 按键,可以提高音频,按 KEY1 按键,可以降低音量。STM32F7
自带的 SPDIFRX 功能,可以实现对光纤、同轴等数字音频信号的解码,在 HiFi 解码方面可以
有广泛的应用前景。
最后,本例程支持 44.1K、48K、88.2K 和 96Khz 等音频采样率,暂不支持 176.4K 和 192K
等高采样率的音频信号。






2
分享淘帖 显示全部楼层

评论

高级模式
您需要登录后才可以回帖 登录 | 注册

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
发资料
关闭

站长推荐 上一条 /7 下一条

快速回复 返回顶部 返回列表