【RT摩拳擦掌】RT600 4路音频同步输入1路TDM输出方案

news2024/11/13 8:43:20

【RT摩拳擦掌】RT600 4路音频同步输入1路TDM输出方案

  • 一, 文章简介
  • 二,硬件平台构建
    • 2.1 音频源板
    • 2.2 音频收发板
    • 2.3 双板硬件连接
  • 三,软件方案与软件实现
    • 3.1 方案实现
    • 3.2 软件代码实现
      • 3.2.1 4路I2S接收
      • 3.2.2 I2S DMA pingpong配置
      • 3.2.3 音频数据收到发转存
      • 3.2.4 发送TDM音频代码
      • 3.2.6音频源代码
  • 四,测试结果
    • 4.2 接收拷贝到发送buffer耗时
      • 4.3 4路接收到的数据同步性验证
    • 4.4 发送buffer对应4路音频的TDM情况
    • 4.5 发送48Khz 32bit 8ch的音频数据波形

一, 文章简介

本篇文章旨在在RT685平台上实现4组48Khz 32bit 2ch的音频数据同时输入,然后把收到的数据组装成一路48Khz 32bit 8ch的音频再通过I2S输出。这种方案用法,也是应客户要求去做的,因为客户那边做出来总是有谐波问题,分析客户的情况,发现客户的主要问题有两个:
(1) 谐波问题:收到4路8字节之后直接拷贝到发送buffer去做,这个就会导致时序上的问题,没有考虑到音频数据存储池去缓冲数据,等到接收足够音频数据,至少要大于拷贝发送所需要的时间的问题,所以最后问题体现在客户测试输出音频波形发现有谐波的问题。
(2) 音频同步问题:客户收取4路音频数据之后,测试接收buffer,发现4路数据存在不同步问题。
所以,为了帮助客户,笔者帮客户直接做了这个应用demo,并且做了配套的测试音频源,用来循环发送一组48Khz采样率32bit双通道,固定递增的音频数据,比如0X00-0XFF循环发。
下面是本应用平台框图:
在这里插入图片描述

图1 系统框图

上图中,一块MIMXRT685-EVK实现输出48Khz,32bit2ch的功能,发送数据循环发送:0X00,0X01….0XFF。
另外一块MIMXRT685-EVK是本文的重点,实现4组I2S分别收取数据48khz采样率32bit
2ch,然后把收到的数据,组装成48Khz采样率32bit*8ch的音频数据发送出去。
上图中,为了减少外部线的连接,对于BCLK,WS信号,直接只接一组到I2S3,其他的I2S2,I2S4,I2S5内部共享I2S3的信号。然后,对于DATA数据,在外部做了一根线一分4头的方式,分别接到各组音频接口的data引脚上。
下面就本方案,给出详细的描述。

二,硬件平台构建

下面分别给出两块板子的引脚规划情况,因为平台所用的引脚较多,所以需要具体分配。

2.1 音频源板

一块MIMXRT685-EVK作为音频源,发送48Khz 32bit*2ch的引脚使用情况如下:
在这里插入图片描述

图2 音频源板引脚分配

2.2 音频收发板

另外一块MIMXRT685-EVK作为音频收发板,实现音频源发送过来的4路音频同步数据收取,并且组装成48Khz 32bit*8ch的波形发送出去。
在这里插入图片描述

图3 音频收发板引脚分配

2.3 双板硬件连接

两块板子source和target连接的情况如下:
在这里插入图片描述

图4 双板引脚连接情况

在这里插入图片描述

图5 实物双板连接情况

硬件准备好之后,就是给出软件方案与代码了。

三,软件方案与软件实现

实际在编写代码的过程中,尝试了很多种的方案,比如:
(1) 接收的时候,直接组装成要发送的TDM格式buffer,供给发送去发,但是由于组装成TDM,需要一组I2S接8个,然后做偏移再去收取下一个,如果接收按照8字节DMA搬运,4组I2S的callback进入很频繁,导致CPU load比较大,所以放弃该方案。
(2) 4组I2S各接各的,接10ms的buffer,然后使用DMA的方式去做memory到memory的拷贝,但是由于RT685的DMA比较弱,最多实现32bit 4word=16byte的偏移,也就是16字节的偏移,但是实际上,一组音频数据就是32bit2,4组就是32bit8=32byte的偏移,所以DMA无法满足,所以放弃DMA的memory到memory拷贝方案,改用memcpy。
(3) 使用I2S_RxTransferReceiveDMA函数去做DMA的接收,但是实际上,调用一组就直接开始接收,等到下一组的I2S接口去调用I2S_RxTransferReceiveDMA,已经出现的异步的情况,纵然在I2S_RxTransferReceiveDMA中关闭I2S的使能,等I2S_RxTransferReceiveDMA的几组I2S都调用完再去做4组I2S的使能,这种方式只能实现第一组数据的收取同步,因为在后面,需要到callback里面去重新触发第二帧数据的接收,所以4组I2S的callback调用I2S_RxTransferReceiveDMA,必然还会出现新的同步问题,所以放弃该方法,考虑使用两组DMA descriptor的方式做乒乓,这样会一直循环去接收,无需CPU代码的介入。

3.1 方案实现

上面已经说明了几种方案的情况,最终选择使用4路音频分别收取音频数据并且缓存10ms的音频数据buffer,接收buffer到发送buffer的转换采用memcpy方式,并且测试这个拷贝时间是否能够满足实际的需求,保证在接收buffer的缓存池大于这个拷贝的时间,足以给发送buffer准备的时间。
接收数据搬运转移的方案如下:
在这里插入图片描述

