i.MX RT1010 之 SAI I2S TDM 传输

一、i.MX RT1010 SAI 简介

i.MX RT1010 的音频接口有:SAI-1、SAI-3、SPDIF (Sony/Philips Digital Interface) 和 MQS (Medium Quality Speaker)。

其中,SAI 就是我们今天介绍的主角,它是用于传输音频数据的同步串行接口,在 RT1010 中有两组 SAI 外设(SAI-1、SAI-3)。



图 1. SAI 接口

可以看到表格中描述 SAI-1 有 2 RX Line、2 TX Line,这里为了防止误解,稍微说明一下,SAI-1 实际上是 1 RX Line + 1 TX Line + 1 RX/TX Line,也就是总共 3 DATA Line,TX、RX 各一条,剩下一条可以做 TX RX。

接下来以 i.MX RT1010 为例,介绍 SAI-1 I2S TDM (Time Division Multiplexing) 传输功能。

 

二、SAI I2S TDM 传输

可以先导入 SDK 例程 evkmimxrt1010_sai_interrupt_transfer,接下来我们以 4slot 双声道(共 8channel)、48K sample rate、24bit data width 为例,在该例程的基础上实现 SAI-1 的 I2S TDM 传输功能。


2.1 SAI
设置

目前在工具 MCUXpresso Config Tools 中没有直接设置 TDM 方式的参数,还是需要在代码中手动配置 TDM 的参数。不过也可以借用该工具配置普通 I2S 的参数,可帮助熟悉代码。 下面我们以 TX 端为例,开始 SAI 的设置(RX 端配置类似):

① SAI clock

比如我们 8channel、48K sample rate、24bit data width,可计算出 I2S 的 bit clock = 8 * 48000 * 24 = 9.216MHz,所以 SAI clock 设置为 9.216MHz 整数倍即可,代码分频后会作为 I2S 的 bit clock 输出。这里我们将时钟配置为了 9.216MHz 的 5 倍,即 46.08MHz。

时钟的配置我们可以借助工具 MCUXpresso Config Tools 的时钟树来进行快速配置,如下图。左边为时钟树配置,右边会自动生成对应的时钟配置代码,可直接用其替换工程中的 clock_config.c 和 .h 。





图 2. 配置 SAI clock

前面提到,SAI clock 会由代码分频后做 I2S 的 bit clock 输出,这里的代码分频是指 SAI 外设配置中设置 bit clock 的部分,如下图所示。



图 3. 设置 bit clock

② 创建发送句柄



图 4. 创建发送句柄

这里回调函数 TxCallback() 是在 TX 传输完成后会调用的,可根据用户的应用层逻辑需求做相应处理。这我们的处理是记标志位表示传输完成,如下图。



图 5. TxCallback

③ 设置 I2S TDM 相关参数

我们可以用函数 SAI_GetTDMConfig() 直接获取默认配置,这样就不需要一个一个设置全部参数,只需要在此基础上修改个别自己特定的参数即可,该函数的第二个入口参数可以选择帧信号的宽度为 1bit clock 还是 1 word,如下图。根据需求设置,这里我们使用的是 1 word。



图 6. 帧信号宽度



图 7. 设置 TDM 参数

上图中,有一个参数 .syncMode 用于配置同步异步模式,这里我们发送端设为异步模式,接收端设为同步模式。

④ 开始发送 TDM 数据

设置好需要发送的数据(该发送数据结构体 tx_xfer 在前面创建发送句柄是有用到,见图 5),如下图。这里我们在 flash 中存了一小段 48K 24bit 的音频数据用于发送。



图 8. 发送 TDM 数据


2.2
测试现象

将代码烧录到 RT1010 EVK 中测试,捕捉 TDM 信号如下图。



图 9. TDM 波形

对比 music 的数组数据,可以看到发送的数据是正确的。



图 10. music 数组

 

三、实验代码

#include "pin_mux.h"
#include "clock_config.h"
#include "board.h"
#include "fsl_debug_console.h"
#include "fsl_sai.h"
#include "music.h"
#include "fsl_codec_common.h"
#include "fsl_wm8960.h"
#include "fsl_codec_adapter.h"

/*******************************************************************************
* Definitions
******************************************************************************/
//----------------------------------------------
#define AUDIO_DataWidth (24U) //(16U) //
//----------------------------------------------

/* SAI instance and clock */
#define DEMO_CODEC_WM8960
#define DEMO_SAI SAI1
#define DEMO_SAI_CHANNEL (0)
#define DEMO_SAI_IRQ SAI1_IRQn
#define DEMO_SAITxIRQHandler SAI1_IRQHandler
#define DEMO_SAI_TX_SYNC_MODE kSAI_ModeAsync
#define DEMO_SAI_RX_SYNC_MODE kSAI_ModeSync
#define DEMO_SAI_MCLK_OUTPUT true
#define DEMO_SAI_MASTER_SLAVE kSAI_Master

