• 正文
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

【经验分享】RT10xx SAI模块基础与工程构建测试

2025/02/11
2023
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

RT10xx的audio模块有SAI,SPDIF和MQS。SAI模块是用于音频数据传输的同步串行接口。SPDIF是立体声收发器,可以接收发送数字音频,MQS用于从SAI3转换I2S音频数据为PWM,然后可以驱动外部扬声器,当然实际中还是需要添加功放驱动电路

在实际使用SAI的过程中,涉及到音频文件的播放或者采集。本文将基于MIMXRT1060-EVK,讲解关于RT10XX SAI模块基础知识,PCM编码waveform音频文件格式,音频剪辑转换工具,MCUXPresso IDE CFG外设模块构建SAI工程并且在MIMXRT1060-EVK开发板上测试播放音乐文件。

1.基础知识与工具

在进入工程的具体测试之前,了解SAI模块知识,以及音频文件格式,音频剪辑转换工具是必不可少的,本章将讲解相关内容。

1.1 SAI模块基础

RT10XX SAI模块可以支持I2S,AC97,TDM和codec/DSP接口。

SAI模块分为发送和接收两部分,相关信号线:

SAI_MCLK: master时钟,用于生成发送接收的Bit clock,主机发从机收

SAI_TX_BCLK:发送位时钟,主机发从机收

SAI_TX_SYNC:发送帧同步信号,主机发从机收,左右声道选择

SAI_TX_DATA[4]:发送数据线,1-3和RX_DATA[1-3]共用

SAI_RX_BCLK:接收位时钟

SAI_RX_SYNC:接收帧同步信号

SAI_RX_DATA[4]:接收数据线

SAI模块时钟有3种: audio master clock,bus clock, bit clock

SAI模块同步模式3种:

发送与接收异步:发送,接收各自用自己的BCLK与SYNC
发送异步,接收同步:发送与接收都用发送的BCLK与SYNC,发送后开先关
接收异步,发送同步:发送与接收都用接收的BCLK与SYNC,接收后开先关

Frame 同步,使能发送接收之后的前4个bitclock不会生成有效帧同步:

SAI模块时钟结构:

SAI模块时钟源有三个:PLL3_PFD3, PLL5,PLL4

上图中配置完的SAI1_CLK_ROOT也是MCLK的时钟,关于BCLK时钟:

BCLK= master clock/(TCR2[DIV]+1)*2

samplerate = Bitclockfreq /(bitwidth*channel)

1.2 waveform音频格式

WAVE文件用来保存PCM编码数据,WAVE以RIFF格式为标准,RIFF文件基本单元是CK结构题,CKID用于标识块中所包含的数据类型,值可以是:“RIFF”,“LIST”,“fmt”, “data”等。

RIFF文件按照小端little-endian字节顺序写入。

RIFF结构体

typedef unsigned long DWORD;//4B
typedef unsigned char BYTE;//1B
typedef DWORD FOURCC; // 4B
typedef struct {
FOURCC ckID; //4B
DWORD ckSize; //4B
union {
FOURCC fccType; // RIFF form type 4B
BYTE ckData[ckSize]; //ckSize*1B
} ckData;
} RIFFCK;

实际举例,取一个16k 2通道的wav音频文件:

图中黄色块为CKID,绿色块数据长度,紫色块数据段。

具体解析结构如下:

所以可以看到,实际的音频数据,去掉头,大小是1279860bytes.

1.3 音频格式转换

在实际的使用中,音频文件可能不是我们所需要的通道频率配置,或者文件格式不是wav,或者时间太长需要截取,或者采样率通道数不对,那么可以使用什么工具转换呢?

这里我们推荐使用ffmpeg工具:

https://ffmpeg.org/

具体使用可以查看该工具的文档和命令,这里给出两种常用的命令:

Mp3文件转成16K,16bit,双通道的wav文件:

ffmpeg -i test.mp3 -acodecpcm_s16le -ar 16000 -ac 2 test.wav

从test.wav 00:00:00开始截取35s,并且转存为test1.wav:

ffmpeg -ss 00:00:00 -i test.wav -t35.0 -c copy test1.wav

2.4 提取wav音频左右声道数据

目前代码是把音频数据直接以数组形式放在RT芯片内存里,所以这里涉及到如何把wav的音频数据给提取出来。

