cancel
Showing results for 
Search instead for 
Did you mean: 

Can't make WM8994 play over SAI on STM32H745I-DISCO board.

HTD
Senior III

My project uses HAL and it's made with STM32CubeIDE.

I've naturally seen the example and tried to configure everything exactly as in the example.

So - the clock for SAI is set to 4.096MHz. I use SAI1 configured as on the screen:

0693W00000WLEAFQA5.png0693W00000WLEAKQA5.png0693W00000WLEAPQA5.pngHere's my code:

#include "sai.h"
 
static int16_t* Audio_Buffer = NULL;
static size_t Audio_Buffer_Size = 0;
static AUDIO_Drv_t* Audio_Drv = NULL;
static WM8994_Init_t codec_init;
 
/**
  * @brief  Register Bus IOs if component ID is OK
  * @retval error status
  */
static int32_t WM8994_Probe()
{
  int32_t ret = BSP_ERROR_NONE;
  WM8994_IO_t              IOCtx;
  static WM8994_Object_t   WM8994Obj;
  uint32_t id;
 
  /* Configure the audio driver */
  IOCtx.Address     = AUDIO_I2C_ADDRESS;
  IOCtx.Init        = BSP_I2C4_Init;
  IOCtx.DeInit      = BSP_I2C4_DeInit;
  IOCtx.ReadReg     = BSP_I2C4_ReadReg16;
  IOCtx.WriteReg    = BSP_I2C4_WriteReg16;
  IOCtx.GetTick     = BSP_GetTick;
 
  if(WM8994_RegisterBusIO (&WM8994Obj, &IOCtx) != WM8994_OK)
  {
    ret = BSP_ERROR_BUS_FAILURE;
  }
  else
  {
    /* Reset the codec */
    if(WM8994_Reset(&WM8994Obj) != WM8994_OK)
    {
      ret = BSP_ERROR_COMPONENT_FAILURE;
    }
    else if(WM8994_ReadID(&WM8994Obj, &id) != WM8994_OK)
    {
      ret = BSP_ERROR_COMPONENT_FAILURE;
    }
    else if(id != WM8994_ID)
    {
      ret = BSP_ERROR_UNKNOWN_COMPONENT;
    }
    else
    {
      Audio_Drv = (AUDIO_Drv_t *) &WM8994_Driver;
      Audio_CompObj = &WM8994Obj;
    }
  }
  return ret;
}
 
/**
 * @fn int16_t generate_sine*(uint32_t, double, double, double, size_t*)
 * @brief Generates a sine wave.
 * @param sampleRate Sample rate in samples per second.
 * @param time Duration of the PCM data [s].
 * @param frequency Frequency of the generated wave [Hz].
 * @param level Amplitude [dB]. Max 0dB.
 * @param size A pointer to the generated data size in bytes.
 * @return A pointer to the buffer containing the generated wave.
 */
static int16_t* generate_sine(uint32_t sampleRate, double time, double frequency, double level, size_t* size)
{
  if (sampleRate < 8000 || sampleRate < 2 * frequency || !time || level > 0) return NULL;
  const size_t sampleCount = ceil(sampleRate * time);
  *size = sampleCount * sizeof(int16_t);
  int16_t* buffer = (int16_t*)malloc(*size);
  const double periodSamplesCount = sampleRate / frequency;
  const double a = pow(10, 0.1 * level);
  for (size_t i = 0; i < sampleCount; i++)
  {
    double x = 6.283185307179586476925286766559 * i / periodSamplesCount;
    double y = sin(x) * a;
    buffer[i] = round(0x7fff * y);
  }
  return buffer;
}
 
HAL_StatusTypeDef Audio_Init()
{
  if (WM8994_Probe() != BSP_ERROR_NONE) return HAL_ERROR;
  if (!Audio_CompObj) return HAL_ERROR;
  codec_init.Resolution   = WM8994_RESOLUTION_16b;
  codec_init.Frequency    = WM8994_FREQUENCY_22K;
  codec_init.InputDevice  = WM8994_IN_NONE;
  codec_init.OutputDevice = AUDIO_OUT_DEVICE_SPEAKER;
  codec_init.Volume       = VOLUME_OUT_CONVERT(100);
  if (Audio_Drv->Init(Audio_CompObj, &codec_init) != 0) return HAL_ERROR;
  if (Audio_Drv->Play(Audio_CompObj) < 0) return HAL_ERROR;
  Audio_Buffer = generate_sine(WM8994_FREQUENCY_22K, 1.0, 1000.0, 0, &Audio_Buffer_Size);
  if (!Audio_Buffer || !Audio_Buffer_Size) return HAL_ERROR;
  return HAL_OK;
}
 
