cancel
Showing results for 
Search instead for 
Did you mean: 

STM32 I2S Callback and Data Width Clarification

lukegalea16
Associate II

Hi,

I am using the STM32F407VG and PmodI2S2 to transfer 24-bit audio. For the most part I have followed this tutorial. So far everything works and I have audio throughput and the ability to manipulate it.

In order to understand better the underlying code, I want to understand the following:
1. In I2S2 > Parameter Settings > Data and Frame Format I have setup 24 bits Data on 32 Bits Frame and in DMA Settings > I2S2_EXT_RX and SPI2_TX I have Data Width set to Half Word. Currently I am receiving 16-bit data which I then restructure into a 32-bit int variable. Would it be possible to receive 32-bit data directly by selecting a data width of Word? I'm a bit confused about this as the method to setup the DMA (HAL_I2SEx_TransmitReceive_DMA) only accepts pointers to a uint16_t even after selecting a data width of word.

2. If I2S2 > DMA Settings has two DMA requests (I2S2_EXT_RX and SPI2_TX), does this mean that the callbacks 'HAL_I2SEx_TxRxHalfCpltCallback' and 'HAL_I2SEx_TxRxCpltCallback' are called on both Tx and Rx interrupts? My concern here is that the incoming samples would then be processed twice.

Thanks

1 ACCEPTED SOLUTION

Accepted Solutions

Hello @lukegalea16 

 

In fact, the content of these flags is cleared before calling the callback function in the DMA IRQ handlerTo address this issue, I propose an alternative solution that involves setting software flags in the DMA complete and half-transfer complete callbacks. These flags can then be checked in the I2S RX/TX complete and half-transfer complete callbacks.

Here is the structure of the proposed solution:

 

// Define the I2S and DMA handles
I2S_HandleTypeDef hi2s2;
DMA_HandleTypeDef hdma_i2s2_tx;
DMA_HandleTypeDef hdma_i2s2_rx;

// Define software flags
volatile uint8_t txHalfCompleteFlag = 0;
volatile uint8_t rxHalfCompleteFlag = 0;
volatile uint8_t txCompleteFlag = 0;
volatile uint8_t rxCompleteFlag = 0;

// Main function
int main(void) {
    // HAL initialization
    HAL_Init();

    // System clock configuration
    SystemClock_Config();

    // Initialize the I2S2ext peripheral
    I2S2ext_Init();

    // Initialize the DMA for I2S2ext Tx and Rx
    DMA_Init();

    // Register custom callbacks for Tx
    HAL_DMA_RegisterCallback(&hdma_i2s2_tx, HAL_DMA_XFER_CPLT_CB_ID, TxTransferComplete);
    HAL_DMA_RegisterCallback(&hdma_i2s2_tx, HAL_DMA_XFER_HALFCPLT_CB_ID, TxHalfTransferComplete);

    // Register custom callbacks for Rx
    HAL_DMA_RegisterCallback(&hdma_i2s2_rx, HAL_DMA_XFER_CPLT_CB_ID, RxTransferComplete);
    HAL_DMA_RegisterCallback(&hdma_i2s2_rx, HAL_DMA_XFER_HALFCPLT_CB_ID, RxHalfTransferComplete);

    // Main loop
    while (1) {
        // Your main loop code here
    }
}

// Custom callback for Tx Half-Transfer Complete
void TxHalfTransferComplete(DMA_HandleTypeDef *hdma) {
    txHalfCompleteFlag = 1;
}

// Custom callback for Rx Half-Transfer Complete
void RxHalfTransferComplete(DMA_HandleTypeDef *hdma) {
    rxHalfCompleteFlag = 1;
}

// Custom callback for Tx Transfer Complete
void TxTransferComplete(DMA_HandleTypeDef *hdma) {
    txCompleteFlag = 1;
}

// Custom callback for Rx Transfer Complete
void RxTransferComplete(DMA_HandleTypeDef *hdma) {
    rxCompleteFlag = 1;
}

// Callback function for Half-Transfer Complete
void HAL_I2SEx_TxRxHalfCpltCallback(I2S_HandleTypeDef *hi2s) {
    if (txHalfCompleteFlag) {
        // Handle Tx half-complete
        txHalfCompleteFlag = 0; // Clear the flag after handling
    }

    if (rxHalfCompleteFlag) {
        // Handle Rx half-complete
        rxHalfCompleteFlag = 0; // Clear the flag after handling
    }
}

