F401RE I2S + DMA Issues

Post here first, or if you can't find a relevant section!
Post Reply
axel
Posts: 6
Joined: Sun Oct 04, 2020 8:12 pm

F401RE I2S + DMA Issues

Post by axel »

(moved to general for better visibility)

Hi all!

Back again with more troubleshooting. Trying to figure out why a "simple" I2S + DMA setup I've got isn't working properly.

I've got a WM8731 hooked up to a Nucleo F401RE board with the following I2S connections:

Code: Select all

F401RE -> WM8731
PA15    LRCLK
PC7     MCLK
PC10    SCLK
PC11    DIN
PC12    DOUT
I've also got I2C up and working properly (thanks to the previous thread I posted) and I believe I'm setting up all of its parameters properly to receive the I2S data. However, I can't even get the STM32 to send I2S data so that's step 1.

Right now, it doesn't seem that I2S is getting initialized properly. After the code starts, none of the clock lines have a clock preset and there's no data on the DOUT line. I have simple debug serial prints in the half-full and full callback functions, just to see if they're running, and they're never printed out so I'm assuming DMA also isn't getting set up properly.

I'm following the example posted on the old forums almost verbatim - that thread is here: https://stm32duinoforum.com/forum/viewt ... _4095.html

Here is my i2s.cpp file. The startI2SDMA() function at the bottom is called from my main runtime .ino in the setup() function and includes all of the setup operations performed in the setup() function in the above example.

Code: Select all

/ self-referential include
#include "i2s.h"

// includes
#include <Arduino.h>
#include "audio.h"          // needed for sample types
#include "debugConsole.h"   // debug messaging

// I2S setups
#define AUD_BUFFER_SIZE 64

// debug defines
#define DEBUG_I2S
#define DEBUG_I2S_DETAILED
#define DEBUG_I2S_NAME              "I2S"

I2S_HandleTypeDef hi2s3;
DMA_HandleTypeDef hdmaSpi3Tx;

uint32_t dmaTxBuffer[AUD_BUFFER_SIZE];

/**
 *  Setup pin assignments and clocks for I2S3
 */
extern "C" void HAL_I2S_MspInit(I2S_HandleTypeDef* hi2s ) {
    #ifdef DEBUG_I2S
        debugMsg(3,DEBUG_I2S_NAME,"initializing I2S hardware on hi2s3");
    #endif

    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    //__HAL_RCC_GPIOB_CLK_ENABLE();  // shouldn't need this, nothing on port B
    __HAL_RCC_SPI3_CLK_ENABLE();
    GPIO_InitTypeDef GPIO_InitStruct;

    /**
     *  I2S Pin Mappings for F401RE
     *      PA15    LRCLK
     *      PC7     MCLK
     *      PC10    SCLK
     *      PC11    DIN
     *      PC12    DOUT
     */
    // LRCLK on port A
    GPIO_InitStruct.Pin = GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    // Rest of pins on port C
    GPIO_InitStruct.Pin = GPIO_PIN_7 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    // Peripheral DMA init
    hdmaSpi3Tx.Instance = DMA1_Stream5;
    hdmaSpi3Tx.Init.Channel = DMA_CHANNEL_0;
    hdmaSpi3Tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdmaSpi3Tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdmaSpi3Tx.Init.MemInc = DMA_MINC_ENABLE;
    hdmaSpi3Tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdmaSpi3Tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdmaSpi3Tx.Init.Mode = DMA_CIRCULAR;
    hdmaSpi3Tx.Init.Priority = DMA_PRIORITY_LOW;
    hdmaSpi3Tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;

    // Initialize the DMA finally
    if (HAL_DMA_Init(&hdmaSpi3Tx) != HAL_OK)
    {
        debugMsg(1,DEBUG_I2S_NAME,"failed to initialize HAL DMA");
    }
    __HAL_LINKDMA(hi2s, hdmatx, hdmaSpi3Tx);
}

/**
 *  Initialize DMA
 */
void MX_DMA_Init(void)
{
    #ifdef DEBUG_I2S
        debugMsg(3,DEBUG_I2S_NAME,"starting DMA clocks and IRQs");
    #endif
    // DMA controller clock enable
    __HAL_RCC_DMA1_CLK_ENABLE();
    // DMA interrupt init
    // DMA1_Stream5_IRQn interrupt configuration
    HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);
}

/**
 *  I2S mode and format setup
 */