HAL_StatusTypeDef Audio_Test()
{
  if (HAL_SAI_Transmit_DMA(&hsai_BlockA1, (uint8_t*)Audio_Buffer, Audio_Buffer_Size) != HAL_OK) return HAL_ERROR;
  return HAL_OK;
}

At the end of the main startup I call Audio_Init(), and if it returns HAL_OK, Audio_Test().

Both functions return HAL_OK, so every function called in the way succeeds.

I connected the "SPK left" output through 2x 2u2 capacitors to the oscilloscope to test if anything goes out. I don't have a speaker right now, but it should work, right? The capacitors are used in series between the scope and both pins, because none of them is ground and I don't want to short anything to the ground, though I'm not sure if that's OK. How else would you test it without actual speaker or headphones?

Naturally - the oscilloscope shows nothing.

What am I doing wrong? My goal is to see a sine wave on one (or best both) speaker outputs. As the WM8994 driver passes initialization, I suspect the SAI doesn't work at all. Or IDK, am I missing something about PCM data? I assumed it's just samples, each 16-bits with sign. But then again, if I messed up the data, but still provided changing values - there should be at least something on the scope. But running the code does absolutely nothing.

OK, now the generated code:

static void MX_SAI1_Init(void)
{
 
  /* USER CODE BEGIN SAI1_Init 0 */
 
  /* USER CODE END SAI1_Init 0 */
 
  /* USER CODE BEGIN SAI1_Init 1 */
 
  /* USER CODE END SAI1_Init 1 */
  hsai_BlockA1.Instance = SAI1_Block_A;
  hsai_BlockA1.Init.AudioMode = SAI_MODEMASTER_TX;
  hsai_BlockA1.Init.Synchro = SAI_ASYNCHRONOUS;
  hsai_BlockA1.Init.OutputDrive = SAI_OUTPUTDRIVE_ENABLE;
  hsai_BlockA1.Init.NoDivider = SAI_MASTERDIVIDER_ENABLE;
  hsai_BlockA1.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_1QF;
  hsai_BlockA1.Init.AudioFrequency = SAI_AUDIO_FREQUENCY_22K;
  hsai_BlockA1.Init.SynchroExt = SAI_SYNCEXT_DISABLE;
  hsai_BlockA1.Init.MonoStereoMode = SAI_STEREOMODE;
  hsai_BlockA1.Init.CompandingMode = SAI_NOCOMPANDING;
  hsai_BlockA1.Init.TriState = SAI_OUTPUT_NOTRELEASED;
  if (HAL_SAI_InitProtocol(&hsai_BlockA1, SAI_PCM_SHORT, SAI_PROTOCOL_DATASIZE_16BIT, 4) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SAI1_Init 2 */
 
  /* USER CODE END SAI1_Init 2 */
 
}
 
HAL_StatusTypeDef HAL_SAI_InitProtocol(SAI_HandleTypeDef *hsai, uint32_t protocol, uint32_t datasize, uint32_t nbslot)
{
  HAL_StatusTypeDef status;
 
  /* Check the parameters */
  assert_param(IS_SAI_SUPPORTED_PROTOCOL(protocol));
  assert_param(IS_SAI_PROTOCOL_DATASIZE(datasize));
 
  switch (protocol)
  {
    case SAI_I2S_STANDARD :
    case SAI_I2S_MSBJUSTIFIED :
    case SAI_I2S_LSBJUSTIFIED :
      status = SAI_InitI2S(hsai, protocol, datasize, nbslot);
      break;
    case SAI_PCM_LONG :
    case SAI_PCM_SHORT :
      status = SAI_InitPCM(hsai, protocol, datasize, nbslot);
      break;
    default :
      status = HAL_ERROR;
      break;
  }
 
  if (status == HAL_OK)
  {
    status = HAL_SAI_Init(hsai);
  }
 
  return status;
}
 
static HAL_StatusTypeDef SAI_InitPCM(SAI_HandleTypeDef *hsai, uint32_t protocol, uint32_t datasize, uint32_t nbslot)
{
  HAL_StatusTypeDef status = HAL_OK;
 
  hsai->Init.Protocol            = SAI_FREE_PROTOCOL;
  hsai->Init.FirstBit            = SAI_FIRSTBIT_MSB;
  /* Compute ClockStrobing according AudioMode */
  if ((hsai->Init.AudioMode == SAI_MODEMASTER_TX) || (hsai->Init.AudioMode == SAI_MODESLAVE_TX))
  {
    /* Transmit */
    hsai->Init.ClockStrobing     = SAI_CLOCKSTROBING_RISINGEDGE;
  }
  else
  {
    /* Receive */
    hsai->Init.ClockStrobing     = SAI_CLOCKSTROBING_FALLINGEDGE;
  }
  hsai->FrameInit.FSDefinition   = SAI_FS_STARTFRAME;
  hsai->FrameInit.FSPolarity     = SAI_FS_ACTIVE_HIGH;
  hsai->FrameInit.FSOffset       = SAI_FS_BEFOREFIRSTBIT;
  hsai->SlotInit.FirstBitOffset  = 0;
  hsai->SlotInit.SlotNumber      = nbslot;
  hsai->SlotInit.SlotActive      = SAI_SLOTACTIVE_ALL;
 
  if (protocol == SAI_PCM_SHORT)
  {
      hsai->FrameInit.ActiveFrameLength = 1;
  }
  else
  {
    /* SAI_PCM_LONG */
      hsai->FrameInit.ActiveFrameLength = 13;
  }
 
  switch (datasize)
  {
    case SAI_PROTOCOL_DATASIZE_16BIT:
      hsai->Init.DataSize = SAI_DATASIZE_16;
      hsai->FrameInit.FrameLength = 16U * nbslot;
      hsai->SlotInit.SlotSize = SAI_SLOTSIZE_16B;
      break;
    case SAI_PROTOCOL_DATASIZE_16BITEXTENDED :
      hsai->Init.DataSize = SAI_DATASIZE_16;
      hsai->FrameInit.FrameLength = 32U * nbslot;
      hsai->SlotInit.SlotSize = SAI_SLOTSIZE_32B;
      break;
    case SAI_PROTOCOL_DATASIZE_24BIT :
      hsai->Init.DataSize = SAI_DATASIZE_24;
      hsai->FrameInit.FrameLength = 32U * nbslot;
      hsai->SlotInit.SlotSize = SAI_SLOTSIZE_32B;
      break;
    case SAI_PROTOCOL_DATASIZE_32BIT:
      hsai->Init.DataSize = SAI_DATASIZE_32;
      hsai->FrameInit.FrameLength = 32U * nbslot;
      hsai->SlotInit.SlotSize = SAI_SLOTSIZE_32B;
      break;
    default :
      status = HAL_ERROR;
      break;
  }
 
  return status;
}

Now wait a minute, I have SAI1 in example, but the board schematics says otherwise:

0693W00000WLEpSQAX.pngI tested it with SAI2, but still, the same, doesn't work.

19 REPLIES 19
MM..1
Chief II

Caps you connect ok and instead speaker use for example 100R .

Plus check buffer values in debuger.

I used 10R parallel to the output (before caps). Still no signal. Driver detects and identifies the chip, the volume is set to max, mute is off, speaker output selected (I even tried to select both speaker and headphone outputs), play called successfully. The SAI is configured as in the example, using the same DMA settings. I even tested both outputs, left and right. Nothing on both.

AScha.3
Chief III

check the SAI lines with scope , see data ?

+ SAI clock is so slow - why ? ( i use 96 MHz sai module clock )

If you feel a post has answered your question, please click "Accept as Solution".
HTD
Senior III

The lines are not exactly accessible on the board, both MCU and WM8994 are BGA, no wires exposed, no other elements that would have the lines exposed. The clock is from the example, I thought if they tested it with that clock that it should work. I changed the clock to 96MHz, but it didn't help either. I even tried to use HAL_SAI_Transmit() in blocking mode, no DMA, of course it didn't work too.

AScha.3
Chief III

so : sai1 can never "work". :)