这里可以使用python读取wav的头,并且获取到音频数据的大小值,然后转存到一个数组中,供SAI去调取。具体Python代码如下:

import sys
import wave

def wav2hex(strWav, strHex):
with wave.open(strWav, "rb") as fWav:
wavChannels = fWav.getnchannels()
wavSampleWidth = fWav.getsampwidth()
wavFrameRate = fWav.getframerate()
wavFrameNum = fWav.getnframes()
wavFrames = fWav.readframes(wavFrameNum)
wavDuration = wavFrameNum / wavFrameRate
wafFramebytes = wavFrameNum * wavChannels * wavSampleWidth
print("Channels: {}".format(wavChannels))
print("Sample width: {}bits".format(wavSampleWidth * 8))
print("Sample rate: {}kHz".format(wavFrameRate/1000))
print("Frames number: {}".format(wavFrameNum))
print("Duration: {}s".format(wavDuration))
print("Frames bytes: {}".format(wafFramebytes))
fWav.close()
pass

with open(strHex, "w") as fHex:
# Print WAV parameters
fHex.write("/*n");
fHex.write(" Channels: {}n".format(wavChannels))
fHex.write(" Sample width: {}bitsn".format(wavSampleWidth * 8))
fHex.write(" Sample rate: {}kHzn".format(wavFrameRate/1000))
fHex.write(" Frames number: {}n".format(wavFrameNum))
fHex.write(" Duration: {}sn".format(wavDuration))
fHex.write(" Frames bytes: {}n".format(wafFramebytes))
fHex.write("*/nn")
# Print WAV frames
fHex.write("uint8_t music[] = {n")
print("Transferring...")
i = 0
while wafFramebytes > 0:
if(wafFramebytes < 16):
BytesToPrint = wafFramebytes
else:
BytesToPrint = 16
fHex.write(" ")
for j in range(0, BytesToPrint):
if j != 0:
fHex.write(' ')
fHex.write("0x{:0>2x},".format(wavFrames[i]))
i+=1
j+=1
fHex.write("n")
wafFramebytes -= BytesToPrint
fHex.write("};n")
fHex.close()
print("Done!")

wav2hex(sys.argv[1], sys.argv[2])

同样以music1.wav 为例,转换音频数据:

2.5 数据与音频对应关系

16bit的数据范围位-32768到32767,对于goldwav中相对值是(-1~1)。

使用goldwave打开例程中的music1.wav,查看位于1s的数据,左声道的相对数据为-0.08227, 右声道的相对数据为-0.2257。

下面开始计算左右声道实际数据,并且找到在例子music1.h中的位置:

从图中可以看到转换好后的音频左右声道数据从第11行开始,每行16字节。

所以根据music1.wav在goldwave中相对数据可以算出对应的左右声道数据,对比提取出来的音频数据可以发现,完全一致。

2. SAI MCUXpresso工程构建

本文使用MCUXpresso基于SDK_2.9.1_EVK-MIMXRT1060,新建一个SAI DMA播放音乐的例程。音频数据即使用上述导出的music1.h。

新建一个bare metal 的project:

Drivers 勾选:

clock,common, dmamux, edma,gpio,i2c,iomuxc,lpuart,sai,sai_edma,xip_device

Utilities勾选:

Debug_console,lpuart_adapter,serial_manager,serial_manager_uart

Board components 勾选:

Xip_board

Abstraction Layer勾选:

Codec,codec_wm8960_adapter,lpi2c_adapter

Software Components勾选:

Codec_i2c,lists,wm8960

新建工程之后,打开clocks,配置时钟,core,FlexSPI时钟使用默认时钟,主要配置SAI1相关时钟:

选择时钟源为PLL4,PLL4_MAIN_CLK配置为786.48Mhz.

SAI1 clock 配置为6.144375Mhz.

配置好后,生成代码。

打开Pins,配置SAI1相关引脚

生成代码。

打开peripherals,配置DMA,SAI,NVIC

DMA配置如下:

配置好后生成代码。

因为SAI模块这边不能直接选择SAI发送,而是playback,既带有SAI发送,也带有SAI接收,所以在生成好的代码SAI1_init函数中可以屏蔽掉关于接收的代码。

上述我们将完成SAI模块DMA传送的配置,SAI master模式,16bit,采样率16Khz,双通道,DMA发送,bit clock512Khz。

在source中添加生成好的musc1.h

