2

I am trying to interface a 'STM32F401RET6 Nucleo-64' microcontroller with an Adafruit I2S microphone in a mono setup. To accomplish this task, I would like to have DMA enabled.

I used the Device Configuration Tool in STM32 Cube IDE to activate I2S3 using the following parameters:

I2S3

  • Full Duplex Master
  • Transmission mode: Mode Master Receive;
  • Communication standard: MSB First (Left Justified);
  • Data and Frame Format: 24 Bits Data on 32 Bits Frame;
  • Selected Audio Frequency: 48 kHz;
  • Clock Source: I2S PLL Clock;
  • Clock Polarity: Low;
  • Master clock output disabled.

DMA

  • SPI_RX3, DMA 1 Stream 2, Peripheral to Memory, High Priority;
  • FIFO, Threshold = Full, Data Width = Half Word, Burst Size = Single;
  • In the NVIC settings, interrupts are enabled for both DMA 1 Stream 2 and SPI3.

Next, the Code Generator Tool was used to automatically generate starting code. Some changes were made to this starting code:

  • Set GPIO_PULL_DOWN so that tri-state always reads in 0;

I already used an oscilloscope to plot the digital data waveform coming from the microphone. This seemed to be correct, i.e., sound triggered the microphone and this was visible in the most significant bits. This makes that the error is in reading in the data into the correct format, if I'm correct. To perform mono measurements, the datasheet states that one should use a 100k Ohm resistor, which is present in my setup.

In my main.c program, I'm using the HAL function 'HAL_I2S_Receive_DMA' to try to fill my array of 500 samples.

main.c:

    /* Includes ------------------------------------------------------------------*/
#include "main.h"
I2S_HandleTypeDef hi2s3;
DMA_HandleTypeDef hdma_spi3_rx;


/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_I2S3_Init(void);
static void MX_DMA_Init(void);


int main(void)
{
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2S3_Init();
  MX_DMA_Init();

  /* Infinite loop */
  HAL_StatusTypeDef retval; // return value

  volatile int16_t data[500] = {0};
  int16_t data_shifted[500];

  while (1)
  {
      retval = HAL_I2S_Receive_DMA(&hi2s3, data, 500);
//    for(short i=0; i<500; i++){
//        data_shifted[i] = data[i] >> 14;
//    }

  HAL_Delay(1000);
  }
}


void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /* Configure the main internal regulator output voltage */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);
  /* Initializes the RCC Oscillators according to the specified parameters
     in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = 8;
  RCC_OscInitStruct.PLL.PLLN = 84;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /* Initializes the CPU, AHB and APB buses clocks */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}


static void MX_I2S3_Init(void)
{
  hi2s3.Instance = SPI3;
  hi2s3.Init.Mode = I2S_MODE_MASTER_RX;
  hi2s3.Init.Standard = I2S_STANDARD_MSB;
  hi2s3.Init.DataFormat = I2S_DATAFORMAT_24B;
  hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
  hi2s3.Init.AudioFreq = I2S_AUDIOFREQ_48K;
  hi2s3.Init.CPOL = I2S_CPOL_LOW;
  hi2s3.Init.ClockSource = I2S_CLOCK_PLL;
  hi2s3.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_ENABLE;
  if (HAL_I2S_Init(&hi2s3) != HAL_OK)
  {
    Error_Handler();
  }
}

// Enable DMA controller clock
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Stream2_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Stream2_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Stream2_IRQn);

}

/*GPIO Initialization Function */
static void MX_GPIO_Init(void)
{

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();

}