on sai2 - clkout A is set ? control the pin numbers, is every pin you use identical with board schematics?

If you feel a post has answered your question, please click "Accept as Solution".
AScha.3
Chief III

++ did you try first the example for the board : SAI_AudioPlayback ?

  • This example shows how to use the SAI to playback audio data
  • The SAI1 transmits audio PCM data to the WM8994 Codec for playback using the DMA2 in circular mode.
If you feel a post has answered your question, please click "Accept as Solution".

I changed the configuration to SAI2 Block A. Pins match the schematics. Where can I set clkout A?

I'm trying to recreate this example on STM32CubeIDE project.

From what I see in the board schematics - the WM8994 is connected to SAI2 not SAI1:

0693W00000WLEuNQAX.pngBTW, in the main function of the example you have:

SaiOutputHandle.Instance            = AUDIO_OUT_SAIx;
 
// and in stm32h745i_discovery_audio.h:
 
/* SAI peripheral configuration defines */
#define AUDIO_OUT_SAIx                           SAI2_Block_A
 
#define AUDIO_OUT_SAIx_DMAx_STREAM               DMA2_Stream1
#define AUDIO_OUT_SAIx_DMAx_REQUEST              DMA_REQUEST_SAI2_A
#define AUDIO_OUT_SAIx_DMAx_IRQ                  DMA2_Stream1_IRQn