// Callback function for Transfer Complete
void HAL_I2SEx_TxRxCpltCallback(I2S_HandleTypeDef *hi2s) {
    if (txCompleteFlag) {
        // Handle Tx complete
        txCompleteFlag = 0; // Clear the flag after handling
    }

    if (rxCompleteFlag) {
        // Handle Rx complete
        rxCompleteFlag = 0; // Clear the flag after handling
    }
}
If your question is answered, please close this topic by clicking "Accept as Solution".

Thanks
Omar

View solution in original post

6 REPLIES 6
AScha.3
Chief II

Hi,

>1. read in rm :

AScha3_0-1719950894236.png

So you can set here 32b data, but the SPI has only 16b shifter, so it needs 2x load with 16b to send /receive a 32b word. (On "bigger" chips you could use SAI , can set data 8...32 bits. But here only 8...16.)

Maybe you could set the buffer as a union, 16b for dma and 32b for "data" .

>2. Try it...

Should be: on half/full TxRx---fCpltCallback you get this buffer to use, and have to set the next data in to send, before next callback coming. Then process read/write the other half of each buffer.

If you feel a post has answered your question, please click "Accept as Solution".
  1. I tried using 32b, but the audio is distorted then - I think that the format of the data is getting changed?
  2. I am reading/writing different parts of the buffer depending on whether it's a half or full callback. I'm just concerned that 'HAL_I2SEx_TxRxHalfCpltCallback' gets called twice (i.e. on Tx and Rx) instead of once for both? (not sure if this makes sense)

 

1. I just can say: with F411, also F4 series, on I2C , with 16b working fine; with 32b - i didnt get it work correct.

Maybe chip problem, because sometimes it working correct, most (!) time it puzzled data, sync was in middle of 32b - unusable. (I use H743 now...SAI working perfect .)

Here probably just wrong order (big/little endian problem), so try : reorder by software. (exchange lo/hi 16b part)

 

2. TxRx is at same time, so if calling TxRxHalfCpltCallback should be only one time, for both, rx tx.

Try...it will nothing explode.

If you feel a post has answered your question, please click "Accept as Solution".
Saket_Om
ST Employee

Hello @lukegalea16 

 

In the STM32 HAL library, the HAL_I2SEx_TransmitReceive_DMA function indeed uses uint16_t pointers for data transfer. This is because the I2S peripheral in the STM32 typically handles data in 16-bit chunks, even if the actual audio data is 24-bit or 32-bit.

When you set the data width to "Half Word" in the DMA settings, it means that each DMA transfer will handle 16 bits (2 bytes) at a time. This is why you are receiving 16-bit data chunks, which you then need to reassemble into 32-bit variables.

If you want to receive 32-bit data directly, you would need to set the DMA data width to "Word" (32 bits). However, the HAL function HAL_I2SEx_TransmitReceive_DMA does not support uint32_t pointers directly. This is a limitation of the HAL library's API design.

To work around this, you can continue using uint16_t pointers and manually reassemble the 32-bit data from two 16-bit transfers.

Here's an example of how you might reassemble 32-bit data from 16-bit chunks:

 

uint16_t rxBuffer[BUFFER_SIZE]; // DMA buffer

uint32_t audioData[BUFFER_SIZE / 2]; // 32-bit audio data buffer



// Assuming BUFFER_SIZE is even and large enough to hold the data

for (int i = 0; i < BUFFER_SIZE / 2; i++) {

    audioData[i] = ((uint32_t)rxBuffer[2 * i] << 16) | rxBuffer[2 * i + 1];

}

 

DMA Callbacks for I2S2_EXT_RX and SPI2_TX

When you configure DMA for both I2S2_EXT_RX and SPI2_TX, each DMA stream will have its own set of interrupts and callbacks. The HAL_I2SEx_TxRxHalfCpltCallback and HAL_I2SEx_TxRxCpltCallback are typically called when the respective DMA streams reach half-completion and full-completion, respectively.

However, these callbacks are shared for both transmit (Tx) and receive (Rx) operations. This means that the callbacks will be triggered for both Tx and Rx DMA events. To differentiate between Tx and Rx events within the callbacks, you can check the state of the DMA streams or use flags.

Here's an example of how you might handle this:

 

 

void HAL_I2SEx_TxRxHalfCpltCallback(I2S_HandleTypeDef *hi2s) {

    if (__HAL_DMA_GET_FLAG(hi2s->hdmatx, DMA_FLAG_HTIF1_5)) {

        // Handle Tx half-complete

    }

    if (__HAL_DMA_GET_FLAG(hi2s->hdmarx, DMA_FLAG_HTIF1_5)) {

        // Handle Rx half-complete

    }

}

 

 

 

