Nucleo F401RE – trying to get I2S and DMA to work

Thu Jun 07, 2018 7:30 am
Greetings !
as posted in the welcome thread, ARM noob here, our lab is migrating from 15 years of dev with microchip 8 and 16 bit MCU to ARM mcu.
We are using the STM32 duino to get trained on something that started as a “simple” project but we’re stumped.

We need to work with I2S and DMA to generate waveforms. We’ve been following a proper learning curve about I/O’s configuration, clock settings etc and even got the I2S to work in polling mode and we could get the samples out, so SPI2 / I2S is properly configured

Now we’re adding the DMA and the board freezes. Happy to post our start code and try to get some help from this place where we’ve been reading quite a fair bit and learned a lot from (cheers).

The code is very basic so far but I don’t have a debugger to check what is happening. The main loop mirrors the switch state to the onboard led to check it’s still running.
When we launch the DMA for the first I2S transaction, the number of samples decided actually go out (master clock, word clock and samples are generated) then it freezes and the main loop isn’t responsive.

My guess is that the IRQ is triggered at the end of the dma transaction but goes to either a fault trap or in the never. I’ve overriden the weak IRQ handles and the I2S callbacks, but so far, I don’t think I get in them.

here’s our code. The only mod we made to the stock STM32 arduino toolchain is to enable the I2S (and the dma I think) in the core config .h file where you define what to compile with and what not
We don’t use the codec yet, we only instanciated the object but it can be avoided for testing. This should work on a naked nucleo if a fellow member wants to test for us

any idea ?

#include <Wire.h>
#include <stm32f4xx_hal.h>
#include <stm32f4xx_hal_i2s.h>
#include <stm32f4xx_hal_i2s_ex.h>
#include "stm32f4xx_hal_gpio.h"
#include <stm32f4xx_hal_dma.h>

#include "stm32f4xx.h"

#include <control_sgtl5000.h>

////////////// VARIABLES DECLARATION ////////////////////////
#if !defined (HSI_VALUE)
#define HSI_VALUE ((uint32_t)16000000) /*!< Value of the Internal oscillator in Hz*/
#endif /* HSI_VALUE */