extern "C" void MX_I2S3_Init(void)
{
    #ifdef DEBUG_I2S
        debugMsg(3,DEBUG_I2S_NAME,"initializing I2S3 mode and format");
    #endif
    hi2s3.Instance = SPI3;
    hi2s3.Init.Mode = I2S_MODE_MASTER_TX;
    hi2s3.Init.Standard = I2S_STANDARD_PHILIPS;
    hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;
    //hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
    hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;
    hi2s3.Init.AudioFreq = I2S_AUDIOFREQ_48K;
    hi2s3.Init.CPOL = I2S_CPOL_LOW;
    hi2s3.Init.ClockSource = I2S_CLOCK_PLL;
    hi2s3.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE;
    if (HAL_I2S_Init(&hi2s3) != HAL_OK) {
        debugMsg(1,DEBUG_I2S_NAME,"Failed to initialize I2S3");
    }
}

/**
 *  starts the I2S circular TX process
 */
void StartAudioBuffers(I2S_HandleTypeDef *hi2s) {
    #ifdef DEBUG_I2S
        debugMsg(3,DEBUG_I2S_NAME,"Starting I2S audio buffer");
    #endif
    // clear buffer
    memset (dmaTxBuffer,0, sizeof (dmaTxBuffer));
    // start circular dma  
    HAL_I2S_Transmit_DMA (hi2s, (uint16_t *) dmaTxBuffer, AUD_BUFFER_SIZE << 1);
}

/**
 *  Fills the DMA buffer with a sine wave
 */
void FillBuffer (uint32_t *buffer, uint16_t len) {
    float     a;
    int16_t   y;
    uint16_t  c;
    float     osc_phi     = 0;
    float     osc_phi_inc = 440.0f / 44100.0f; // generating 440HZ
    for (c = 0; c < len; c++)
    {
        // calculate sin
        a = (float) sin (osc_phi * 6.2832f) * 0.20f;
        osc_phi += osc_phi_inc;
        osc_phi -= (float) ((uint16_t) osc_phi);
        //   float to integer
        y = (int16_t) (a * 32767.0f);
        // auf beide kanäle
        buffer [c] =  ((uint32_t) (uint16_t) y) <<  0  |
                      ((uint32_t) (uint16_t) y) << 16;
    }
}

/**
 *  Full TX Buffer Callback
 */
extern "C" void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s) {
    #ifdef DEBUG_I2S_DETAILED
        debugMsg(3,DEBUG_I2S_NAME,"DMA TX Full Buffer Callback");
    #endif
    // second half finished, filling it up again while first  half is playing
    FillBuffer  (&(dmaTxBuffer [AUD_BUFFER_SIZE >> 1]), AUD_BUFFER_SIZE >> 1);
}

/**
 *  Half-full TX Buffer Callback
 */
extern "C" void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s) {
    #ifdef DEBUG_I2S_DETAILED
        debugMsg(3,DEBUG_I2S_NAME,"DMA TX Half-Full Buffer Callback");
    #endif
    // first half finished, filling it up again while second half is playing
    FillBuffer  (&(dmaTxBuffer [0]), AUD_BUFFER_SIZE >> 1);
}

/**
 *  needed to prevent a DMA crash
 */
extern "C" void DMA1_Stream5_IRQHandler(void)
{
  HAL_DMA_IRQHandler(&hdmaSpi3Tx);
}

/**
 *  Initializes I2S and DMA components
 */
void startI2SDMA() {
    #ifdef DEBUG_I2S
        debugMsg(3,DEBUG_I2S_NAME,"Starting I2S and DMA");
    #endif
    // sets up pins and clocks, routes SPI3 to DMA
    HAL_I2S_MspInit(&hi2s3);
    // initializes DMA clocks and IRQs
    MX_DMA_Init();
    // sets up I2S format and mode
    MX_I2S3_Init(); 
    // starts the audio TX buffer
    StartAudioBuffers(&hi2s3);
}
I'm not getting any compile errors, nor am I getting any debug errors on the serial port, so I believe all the functions are at least being called properly. Something else is amiss and I don't have nearly enough experience to start figuring out what.

I can probe all the I2S pins and none of them have any data. A couple are floating at around 1.6V which is...interesting (maybe still High-Z and the WM8731 is pulling them up a bit?) but there's most certainly not any I2S data on them.

I know there's a lot going on in this post, and that code is pretty long, but hopefully someone will be able to help shed some light on my issues. I'm not a programmer by trade and low-level complex stuff like this is all new territory for me. Hoping it's something dead-simple I'm just missing.

Thanks in advance!
User avatar
fpiSTM
Posts: 1738
Joined: Wed Dec 11, 2019 7:11 pm
Answers: 91
Location: Le Mans
Contact:

