查看: 2917|回复: 1

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

[复制链接]

该用户从未签到

656

主题

6312

帖子

0

超级版主

Rank: 8Rank: 8

积分
20014
最后登录
2024-4-25
发表于 2021-3-12 11:13:40 | 显示全部楼层 |阅读模式
本帖最后由 小恩GG 于 2021-3-26 15:35 编辑

【经验分享】RT10xx SAI模块基础与工程构建测试
一 文档简介

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

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


二 基础知识与工具

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

2.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不会生成有效帧同步:

1.jpg

图1

SAI模块时钟结构:

2.jpg

图2

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

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

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

samplerate = Bitclockfreq /(bitwidth*channel)

2.2 waveform音频格式

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

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

RIFF结构体:

  1. typedef unsigned long DWORD;//4B
  2. typedef unsigned char BYTE;//1B
  3. typedef DWORD         FOURCC;    // 4B
  4. typedef struct {
  5.      FOURCC ckID; //4B
  6.      DWORD ckSize; //4B
  7.      union {
  8.           FOURCC fccType;          // RIFF form type 4B
  9.           BYTE ckData[ckSize];     //ckSize*1B
  10.      } ckData;
  11. } RIFFCK;
复制代码


3.jpg

图3

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

4.jpg

图4

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

具体解析结构如下:

5.jpg

图5

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


2.3 音频格式转换

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

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


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

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

6.jpg

图6

7.jpg

图7

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

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

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

  1. import sys
  2. import wave

  3. def wav2hex(strWav, strHex):
  4.     with wave.open(strWav, "rb") as fWav:
  5.         wavChannels = fWav.getnchannels()
  6.         wavSampleWidth = fWav.getsampwidth()
  7.         wavFrameRate = fWav.getframerate()
  8.         wavFrameNum = fWav.getnframes()
  9.         wavFrames = fWav.readframes(wavFrameNum)
  10.         wavDuration = wavFrameNum / wavFrameRate
  11.         wafFramebytes = wavFrameNum * wavChannels * wavSampleWidth
  12.         print("Channels: {}".format(wavChannels))
  13.         print("Sample width: {}bits".format(wavSampleWidth * 8))
  14.         print("Sample rate: {}kHz".format(wavFrameRate/1000))
  15.         print("Frames number: {}".format(wavFrameNum))
  16.         print("Duration: {}s".format(wavDuration))
  17.         print("Frames bytes: {}".format(wafFramebytes))
  18.         fWav.close()
  19.         pass

  20.     with open(strHex, "w") as fHex:
  21.         # Print WAV parameters
  22.         fHex.write("/*\n");
  23.         fHex.write("  Channels: {}\n".format(wavChannels))
  24.         fHex.write("  Sample width: {}bits\n".format(wavSampleWidth * 8))
  25.         fHex.write("  Sample rate: {}kHz\n".format(wavFrameRate/1000))
  26.         fHex.write("  Frames number: {}\n".format(wavFrameNum))
  27.         fHex.write("  Duration: {}s\n".format(wavDuration))
  28.         fHex.write("  Frames bytes: {}\n".format(wafFramebytes))
  29.         fHex.write("*/\n\n")
  30.         # Print WAV frames
  31.         fHex.write("uint8_t music[] = {\n")
  32.         print("Transferring...")
  33.         i = 0
  34.         while wafFramebytes > 0:
  35.             if(wafFramebytes < 16):
  36.                 BytesToPrint = wafFramebytes
  37.             else:
  38.                 BytesToPrint = 16
  39.             fHex.write("    ")
  40.             for j in range(0, BytesToPrint):
  41.                 if j != 0:
  42.                     fHex.write(' ')
  43.                 fHex.write("0x{:0>2x},".format(wavFrames[i]))
  44.                 i+=1
  45.                 j+=1
  46.             fHex.write("\n")
  47.             wafFramebytes -= BytesToPrint
  48.         fHex.write("};\n")
  49.         fHex.close()
  50.         print("Done!")

  51. wav2hex(sys.argv[1], sys.argv[2])
复制代码
同样以music1.wav 为例,转换音频数据:
8.jpg

图8

2.5 数据与音频对应关系

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

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

9.jpg 10.jpg

图9                                                                                          图10

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

11.jpg

图11

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

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

三 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相关时钟:

12.jpg

图12

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

SAI1 clock 配置为6.144375Mhz.

配置好后,生成代码。

打开Pins,配置SAI1相关引脚:

13.jpg

图13

生成代码。

打开peripherals,配置DMA,SAI,NVIC

14.jpg

图14

15.jpg 图15

DMA配置如下:

16.jpg

图16

配置好后生成代码。

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

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

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

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

