Electronic – I2S input (ADC) yields garbage, output (DAC) works fine

i2sstm32

Setup:

  • STM32F407 (discovery board) used in I2S master mode
  • I2S in/out module, 24 bit over 32 bits frame, used in I2S slave mode
  • using CubeMX's HAL for the prototype (this might not be relevant)

I2S output is connected to I2S3 (MCLK=PC7, WS=PA15, CK=PC10, SD=PB5) and works fine. I can hear a test wave.

I2S input is connected to I2S2 (MCLK=PC6, WS=PB12, CK=PB13, SD=PB15). All I get is garbage data with amplitudes way beyond the expected 24 bits (IOW the 4th byte is also garbage, but even when masked the resulting data is garbage). My first reaction was "noise on the line" (since it's a "hairy" circuit for now) but when I disconnect only MCLK from the module (not from the µproc, so MCLK can still interfere) I get straight zeros, which I think invalidates the "noise on the line" option.

Of course, being paranoid, I checked multiple times that everything is soldered at the right place and connects to the right pin.

Now I'm stumped. Any idea what could be wrong, or at least where should I look?

For the record, here is the (autogenerated) configuration and my test code:

/* I2S2 init function */
void MX_I2S2_Init(void)
{

  hi2s2.Instance = SPI2;
  hi2s2.Init.Mode = I2S_MODE_MASTER_RX;
  hi2s2.Init.Standard = I2S_STANDARD_PHILIPS;
  hi2s2.Init.DataFormat = I2S_DATAFORMAT_24B;
  hi2s2.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;
  hi2s2.Init.AudioFreq = I2S_AUDIOFREQ_8K;
  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)
  {
    Error_Handler();
  }

}
/* I2S3 init function */
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_24B;
  hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;
  hi2s3.Init.AudioFreq = I2S_AUDIOFREQ_8K;
  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)
  {
    Error_Handler();
  }

}

void HAL_I2S_MspInit(I2S_HandleTypeDef* i2sHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(i2sHandle->Instance==SPI2)
  {
  /* USER CODE BEGIN SPI2_MspInit 0 */

  /* USER CODE END SPI2_MspInit 0 */
    /* I2S2 clock enable */
    __HAL_RCC_SPI2_CLK_ENABLE();

    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    /**I2S2 GPIO Configuration
    PB12     ------> I2S2_WS
    PB13     ------> I2S2_CK
    PB15     ------> I2S2_SD
    PC6     ------> I2S2_MCK
    */
    GPIO_InitStruct.Pin = I2S_RCV_WS_Pin|I2S_RCV_CK_Pin|I2S_RCV_SD_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = I2S_RCV_MCK_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(I2S_RCV_MCK_GPIO_Port, &GPIO_InitStruct);

  /* USER CODE BEGIN SPI2_MspInit 1 */

  /* USER CODE END SPI2_MspInit 1 */
  }
  else if(i2sHandle->Instance==SPI3)
  {
  /* USER CODE BEGIN SPI3_MspInit 0 */

  /* USER CODE END SPI3_MspInit 0 */
    /* I2S3 clock enable */
    __HAL_RCC_SPI3_CLK_ENABLE();

    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**I2S3 GPIO Configuration
    PC7     ------> I2S3_MCK
    PA15     ------> I2S3_WS
    PC10     ------> I2S3_CK
    PB5     ------> I2S3_SD
    */
    GPIO_InitStruct.Pin = I2S_SND_MCK_Pin|I2S_SND_CK_Pin;
    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);

    GPIO_InitStruct.Pin = I2S_SND_WS_Pin;
    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(I2S_SND_WS_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = I2S_SND_SD_Pin;
    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(I2S_SND_SD_GPIO_Port, &GPIO_InitStruct);

  /* USER CODE BEGIN SPI3_MspInit 1 */

  /* USER CODE END SPI3_MspInit 1 */
  }
}
  // SEND (WORKS FINE)
  static constexpr int32_t amplitude = 80000;
  int32_t wave[] = { 0, 0,
                     amplitude / 2, amplitude / 2,
                     amplitude, amplitude,
                     amplitude / 2, amplitude / 2,
                     0, 0,
                     -amplitude / 2, -amplitude / 2,
                     -amplitude, -amplitude,
                     -amplitude / 2, -amplitude / 2,
                     };
  while (true) {
    HAL_I2S_Transmit(&I2S_SND, (uint16_t*)wave, sizeof(wave) / 4, 500);
  }
  // RECEIVE (GARBAGE)
  int32_t data[2]; 
  while (true) {
    if (HAL_OK == HAL_I2S_Receive(&I2S_RCV, (uint16_t*)data, sizeof(data) / 4, 500)) {
      debug("%08x, %08x\n", data[0], data[1]);
    }
  }

And a sample of the data I get:

00000000, 00000000
00000000, 00000000
bf00085b, fb000848
030007d2, 110007c2
0729f600, 071ca400
95000676, 8c000683
8b0005d2, b40005db
05363b00, 0540c600
04acb200, 04b4eb00
9700042b, 54000423
03afd600, 03a97300
030e8c00, 03074700
800002d3, 150002d8
71000276, 1b00027a
021f2500, 02213700
f00001a2, 400001a0

Note that the first samples are 0 before they go wild. Please also note that I get the same kind of results with an open line (nothing connected), with a microphone connected, or even looping the wave I generate with the DAC into the ADC.

Lastly, before any remarks: I use separate I2S ports because the STM32F407 is not the final target (which is STM32F730) which doesn't support full duplex I2S. This shouldn't change anything past cabling.

Edit: I don't have an oscilloscope at hand, but I nevertheless tried to observe the signals with a mere voltmeter. All 3 clocks are perfectly stable (1.46V) as is to be expected with symmetric signals. However the data signal (taken directly on the module, disconnected from the µproc so the wire doesn't interfere) varies wildly in the 0.5-1.7V range. So the problem appears to come from the module itself, which is doubtful. There has to be something I missed.

Best Answer

After re-checking everything, including going through (again) the board schematics to rule out any pin incompatibility, I was fed up and decided to move on and try DMA transfers since I'll need them anyway (I tried the synchronous API first as a POC, thinking --erroneously-- that it would be simpler).

On first try it worked, with line-in being successfully copied to line-out (verified with PC -> board -> headphones):

  volatile int32_t data[4];
  HAL_I2S_Receive_DMA(&I2S_RCV, data, sizeof(data) / sizeof(*data));
  HAL_I2S_Transmit_DMA(&I2S_SND, data, sizeof(data) / sizeof(*data));

The main loop, just dumping asynchronously the buffer's content on UART, confirmed the coherence of the data.

I tried to reproduce it using the synchronous calls HAL_I2S_Receive and HAL_I2S_Transmit, and again got incoherent input data. :(

My best guess right now is that HAL's synchronous functions HAL_I2S_Receive and HAL_I2S_Transmit are buggy, since DMA works out of the box.

I for one won't research this issue further since I need DMA anyway in the final product, so those issues specific to the synchronous API are moot. But if someone has an idea of the why I'd still be interested to hear it.

Related Topic