Re: F401RE I2S + DMA Issues

Post by fpiSTM »

You have to ensure that no functions are missing compare to CubeMX generated file (from msp, it,...).

Note that changing section will not add too much visibility. Moreover it relies to the STM32 core.
axel
Posts: 6
Joined: Sun Oct 04, 2020 8:12 pm

Re: F401RE I2S + DMA Issues

Post by axel »

So I've been digging into this issue more this weekend.

I copied the code from https://stm32duinoforum.com/forum/viewt ... _4095.html verbatim into the arduino IDE, with some minor formatting cleanup and shuffling of the function order so everything would compile. See below:

Code: Select all

/* generic I2S example for any STM32duino HAL core F4
Original code by Rene Böllhoff
translated to STM32duino by Matthias Diro ("madias" -> STM32duino forum)
Features: Circular Buffer with DMA IRQ (half full, full), Master Clock enabled
This example uses the SPI3 port tested on STM32F407VET "black" board. On other boards please define LED0_BUILTIN and LED1_BUILTIN
*/

#define   I2S_BUFFER_SIZE    64
I2S_HandleTypeDef hi2s3;
DMA_HandleTypeDef hdma_spi3_tx;
uint32_t  dma_buffer  [I2S_BUFFER_SIZE];

// sinus oszillator
float     osc_phi     = 0;
float     osc_phi_inc = 440.0f / 44100.0f; // generating 440HZ

extern "C" void DMA1_Stream5_IRQHandler(void) { // this function must be included to avoid DMA to crash!
    HAL_DMA_IRQHandler(&hdma_spi3_tx);
}

void ErrorHandler(char errorcode) { // if something goes wrong counter the blinks for rudimentary debugging
    digitalWrite(LED_BUILTIN, 1);
    while (1)
    {
        for (int x = 0; x < errorcode; x++) {
        digitalWrite(LED_BUILTIN, 1);
        delay(100);
        digitalWrite(LED_BUILTIN, 0);
        delay(100);
        }
        delay(1000);
    }
}

void FillBuffer (uint32_t *buffer, uint16_t len) {
    float     a;
    int16_t   y;
    uint16_t  c;
    for (c = 0; c < len; c++)
    {
        // calculate sin
        a = (float) sin (osc_phi * 6.2832f) * 0.20f;
        osc_phi += osc_phi_inc;
        osc_phi -= (float) ((uint16_t) osc_phi);
        //   float to integer
        y = (int16_t) (a * 32767.0f);
        // auf beide kanäle
        buffer[c] = ((uint32_t) (uint16_t) y) <<  0  |
                    ((uint32_t) (uint16_t) y) << 16;
    }
}

void StartAudioBuffers (I2S_HandleTypeDef *hi2s) {
    // clear buffer
    memset (dma_buffer,0, sizeof (dma_buffer ));
    // start circular dma  
    HAL_I2S_Transmit_DMA (hi2s, (uint16_t *) dma_buffer, I2S_BUFFER_SIZE << 1);
}

extern "C" void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s) {
    // second half finished, filling it up again while first  half is playing
    FillBuffer  (&(dma_buffer [I2S_BUFFER_SIZE  >> 1]), I2S_BUFFER_SIZE >> 1);
}

extern "C" void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s) {
    // first half finished, filling it up again while second half is playing
    FillBuffer  (&(dma_buffer [0]), I2S_BUFFER_SIZE >> 1);
}

// setting up I2S
extern "C" void MX_I2S3_Init(void) {
    hi2s3.Instance = SPI3;
    hi2s3.Init.Mode = I2S_MODE_MASTER_TX;
    hi2s3.Init.Standard = I2S_STANDARD_PHILIPS;
    hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;
    //hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
    hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;
    hi2s3.Init.AudioFreq = I2S_AUDIOFREQ_44K;
    hi2s3.Init.CPOL = I2S_CPOL_LOW;
    hi2s3.Init.ClockSource = I2S_CLOCK_PLL;
    hi2s3.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE;
    if (HAL_I2S_Init(&hi2s3) != HAL_OK)
    {
        ErrorHandler(1); // on error: one blink
    }
}