// A full cycle, 16-bit, 2's complement Sine wave lookup table
uint16_t sine_table[256] = {
0x0000, 0x0324, 0x0647, 0x096a, 0x0c8b, 0x0fab, 0x12c8, 0x15e2,
0x18f8, 0x1c0b, 0x1f19, 0x2223, 0x2528, 0x2826, 0x2b1f, 0x2e11,
0x30fb, 0x33de, 0x36ba, 0x398c, 0x3c56, 0x3f17, 0x41ce, 0x447a,
0x471c, 0x49b4, 0x4c3f, 0x4ebf, 0x5133, 0x539b, 0x55f5, 0x5842,
0x5a82, 0x5cb4, 0x5ed7, 0x60ec, 0x62f2, 0x64e8, 0x66cf, 0x68a6,
0x6a6d, 0x6c24, 0x6dca, 0x6f5f, 0x70e2, 0x7255, 0x73b5, 0x7504,
0x7641, 0x776c, 0x7884, 0x798a, 0x7a7d, 0x7b5d, 0x7c29, 0x7ce3,
0x7d8a, 0x7e1d, 0x7e9d, 0x7f09, 0x7f62, 0x7fa7, 0x7fd8, 0x7ff6,
0x7fff, 0x7ff6, 0x7fd8, 0x7fa7, 0x7f62, 0x7f09, 0x7e9d, 0x7e1d,
0x7d8a, 0x7ce3, 0x7c29, 0x7b5d, 0x7a7d, 0x798a, 0x7884, 0x776c,
0x7641, 0x7504, 0x73b5, 0x7255, 0x70e2, 0x6f5f, 0x6dca, 0x6c24,
0x6a6d, 0x68a6, 0x66cf, 0x64e8, 0x62f2, 0x60ec, 0x5ed7, 0x5cb4,
0x5a82, 0x5842, 0x55f5, 0x539b, 0x5133, 0x4ebf, 0x4c3f, 0x49b4,
0x471c, 0x447a, 0x41ce, 0x3f17, 0x3c56, 0x398c, 0x36ba, 0x33de,
0x30fb, 0x2e11, 0x2b1f, 0x2826, 0x2528, 0x2223, 0x1f19, 0x1c0b,
0x18f8, 0x15e2, 0x12c8, 0x0fab, 0x0c8b, 0x096a, 0x0647, 0x0324,
0x0000, 0xfcdc, 0xf9b9, 0xf696, 0xf375, 0xf055, 0xed38, 0xea1e,
0xe708, 0xe3f5, 0xe0e7, 0xdddd, 0xdad8, 0xd7da, 0xd4e1, 0xd1ef,
0xcf05, 0xcc22, 0xc946, 0xc674, 0xc3aa, 0xc0e9, 0xbe32, 0xbb86,
0xb8e4, 0xb64c, 0xb3c1, 0xb141, 0xaecd, 0xac65, 0xaa0b, 0xa7be,
0xa57e, 0xa34c, 0xa129, 0x9f14, 0x9d0e, 0x9b18, 0x9931, 0x975a,
0x9593, 0x93dc, 0x9236, 0x90a1, 0x8f1e, 0x8dab, 0x8c4b, 0x8afc,
0x89bf, 0x8894, 0x877c, 0x8676, 0x8583, 0x84a3, 0x83d7, 0x831d,
0x8276, 0x81e3, 0x8163, 0x80f7, 0x809e, 0x8059, 0x8028, 0x800a,
0x8000, 0x800a, 0x8028, 0x8059, 0x809e, 0x80f7, 0x8163, 0x81e3,
0x8276, 0x831d, 0x83d7, 0x84a3, 0x8583, 0x8676, 0x877c, 0x8894,
0x89bf, 0x8afc, 0x8c4b, 0x8dab, 0x8f1e, 0x90a1, 0x9236, 0x93dc,
0x9593, 0x975a, 0x9931, 0x9b18, 0x9d0e, 0x9f14, 0xa129, 0xa34c,
0xa57e, 0xa7be, 0xaa0b, 0xac65, 0xaecd, 0xb141, 0xb3c1, 0xb64c,
0xb8e4, 0xbb86, 0xbe32, 0xc0e9, 0xc3aa, 0xc674, 0xc946, 0xcc22,
0xcf05, 0xd1ef, 0xd4e1, 0xd7da, 0xdad8, 0xdddd, 0xe0e7, 0xe3f5,
0xe708, 0xea1e, 0xed38, 0xf055, 0xf375, 0xf696, 0xf9b9, 0xfcdc,

// I2S init stuctures
__I2S_HandleTypeDef hi2s2;

// DMA init & structure
DMA_InitTypeDef DMA_InitStruct;
DMA_HandleTypeDef hdma1;

// SGTL5000 I2C control
AudioControlSGTL5000 MyCodec;

int i = 0;
uint16_t datasize = 4;
uint16_t AudioSampleLR[12];
typedef enum LeftOrRight {LEFT_CHANNEL = 0, RIGHT_CHANNEL};
bool FlagSendNewSample = false;
uint16_t SampleCounter = 0;

uint32_t transmitdelay = 1000;
uint32_t RunningTime = 0;
HAL_StatusTypeDef msg;

int led = 13;
int user_switch = 23; // PC13 = Digital Pin 23

/////////// END OF DECLARATION /////////////////////////////////////////////

static void MX_I2S2_Init(void)
// [email protected] Make sure that either:
// ([email protected]) I2S PLL is configured or
// ([email protected]) External clock source is configured after setting correctly
// the define constant EXTERNAL_CLOCK_VALUE in the stm32f4xx_hal_conf.h file.
hi2s2.Instance = SPI2;
hi2s2.Init.Mode = I2S_MODE_MASTER_TX;
hi2s2.Init.Standard = I2S_STANDARD_PHILIPS;
hi2s2.Init.DataFormat = I2S_DATAFORMAT_16B;
hi2s2.Init.AudioFreq = I2S_AUDIOFREQ_48K;
hi2s2.Init.CPOL = I2S_CPOL_LOW;
hi2s2.Init.ClockSource = I2S_CLOCK_PLL;
hi2s2.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE;
if (HAL_I2S_Init(&hi2s2) != HAL_OK)
Serial.println("erreur I2S init");

// (#) Initialize the I2S low level resources by implement the HAL_I2S_MspInit() API:
// I/O mapping init for I2S
void HAL_I2S_MspInit(I2S_HandleTypeDef* hi2s)
Serial.println("MspInit ici!!!");
// (##) I2S pins configuration:
// (+++) Enable the clock for the I2S GPIOs
// INSTRUCTIONS FROM THE FILE stm32f4xx_gpio.c
/* (#) Enable the GPIO AHB clock using the following function: __HAL_RCC_GPIOx_CLK_ENABLE().
(#) Configure the GPIO pin(s) using HAL_GPIO_Init().
(++) Configure the IO mode using "Mode" member from GPIO_InitTypeDef structure
(++) Activate Pull-up, Pull-down resistor using "Pull" member from GPIO_InitTypeDef
(++) In case of Output or alternate function mode selection: the speed is
configured through "Speed" member from GPIO_InitTypeDef structure.
(++) In alternate mode is selection, the alternate function connected to the IO
is configured through "Alternate" member from GPIO_InitTypeDef structure. */

// Beware, Clock enable has to be set for both port B and C (!)

// (+++) Configure these I2S pins as alternate function pull-up.
GPIO_InitTypeDef GPIO_InitStruct;

/* Peripheral clock enable */

/**I2S2 GPIO Configuration
PB12 ------> I2S2_WS
PB13 ------> I2S2_CK
PB14 ------> I2S2_ext_SD
PB15 ------> I2S2_SD
PC6 ------> I2S2_MCK

// Word Clock, Bit Clock, Data (MOSI)
GPIO_InitStruct.Pin = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_15 ;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

// Master Clock
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

// External SD (full duplex, incoming I2S)
GPIO_InitStruct.Pin = GPIO_PIN_14;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
GPIO_InitStruct.Alternate = GPIO_AF6_I2S2ext;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

// DMA stuff

// DMA1_Stream4_IRQn interrupt configuration
// AN4031 ST : table of DMA instance (controller #) vs channel vs peripheral
// SPI2 TX (used for I2S) is DMA Controller #1 (out of 2) stream 4 channel 0
hdma1.Instance = DMA1_Stream4;
hdma1.Init.Channel = DMA_CHANNEL_0;
hdma1.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma1.Init.MemInc = DMA_MINC_ENABLE;
//hdma1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
//hdma1.Init.MemDataAlignment = DMA_PDATAALIGN_WORD;
hdma1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma1.Init.MemDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma1.Init.MemBurst = DMA_MBURST_SINGLE;
hdma1.Init.PeriphBurst = DMA_PBURST_SINGLE;
hdma1.Init.Mode = DMA_NORMAL;
hdma1.Init.Priority = DMA_PRIORITY_LOW;

if (HAL_DMA_Init(&hdma1) != HAL_OK)
Serial.println("DMA init Error");


HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 2, 0);


//Serial.println("MspInit fini...");

void DMA1_Stream4_IRQHandler(void)
digitalWrite(led, HIGH);

void SPI2_IRQHandler(void)
digitalWrite(led, HIGH);

void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s)
digitalWrite(led, HIGH);

void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s)
digitalWrite(led, HIGH);
AudioSampleLR[LEFT_CHANNEL] = sine_table[SampleCounter];
//AudioSampleLR[RIGHT_CHANNEL] = sine_table[SampleCounter];
AudioSampleLR[RIGHT_CHANNEL] = 0;
FlagSendNewSample = true;
SampleCounter = 0;

HAL_I2S_Transmit_DMA(&hi2s2, AudioSampleLR, 1);

void setup() {

pinMode(led, OUTPUT);
pinMode(user_switch, INPUT);

// Fills up the I2S handle structure with the I2S configuration (mode, SR etc)
// (#) Program the Mode, Standard, Data Format, MCLK Output, Audio frequency and Polarity
// using HAL_I2S_Init() function.
// note that there's no direct call to HAL_I2S_MspInit() as we override it locally and it takes over the weak
// prototype defined in HAL. It's called within HAL_I2S_Init()

// PLL & Clock Setup for the I2S module
// With a 48k Sample rate, I2S modules creates a master clock => FC = FS * 256 (or 12.288 MHz with FS = 48k)
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;
// with HSI, leads to 48.08 KHz Word clock - adjust +/- 1 for fine tuning or use crystal (HSE)
PeriphClkInitStruct.PLLI2S.PLLI2SN = 195;
//PeriphClkInitStruct.PLLI2S.PLLI2SN = 390;
PeriphClkInitStruct.PLLI2S.PLLI2SR = 1;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
Serial.println("erreur config horloge periph I2S ...");


AudioSampleLR[LEFT_CHANNEL] = AudioSampleLR[RIGHT_CHANNEL] = 0xAA55;
SampleCounter = 0;
FlagSendNewSample = false;

// HAL_I2S_DMAResume(&hi2s2);
HAL_I2S_Transmit_DMA(&hi2s2, AudioSampleLR, 1);

digitalWrite(led, HIGH);

// SGTL5000 Codec start
// This will also launch Wire.begin() and will initialize the codec with default values
//MyCodec.volume(0.5, 0.5);

/* Wire.beginTransmission(0x0A);
if(Wire.endTransmission(false) != 0)
Serial.println("Wire write error");
if (Wire.requestFrom((int)0x0A, 2) < 2)
Serial.println("Wire read error");
unsigned int val = << 8;
val |=;


RunningTime = micros();


void loop()
digitalWrite(led, HIGH);
digitalWrite(led, LOW);


Thu Jun 07, 2018 7:31 pm
It could be that the GENERIC Framework just works out of the box for this MCU. I’m not sure but there are also different branches further advanced:
I2S example
DMA buffers are included.

Thu Jun 07, 2018 8:29 pm
Sketch is Cpp so you have to use extern "C"

Fri Jun 08, 2018 1:06 pm
thank you so much to both of you, I’m fairly sure the extern C explains why my callbacks aren’t compiled, which create a bad trip when the IRQ quicks in.
Will keep doing my homeworks and will let you know about the results !


Mon Jun 11, 2018 3:25 pm
so, I’ve tried extern C callbacks with no results (board still freezes after the first DMA array gets sent).

I’ve also installed the generic core and compiled the example, but the sawtooth example give absoluty NOTHING out, tried SPI3 and SPI2.

I’ve also added my switch to LED code to see if it works, I can’t read the switch if I call I2S.write(dacValue);

if I remove the call, the mainloop is active again. So it seems I have a similar crash. If I put a delay of a few ms in the main loop between each I2S.write call, it seems to work for a while (a few seconds) then the main loop activity led and it’s frozen again.

is the generic core + I2S lib been tested on a F401 RE nucleo board ?

thank you

Mon Jun 11, 2018 6:14 pm
Which core you used?

Tue Jun 12, 2018 3:04 pm
[fpiSTM – Mon Jun 11, 2018 6:14 pm] –
Which core you used?

I tried both. Our code (OP) runs on STM core installed from the board manager. I’ve only modified the config.h in which you enable certain peripheral (like I2S and DMA), this was needed to be able to compile. The I2S actually “works” in polling mode (blocking write routine) but crashes in dma mode as explained above (first dma request sends the data out with the number of desired samples, then the CPU locks somewhere)

I then tried the generic code proposed above and compiled the I2S example (simple sawtooth). I had to put a delay in the main loop to get it to work but it still crashes after some time (possibly once the whole buffer has been sent)

here’s what I tried to compile with the GENERIC core and which freezes shortly after reset (with or without serial prints). I tried implementing with SPI2 or SPI3. Thanks

I2S sawtooth

This example produces a digital sawtooth on the I2S interface for the

If the microcontroller on your board supports an I2S interface ( not all do )
you can use e.g an Adafruit I2S amplifier with the MAX98357a chip to produce sound

Check your microcontroller if it has an I2S interface.
E.g. the STM32F103C8 ( on the BluePill board ) has no I2S
On this board you can use only certain chips like the
PT8211 16 Bit Digital to Analog Converter togehter with the SPI interface

Pin description

sd = DIN // I2S data
ws = LRC // left/right channel
ck = SCLK // serial data clock
MCK = MCLK // master clock, some I2S chips ( like the CS42L22 ) need fast external clock provided by the MCU

April 2017 ChrisMicro

#include "I2S.h"

// to use the example check your board schematic
// if this is the correct pin setting for your board.
// general setup without master clock
//I2SClass I2S(SPI3, PC12 /*sd*/ , PA4 /*ws*/, PC10 /*ck*/);