图6 数据buffer转移情况

上面是4组I2S分别接收自己的10ms数据,buffer实际上上准备了20ms,单次DMA接收一帧是10ms,然后另外10ms是做pingpong buffer。发送buffer是用来把收到的4组I2S buffer拷贝成TDM格式的32bit8ch的数组,然后也做两组乒乓buffer。
实际上就是缓存10ms的数据,buffer准备两组10ms,当第一个10ms帧接收完之后,用第二个buffer去接收,同时把第一个buffer的数据拷贝给发送buffer的第一个buffer,并且使用这个第一个buffer去做发送,发送完成之后,转到第二个buffer去接并且发送。
这样只要时间控制好,就不会出现数据错误的问题。
10ms的数据量是3840Byte,因为对于接收是48Khz,也就是1s有48000帧,每帧是32bit
2=8Byte,则10ms=>4800*8Byte=38400Byte。

3.2 软件代码实现

软件代码实现部分,主要分为4路I2S接收信号分享,I2S DMA pingpong配置,数据转运,发送I2S等几部分,下面给出详细情况

3.2.1 4路I2S接收

从上面可以知道,4路I2S的接收信号并不是完全拉线去接的,而是采用了BCLK,WS信号共享,DATA分别接收的方法。
I2S2,I2S4,I2S5共享I2S3的BCLK,WS代码如下:

 /* Set shared signal set 0: SCK, WS from Flexcomm1 */
    I2S_BRIDGE_SetShareSignalSrc(kI2S_BRIDGE_ShareSet0, kI2S_BRIDGE_SignalSCK, kI2S_BRIDGE_Flexcomm3);
    I2S_BRIDGE_SetShareSignalSrc(kI2S_BRIDGE_ShareSet0, kI2S_BRIDGE_SignalWS, kI2S_BRIDGE_Flexcomm3);
    /* Set flexcomm3 SCK, WS from shared signal set 0 */
    I2S_BRIDGE_SetFlexcommSignalShareSet(kI2S_BRIDGE_Flexcomm2, kI2S_BRIDGE_SignalSCK, kI2S_BRIDGE_ShareSet0);
    I2S_BRIDGE_SetFlexcommSignalShareSet(kI2S_BRIDGE_Flexcomm2, kI2S_BRIDGE_SignalWS, kI2S_BRIDGE_ShareSet0);

    I2S_BRIDGE_SetFlexcommSignalShareSet(kI2S_BRIDGE_Flexcomm4, kI2S_BRIDGE_SignalSCK, kI2S_BRIDGE_ShareSet0);
    I2S_BRIDGE_SetFlexcommSignalShareSet(kI2S_BRIDGE_Flexcomm4, kI2S_BRIDGE_SignalWS, kI2S_BRIDGE_ShareSet0);

    I2S_BRIDGE_SetFlexcommSignalShareSet(kI2S_BRIDGE_Flexcomm5, kI2S_BRIDGE_SignalSCK, kI2S_BRIDGE_ShareSet0);
    I2S_BRIDGE_SetFlexcommSignalShareSet(kI2S_BRIDGE_Flexcomm5, kI2S_BRIDGE_SignalWS, kI2S_BRIDGE_ShareSet0);

3.2.2 I2S DMA pingpong配置

为了实现4路音频同步,去接收10ms的音频buffer,采用了两个I2S DMA descriptors实现乒乓功能去轮流采集数据到两个乒乓buffer。
代码如下:

#define I2S_BUFFER_SIZE 3840 //10ms

SDK_ALIGN(static dma_descriptor_t I2S2_s_rxDmaDescriptors[2U], FSL_FEATURE_DMA_LINK_DESCRIPTOR_ALIGN_SIZE);
SDK_ALIGN(static dma_descriptor_t I2S3_s_rxDmaDescriptors[2U], FSL_FEATURE_DMA_LINK_DESCRIPTOR_ALIGN_SIZE);
SDK_ALIGN(static dma_descriptor_t I2S4_s_rxDmaDescriptors[2U], FSL_FEATURE_DMA_LINK_DESCRIPTOR_ALIGN_SIZE);
SDK_ALIGN(static dma_descriptor_t I2S5_s_rxDmaDescriptors[2U], FSL_FEATURE_DMA_LINK_DESCRIPTOR_ALIGN_SIZE);

SDK_ALIGN(static uint8_t I2S2_s_Buffer[2][I2S_BUFFER_SIZE],   sizeof(uint32_t));
SDK_ALIGN(static uint8_t I2S3_s_Buffer[2][I2S_BUFFER_SIZE],   sizeof(uint32_t));
SDK_ALIGN(static uint8_t I2S4_s_Buffer[2][I2S_BUFFER_SIZE],   sizeof(uint32_t));
SDK_ALIGN(static uint8_t I2S5_s_Buffer[2][I2S_BUFFER_SIZE],   sizeof(uint32_t));

static i2s_transfer_t I2S2_s_RxTransfer[2] = {{
                                                 .data     = I2S2_s_Buffer[0],
                                                 .dataSize = I2S_BUFFER_SIZE,
                                              },
                                              {
                                                 .data     = I2S2_s_Buffer[1],
                                                 .dataSize = I2S_BUFFER_SIZE,
                                              }};