//添加代码

  1. void callback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
  2. {
  3.     if (kStatus_SAI_RxError == status)
  4.     {
  5.     }
  6.     else
  7.     {
  8.         finishIndex++;
  9.         emptyBlock++;
  10.         /* Judge whether the music array is completely transfered. */
  11.         if (MUSIC_LEN / BUFFER_SIZE == finishIndex)
  12.         {
  13.             isFinished = true;

  14.             finishIndex = 0;
  15.             emptyBlock  = BUFFER_NUM;
  16.             tx_index = 0;
  17.             cpy_index = 0;
  18.         }
  19.     }
  20. }


  21. void DelayMS(uint32_t ms)
  22. {
  23.     for (uint32_t i = 0; i < ms; i++)
  24.     {
  25.         SDK_DelayAtLeastUs(1000, SystemCoreClock);
  26.     }
  27. }


  28. /*
  29. * @brief   Application entry point.
  30. */
  31. int main(void) {

  32.          sai_transfer_t xfer;
  33.     /* Init board hardware. */
  34.     BOARD_ConfigMPU();
  35.     BOARD_InitPins();
  36.     BOARD_InitBootClocks();
  37.     BOARD_InitBootPeripherals();
  38. #ifndef BOARD_INIT_DEBUG_CONSOLE_PERIPHERAL
  39.     /* Init FSL debug console. */
  40.     BOARD_InitDebugConsole();
  41. #endif


  42.     PRINTF(" SAI wav module test!\n\r");
  43.     /* Use default setting to init codec */
  44.     if (CODEC_Init(&codecHandle, &boardCodecConfig) != kStatus_Success)
  45.     {
  46.         assert(false);
  47.     }
  48.     /* delay for codec output stable */
  49.     DelayMS(DEMO_CODEC_INIT_DELAY_MS);
  50.     CODEC_SetVolume(&codecHandle,2U,50); // set 50% value



  51.     EnableIRQ(DEMO_SAI_IRQ);
  52.     SAI_TxEnableInterrupts(DEMO_SAI, kSAI_FIFOErrorInterruptEnable);

  53.     PRINTF(" MUSIC PLAY Start!\n\r");
  54.     while (1)
  55.     {
  56.             PRINTF(" MUSIC PLAY Again\n\r");
  57.             isFinished = false;
  58.         while (!isFinished)
  59.         {
  60.             if ((emptyBlock > 0U) && (cpy_index < MUSIC_LEN / BUFFER_SIZE))
  61.             {
  62.                 /* Fill in the buffers. */
  63.                 memcpy((uint8_t *)&buffer[BUFFER_SIZE * (cpy_index % BUFFER_NUM)],
  64.                        (uint8_t *)&music[cpy_index * BUFFER_SIZE], sizeof(uint8_t) * BUFFER_SIZE);
  65.                 emptyBlock--;
  66.                 cpy_index++;
  67.             }
  68.             if (emptyBlock < BUFFER_NUM)
  69.             {
  70.                 /*  xfer structure */
  71.                 xfer.data     = (uint8_t *)&buffer[BUFFER_SIZE * (tx_index % BUFFER_NUM)];
  72.                 xfer.dataSize = BUFFER_SIZE;
  73.                 /* Wait for available queue. */
  74.                 if (kStatus_Success == SAI_TransferSendEDMA(DEMO_SAI, &SAI1_SAI_Tx_eDMA_Handle, &xfer))
  75.                 {
  76.                     tx_index++;
  77.                 }
  78.             }
  79.         }

  80.     }

  81. }

  82. void DEMO_SAITxIRQHandler(void)
  83. {
  84.     /* Clear the FIFO error flag */
  85.     SAI_TxClearStatusFlags(DEMO_SAI, kSAI_FIFOErrorFlag);

  86.     /* Reset FIFO */
  87.     SAI_TxSoftwareReset(DEMO_SAI, kSAI_ResetTypeFIFO);
  88.     SDK_ISR_EXIT_BARRIER;
  89. }
复制代码

四 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位置:

17.jpg

图17

4.1 逻辑分析测试波形

18.jpg

图18

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

19.jpg

图19

第一帧数据为:1010101001010101 0000000000000001

0XAA55 0X0001

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

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


4.2 示波器测试波形

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

20.png

图20


音频实际播放效果,添加配置好的music.h文件,并且让主程序循环播放music文件里面的音频数据。具体测试结果见附件.mp3,附件还添加另外一个左右声道区分明显的音乐文件,可以直接放到代码测试。


MIMXRT1062_SAI_wav.zip (4.01 MB, 下载次数: 27)
回复

使用道具 举报

该用户从未签到

0

主题

14

帖子

0

注册会员

Rank: 2

积分
106
最后登录
2022-10-26
发表于 2021-3-15 16:56:54 | 显示全部楼层
好详细啊,感谢分享
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

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

Archiver|手机版|小黑屋|恩智浦技术社区

GMT+8, 2024-4-25 14:09 , Processed in 0.113473 second(s), 20 queries , MemCache On.

Powered by Discuz! X3.4

Copyright © 2001-2024, Tencent Cloud.

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