// setup with masterclock enabled
I2SClass I2S(SPI3, PC12 /*sd*/ , PA4 /*ws*/, PC10 /*ck*/, PC7/* MCK*/);

#define USER_SWITCH PC13

//int led = 13;
//int user_switch = 23; // PC13 = Digital Pin 23

void setup()
I2S.begin(I2S_PHILIPS_MODE, 44000, 16);


digitalWrite(BUILTIN_LED, HIGH);


void loop()
static uint16_t dacValue;


dacValue+=300; // saw tooth


digitalWrite(BUILTIN_LED, HIGH);
digitalWrite(BUILTIN_LED, LOW);


Wed Jun 13, 2018 4:23 am
It might be that the F401RE in the main trunk of the STM32GENERIC framework is not so well tested. Probably there are more advanced branches of this from other people like from huaweiwx



Wed Jun 13, 2018 2:27 pm
I tried both, huaweiwx doesn’t compile (compiler doesn’t understand a #endif while there’s the proper ifdef right above, correcting one thing leads to more issues)

K anderson branch compiles like the former generic branch I’ve tried, but provides the same issues : main loop doesn’t work or stops running after a while if I call I2S write.

at that point I’m at my wit end : our code, which call basic HAL function almost works but doesn’t catch IRQ and a lib supposed to work (which code has a lot in common with ours) crashes too… :?

Wed Jun 13, 2018 5:12 pm
Normally the GENERIC Core works quite good. But it might be that some MCUs are not so well defined.

For instance: The I2S of the BLACK_F407VE variant works really well.
The definitions for this variant are found here: … ACK_F407VE

The definitions of the Nucleo401RE are found here: … LEO_F401RE

Edit: I think in the variant script the I2S initialization is missing. Probably you can just copy it from the f407.

Sun Jun 17, 2018 6:08 am
On my board I2S is working now.
I corrected the variants.c initialization and added two examples: … cleoF401RE
The PLL-setup is copied from another MCU and I corrected it experimental. If someone has the STM32 clock configurator installed he could probably give me the more accurate values.

Mon Jun 18, 2018 6:45 am
thanks a lot for testing and correcting the variant, I’ll give it a try asap. We had already worked out the value for the I2S clock. I’ll try to compare and see what’s wrong with our code

Fri Jun 22, 2018 3:42 am
Thanks for the answer. I’m curious about the result.

Leave a Reply

Your email address will not be published. Required fields are marked *