static i2s_transfer_t I2S3_s_RxTransfer[2] = {{
                                                 .data     = I2S3_s_Buffer[0],
                                                 .dataSize = I2S_BUFFER_SIZE,
                                              },
                                              {
                                                 .data     = I2S3_s_Buffer[1],
                                                 .dataSize = I2S_BUFFER_SIZE,
                                              }};
static i2s_transfer_t I2S4_s_RxTransfer[2] = {{
                                                 .data     = I2S4_s_Buffer[0],
                                                 .dataSize = I2S_BUFFER_SIZE,
                                              },
                                              {
                                                 .data     = I2S4_s_Buffer[1],
                                                 .dataSize = I2S_BUFFER_SIZE,
                                              }};
static i2s_transfer_t I2S5_s_RxTransfer[2] = {{
                                                 .data     = I2S5_s_Buffer[0],
                                                 .dataSize = I2S_BUFFER_SIZE,
                                              },
                                              {
                                                 .data     = I2S5_s_Buffer[1],
                                                 .dataSize = I2S_BUFFER_SIZE,
                                              }};

    I2S_RxGetDefaultConfig(&I2S2_s_RxConfig);
    I2S2_s_RxConfig.divider     = DEMO_I2S_CLOCK_DIVIDER;
    I2S2_s_RxConfig.masterSlave = DEMO_I2S_TX_MODE;//DEMO_I2S_RX_MODE
    I2S_RxInit(DEMO_I2S2_RX, &I2S2_s_RxConfig);

    I2S_RxGetDefaultConfig(&I2S3_s_RxConfig);
    I2S3_s_RxConfig.divider     = DEMO_I2S_CLOCK_DIVIDER;
    I2S3_s_RxConfig.masterSlave = DEMO_I2S_TX_MODE;//DEMO_I2S_RX_MODE
    I2S_RxInit(DEMO_I2S3_RX, &I2S3_s_RxConfig);

    I2S_RxGetDefaultConfig(&I2S4_s_RxConfig);
    I2S4_s_RxConfig.divider     = DEMO_I2S_CLOCK_DIVIDER;
    I2S4_s_RxConfig.masterSlave = DEMO_I2S_TX_MODE;//DEMO_I2S_RX_MODE
    I2S_RxInit(DEMO_I2S4_RX, &I2S4_s_RxConfig);

    I2S_RxGetDefaultConfig(&I2S5_s_RxConfig);
    I2S5_s_RxConfig.divider     = DEMO_I2S_CLOCK_DIVIDER;
    I2S5_s_RxConfig.masterSlave = DEMO_I2S_TX_MODE;//DEMO_I2S_RX_MODE
    I2S_RxInit(DEMO_I2S5_RX, &I2S5_s_RxConfig);


    DMA_Init(DEMO_DMA);

    DMA_EnableChannel(DEMO_DMA, DEMO_I2S2_RX_CHANNEL);
    DMA_SetChannelPriority(DEMO_DMA, DEMO_I2S2_RX_CHANNEL, kDMA_ChannelPriority1);
    DMA_CreateHandle(&I2S2_s_DmaRxHandle, DEMO_DMA, DEMO_I2S2_RX_CHANNEL);
    I2S_RxTransferCreateHandleDMA(DEMO_I2S2_RX, &I2S2_s_RxHandle, &I2S2_s_DmaRxHandle, I2S2_RxCallback, (void *)&I2S2_s_RxTransfer);

    DMA_EnableChannel(DEMO_DMA, DEMO_I2S3_RX_CHANNEL);
    DMA_SetChannelPriority(DEMO_DMA, DEMO_I2S3_RX_CHANNEL, kDMA_ChannelPriority1);
    DMA_CreateHandle(&I2S3_s_DmaRxHandle, DEMO_DMA, DEMO_I2S3_RX_CHANNEL);
    I2S_RxTransferCreateHandleDMA(DEMO_I2S3_RX, &I2S3_s_RxHandle, &I2S3_s_DmaRxHandle, I2S3_RxCallback, (void *)&I2S3_s_RxTransfer);

    DMA_EnableChannel(DEMO_DMA, DEMO_I2S4_RX_CHANNEL);
    DMA_SetChannelPriority(DEMO_DMA, DEMO_I2S4_RX_CHANNEL, kDMA_ChannelPriority1);
    DMA_CreateHandle(&I2S4_s_DmaRxHandle, DEMO_DMA, DEMO_I2S4_RX_CHANNEL);
    I2S_RxTransferCreateHandleDMA(DEMO_I2S4_RX, &I2S4_s_RxHandle, &I2S4_s_DmaRxHandle, I2S4_RxCallback, (void *)&I2S4_s_RxTransfer);

    DMA_EnableChannel(DEMO_DMA, DEMO_I2S5_RX_CHANNEL);
    DMA_SetChannelPriority(DEMO_DMA, DEMO_I2S5_RX_CHANNEL, kDMA_ChannelPriority2);
    DMA_CreateHandle(&I2S5_s_DmaRxHandle, DEMO_DMA, DEMO_I2S5_RX_CHANNEL);
    I2S_RxTransferCreateHandleDMA(DEMO_I2S5_RX, &I2S5_s_RxHandle, &I2S5_s_DmaRxHandle, I2S5_RxCallback, (void *)&I2S5_s_RxTransfer);

    I2S_TransferInstallLoopDMADescriptorMemory(&I2S2_s_RxHandle, I2S2_s_rxDmaDescriptors, 2U);
    I2S_TransferInstallLoopDMADescriptorMemory(&I2S3_s_RxHandle, I2S3_s_rxDmaDescriptors, 2U);
    I2S_TransferInstallLoopDMADescriptorMemory(&I2S4_s_RxHandle, I2S4_s_rxDmaDescriptors, 2U);