主函数文件中添加关于codec初始化,音频数据调用,以及DMA中断callback, SAI模块中断服务函数的处理。

//添加代码

void callback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
{
if (kStatus_SAI_RxError == status)
{
}
else
{
finishIndex++;
emptyBlock++;
/* Judge whether the music array is completely transfered. */
if (MUSIC_LEN / BUFFER_SIZE == finishIndex)
{
isFinished = true;

finishIndex = 0;
emptyBlock = BUFFER_NUM;
tx_index = 0;
cpy_index = 0;
}
}
}

void DelayMS(uint32_t ms)
{
for (uint32_t i = 0; i < ms; i++)
{
SDK_DelayAtLeastUs(1000, SystemCoreClock);
}
}

/*
* @brief Application entry point.
*/
int main(void) {

sai_transfer_t xfer;
/* Init board hardware. */
BOARD_ConfigMPU();
BOARD_InitPins();
BOARD_InitBootClocks();
BOARD_InitBootPeripherals();
#ifndef BOARD_INIT_DEBUG_CONSOLE_PERIPHERAL
/* Init FSL debug console. */
BOARD_InitDebugConsole();
#endif

PRINTF(" SAI wav module test!nr");
/* Use default setting to init codec */
if (CODEC_Init(&codecHandle, &boardCodecConfig) != kStatus_Success)
{
assert(false);
}
/* delay for codec output stable */
DelayMS(DEMO_CODEC_INIT_DELAY_MS);
CODEC_SetVolume(&codecHandle,2U,50); // set 50% value

 

EnableIRQ(DEMO_SAI_IRQ);
SAI_TxEnableInterrupts(DEMO_SAI, kSAI_FIFOErrorInterruptEnable);

PRINTF(" MUSIC PLAY Start!nr");
while (1)
{
PRINTF(" MUSIC PLAY Againnr");
isFinished = false;
while (!isFinished)
{
if ((emptyBlock > 0U) && (cpy_index < MUSIC_LEN / BUFFER_SIZE))
{
/* Fill in the buffers. */
memcpy((uint8_t *)&buffer[BUFFER_SIZE * (cpy_index % BUFFER_NUM)],
(uint8_t *)&music[cpy_index * BUFFER_SIZE], sizeof(uint8_t) * BUFFER_SIZE);
emptyBlock--;
cpy_index++;
}
if (emptyBlock < BUFFER_NUM)
{
/* xfer structure */
xfer.data = (uint8_t *)&buffer[BUFFER_SIZE * (tx_index % BUFFER_NUM)];
xfer.dataSize = BUFFER_SIZE;
/* Wait for available queue. */
if (kStatus_Success == SAI_TransferSendEDMA(DEMO_SAI, &SAI1_SAI_Tx_eDMA_Handle, &xfer))
{
tx_index++;
}
}
}

}

}

void DEMO_SAITxIRQHandler(void)
{
/* Clear the FIFO error flag */
SAI_TxClearStatusFlags(DEMO_SAI, kSAI_FIFOErrorFlag);

/* Reset FIFO */
SAI_TxSoftwareReset(DEMO_SAI, kSAI_ResetTypeFIFO);
SDK_ISR_EXIT_BARRIER;
}

3. SAI测试结果

为了查看实际左右声道数据发送出去的波形对应情况,下面修改music开始的16字节左右声道数据为:0x55,0xaa,0x01,0x00,0x02,0x00,0x03,0x00,0x04,0x00,0x05,0x00,0x06,0x00,0x07,0x00

然后测试SAI_MCLK,SAI_TX_BCLK,SAI_TX_SYNC,SAI_TXD的波形,查看数据对应的情况。

因为代码配置的是极性是低有效,即下降沿输出,上升沿采样。

测试点在MIMXRT1060-EVK板子Codec位置:

3.1 逻辑分析测试波形

MCLK的频率为6.144375Mhz, BCLK频率为512khz,SYNC的频率为16Khz。

第一帧数据为:1010101001010101 0000000000000001

0XAA55 0X0001

和表格中左右声道一致,小端存放。

SYNC为低左声道16bit,为高右声道16bit。

3.2 示波器测试波形

逻辑分析仪同样的数据,示波器测试结果:


音频实际播放效果,添加配置好的music.h文件,并且让主程序循环播放music文件里面的音频数据。

相关推荐

电子产业图谱