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
AScha.3
Chief III

make new project and try the original example first.

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

Yes. I set it that way. Still doesn't work. My current Cube configuration:

0693W00000WLExMQAX.png

The original example doesn't show how to use audio on STM32CubeIDE project. The problem is I have big existing project I just need to add a simple beep feature. I've analyzed most of the differences between those two projects and tried to copy everything I could from the original.

My question is how to exactly do beep on STM32H745I-DISCO without starting a new project in Embedded Wizard.

HTD
Senior III

Have you looked at my generate_sine() function? I assumed PCM are just samples. Like int16_t numbers. So +0x7fff is full power in plus, -0x7fff is full power in minus. But maybe the encoding is different? I couldn't find any documentation on it. I found general PCM definition that defines it just like I described and coded, but I may be wrong, maybe there's a catch and the data should be encoded differently?

For beep only is waste of resources use SAI and WM. Use one pin TIM PWM DMA and drived out.

More info SAI en.STM32F7_Peripheral_SAI.pdf

HTD
Senior III

Sure. That's plan B. However, ST sells discovery boards with WM chip, I already have a couple of them and it would be nice if they could be used with the software provided. I'm sure it can be done somehow, I just don't know what in my configuration is wrong. Beep is to test if it works, when it beeps, I can make it talk. So far I have full success with testing almost all peripherals on the board, all within one big STM32CubeIDE project. It's the last missing part in my project.

AScha.3
Chief III

>My question is how to exactly do beep on STM32H745I-DISCO without starting a new project in Embedded Wizard.

ok, so take plan B. 10 minutes and ready.

+

if you have too much time and nothing better to do :

make new stm32project -> board selector-> h745-disco -> name: test745 (or any else) and try the original example first.

when this is running, you see, what you need and adjust , and use /copy this to your project.

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

Then try start here C:\Users\YUO\STM32Cube\Repository\STM32Cube_FW_H7_V1.9.1\Projects\STM32H745I-DISCO\Examples\BSP\readme.txt 

 ** AUDIO PLAY **

This example shows how to play an audio file using the DMA circular mode and 

how to handle the buffer update.

Plug a headphone to hear the sound 

@Note: Copy file 'audio_sample_tdm.bin' (available in Binary) directly in 

    the flash at @0x08080000 using STM32CubeProgrammer utility

Oh, I could swear a couple of months ago the example didn't have a project file for Cube, now it does and I checked it. Of course it worked, but, related to my other (next) question - there is a problem. The example plays over headphone output. I changed it to a speaker output and well, it doesn't play. Is it possible I fried my WM8994 amplifier by connecting it (through 2 capacitors) to an oscilloscope? I'll check it soon, but that seems to be a chip installed in some PCs, and AFAIK they are nearly indestructible, at least fully immune to shorting any I/O.

Though example works, I also read that people say BSP drivers work. I checked that, and I can confirm. BSP drivers work. (No sound on speaker output, but that's another thing).

I analyzed differences between HAL and BSP code. After like 6 hours of investigating - both pieces of code seem to be nearly identical. But HAL doesn't work, BSP works. There must be a single tiny detail making the difference. I configured the SAI in STM32CubeIDE exactly as the BSP driver is configured. I even tested both versions on the same device and project. HAL version produces just a click when it's run, as if it produced DC on the output.

Another weirdness: HAL and BSP drivers interfere with each other. In an useful way. HAL settings (set with CubeIDE) seem to have priority over BSP ones. So I was able to reconfigure DMA to regular mode as circular audio buffer was annoying in my experiments. Of course removing all code generation for SAI also works, but then in order to reconfigure anything I would have to modify the original BSP files and I don't want to.

I suspect there are 2 important details left to be figured out here. One is what is preventing HAL from making SAI + WM8994 play, another one is what's up with the speaker output. My guess it can be an actual bug in BSP driver, maybe enabling the speaker output requires sending something special to WM8994. Or maybe this discovery board just lacks that feature, there is no example using the speaker output so maybe no one really tested it ever ;) But I connected a 4R speaker to the headphone output and it works as charm, it's even loud enough to use. I doubt the speaker output is louder, it seems to be impossible considering there is no special power circuit on the board, the WM8994 is powered from 3V3 line and is a small, low power chip.

>But I connected a 4R speaker to the headphone output and it works as charm

ever read the ds of WM8994 ?

  • 5.3 mW total power for DAC playback to headphones
  • 2W stereo (2 x 2W) class D/AB speaker driver

so : d-amp for speaker can be much more powerful than you think.

+

> there is no example using the speaker output so maybe no one really tested it ever ;)

you maybe right :)

+

>One is what is preventing HAL from making SAI + WM8994 play

  • look at the init/command sequence going to WM8994 over I2C ; this is most important for the function you will see from such complex codec chip; one wrong or missing command here, like "enable output xy" , and nothing coming out.
  • if you want speaker out working, read in manual , what commands you have to send; the chip has about 200 command registers (on 360 pages), so we talk about your success in maybe 2 months again.... :)

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