I2S_TransferInstallLoopDMADescriptorMemory(&I2S5_s_RxHandle, I2S5_s_rxDmaDescriptors, 2U);

    if (I2S_TransferReceiveLoopDMA(DEMO_I2S2_RX, &I2S2_s_RxHandle, &I2S2_s_RxTransfer[0], 2U) != kStatus_Success)
    {
        assert(false);
    }
    if (I2S_TransferReceiveLoopDMA(DEMO_I2S3_RX, &I2S3_s_RxHandle, &I2S3_s_RxTransfer[0], 2U) != kStatus_Success)
    {
        assert(false);
    }
    if (I2S_TransferReceiveLoopDMA(DEMO_I2S4_RX, &I2S4_s_RxHandle, &I2S4_s_RxTransfer[0], 2U) != kStatus_Success)
    {
        assert(false);
    }
    if (I2S_TransferReceiveLoopDMA(DEMO_I2S5_RX, &I2S5_s_RxHandle, &I2S5_s_RxTransfer[0], 2U) != kStatus_Success)
    {
        assert(false);
    }
    I2S_Enable(DEMO_I2S2_RX);
    I2S_Enable(DEMO_I2S3_RX);
    I2S_Enable(DEMO_I2S4_RX);
    I2S_Enable(DEMO_I2S5_RX);

这里,代码有修改,主要是fsl_i2s_dma.c的I2S_TransferLoopDMA函数,屏蔽掉:
I2S_Enable(base);
为了实现4路同步收取的功能。

3.2.3 音频数据收到发转存

因为收取的时候是每个音频接口分别轮流接自己的2ch数据,但是发送是需要发送4路收到的音频双通道数据,也就是32bit*8ch的数据,所以在接收了ping之后,需要把ping的数据转存到发送ping buffer里面去,转存的代码如下:

#define I2S_BUFFER_SIZE 3840 //10ms
SDK_ALIGN(static uint8_t I2S2_s_Buffer[2][I2S_BUFFER_SIZE],   sizeof(uint32_t));
SDK_ALIGN(static uint8_t I2S3_s_Buffer[2][I2S_BUFFER_SIZE],   sizeof(uint32_t));
SDK_ALIGN(static uint8_t I2S4_s_Buffer[2][I2S_BUFFER_SIZE],   sizeof(uint32_t));
SDK_ALIGN(static uint8_t I2S5_s_Buffer[2][I2S_BUFFER_SIZE],   sizeof(uint32_t));
SDK_ALIGN(static uint8_t I2S1_s_Buffer[2][I2S_BUFFER_SIZE*4], sizeof(uint32_t));   
if( s_pingpong ==  1)
         {
             for(ch = 0;ch < 480; ch++) //480=I2S_BUFFER_SIZE(3840)/8
             {
                 memcpy(&I2S1_s_Buffer[0][0  + (32*ch)], &I2S2_s_Buffer[0][8*ch], 8);
                 memcpy(&I2S1_s_Buffer[0][8  + (32*ch)], &I2S3_s_Buffer[0][8*ch], 8);
                 memcpy(&I2S1_s_Buffer[0][16 + (32*ch)], &I2S4_s_Buffer[0][8*ch], 8);
                 memcpy(&I2S1_s_Buffer[0][24 + (32*ch)], &I2S5_s_Buffer[0][8*ch], 8);
             }
         }
         else
         {
             for(ch = 0;ch < 480; ch++)
             {
                 memcpy(&I2S1_s_Buffer[1][0  + (32*ch)], &I2S2_s_Buffer[1][8*ch], 8);
                 memcpy(&I2S1_s_Buffer[1][8  + (32*ch)], &I2S3_s_Buffer[1][8*ch], 8);
                 memcpy(&I2S1_s_Buffer[1][16 + (32*ch)], &I2S4_s_Buffer[1][8*ch], 8);
                 memcpy(&I2S1_s_Buffer[1][24 + (32*ch)], &I2S5_s_Buffer[1][8*ch], 8);
             }
         }

3.2.4 发送TDM音频代码

发送代码采用的也是I2S DMA的方式,只不过因为不需要多个通道同时发送,只是单路,所以不用考虑同步问题,没有采用DMA descriptor,直接在发送buffer准备好之后,采用I2S_TxTransferSendDMA方式去做,代码如下:

I2S_TxGetDefaultConfig(&I2S1_s_TxConfig);
    I2S1_s_TxConfig.divider     = DEMO_I2S1_CLOCK_DIVIDER;
    I2S1_s_TxConfig.masterSlave = kI2S_MasterSlaveNormalMaster;
    I2S1_s_TxConfig.wsPol       = true;
    I2S1_s_TxConfig.mode        = kI2S_ModeDspWsLong;//kI2S_ModeDspWsShort;
    I2S1_s_TxConfig.dataLength  = 32U;
    I2S1_s_TxConfig.frameLength = 32 * 8U;
    I2S1_s_TxConfig.position    = DEMO_TDM_DATA_START_POSITION;
    I2S1_s_TxConfig.pack48      = true;
	I2S_TxInit(DEMO_I2S1_TX, &I2S1_s_TxConfig);
	I2S_EnableSecondaryChannel(DEMO_I2S1_TX, kI2S_SecondaryChannel1, false, 64 + DEMO_TDM_DATA_START_POSITION);
	I2S_EnableSecondaryChannel(DEMO_I2S1_TX, kI2S_SecondaryChannel2, false, 128 + DEMO_TDM_DATA_START_POSITION);
	I2S_EnableSecondaryChannel(DEMO_I2S1_TX, kI2S_SecondaryChannel3, false, 192 + DEMO_TDM_DATA_START_POSITION);

    DMA_EnableChannel(DEMO_DMA, DEMO_I2S1_TX_CHANNEL);
    DMA_SetChannelPriority(DEMO_DMA, DEMO_I2S1_TX_CHANNEL, kDMA_ChannelPriority3);
    DMA_CreateHandle(&I2S1_s_DmaTxHandle, DEMO_DMA, DEMO_I2S1_TX_CHANNEL);