#define DEMO_AUDIO_DATA_CHANNEL (8U) //(2U) //

#if (AUDIO_DataWidth == 16)
#define DEMO_AUDIO_BIT_WIDTH kSAI_WordWidth16bits
#elif (AUDIO_DataWidth == 24)
#define DEMO_AUDIO_BIT_WIDTH kSAI_WordWidth24bits
#elif (AUDIO_DataWidth == 32)
#define DEMO_AUDIO_BIT_WIDTH kSAI_WordWidth32bits
#endif

#define DEMO_AUDIO_SAMPLE_RATE (kSAI_SampleRate48KHz) //(kSAI_SampleRate16KHz)
//#define DEMO_AUDIO_MASTER_CLOCK DEMO_SAI_CLK_FREQ
#define DEMO_AUDIO_MASTER_CLOCK BOARD_BOOTCLOCKRUN_SAI1_MCLK1 //16K sample rate : 6.2M / 12.4M

/* I2C instance and clock */
#define DEMO_I2C LPI2C1

#define BOARD_MASTER_CLOCK_CONFIG()
#define BOARD_SAI_RXCONFIG(config, mode)
#ifndef DEMO_CODEC_INIT_DELAY_MS
#define DEMO_CODEC_INIT_DELAY_MS (1000U)
#endif
#ifndef DEMO_CODEC_VOLUME
#define DEMO_CODEC_VOLUME 100U
#endif

/*******************************************************************************

* Prototypes
******************************************************************************/
//extern void BOARD_SAI_RXConfig(sai_transceiver_t *config, sai_sync_mode_t sync);
/*******************************************************************************
* Variables
******************************************************************************/
sai_handle_t txHandle = {0};
sai_handle_t rxHandle = {0}; //增加,Yaky 20230712
static volatile bool isFinished = false;
static volatile bool TxisFinished = false;
static volatile bool RxisFinished = false;
extern codec_config_t boardCodecConfig;
codec_handle_t codecHandle;

uint8_t s_RxBuffer[1024] __attribute__((aligned(4))); //增加,Yaky 20230712
/*******************************************************************************
* Code
******************************************************************************/
void BOARD_EnableSaiMclkOutput(bool enable)
{
if (enable)
{
IOMUXC_GPR->GPR1 |= IOMUXC_GPR_GPR1_SAI1_MCLK_DIR_MASK;
}
else
{
IOMUXC_GPR->GPR1 &= (~IOMUXC_GPR_GPR1_SAI1_MCLK_DIR_MASK);
}
}

static void TxCallback(I2S_Type *base, sai_handle_t *handle, status_t status, void *userData)
{
// isFinished = true;
TxisFinished = true;
}

static void RxCallback(I2S_Type *base, sai_handle_t *handle, status_t status, void *userData)
{
RxisFinished = true;
}

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

/*!
* @brief Main function
*/