// setting up DMA
void MX_DMA_Init(void) {
    /* DMA controller clock enable */
    __HAL_RCC_DMA1_CLK_ENABLE();
    /* DMA interrupt init */
    /* DMA1_Stream5_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);
}

// setting up pins and clocks; routing SPI3 to DMA
extern "C"  void HAL_I2S_MspInit(I2S_HandleTypeDef* hi2s ) {
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_SPI3_CLK_ENABLE();
    GPIO_InitTypeDef GPIO_InitStruct;
    /* I2S standard configurations:
    SPI2
    PB15 DIN
    PB12 LRC
    PB13 SCLK
    PC6 MCK
    SPI3
    PB5 DIN
    PA4 LRC
    PB3 SCLK
    PC7 MCK
    */
    //I2S3 used GPIO configuration in this example:
    // PB5 DIN / SD
    // PA4 LRC /WD
    // PB3 SCLK /CK
    // PC7 MCK
    GPIO_InitStruct.Pin = GPIO_PIN_4;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    GPIO_InitStruct.Pin = GPIO_PIN_3 | GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    // master clock
    GPIO_InitStruct.Pin = GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate =  GPIO_AF6_SPI3;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
    // Peripheral DMA init
    hdma_spi3_tx.Instance = DMA1_Stream5;
    hdma_spi3_tx.Init.Channel = DMA_CHANNEL_0;
    hdma_spi3_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_spi3_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi3_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_spi3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_spi3_tx.Init.Mode = DMA_CIRCULAR;
    hdma_spi3_tx.Init.Priority = DMA_PRIORITY_LOW;
    hdma_spi3_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_spi3_tx) != HAL_OK)
    {
        ErrorHandler(5); // on error: five blinks
    }
    __HAL_LINKDMA(hi2s, hdmatx, hdma_spi3_tx);
}

extern "C"  void  HAL_MspInit(void) { // maybe useful, not included in this example
    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
    /* System interrupt init*/
    /* MemoryManagement_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(MemoryManagement_IRQn, 0, 0);
    /* BusFault_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(BusFault_IRQn, 0, 0);
    /* UsageFault_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(UsageFault_IRQn, 0, 0);
    /* SVCall_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(SVCall_IRQn, 0, 0);
    /* DebugMonitor_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DebugMonitor_IRQn, 0, 0);
    /* PendSV_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(PendSV_IRQn, 0, 0);
    /* SysTick_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

void setup() {
    // HAL_MspInit(); // not important by default
    HAL_I2S_MspInit(&hi2s3);// setting up pins and clocks; routing SPI3 to DMA
    MX_DMA_Init();
    MX_I2S3_Init(); 
    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, 0);
    StartAudioBuffers (&hi2s3);
    digitalWrite(LED_BUILTIN, 1);
}

void loop() {
    // just a dummy code in loop, audio out is generated by ISR
    digitalWrite(LED_BUILTIN, HIGH);
    delay(100);
    digitalWrite(LED_BUILTIN, LOW);
    delay(900);
}
I also made sure to enable I2S and DMA in the hal_conf_extra.h file in the same directory as my .ino:

Code: Select all

// include the STM32F4xx I2S & DMA HAL libraries
#define HAL_I2S_MODULE_ENABLED
#define HAL_DMA_MODULE_ENABLED
And still I have no output. PA4 (LRCLK) is constant HIGH, while PB3 (SCLK) and PC7 (MCLK) are both constant LOW. No presence of a clock on any of the three pins. The LED, however, is flashing, so I know the code is running properly and at least making it to the loop() function, but it appears none of the I2S or DMA setup is working right. I have no compile errors or warnings either.

Any suggestions? My next step will be to try and build up code directly from CubeMX for I2S + DMA, to see if that yields a different result. As far as I can tell, that's exactly what is being done with the code I linked to, so I can't see how the end result would be any different.

Apologies for the dump of code & text, but I'm getting increasingly frustrated that something so simple seems to be so hard to get working.
axel
Posts: 6
Joined: Sun Oct 04, 2020 8:12 pm

Re: F401RE I2S + DMA Issues

Post by axel »

Any recommended reading would be appreciated as well. I'm way out of my element in trying to get this to work, and it seems like not too many other people are using I2S + DMA with the stm32duino core.

It seems like for most people it "just works" but for whatever reason my nucleo board refuses to even output a simple I2S clock :(
ryanodine
Posts: 1
Joined: Wed Oct 21, 2020 12:21 am

Re: F401RE I2S + DMA Issues

Post by ryanodine »

I am not sure this addresses your issue but there is video on YouTube called "Fixing The STM32 CubeMX Bug When Using The I2S With The DMA" which describes an issue with CubeMX generated code.
tschrama
Posts: 4
Joined: Wed Sep 09, 2020 1:57 pm

Re: F401RE I2S + DMA Issues

Post by tschrama »

Cannt get this to work on my STM32F4VEt6... is the original author still around?

Anybody else ant tips?
Post Reply

Return to “General discussion”