I2S_TxTransferCreateHandleDMA(DEMO_I2S1_TX, &I2S1_s_TxHandle, &I2S1_s_DmaTxHandle, I2S1_TxCallback, (void *)&I2S1_s_TxTransfer);

        if( s_pingpong ==  1)
         {
        	 I2S1_s_TxTransfer.data     = I2S1_s_Buffer[0];
        	 I2S1_s_TxTransfer.dataSize = I2S_BUFFER_SIZE*4;
        	 I2S_TxTransferSendDMA(DEMO_I2S1_TX, &I2S1_s_TxHandle, I2S1_s_TxTransfer);
         }
         else
         {
        	 I2S1_s_TxTransfer.data     = I2S1_s_Buffer[1];
        	 I2S1_s_TxTransfer.dataSize = I2S_BUFFER_SIZE*4;
        	 I2S_TxTransferSendDMA(DEMO_I2S1_TX, &I2S1_s_TxHandle, I2S1_s_TxTransfer);
         }

3.2.5 发送接收I2S callback处理
对于接收的I2S2,3,4,5一共4路,每次接收完10ms的数据,会进入一次callback,在callback里面只需要记录下标志,当4路标志都记录完整之后,说明4路同一10ms的数据都接完,就可以进行数据拷贝给发送buffer了。当然,为了测试callback的进入频率是不是10ms一次,本文做了一个GPIO反正在callback用来测试。
下面给出记录I2S callback的代码

static void I2S2_RxCallback(I2S_Type *base, i2s_dma_handle_t *handle, status_t completionStatus, void *userData)
{
	 s_allRXTriggerred |= 0x01;
}


static void I2S3_RxCallback(I2S_Type *base, i2s_dma_handle_t *handle, status_t completionStatus, void *userData)
{
	s_allRXTriggerred |= 0x02;
}

static void I2S4_RxCallback(I2S_Type *base, i2s_dma_handle_t *handle, status_t completionStatus, void *userData)
{
	s_allRXTriggerred |= 0x04;
}


static void I2S5_RxCallback(I2S_Type *base, i2s_dma_handle_t *handle, status_t completionStatus, void *userData)
{
    /* Enqueue the same original buffer all over again */
	s_allRXTriggerred |= 0x08;
	GPIO_PortToggle(GPIO, 1, 1<<0);
    if( s_pingpong == 0)
    {
   	 s_pingpong = 1;
    }
    else
    {
   	 s_pingpong = 0;
    }
}

static void I2S1_TxCallback(I2S_Type *base, i2s_dma_handle_t *handle, status_t completionStatus, void *userData)
{
	GPIO_PortToggle(GPIO, 1, 1<<8);
	//__NOP();
}


至此,对于一块MIMXRT685-EVK用来做4路接收1路TDM发送的所有功能已经完成了。

3.2.6音频源代码

音频源是在另外一块的MIMXRT685-EVK上面做的发送48Khz,32bit*2ch的音频数据,数据是0X00-0XFF循环发送的。
代码如下:

int main(void)
{
    BOARD_InitBootPins();
    BOARD_InitBootClocks();
    BOARD_InitDebugConsole();
    BOARD_I3C_ReleaseBus();
    BOARD_InitI3CPins();

    CLOCK_EnableClock(kCLOCK_InputMux);

    /* attach main clock to I3C (500MHz / 20 = 25MHz). */
    CLOCK_AttachClk(kMAIN_CLK_to_I3C_CLK);
    CLOCK_SetClkDiv(kCLOCK_DivI3cClk, 20);

    /* attach AUDIO PLL clock to FLEXCOMM1 (I2S1) */
    CLOCK_AttachClk(kAUDIO_PLL_to_FLEXCOMM1);
    /* attach AUDIO PLL clock to FLEXCOMM3 (I2S3) */
    CLOCK_AttachClk(kAUDIO_PLL_to_FLEXCOMM3);

    /* attach AUDIO PLL clock to MCLK */
    CLOCK_AttachClk(kAUDIO_PLL_to_MCLK_CLK);
    CLOCK_SetClkDiv(kCLOCK_DivMclkClk, 1);
    SYSCTL1->MCLKPINDIR = SYSCTL1_MCLKPINDIR_MCLKPINDIR_MASK;

    wm8904Config.i2cConfig.codecI2CSourceClock = CLOCK_GetI3cClkFreq();
    wm8904Config.mclk_HZ                       = CLOCK_GetMclkClkFreq();

    /* Set shared signal set 0: SCK, WS from Flexcomm1 */
    I2S_BRIDGE_SetShareSignalSrc(kI2S_BRIDGE_ShareSet0, kI2S_BRIDGE_SignalSCK, kI2S_BRIDGE_Flexcomm1);
    I2S_BRIDGE_SetShareSignalSrc(kI2S_BRIDGE_ShareSet0, kI2S_BRIDGE_SignalWS, kI2S_BRIDGE_Flexcomm1);
    /* Set flexcomm3 SCK, WS from shared signal set 0 */
    I2S_BRIDGE_SetFlexcommSignalShareSet(kI2S_BRIDGE_Flexcomm3, kI2S_BRIDGE_SignalSCK, kI2S_BRIDGE_ShareSet0);
    I2S_BRIDGE_SetFlexcommSignalShareSet(kI2S_BRIDGE_Flexcomm3, kI2S_BRIDGE_SignalWS, kI2S_BRIDGE_ShareSet0);
#if 1
    PRINTF("Configure codec\r\n");

    /* protocol: i2s
     * sampleRate: 48K
     * bitwidth:16
     */
    if (CODEC_Init(&codecHandle, &boardCodecConfig) != kStatus_Success)
    {
        PRINTF("codec_Init failed!\r\n");
        assert(false);
    }

    /* Initial volume kept low for hearing safety.
     * Adjust it to your needs, 0-100, 0 for mute, 100 for maximum volume.
     */
    if (CODEC_SetVolume(&codecHandle, kCODEC_PlayChannelHeadphoneLeft | kCODEC_PlayChannelHeadphoneRight,
                        DEMO_CODEC_VOLUME) != kStatus_Success)
    {
        assert(false);
    }

    PRINTF("Configure I2S\r\n");
#endif
    /*
     * masterSlave = kI2S_MasterSlaveNormalMaster;
     * mode = kI2S_ModeI2sClassic;
     * rightLow = false;
     * leftJust = false;
     * pdmData = false;
     * sckPol = false;
     * wsPol = false;
     * divider = 1;
     * oneChannel = false;
     * dataLength = 16;
     * frameLength = 32;
     * position = 0;
     * watermark = 4;
     * txEmptyZero = true;
     * pack48 = false;
     */
    I2S_TxGetDefaultConfig(&s_TxConfig);
    s_TxConfig.divider     = DEMO_I2S_CLOCK_DIVIDER;
    s_TxConfig.masterSlave = DEMO_I2S_TX_MODE;

    I2S_TxInit(DEMO_I2S_TX, &s_TxConfig);

    DMA_Init(DEMO_DMA);

    DMA_EnableChannel(DEMO_DMA, DEMO_I2S_TX_CHANNEL);
    DMA_SetChannelPriority(DEMO_DMA, DEMO_I2S_TX_CHANNEL, kDMA_ChannelPriority3);
    DMA_CreateHandle(&s_DmaTxHandle, DEMO_DMA, DEMO_I2S_TX_CHANNEL);

    StartSoundPlayback();

    while (1)
    {
    }
}

static void StartSoundPlayback(void)
{
    PRINTF("Setup looping playback of sine wave\r\n");

    s_TxTransfer.data     = &g_Music[0];
    s_TxTransfer.dataSize = sizeof(g_Music);

    I2S_TxTransferCreateHandleDMA(DEMO_I2S_TX, &s_TxHandle, &s_DmaTxHandle, TxCallback, (void *)&s_TxTransfer);
    /* need to queue two transmit buffers so when the first one
     * finishes transfer, the other immediatelly starts */
    I2S_TxTransferSendDMA(DEMO_I2S_TX, &s_TxHandle, s_TxTransfer);
    I2S_TxTransferSendDMA(DEMO_I2S_TX, &s_TxHandle, s_TxTransfer);
}

static void TxCallback(I2S_Type *base, i2s_dma_handle_t *handle, status_t completionStatus, void *userData)
{
    /* Enqueue the same original buffer all over again */
    i2s_transfer_t *transfer = (i2s_transfer_t *)userData;
    I2S_TxTransferSendDMA(base, handle, *transfer);
}

音频数据buffer:
在这里插入图片描述

图7 音频源发送buffer

给出对应的测结果情况:
在这里插入图片描述

图8 音频源发送数据测试

可以看到,音频源发送出来的数据是循环的,并且能够一直递增循环发送。

四,测试结果

关于测试结果需要验证几个点:
(1)4路音频收取pingpong buffer,单个buffer是否是10ms,也就是10ms的音频数据池。
(2)数据memory拷贝时间有多少,是否会超出接收音频数据池长度。
(3)接收的4路数据是否同步,组装好的发送buffer数据是否是对应的4路2ch数据组装的32bit8ch数据。
(4)发送出来的音频波形是否是正确的32bit
8ch的TDM数据。
下面就这几点给出验证测试结果
4.1 4路音频10ms数据池
这个验证很简单,定义一个引脚GPIO,初始化输出0,然后在接收的callback中断中反转,本文选择在I2S5的callback反转,结果测试如下:
在这里插入图片描述

图9 黄色10ms

通道1就是因为接收的callback反转情况,可以看到是准确的10ms。
这里给一个测试的总图:
在这里插入图片描述

图 10 时间测试总图
Ch1: I2S5 callback进入频率 Ch2: memory 拷贝时间 Ch3: 发送callback进入频率 可以看到其实收发的频率都是10ms,因为对于发送,也是发送的48Khz,只不过因为是8ch,数据量相对接收翻了4倍,需要把接收的4个通道数据全部塞进去。

4.2 接收拷贝到发送buffer耗时

对于数据拷贝,也就是把4路I2S分别接收到4个buffer里面的数据组装到发送的buffer中,这个时间测试是在示波器的第二通道,结果如下:
在这里插入图片描述