int main(void)
{
sai_transfer_t tx_xfer;
sai_transfer_t rx_xfer;
uint32_t temp = 0;
sai_transceiver_t saiConfig;
sai_transceiver_t SAI1_TDM_Txconfig;
sai_transceiver_t SAI1_TDM_Rxconfig;

BOARD_ConfigMPU();
BOARD_InitBootPins();
BOARD_InitBootClocks();
// CLOCK_InitAudioPll(&audioPllConfig);
BOARD_InitDebugConsole();

// /*Clock setting for LPI2C*/
// CLOCK_SetMux(kCLOCK_Lpi2cMux, DEMO_LPI2C_CLOCK_SOURCE_SELECT);
// CLOCK_SetDiv(kCLOCK_Lpi2cDiv, DEMO_LPI2C_CLOCK_SOURCE_DIVIDER);
//
// /*Clock setting for SAI1*/
// CLOCK_SetMux(kCLOCK_Sai1Mux, DEMO_SAI1_CLOCK_SOURCE_SELECT);
// CLOCK_SetDiv(kCLOCK_Sai1PreDiv, DEMO_SAI1_CLOCK_SOURCE_PRE_DIVIDER);
// CLOCK_SetDiv(kCLOCK_Sai1Div, DEMO_SAI1_CLOCK_SOURCE_DIVIDER);

/*Enable MCLK clock*/
BOARD_EnableSaiMclkOutput(true);

PRINTF("\n\r=====================================================\n\r");
PRINTF("SAI interrupt_multi_channel started!\n\r");
uint32_t audio_pll_clk = CLOCK_GetFreq(kCLOCK_AudioPllClk);
PRINTF(" audio_pll_clk = %d, SAI1_CLK = %d\n\r", audio_pll_clk, BOARD_BOOTCLOCKRUN_SAI1_CLK_ROOT);
PRINTF(" music data width = %d bit, sample rate = %d Hz\n\r", AUDIO_DataWidth, DEMO_AUDIO_SAMPLE_RATE);

//---------------------------------- SAI1 ----------------------------------
/* SAI init */
SAI_Init(DEMO_SAI);

//------------------- SAI1 tx -------------------
SAI_TransferTxCreateHandle(DEMO_SAI, &txHandle, TxCallback, &tx_xfer);

SAI_GetTDMConfig(&SAI1_TDM_Txconfig, kSAI_FrameSyncLenPerWordWidth, DEMO_AUDIO_BIT_WIDTH, DEMO_AUDIO_DATA_CHANNEL, kSAI_Channel0Mask);
SAI1_TDM_Txconfig.syncMode = kSAI_ModeAsync; //DEMO_SAI_TX_SYNC_MODE;
SAI1_TDM_Txconfig.masterSlave = kSAI_Master;
SAI1_TDM_Txconfig.frameSync.frameSyncPolarity = kSAI_PolarityActiveHigh;
SAI1_TDM_Txconfig.frameSync.frameSyncEarly = true; //frame sync assert one bit before the first bit of frame
SAI1_TDM_Txconfig.serialData.dataFirstBitShifted = 24U; //TDM
SAI_TransferTxSetConfig(DEMO_SAI, &txHandle, &SAI1_TDM_Txconfig); //saiConfig //SAI1_Tx_config

/* set bit clock divider */
SAI_TxSetBitClockRate(DEMO_SAI, DEMO_AUDIO_MASTER_CLOCK, DEMO_AUDIO_SAMPLE_RATE, DEMO_AUDIO_BIT_WIDTH, DEMO_AUDIO_DATA_CHANNEL);

//------------------- SAI1 rx -------------------
SAI_TransferRxCreateHandle(DEMO_SAI, &rxHandle, RxCallback, &rx_xfer);
SAI_GetTDMConfig(&SAI1_TDM_Rxconfig, kSAI_FrameSyncLenPerWordWidth, DEMO_AUDIO_BIT_WIDTH, DEMO_AUDIO_DATA_CHANNEL, kSAI_Channel0Mask);
SAI1_TDM_Rxconfig.syncMode = kSAI_ModeSync; //DEMO_SAI_RX_SYNC_MODE;
SAI1_TDM_Rxconfig.masterSlave = kSAI_Slave;
SAI1_TDM_Rxconfig.frameSync.frameSyncPolarity = kSAI_PolarityActiveHigh;
SAI1_TDM_Rxconfig.frameSync.frameSyncEarly = true; //frame sync assert one bit before the first bit of frame
SAI1_TDM_Rxconfig.serialData.dataFirstBitShifted = 24U; //TDM
SAI_TransferRxSetConfig(DEMO_SAI, &rxHandle, &SAI1_TDM_Rxconfig);
/* set bit clock divider */
SAI_RxSetBitClockRate(DEMO_SAI, DEMO_AUDIO_MASTER_CLOCK, DEMO_AUDIO_SAMPLE_RATE, DEMO_AUDIO_BIT_WIDTH, DEMO_AUDIO_DATA_CHANNEL);

#if 1 //1: tx 循环播放, 0: tx rx 自发自收

while (1) //循环播放, 20230712
{

/* xfer structure */
temp = (uint32_t)music;
tx_xfer.data = (uint8_t *)temp;
tx_xfer.dataSize = MUSIC_LEN;

SAI_TransferSendNonBlocking(DEMO_SAI, &txHandle, &tx_xfer);
/* Wait until finished */
while (TxisFinished != true)
{
}
TxisFinished = false; //循环播放, 20230712
}

#else

//短接 tx rx 测试 rx
//------------------- I2S3 rx -------------------
rx_xfer.data = &s_RxBuffer[0];
rx_xfer.dataSize = sizeof(s_RxBuffer);
SAI_TransferReceiveNonBlocking(DEMO_SAI, &rxHandle, &rx_xfer);

//------------------- I2S1 tx -------------------
/* xfer structure */
temp = (uint32_t)music;
tx_xfer.data = (uint8_t *)temp;
tx_xfer.dataSize = sizeof(s_RxBuffer); //MUSIC_LEN;
SAI_TransferSendNonBlocking(DEMO_SAI, &txHandle, &tx_xfer);

PRINTF("Wait until finished\n\r ");
/* Wait until finished */
while ((TxisFinished != true) || (RxisFinished != true))
{
}

for(uint8_t j = 0; j < 200; j++)
{
PRINTF("s_RxBuffer[%d] = 0x%x\n\r", j, s_RxBuffer[j]);
}

#endif

PRINTF("\n\r SAI example finished!\n\r ");
while (1)
{
}
}


 

四、参考资料

(1)RT1010 User Manual,可在 NXP 官网下载,网址如下:

https://www.nxp.com/webapp/sps/download/preDownload.jsp?render=true

★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论