/* This function is executed in case of error occurrence. */
void Error_Handler(void)
{
  /* Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
}

#ifdef  USE_FULL_ASSERT
/**
  Reports the name of the source file and the source line number
  where the assert_param error has occurred.
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
}
#endif /* USE_FULL_ASSERT */

When I debug my code and put a breakpoint on line 34 in 'main.c', then the microcontroller memory does not update its values accordingly. All values stay equal to zero. Both DMA Status Flags are set to 1. I guessing that the problem has something to do with timings, but I wasn't able to solve this problem until now.

Link to complete source code on GitHub

Link to HAL_I2S_Receive_DMA function on GitHub

Thanks in advance.

IsaacNuketon
  • 55
  • 1
  • 10
  • 1
    Have you solved the task without DMA? This might not be your problem, but function `HAL_I2S_Receive_DMA` returns before anything is received, the commented-out loop will have no data to work with. Use `HAL_I2S_GetState` to check the transfer status, or the callbacks - `HAL_I2S_RxCpltCallback`/`HAL_I2S_ErrorCallback`. – Flexz Nov 22 '21 at 09:07
  • Till date, I was not able to solve the task without DMA. I enabled DMA because I thought the CPU would be too busy handling the interrupts coming from microphone data, since it's working at 48kHz audio sampling rate and 3.3 MHz bit clock. However, this was only a presumption. – IsaacNuketon Nov 23 '21 at 10:51

2 Answers2

2

DMA must be initialized before ADC peripheral, but generated code performs initialization in wrong order. It is well known minor flaw in default configuration of current STM32CubeIDE. Try to change

MX_GPIO_Init();
MX_I2S3_Init();
MX_DMA_Init(); // wrong order

to

MX_GPIO_Init();
MX_DMA_Init(); // right order
MX_I2S3_Init();

To make permanent changes,

  1. Open "Project Manager" (tab near Pinout / Clock Configuration)
  2. Project Manager → Advanced Settings → Generated Function Calls
  3. Use tiny arrow buttons near this list to move MX_DMA_Init() above MX_I2S3_Init()
  4. Save the project, generated code will have correct order
  • Thanks for your suggestion! I changed the order accordingly (both in code and in Project Manager). Unfortunately it doesn't solve the problem, although it could certainly be part of the solution. Therefore, I will keep on trying with this correct order (MX_DMA_Init before MX_I2S3_Init). – IsaacNuketon Nov 23 '21 at 10:46
1

for my experience,

HAL_I2S_Receive_DMA() function is to configure the address (if you learn the LL API, you need manually set source/dest address and length of data: [LL_DMA_SetDataLength()])

So, you can move it before While(1) loop.

if you want to read/process the buffer "data", you can use dma interrupt callback function, in HAL API is : HAL_I2S_RxHalfCpltCallback(), HAL_I2S_RxCpltCallback()

===================================================================== update:

// init method and buff
...
xx_init()
volatile int16_t data[500] = {0};
int16_t data_shifted[500];
...
// 
HAL_I2S_Receive_DMA(&hi2s3, &data[0], 250);
while(1){
  if (FLAG_half){
    FLAG_half=0;
    // add your code: data shift [0:250]... 
  }
  if (FLAG_comp){
    FLAG_comp=0;
    // add your code: data shift [250:500]... 
  }


} // end while

} end main

HAL_I2S_RxHalfCpltCallback(){ 
  FLAG_half=1;
}
HAL_I2S_RxCpltCallback(){ 
  FLAG_comp=1;
}
Yong Wang
  • 59
  • 1
  • 5
  • LL API function is : LL_DMA_ConfigAddresses() and LL_DMA_SetDataLength() , which are corresponding to HAL_I2S_Receive_DMA(). – Yong Wang Nov 24 '21 at 03:19
  • First of all: my apologies for the late reply. I doubt whether this method would solve the problem: [this stm32f4xx_hal_i2s code comment](https://github.com/STMicroelectronics/STM32CubeF4/blob/4aba24d78fef03d797a82b258f37dbc84728bbb5/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2s.c#L81) says that HAL_I2S_Receive_DMA() has to be used to receive data using DMA. This function then calls HAL_I2S_RxHalfCpltCallback() and HAL_I2S_RxCpltCallback(). – IsaacNuketon Dec 01 '21 at 11:01
  • I have update code that I used to receive mic data from I2S. DMA use circle mode. HAL_I2S_Receive_DMA() seems to be a init-method, that tells DMA where to save data. (&data[0]). then we can use interrupt callback function to do some work as I showed. Note that your data length is 500, so half of it is 250. you may need to set 250 in HAL_I2S_Receive_DMA() – Yong Wang Dec 03 '21 at 01:20