Can't make WM8994 play over SAI on STM32H745I-DISCO board.
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:


Here'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:
I tested it with SAI2, but still, the same, doesn't work.
