This article aims to implement the simultaneous input of 4 groups of 48Khz 32bit 2ch audio data on the RT685 platform, and then assemble the received data into a 48Khz 32bit 8ch audio and output it through I2S. This solution is also done at the request of customers, because there are always harmonic problems when customers make it. After analyzing the customer's situation, it is found that the customer has two main problems:
(1) Harmonic problem: After receiving 4 channels of 8 bytes, it is directly copied to the sending buffer. This will cause timing problems. It does not take into account the problem of buffering data in the audio data storage pool. The time required to receive enough audio data is at least greater than the time required for copying and sending. Therefore, the problem is reflected in the problem that the customer found harmonic problems when testing the output audio waveform.
(2) Audio synchronization problem: After the customer received 4 channels of audio data, he tested the receiving buffer and found that the 4 channels of data were out of sync.
Therefore, in order to help customers, I helped customers make this application demo directly, and made a matching test audio source to send a set of 48Khz sampling rate 32bit dual-channel, fixed increment audio data in a loop, such as 0X00-0XFF in a loop.
The following is the block diagram of this application platform:
Figure 1 System Block Diagram
In the above figure, a MIMXRT685-EVK implements the function of outputting 48Khz, 32bit*2ch, and sends data in a loop: 0X00, 0X01….0XFF.
Another MIMXRT685-EVK is the focus of this article, which implements 4 groups of I2S to receive data at a sampling rate of 48khz and 32bit*2ch, and then assembles the received data into audio data with a sampling rate of 48Khz and 32bit*8ch and sends it out.
In the above figure, in order to reduce the connection of external lines, for BCLK and WS signals, only one group is directly connected to I2S3, and the other I2S2, I2S4, and I2S5 share the I2S3 signal internally. Then, for DATA data, a line is made externally with 4 heads, and connected to the data pins of each group of audio interfaces respectively.
The following is a detailed description of this solution.
The pinouts of the two boards are given below. Because the platform uses many pins, specific allocation is required.
A MIMXRT685-EVK is used as an audio source, and the pinouts for sending 48Khz 32bit*2ch are as follows:
Figure 2 Pinout of audio source board
Another MIMXRT685-EVK is used as an audio transceiver board to receive 4-channel audio synchronization data sent by the audio source and assemble it into a 48Khz 32bit*8ch waveform for transmission.
Figure 3 Pin assignment of audio transceiver board
2.3 Dual-board hardware connection
The source and target connections of the two boards are as follows:
Figure 4 Two board pin connection
Figure 5 two board connection
After the hardware is ready, the software solution and code are provided.
In the process of writing the code, we tried many solutions, such as:
(1) When receiving, directly assemble it into the TDM format buffer to be sent, and then send it. However, since it is assembled into TDM, one I2S needs to receive 8 byte per frame, and then do the offset to receive the next one. If the reception is carried out according to the 8-byte DMA, the callback of the 4 groups of I2S will enter frequently, resulting in a large CPU load, so this solution is abandoned.
(2) The 4 groups of I2S are connected to each other, and the 10ms buffer is connected, and then the DMA method is used to copy from memory to memory. However, since the DMA of RT685 is relatively weak, it can only achieve a maximum offset of 32bit 4word=16byte, that is, a 16-byte offset. However, in fact, a group of audio data is 32bit*2, and 4 groups are 32bit*8=32byte offset, so DMA cannot meet the requirements. Therefore, the DMA memory-to-memory copy solution is abandoned and memcpy is used instead.
(3) Use the I2S_RxTransferReceiveDMA function to perform DMA reception. However, in fact, when one group is called, it starts receiving directly, and waits until the next group of I2S interfaces calls I2S_RxTransferReceiveDMA. This has caused an asynchronous situation. Even if the I2S enable is turned off in I2S_RxTransferReceiveDMA, the 4th group of I2S is enabled after the several groups of I2S of I2S_RxTransferReceiveDMA are called. This method can only achieve the synchronization of the reception of the first group of data, because later, it is necessary to go to the callback to re-trigger the reception of the second frame of data. Therefore, the callback of the 4 groups of I2S calls I2S_RxTransferReceiveDMA, which will inevitably cause new synchronization problems. Therefore, this method is abandoned and it is considered to use two groups of DMA descriptors to do ping-pong. In this way, the reception will continue in a loop without the intervention of CPU code.
Several solutions have been described above. Finally, we choose to use 4 audio channels to receive audio data and cache 10ms audio data buffer. The conversion from receiving buffer to sending buffer adopts memcpy method, and test whether this copy time can meet the actual needs, to ensure that the buffer pool of receiving buffer is greater than this copy time, which is enough to prepare the sending buffer.
The solution for receiving data transfer is as follows:
Figure 6 Data buffer transfer
The above is 4 groups of I2S receiving their own 10ms data respectively. The buffer is actually prepared for 20ms. A single DMA receives a frame for 10ms, and the other 10ms is used for pingpong buffer. The sending buffer is used to copy the received 4 groups of I2S buffers into a 32bit*8ch array in TDM format, and then two groups of ping-pong buffers are also made.
In fact, it is to cache 10ms data. The buffer prepares two groups of 10ms. When the first 10ms frame is received, the second buffer is used to receive it. At the same time, the data of the first buffer is copied to the first buffer of the sending buffer, and the first buffer is used for sending. After the sending is completed, it is transferred to the second buffer to receive and send.
In this way, as long as the time is controlled well, there will be no data error problem.
The data volume of 10ms is 3840Byte, because the receiving frequency is 48Khz, that is, there are 48000 frames in 1s, and each frame is 32bit*2=8Byte, then 10ms=>4800*8Byte=38400Byte.
The software code implementation part is mainly divided into 4 I2S receiving signal sharing, I2S DMA pingpong configuration, data transfer, sending I2S and other parts. The details are given below
From the above, we can know that the 4 I2S receiving signal is not completely connected with wires, but adopts the method of sharing BCLK and WS signals and receiving DATA separately.
I2S2, I2S4, I2S5 share the BCLK of I2S3, and the WS code is as follows:
/* 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);
In order to achieve 4-channel audio synchronization and receive 10ms audio buffer, two I2S DMA descriptors are used to implement the ping-pong function to collect data to two ping-pong buffers in turn.
The code is as follows:
#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);
Here, the code has been modified, mainly the I2S_TransferLoopDMA function in fsl_i2s_dma.c, which is blocked:
I2S_Enable(base);
In order to realize the function of 4-channel synchronous reception.
Because when receiving, each audio interface takes turns to receive its own 2ch data, but when sending, it is necessary to send 4-channel received audio dual-channel data, that is, 32bit*8ch data, so after receiving ping, the ping data needs to be transferred to the sending ping buffer. The code for transfer is as follows:
#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);
}
}
The sending code also uses the I2S DMA method, but because there is no need to send multiple channels at the same time, only a single channel, there is no need to consider the synchronization problem, and no DMA descriptor is used. After the sending buffer is ready, the I2S_TxTransferSendDMA method is used. The code is as follows:
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);
}
For the receiving I2S2, 3, 4, 5, there are 4 channels in total. Each time 10ms of data is received, a callback will be entered. In the callback, you only need to record the flag. When all 4 flags are recorded, it means that the 4 channels of the same 10ms data have been received, and the data can be copied to the sending buffer. Of course, in order to test whether the callback entry frequency is once every 10ms, this article makes a GPIO callback for testing.
The following is the code for recording 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();
}
So far, all functions of a MIMXRT685-EVK for 4 I2S reception and 1 I2S TDM transmission have been completed.
The audio source is made on another MIMXRT685-EVK to send 48Khz, 32bit*2ch audio data, and the data is sent in a loop from 0X00 to 0XFF.
The code is as follows:
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);
}
Audio data buffer:
Figure 7 Audio source sends buffer
The corresponding test results are given:
Figure 8 Audio source sending data test
It can be seen that the data sent by the audio source is cyclical and can be sent in an increasing loop.
4. Test results
There are several points to verify about the test results:
(1) 4-channel audio receives pingpong buffer, whether a single buffer is 10ms, that is, a 10ms audio data pool.
(2) How long is the data memory copy time, whether it will exceed the length of the receiving audio data pool.
(3) Whether the received 4-channel data is synchronized, whether the assembled send buffer data is the 32bit*8ch data assembled from the corresponding 4-channel 2ch data.
(4) Whether the sent audio waveform is the correct 32bit*8ch TDM data.
The following are the verification test results for these points.
This verification is very simple. Define a pin GPIO, initialize the output to 0, and then reverse it in the received callback interrupt. This article chooses to reverse it in the I2S5 callback. The test results are as follows:
Figure 9 ch1 10ms duration
Channel 1 is the exact 10ms because of the callback reversal received.
Here is a general picture of the test:
Figure 10 Time test overview
Ch1: I2S5 callback entry frequency
Ch2: memory copy time
Ch3: Send callback entry frequency
It can be seen that the frequency of sending and receiving is 10ms, because the sending frequency is also 48Khz, but because it is 8ch, the data volume is 4 times that of receiving, and all the data of the 4 receiving channels need to be stuffed in.
For data copying, that is, assembling the data received from 4 I2S channels into 4 buffers into the sending buffer, this time test is on the second channel of the oscilloscope, and the results are as follows:
Figure 11 copy data time
It can be seen that the copying time is less than 500us, which is much shorter than the 10ms of the audio receiving data pool. Therefore, you can use memcpy casually without worrying about the copying time being too long. This also makes up for the regret that I wanted to use DMA for memory to memory copying before, but it could not be realized due to DMA performance issues.
In order to verify the synchronization, this article closes the 4 I2S receiving channel after receiving 100times 10ms, and prints out the corresponding 4 I2S audio receiving buffer. The results of the 4 I2S buffer are as follows:
Figure12 I2S2 receive buffer
Figure 13 I2S3 receive buffer
Figure 14 I2S4 receive buffer
Figure 15 I2S5 receive buffer
It can be seen that the receive buffer data of the 4 I2S are completely synchronized, and all start from 0XB8.
The send buffer is printed after 100 receptions, and then memcpy is performed to the send buffer, and the printout of the send buffer data is as follows:
Figure 16 I2S1 transfer buffer
It can be seen that the buffer also starts from 0XB8, and the 4 groups of received data are copied to the send buffer and assembled into 32bit*8ch data. It can be seen that the TDM send buffer is also correct.
Figure 17 Transmitting and receiving audio waveforms
The upper group is the waveform of the audio source, and the lower group is the waveform of TDM transmission.
Due to the limitation of the analysis software of the logic analyzer, it can only analyze 2ch 64bit data at most, so only part of the data can be seen here, but from the waveform, it can be seen that the waveform of sending TDM can achieve 32bit*8ch, and every 8byte data in a frame is the same, which also explains the synchronization of 4-channel audio reception.
In the above figure, the data of ch2 is actually 00, 01, 02, 03, 04, 05, 06, 07, 4 groups of the same data in one frame, and the waveform can also be seen that there are 4 groups of the same data, and 4 groups of 2ch are enough to form 32bit 8ch TDM.
Finally, here is another TDM waveform tested on the oscilloscope:
Figure 18 Sending TDM waveform
It can be seen that BCLK=12.28Mhz is consistent with the expected 48khz*32bit*8=12.288Mhz.
The WS signal is also measured to be 48Khz, which meets the set 48Khz sampling rate.
DATA is also transmitting with data changes, and it can be seen that the waveform pattern within a frame is repeated by about 4 groups, which also shows that the 4 groups of received data are synchronized.
So far, the function of RT600 4-channel 48KHZ 32bit*2ch input and assembling into 48Khz 32bit*8ch output has been realized!
Thanks so much for my colleage's help, my best internal Collaborators, my audio mentor! @james_fan !!!
Also thanks my software colleague Qiangzhang( @Skybegonia ), he is very familiar with the SDK, he shares the DMA pingpong demo which speed my application code and at last resolve my sync issues.