Skip to main content
Visitor II
May 9, 2025
Solved

I2S + DMA configuration

  • May 9, 2025
  • 4 replies
  • 1878 views

I have NUCLEO-F756ZG board with STM32F765ZG and I have hooked up ICS43434 mic into I2S1 bus. Now I have been scratching my head for a day to figure out how to get data out from that puppy. Or actually getting data out is simple, but to make it sane is not. I have configured I2S and DMA like below

JusbeR_0-1746793869493.pngJusbeR_1-1746793913913.png

Then I allocate some buffers and write DMA callbacks and send the data to freeRTOS task for processing. Now if I play 1kHz sound and dump the raw data from DMA buffers I get something like this

113,58880,0,0,116,43008,0,0,123,4608,0,0,134,42496,0,0

If I draw the 113, 166 so every 4th sample to graph it is the sine wave what I am playing. My friend the AI said that the data format is left_high, left_low, right_high, righ_low. That might very well be, but I don't want that. If the mic sends 24 bit of data in 32 bit frames it feels insane that I have to spend 2*32bits of memory for it.

I just can't figure out what am I missing. It does not help HAL_I2S_Receive_DMA is one big red herring, but I believe it probably isn't the problem. I did try to configure DMA to half word in hope to get the 24 bits into 2*16bit, but that didn't fly. I might have missed something in this though.

Any ideas anyone?

    This topic has been closed for replies.
    Best answer by TDK

    The DMA should be set up for half-word access on the peripheral side since the DR register is 16-bits.

    TDK_0-1746814515501.png

    You can keep the memory side at 32 bits, but the bytes might not be in the right order.

     

    When it's set up as a word, it's reading 32 bits but only half of them are relevant, so you're getting double the data.

     

    > I did try to configure DMA to half word in hope to get the 24 bits into 2*16bit, but that didn't fly.

    Explain what is not flying.

    4 replies

    Technical Moderator
    May 9, 2025

    Hello @JusbeR 

    The microphone you are using is mono with output in sterio format. 

    Saket_Om_0-1746797501271.png

    According to the datasheet we have a pin LR that should be set to select the output on the right or the left chanel.

    So it is normal that you receive the half of data as 0. 

     

    Super User
    May 9, 2025

    Looks like these are meant to be in stereo, with one device outputting on the left channel, and the other on the right channel. With only one device, half of the data will be 0, as you see.

    TDK_0-1746797384628.png

     

    If you only have one device, you could set it up to output on the left channel and use SPI to read it instead and use WS as the CS signal. Don't think there's an I2S setting to ignore half of the data or capture "mono" audio.

     

    You could look into using SAI instead.

     

    I2S mono audio capture - STMicroelectronics Community

    SAI I2S Mono Mode Not An Option - STMicroelectronics Community

     

    JusbeRAuthor
    Visitor II
    May 9, 2025

    Thanks for the answers. Yes. I understand that I have only left channel there, but the question is that why the 24bit sample is divided into two 32 bit memory blocks. And to be precise, I am not sure if this is happening or are the other values just rubbish or what. So to clarify. I read this from DMA buffer:

    113,58880,0,0,116,43008,0,0,123,4608,0,0,134,42496,0,0

    and each number here is uint32_t, and every 4th item produces a sine wave if I draw it. So this is maybe something like:

    113 58880 0 0 116
    0x00000071 0x0000E600 0x00000000 0x00000000 0x00000074 0x...
     0x71E600 maybe? 64 bits right chan? Another 64 bits for left?

    I would want that there is something like

     Left Right Left
    0x0071E600 0x00000000 0x...

    That would make more sense and consumes 50% less memory

    Super User
    May 9, 2025

    Those look like uint16_t values. Not a single one is above 65536. Are you sure they are uint32_t?

    You have "word" configured in the DMA, but the actual array being uint16_t would explain everything you see here.

    JusbeRAuthor
    Visitor II
    May 9, 2025

    Looks like 16 bits in one and 8 bits in another yes. I can't understand why the data is like that in the buffer. I have stared at these for hours and finally came here to see if anyone else is less blind than me. To me they look like 32bit values

    #define SINGLE_SAMPLE_SIZE sizeof(uint32_t)
    #define I2S_DMA_SAMPLE_COUNT 128
    #define I2S_DMA_SAMPLE_SIZE_BYTES (SINGLE_SAMPLE_SIZE * I2S_DMA_SAMPLE_COUNT)
    #define I2S_DMA_BUF_COUNT (2 * I2S_DMA_SAMPLE_COUNT)
    #define CHANNEL_COUNT 3
    ...
    static uint32_t dmabuf[CHANNEL_COUNT][I2S_DMA_BUF_COUNT];
    ...
    HAL_I2S_Receive_DMA(I2SHandles[i], (uint16_t *)dmabuf[i], I2S_DMA_BUF_COUNT/2);

    Then if I send, print or check the dmabuf content after e.g. `HAL_I2S_RxHalfCpltCallback` I see the numbers above in the dmabuf

    JusbeRAuthor
    Visitor II
    May 12, 2025

    All right. I think I got it now by combining all the hints you guys gave me. The final clue was @TDKs half word hint. So the culprit was

    - Use half word DMA setting even if you use word memory block

    - Swap the half words (as in not Little/Big endian swap, just swap the half words)

    - Give HAL_I2S_Receive_DMA the actual count of words in the buf not half words

    Here some screenshots and code for future reference

    JusbeR_0-1747033818693.png

    static inline int raw_to_sample(uint32_t dma_raw) {
     return ((dma_raw & 0xFFFF) << 8) | (dma_raw >> 24);
    }
    static uint32_t dmabuf[128]
    ...
    HAL_I2S_Receive_DMA(h, (uint16_t *)dmabuf, 128);

    This way you when you get the half full interrupt you will have data in 0-63 where left in even and right in odd numbers. After full interrupt data is on 64-127.