void HAL_I2SEx_TxRxCpltCallback(I2S_HandleTypeDef *hi2s) {

    if (__HAL_DMA_GET_FLAG(hi2s->hdmatx, DMA_FLAG_TCIF1_5)) {

        // Handle Tx complete

    }

    if (__HAL_DMA_GET_FLAG(hi2s->hdmarx, DMA_FLAG_TCIF1_5)) {

        // Handle Rx complete

    }

}

 

In this example, __HAL_DMA_GET_FLAG is used to check the DMA flags to determine whether the interrupt was caused by the Tx or Rx DMA stream. This way, you can ensure that the incoming samples are processed only once.

If your question is answered, please close this topic by clicking "Accept as Solution".

Thanks
Omar

Hi Omar,

 

I tested your DMA callback code, and the if statements are not being executed. Are you sure that the flags are correct?
Regards,
Luke

Hello @lukegalea16 

 

In fact, the content of these flags is cleared before calling the callback function in the DMA IRQ handlerTo address this issue, I propose an alternative solution that involves setting software flags in the DMA complete and half-transfer complete callbacks. These flags can then be checked in the I2S RX/TX complete and half-transfer complete callbacks.

Here is the structure of the proposed solution:

 

// Define the I2S and DMA handles
I2S_HandleTypeDef hi2s2;
DMA_HandleTypeDef hdma_i2s2_tx;
DMA_HandleTypeDef hdma_i2s2_rx;

// Define software flags
volatile uint8_t txHalfCompleteFlag = 0;
volatile uint8_t rxHalfCompleteFlag = 0;
volatile uint8_t txCompleteFlag = 0;
volatile uint8_t rxCompleteFlag = 0;

// Main function
int main(void) {
    // HAL initialization
    HAL_Init();

    // System clock configuration
    SystemClock_Config();

    // Initialize the I2S2ext peripheral
    I2S2ext_Init();

    // Initialize the DMA for I2S2ext Tx and Rx
    DMA_Init();

    // Register custom callbacks for Tx
    HAL_DMA_RegisterCallback(&hdma_i2s2_tx, HAL_DMA_XFER_CPLT_CB_ID, TxTransferComplete);
    HAL_DMA_RegisterCallback(&hdma_i2s2_tx, HAL_DMA_XFER_HALFCPLT_CB_ID, TxHalfTransferComplete);

    // Register custom callbacks for Rx
    HAL_DMA_RegisterCallback(&hdma_i2s2_rx, HAL_DMA_XFER_CPLT_CB_ID, RxTransferComplete);
    HAL_DMA_RegisterCallback(&hdma_i2s2_rx, HAL_DMA_XFER_HALFCPLT_CB_ID, RxHalfTransferComplete);

    // Main loop
    while (1) {
        // Your main loop code here
    }
}

// Custom callback for Tx Half-Transfer Complete
void TxHalfTransferComplete(DMA_HandleTypeDef *hdma) {
    txHalfCompleteFlag = 1;
}

// Custom callback for Rx Half-Transfer Complete
void RxHalfTransferComplete(DMA_HandleTypeDef *hdma) {
    rxHalfCompleteFlag = 1;
}

// Custom callback for Tx Transfer Complete
void TxTransferComplete(DMA_HandleTypeDef *hdma) {
    txCompleteFlag = 1;
}

// Custom callback for Rx Transfer Complete
void RxTransferComplete(DMA_HandleTypeDef *hdma) {
    rxCompleteFlag = 1;
}

// Callback function for Half-Transfer Complete
void HAL_I2SEx_TxRxHalfCpltCallback(I2S_HandleTypeDef *hi2s) {
    if (txHalfCompleteFlag) {
        // Handle Tx half-complete
        txHalfCompleteFlag = 0; // Clear the flag after handling
    }

    if (rxHalfCompleteFlag) {
        // Handle Rx half-complete
        rxHalfCompleteFlag = 0; // Clear the flag after handling
    }
}

// Callback function for Transfer Complete
void HAL_I2SEx_TxRxCpltCallback(I2S_HandleTypeDef *hi2s) {
    if (txCompleteFlag) {
        // Handle Tx complete
        txCompleteFlag = 0; // Clear the flag after handling
    }

    if (rxCompleteFlag) {
        // Handle Rx complete
        rxCompleteFlag = 0; // Clear the flag after handling
    }
}
If your question is answered, please close this topic by clicking "Accept as Solution".

Thanks
Omar