There are however significant differences in protocol init:

// Example:
  SaiOutputHandle.Init.ClockStrobing  = SAI_CLOCKSTROBING_FALLINGEDGE;
 
  SaiOutputHandle.FrameInit.FrameLength       = 128;
  SaiOutputHandle.FrameInit.ActiveFrameLength = 64;
  SaiOutputHandle.FrameInit.FSDefinition      = SAI_FS_CHANNEL_IDENTIFICATION;
  SaiOutputHandle.FrameInit.FSPolarity        = SAI_FS_ACTIVE_LOW;
  SaiOutputHandle.FrameInit.FSOffset          = SAI_FS_BEFOREFIRSTBIT;
 
  SaiOutputHandle.SlotInit.FirstBitOffset = 0;
  SaiOutputHandle.SlotInit.SlotSize       = SAI_SLOTSIZE_DATASIZE;
  SaiOutputHandle.SlotInit.SlotNumber     = 4;
  SaiOutputHandle.SlotInit.SlotActive     = (SAI_SLOTACTIVE_0 | SAI_SLOTACTIVE_2);

CubeMX defines this with the following function:

static HAL_StatusTypeDef SAI_InitPCM(SAI_HandleTypeDef *hsai, uint32_t protocol, uint32_t datasize, uint32_t nbslot)
{
  HAL_StatusTypeDef status = HAL_OK;
 
  hsai->Init.Protocol            = SAI_FREE_PROTOCOL;
  hsai->Init.FirstBit            = SAI_FIRSTBIT_MSB;
  /* Compute ClockStrobing according AudioMode */
  if ((hsai->Init.AudioMode == SAI_MODEMASTER_TX) || (hsai->Init.AudioMode == SAI_MODESLAVE_TX))
  {
    /* Transmit */
    hsai->Init.ClockStrobing     = SAI_CLOCKSTROBING_RISINGEDGE;
  }
  else
  {
    /* Receive */
    hsai->Init.ClockStrobing     = SAI_CLOCKSTROBING_FALLINGEDGE;
  }
  hsai->FrameInit.FSDefinition   = SAI_FS_STARTFRAME;
  hsai->FrameInit.FSPolarity     = SAI_FS_ACTIVE_HIGH;
  hsai->FrameInit.FSOffset       = SAI_FS_BEFOREFIRSTBIT;
  hsai->SlotInit.FirstBitOffset  = 0;
  hsai->SlotInit.SlotNumber      = nbslot;
  hsai->SlotInit.SlotActive      = SAI_SLOTACTIVE_ALL;
 
  if (protocol == SAI_PCM_SHORT)
  {
      hsai->FrameInit.ActiveFrameLength = 1;
  }
  else
  {
    /* SAI_PCM_LONG */
      hsai->FrameInit.ActiveFrameLength = 13;
  }
 
  switch (datasize)
  {
    case SAI_PROTOCOL_DATASIZE_16BIT:
      hsai->Init.DataSize = SAI_DATASIZE_16;
      hsai->FrameInit.FrameLength = 16U * nbslot;
      hsai->SlotInit.SlotSize = SAI_SLOTSIZE_16B;
      break;
    case SAI_PROTOCOL_DATASIZE_16BITEXTENDED :
      hsai->Init.DataSize = SAI_DATASIZE_16;
      hsai->FrameInit.FrameLength = 32U * nbslot;
      hsai->SlotInit.SlotSize = SAI_SLOTSIZE_32B;
      break;
    case SAI_PROTOCOL_DATASIZE_24BIT :
      hsai->Init.DataSize = SAI_DATASIZE_24;
      hsai->FrameInit.FrameLength = 32U * nbslot;
      hsai->SlotInit.SlotSize = SAI_SLOTSIZE_32B;
      break;
    case SAI_PROTOCOL_DATASIZE_32BIT:
      hsai->Init.DataSize = SAI_DATASIZE_32;
      hsai->FrameInit.FrameLength = 32U * nbslot;
      hsai->SlotInit.SlotSize = SAI_SLOTSIZE_32B;
      break;
    default :
      status = HAL_ERROR;
      break;
  }
 
  return status;
}

I tried to use the initialization from the example instead of this function, but it didn't help.

set mode in Cube

0693W00000WLEvpQAH.png

If you feel a post has answered your question, please click "Accept as Solution".