图 11 拷贝时间

可以看到拷贝时间不到500us,远远小于音频接收数据池的10ms,所以随性使用memcpy,完全不需要担心拷贝时间太长,也正好弥补了之前想用DMA做memory到memory拷贝,因为DMA性能问题不能实现的遗憾。

4.3 4路接收到的数据同步性验证

为了验证同步性,本文做了接收100个10ms之后关闭4路I2S接收通道,把对应的4路音频接收buffer全部打印出来。4路结果分别如下:
在这里插入图片描述

图12 I2S2接收buffer
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b876b0c3ec0146d8bae1e1b08783098f.jpeg#pic_center)
图13 I2S3接收buffer
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/26b8607f6d8a42c6b7cf6b71f47fc83c.jpeg#pic_center)
图14 I2S4接收buffer
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6037e702702e404f895e27052831afb4.jpeg#pic_center)
图15 I2S5接收buffer

可以看到,4路的I2S的接收buffer数据完全同步了,而且都是从0XB8开始的。

4.4 发送buffer对应4路音频的TDM情况

发送buffer是在100次接收打印接收buffer之后,然后做了memcpy到发送buffer,打印出的发送buffer数据情况:
在这里插入图片描述

图16 I2S1发送buffer
可以看到,buffer也是从0XB8开始的,并且4组接收数据到拷贝到发送的buffer中,组装成了32bit*8ch的数据。可以看到TDM发送buffer也是正确的。

4.5 发送48Khz 32bit 8ch的音频数据波形

在这里插入图片描述

图17 收发音频波形

上面一组是音频源的波形,下面一组是TDM发送的波形。
由于逻辑分析仪解析软件的能力限制,最多只能解析2ch 64bit的数据,所以这里只能看到部分的数据,但是从波形上看,可以看到发送TDM的波形是能够做到32bit*8ch的,而且在一帧之内的每8byte数据是一样的,这也见解说明了4路音频收取的同步性。
上面图中,实际上ch2的数据是00,01,02,03,04,05,06,07,一帧内4组一样的数据,看波形也可以看到是4组一样的,4组2ch够成了32bit 8ch的TDM。
最后,再给一张从示波器上测试的发送TDM波形:
在这里插入图片描述

图18 发送TDM波形

可以看到,BCLK=12.28Mhz,和期望的48khz32bit8=12.288Mhz是吻合的。
WS信号测出来也是48Khz满足设定的48Khz采样率。
DATA也是有数据改变在传送,而且可以看到一帧内的波形规律是由大概4组重复的,也可以说明4组收取数据是同步的。
至此,RT600 4通道48KHZ 32bit2ch输入,组装成48Khz 32bit8ch输出的功能已经实现!
代码源码链接:
https://community.nxp.com/t5/i-MX-RT-Knowledge-Base/RT600-4-I2S-input-to-1-TDM-output-solution/tac-p/1914092#M218

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1942385.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

卧室激光投影仪推荐一下哪款效果最好?当贝X5S亮度卧室开灯照样清晰

现在家庭卧室装投影仪也不是什么稀奇的事情了&#xff0c;外面客厅看电视机&#xff0c;里面卧室投影仪直接投白墙各有各的优势。躺在卧室的床上&#xff0c;看超大屏投影真的很惬意。卧室投影的品类比较多&#xff0c;有些价格便宜的投影宣传说卧室看很适合&#xff0c;其实不…

设计模式12-构建器

设计模式12-构建器 由来和动机原理思想构建器模式的C代码实现构建器模式中的各个组件详解1. 产品类&#xff08;Product&#xff09;2. 构建类&#xff08;Builder&#xff09;3. 具体构建类&#xff08;ConcreteBuilder&#xff09;4. 指挥者类&#xff08;Director&#xff0…

实战:OpenFeign使用以及易踩坑说明

OpenFeign是SpringCloud中的重要组件&#xff0c;它是一种声明式的HTTP客户端。使用OpenFeign调用远程服务就像调用本地方法一样&#xff0c;但是如果使用不当&#xff0c;很容易踩到坑。 Feign 和OpenFeign Feign Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客…

rabbitmq生产与消费

一、rabbitmq发送消息 一、简单模式 概述 一个生产者一个消费者模型 代码 //没有交换机&#xff0c;两个参数为routingKey和消息内容 rabbitTemplate.convertAndSend("test1_Queue","haha");二、工作队列模式 概述 一个生产者&#xff0c;多个消费者&a…

C4D2024软件下载+自学C4D 从入门到精通【学习视频教程全集】+【素材笔记】

软件介绍与下载&#xff1a; 链接&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1n8cripcv6ZTx4TBNj5N04g?pwdhfg5 提取码&#xff1a;hfg5 基础命令的讲解&#xff1a; 掌握软件界面和基础操作界面。学习常用的基础命令&#xff0c;如建模、材质、灯光、摄像机…

设计模式-领域逻辑模式-结构映射模式

对象和关系之间的映射&#xff0c;关键问题在于二者处理连接的方式不同。 表现出两个问题&#xff1a; 表现方法不同。对象是通过在运行时&#xff08;内存管理环境或内存地址&#xff09;中保存引用的方式来处理连接的&#xff0c;关系数据库则通过创建到另外一个表的键值来处…

昇思25天学习打卡营第19天|munger85

Diffusion扩散模型 它并没有那么复杂&#xff0c;它们都将噪声从一些简单分布转换为数据样本&#xff0c;Diffusion也是从纯噪声开始通过一个神经网络学习逐步去噪&#xff0c;最终得到一个实际图像 def rearrange(head, inputs): b, hc, x, y inputs.shape c hc // head r…

大数据平台之HBase

HBase是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统&#xff0c;是Apache Hadoop生态系统的重要组成部分。它特别适合大规模结构化和半结构化数据的存储和检索&#xff0c;能够处理实时读写和批处理工作负载。以下是对HBase的详细介绍。 1. 核心概念 1.1 表&#x…

TIA博途V19无法勾选来自远程对象的PUT/GET访问的解决办法

TIA博途V19无法勾选来自远程对象的PUT/GET访问的解决办法 TIA博途升级到V19之后,1500CPU也升级到了V3.1的固件,1200CPU升级到了V4.6.1的固件, 固件升级之后,又出现了很多问题,如下图所示,在组态的时候会多出一些东西, 添加CPU之后,在属性界面可以看到“允许来自远程对象…

第二讲:NJ网络配置

Ethernet/IP网络拓扑结构 一. NJ EtherNet/IP 1、网络端口位置 NJ的CPU上面有两个RJ45的网络接口,其中一个是EtherNet/IP网络端口(另一个是EtherCAT的网络端口) 2、网络作用 如图所示,EtherNet/IP网络既可以做控制器与控制器之间的通信,也可以实现与上位机系统的对接通…

python爬虫基础——Webbot库介绍

本文档面向对自动化网页交互、数据抓取和网络自动化任务感兴趣的Python开发者。无论你是初学者还是有经验的开发者&#xff0c;Webbot库都能为你的自动化项目提供强大的支持。 Webbot库概述 Webbot是一个专为Python设计的库&#xff0c;用于简化网页自动化任务。它基于Seleniu…

高速ADC模拟输入接口设计

目录 基本输入接口考虑 输入阻抗 输入驱动 带宽和通带平坦度 噪声 失真 变压器耦合前端 有源耦合前端网络 基本输入接口考虑 采用高输入频率、高速模数转换器(ADC)的系统设计是一 项具挑战性的任务。ADC输入接口设计有6个主要条件&#xff1a; 输入阻抗、输入驱动、带宽…

【RaspberryPi】树莓派系统UI优化

接上文&#xff0c;如何去定制一个树莓派的桌面系统&#xff0c;还是以CM4为例。 解除CM4上电USB无法使用问题 将烧录好的tf卡通过读卡器插入到电脑上&#xff0c;进入boot磁盘&#xff0c;里面有一个Config文件&#xff0c;双击用记事本打开&#xff0c;在【pi4】一栏里加入一…

农业农村大数据底座:实现智慧农业的关键功能

随着信息技术的快速发展&#xff0c;农业领域也在逐步实现数字化转型。农业农村大数据底座作为支持智慧农业发展的重要基础设施&#xff0c;承载了多种关键功能&#xff0c;为农业生产、管理和决策提供了前所未有的支持和可能性。 ### 1. 数据采集与监测 农业农村大数据底座首…

【k8s故障处理篇】calico-kube-controllers状态为“ImagePullBackOff”解决办法

【k8s故障处理篇】calico-kube-controllers状态为“ImagePullBackOff”解决办法 一、环境介绍1.1 本次环境规划1.2 kubernetes简介1.3 kubernetes特点二、本次实践介绍2.1 本次实践介绍2.2 报错场景三、查看报错日志3.1 查看pod描述信息3.2 查看pod日志四、报错分析五、故障处理…

【Docker】Docker Desktop - WSL update failed

问题描述 Windows上安装完成docker desktop之后&#xff0c;第一次启动失败&#xff0c;提示&#xff1a;WSL update failed 解决方案 打开Windows PowerShell 手动执行&#xff1a; wsl --set-default-version 2 wsl --update

使用C#手搓Word插件

WordTools主要功能介绍 编码语言&#xff1a;C#【VSTO】 1、选择 1.1、表格 作用&#xff1a;全选文档中的表格&#xff1b; 1.2、表头 作用&#xff1a;全选文档所有表格的表头【第一行】&#xff1b; 1.3、表正文 全选文档中所有表格的除表头部分【除第一行部分】 1.…

便携式自动气象站:科技赋能气象观测

便携式自动气象站&#xff0c;顾名思义&#xff0c;就是一款集成了多种气象传感器&#xff0c;能够自动进行气象观测和数据记录的设备。它体积小巧、重量轻&#xff0c;便于携带和快速部署&#xff0c;可以在各种环境下进行气象数据的实时监测。同时&#xff0c;通过内置的无线…

Flex布局中元素主轴上平均分布 多余的向左对齐

content&#xff1a;父元素 content-item: 子元素 主轴上子元素平均分布 .content {display: flex;flex-wrap: wrap;justify-content: space-between;.service-item {display: flex;flex-direction: column;justify-content: center;align-items: center;width: 80px;height:…

万字长文之分库分表里无分库分表键如何查询【后端面试题 | 中间件 | 数据库 | MySQL | 分库分表 | 其他查询】

在很多业务里&#xff0c;分库分表键都是根据主要查询筛选出来的&#xff0c;那么不怎么重要的查询怎么解决呢&#xff1f; 比如电商场景下&#xff0c;订单都是按照买家ID来分库分表的&#xff0c;那么商家该怎么查找订单呢&#xff1f;或是买家找客服&#xff0c;客服要找到对…