cancel
Showing results for 
Search instead for 
Did you mean: 

Cannot make SPDIF TX work

nt2ds
Associate III

Hello, I have made my own STM32F732RET6 board that has a USB FS configured as a Stereo, 24 bit 48kHz Audio Interface and SAI2 connected to an SN75176BDR (Differential Bus Transceiver IC) because I want to transmit AES3-AES/EBU (same as SPDIF) audio data coming from the USB). So USB Audio -> SPDIF TX. Problem is I cannot get it to work right and ALWAYS get noise in the output signal (I have hardware that can receive AES3-AES/EBU so I can hear it). I am suspecting this is a clock issue where the frequency of the SPDIF signal is not high enough to support 24 bit stereo 48kHz, from what I've read, this would require an SPDIF frequency of 6.144MHz. I don't know if "Real Audio Frequency" in the .ioc "Pinout & Configuration" on SAI2 should match this number (6.144MHz) or the Audio Sampling Frequency (48kHz). I cannot get "Real Audio Frequency" to be exactly 6.144MHz. I don't know if this should be the frequency the the SAI2 Clock Mux outputs (SAI2 Clock Mux sources from PLLSAI1), I can get PLLSAI1 to output a frequency of 6.144MHz but then "Real Audio Frequency" becomes 96kHz (and when this happens I get no sound, no noise, absolutely nothing on the receiving end). When I get PLLSAI1 to output 192MHz, the signal I get contains some of the audio I sent from the computer (USB) but it is mostly noise. Is there any way that SAI2 works exactly on the frequency provided by PLLSAI1 (through the MUX) without a divider? I read an older post about CubeMX generating wrong Clock Config for SPDIF but I really cannot understand how he fixes it. He also mentions something about having to set the Audio Frequency to 96kHz in order to get working 48kHz but further than that I don't understand.
Below I have posted some files so you can hear what it sound like currently.
Code Below, this is from the usbd_audio_if.c file that sends the USB Audio Data to the SAI DMA, no other function has been touched besides AUDIO_PeriodicTC_FS

 

static int8_t AUDIO_PeriodicTC_FS(uint8_t *pbuf, uint32_t size, uint8_t cmd)
{
    if (cmd == AUDIO_OUT_TC)
    {
        // Convert USB 24-bit stereo to SPDIF 32-bit frames
    	uint32_t *dst = sai_tx_buffer;
        uint8_t *src=pbuf;

        for (uint32_t i = 0; i < size / 3; i++)
        {
            // USB is 24-bit little endian: LSB, Mid, MSB
            uint32_t sample = (uint32_t)(src[0] | (src[1] << 8) | (src[2] << 16));

            // Sign extend from 24 to 32 bits

            if (sample & 0x800000){
            	sample = sample | 0xFF000000;
            }



            *dst++ = (uint32_t)sample;

            src += 3;
        }

        // Now transmit aligned SPDIF words
        HAL_SAI_Transmit_DMA(&hsai_BlockB2, (uint8_t*)sai_tx_buffer, size / 3);
    }
    return USBD_OK;
}

Below are both clock configurations, the first with the 3MHz Real Audio Frequency is the one the merely works. Second is the one with 48kHz Real Audio Frequency, matching the Audio Frequency which gives no audio.

192MHz Clock, 3MHz Real Audio Frequency192MHz Clock, 3MHz Real Audio Frequency3.072 Clock, 48kHz Real Audio Frequency3.072 Clock, 48kHz Real Audio Frequency
I've been dealing with this for days, any help would be greatly appreciated, Thanks in Advance! :)

22 REPLIES 22
FBL
ST Employee

Hi again @nt2ds 

Maybe a simple test in loopback mode can help!

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.


nt2ds
Associate III

YES! This seems to be the cause so I correctly suspected the divider and the clock! I have now changed it to 6.144MHz but something strange happens again. When I plug the device and start playing something, for the first couple of second it plays properly and then it gets progressively worse and worse until after about 2 and a half minutes it plays properly again and then getting progressively worse. I have attached a .wav file so you can listen the problem yourself, in the beginning it plays properly then gets worse and at 2:27 it goes back to normal for about 20 seconds until it starts worsening again. Now, we have fixed the clock but I don't know if this again a clock issue that has to do with Clock Drifting or if it is a buffering issue with the USB and the SAI DMA.
Below is the code from usbd_audio_if.c that also sends the USB Audio Packets to the SAI DMA:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : usbd_audio_if.c
  * @version        : v1.0_Cube
  * @brief          : Generic media access layer.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
 /* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "usbd_audio_if.h"

/* USER CODE BEGIN INCLUDE */

/* USER CODE END INCLUDE */

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/

/* USER CODE END PV */

/** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY
  * @brief Usb device library.
  * @{
  */

/** @addtogroup USBD_AUDIO_IF
  * @{
  */

/** @defgroup USBD_AUDIO_IF_Private_TypesDefinitions USBD_AUDIO_IF_Private_TypesDefinitions
  * @brief Private types.
  * @{
  */

/* USER CODE BEGIN PRIVATE_TYPES */

/* USER CODE END PRIVATE_TYPES */

/**
  * @}
  */

/** @defgroup USBD_AUDIO_IF_Private_Defines USBD_AUDIO_IF_Private_Defines
  * @brief Private defines.
  * @{
  */

/* USER CODE BEGIN PRIVATE_DEFINES */

/* USER CODE END PRIVATE_DEFINES */

/**
  * @}
  */

/** @defgroup USBD_AUDIO_IF_Private_Macros USBD_AUDIO_IF_Private_Macros
  * @brief Private macros.
  * @{
  */

/* USER CODE BEGIN PRIVATE_MACRO */

/* USER CODE END PRIVATE_MACRO */

/**
  * @}
  */

/** @defgroup USBD_AUDIO_IF_Private_Variables USBD_AUDIO_IF_Private_Variables
  * @brief Private variables.
  * @{
  */

/* USER CODE BEGIN PRIVATE_VARIABLES */

/* USER CODE END PRIVATE_VARIABLES */

/**
  * @}
  */

/** @defgroup USBD_AUDIO_IF_Exported_Variables USBD_AUDIO_IF_Exported_Variables
  * @brief Public variables.
  * @{
  */

extern USBD_HandleTypeDef hUsbDeviceFS;

/* USER CODE BEGIN EXPORTED_VARIABLES */

/* USER CODE END EXPORTED_VARIABLES */

/**
  * @}
  */

/** @defgroup USBD_AUDIO_IF_Private_FunctionPrototypes USBD_AUDIO_IF_Private_FunctionPrototypes
  * @brief Private functions declaration.
  * @{
  */

static int8_t AUDIO_Init_FS(uint32_t AudioFreq, uint32_t Volume, uint32_t options);
static int8_t AUDIO_DeInit_FS(uint32_t options);
static int8_t AUDIO_AudioCmd_FS(uint8_t* pbuf, uint32_t size, uint8_t cmd);
static int8_t AUDIO_VolumeCtl_FS(uint8_t vol);
static int8_t AUDIO_MuteCtl_FS(uint8_t cmd);
static int8_t AUDIO_PeriodicTC_FS(uint8_t *pbuf, uint32_t size, uint8_t cmd);
static int8_t AUDIO_GetState_FS(void);

/* USER CODE BEGIN PRIVATE_FUNCTIONS_DECLARATION */
extern USBD_HandleTypeDef hUsbDeviceFS;
extern DMA_HandleTypeDef hdma_sai2_b;
extern SAI_HandleTypeDef hsai_BlockB2;
#define SAI_BUFFER_SAMPLES  (AUDIO_OUT_PACKET_24B / 3) // 24-bit = 3 bytes/sample
static uint32_t sai_tx_buffer[SAI_BUFFER_SAMPLES]; // SPDIF expects 32-bit
/* USER CODE END PRIVATE_FUNCTIONS_DECLARATION */

/**
  * @}
  */

USBD_AUDIO_ItfTypeDef USBD_AUDIO_fops_FS =
{
  AUDIO_Init_FS,
  AUDIO_DeInit_FS,
  AUDIO_AudioCmd_FS,
  AUDIO_VolumeCtl_FS,
  AUDIO_MuteCtl_FS,
  AUDIO_PeriodicTC_FS,
  AUDIO_GetState_FS,
};

/* Private functions ---------------------------------------------------------*/
/**
  * @brief  Initializes the AUDIO media low layer over USB FS IP
  * @PAram  AudioFreq: Audio frequency used to play the audio stream.
  * @PAram  Volume: Initial volume level (from 0 (Mute) to 100 (Max))
  * @PAram  options: Reserved for future use
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t AUDIO_Init_FS(uint32_t AudioFreq, uint32_t Volume, uint32_t options)
{
  /* USER CODE BEGIN 0 */
  UNUSED(AudioFreq);
  UNUSED(Volume);
  UNUSED(options);
  return (USBD_OK);
  /* USER CODE END 0 */
}

/**
  * @brief  De-Initializes the AUDIO media low layer
  * @PAram  options: Reserved for future use
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t AUDIO_DeInit_FS(uint32_t options)
{
  /* USER CODE BEGIN 1 */
  UNUSED(options);
  return (USBD_OK);
  /* USER CODE END 1 */
}

/**
  * @brief  Handles AUDIO command.
  * @PAram  pbuf: Pointer to buffer of data to be sent
  * @PAram  size: Number of data to be sent (in bytes)
  * @PAram  cmd: Command opcode
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t AUDIO_AudioCmd_FS(uint8_t* pbuf, uint32_t size, uint8_t cmd)
{
  /* USER CODE BEGIN 2 */
  switch(cmd)
  {
    case AUDIO_CMD_START:
    break;

    case AUDIO_CMD_PLAY:
    break;
  }
  UNUSED(pbuf);
  UNUSED(size);
  UNUSED(cmd);
  return (USBD_OK);
  /* USER CODE END 2 */
}

/**
  * @brief  Controls AUDIO Volume.
  * @PAram  vol: volume level (0..100)
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t AUDIO_VolumeCtl_FS(uint8_t vol)
{
  /* USER CODE BEGIN 3 */
  UNUSED(vol);
  return (USBD_OK);
  /* USER CODE END 3 */
}

/**
  * @brief  Controls AUDIO Mute.
  * @PAram  cmd: command opcode
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t AUDIO_MuteCtl_FS(uint8_t cmd)
{
  /* USER CODE BEGIN 4 */
  UNUSED(cmd);
  return (USBD_OK);
  /* USER CODE END 4 */
}

/**
  * @brief  AUDIO_PeriodicT_FS
  * @PAram  cmd: Command opcode
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t AUDIO_PeriodicTC_FS(uint8_t *pbuf, uint32_t size, uint8_t cmd)
{
    if (cmd == AUDIO_OUT_TC)
    {
        // Convert USB 24-bit stereo to SPDIF 32-bit frames
    	uint32_t *dst = sai_tx_buffer;
        uint8_t *src=pbuf;

        for (uint32_t i = 0; i < size / 3; i++)
        {
            // USB is 24-bit little endian: LSB, Mid, MSB
            uint32_t sample = (uint32_t)(src[0] | (src[1] << 8) | (src[2] << 16));

            // Sign extend from 24 to 32 bits

            if (sample & 0x800000){
            	sample = sample | 0xFF000000;
            }



            *dst++ = (uint32_t)sample;

            src += 3;
        }

        // Now transmit aligned SPDIF words
        HAL_SAI_Transmit_DMA(&hsai_BlockB2, (uint8_t*)sai_tx_buffer, size / 3);

    }
    return USBD_OK;
}

/**
  * @brief  Gets AUDIO State.
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t AUDIO_GetState_FS(void)
{
  /* USER CODE BEGIN 6 */
  return (USBD_OK);
  /* USER CODE END 6 */
}

/**
  * @brief  Manages the DMA full transfer complete event.
  * @retval None
  */
void TransferComplete_CallBack_FS(void)
{
  /* USER CODE BEGIN 7 */
  USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL);
  /* USER CODE END 7 */
}

/**
  * @brief  Manages the DMA Half transfer complete event.
  * @retval None
  */
void HalfTransfer_CallBack_FS(void)
{
  /* USER CODE BEGIN 8 */
  USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);
  /* USER CODE END 8 */
}

/* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION */

/* USER CODE END PRIVATE_FUNCTIONS_IMPLEMENTATION */

/**
  * @}
  */

/**
  * @}
  */

Only PeriodicTC has been changed to send the received USB Audio Packets to the SAI DMA Stream. I also cannot understand when and how void HalfTransfer_CallBack_FS and void TransferComplete_CallBack_FS are called.

LCE
Principal II

Now I downloaded the wav anyway.

Although I strongly approve of the music selection, a source with a saw-tooth or something similar would be more helpful to find the problem. From a measurement point of view: music is noise.

Then you could check:

- is the USB data okay

- translation to SPDIF buffer okay

- check DAC / audio output sourced by SPDIF on a scope

 

My assumption:

with the isochronous USB transfer of audio data you have an input clock, but the SPDIF output is sourced by a different clock. This might cause glitches after some time, as these clocks slowly drift apart.

I would not directly playback USB data, but put this into a BIG buffer queue, and only start playing to SPDIF when a certain buffer threshold is reached.

Or is there some way to get a clock source from USB to feed SPDIF? I guess not...

Hi @nt2ds 

I agree with @LCEwithout non-repetitive waveforms, you are making it hard to visually analyze and detect glitches or timing issues. Using a test signal such as a sawtooth at a known frequency is much better for debugging timing, jitter...

USB start of frame SOF signal is highly recommended for synchronizing audio streaming. it triggers USB isochronous IN transfers at precise intervals.

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.


nt2ds
Associate III

I did what you said with the Sawtooth at 2 frequencies. 200Hz and 1kHz. Strange behavior appears. On 1kHz it plays properly no matter how long I let the Sawtooth wave play. On 200Hz artifacts begin. I haven't found the frequency from which below it, artifacts start. I have Taken some pictures of the RTA so you can see yourself.
Below is the Proper 1kHz Sawtooth from my computer to the RTA (Audio Jack so I know it works):

Proper 1kHz Sawtooth.jpg

Below is the SPDIF 1kHz which as I said (surprisingly) works properly on this frequency no matter how long I play the wave:

SPDIF 1kHz Sawtooth RTA.jpg

Below is the 200Hz that plays properly from the Audio Jack and I monitor the RTA:

Proper 200Hz Sawtooth.jpg

Below is the 200Hz that plays from SPDIF, and as you can see as well, even from the first harmonic there is difference from what SHOULD be playing in comparison to the proper 200Hz I should be seeing on the RTA. The Loudness of each frequency is different and some artifacts start appearing very loud:

SPDIF 200Hz Sawtooth.jpg

I see you also mentioned that I can use the USB SOF to sync the audio. Can you elaborate more on than as to how it should be done? I am thinking that when the SOF is received that's when I should start the DMA, but if that is the case, isn't that the same thing as to starting the DMA when PeriodicTC is called (which is called whenever an audio packet is received)

LCE
Principal II

You should look at the waveform / time signal of the SPDIF result, maybe then you can see that there are some "jumps" in the signal every now and then.

And for this kind of signal analysis, I recommend an analyser with a) higher resolution, and b) a linear frequency x-axis .

1 kHz: maybe you don't see any glitches because it equals the USB iso freq.

Sync: it's not a matter of the DMA start or clock, rather the SPDIF audio clock should be synced with the USB SOF.
I think the F7 (or any other STM32) does not have such a feature, so it would be quite complex to sync these (something like: audio VCO counting SOF pulse output (I guess the F7 can do that?) via timer, then DAC output to VCO, ...).

nt2ds
Associate III

I fixed the problem eventually. No clock drifting. It was a buffering problem. It now works properly

LCE
Principal II

Buffering - surprise! :D

@nt2ds  Please enlighten us how you solved the problem, so others can learn from it!

nt2ds
Associate III

Yes sure, I was working those days (as I said I work in PA and Live Productions) and didn't have time to post the code or explain anything. I will make a other post tonight or tomorrow explainingn every step I took in order to make SPDIF TX work.

nt2ds
Associate III

To begin with the most important, SPDIF Clock.
1) Figure out what was the correct clock in order to transmit 48kHz Stereo Audio.
The frequency which you feed the SAI Clock MUX with. For SPDIF it should be (Sampling Rate*64)*2, giving a frequency of 6.144MHz. We do that x2 because SPDIF also carries the clock in the same signal as the audio, in BMC Encoding, which by spec requires double the frequency thus x2 and resulting in 6.144MHz. For 96kHz it would be 12.288MHz.
2) VERY IMPORTANT THAT TOOK DAYS TO FIGURE OUT BECAUSE THE GENERATED CODE IS WRONG. Assuming you have fed a clock with the correct frequency to the SAI Clock MUX, ignore the "Real Audio Frequency" field in SAI Parameter Settings. The clock fed into the SAI must NOT be divided (the CubeMX Generated code divides it, we have to stop it from doing so). Inside the MX_SAIx_Init() function in main.c add this line:

hsai_BlockB2.Init.NoDivider = SAI_MASTERDIVIDER_DISABLE;

3) Replace the wrong generated usbd_audio_if.c functions with the correct ones.
CubeMX (once again) generated wrong code. The Half and Transfer complete functions inside uisbd_audio_if.c are WRONG and they NEVER RUN. Replace them with these:

void TransferComplete_CallBack_FS(void) -> void HAL_SAI_TxCpltCallback(SAI_HandleTypeDef *hsai)
void HalfTransfer_CallBack_FS(void) -> void HAL_SAI_TxHalfCpltCallback(SAI_HandleTypeDef *hsai)

void HAL_SAI_TxHalfCpltCallback(SAI_HandleTypeDef *hsai) and void HAL_SAI_TxCpltCallback(SAI_HandleTypeDef *hsai) are ALREADY DEFINED by HAL so no need to define them anywhere.
4) Create the code that handles the buffering and sending the actual USB Audio Data.
I have edited the USB Audio Class Descriptors to support 24 Bit audio (won't get into detail how I did that, if you want more info I can post another reply), this doesn't affect anything related to SPDIF, the buffering and packet handling is just made for 24 Bits.
the Function in usbd_audio_if.c: 

static int8_t AUDIO_PeriodicTC_FS(uint8_t *pbuf, uint32_t size, uint8_t cmd)

is called whenever a new USB Packet is received, meaning, all buffer handling must be done inside this function. Here is the complete function that handles double buffering.

static int8_t AUDIO_PeriodicTC_FS(uint8_t *pbuf, uint32_t size, uint8_t cmd)
{
    if (cmd != AUDIO_OUT_TC) return USBD_OK;

    uint32_t sample_count = size / 3U; // Each sample is 3 bytes
    if (sample_count == 0) return USBD_OK;

    // Convert to 32-bit sign-extended
    uint8_t *src=pbuf;
    for (uint32_t i = 0; i < sample_count; i++)
    {
        uint32_t sample = (uint32_t)(src[0] | (src[1] << 8) | (src[2] << 16));
        sample &= 0x00FFFFFFU;
        if (sample & 0x00800000U) sample |= 0xFF000000U;
        sai_temp_buffer[i] = sample;
        src += 3;


    }

    // Copy converted samples into the current writable half
    uint32_t idx = 0;
    while (idx < sample_count)
    {

        if (!half_ready[current_half])
        {

            // Try the other half
            if (half_ready[1U - current_half])
            {
                current_half = 1U - current_half;
                half_fill_pos = 0;
            }
            else
            {
                // Both halves busy -> drop remaining samples
                break;
            }
        }

        uint32_t space = HALF_SIZE - half_fill_pos;
        uint32_t to_copy = sample_count - idx;
        if (to_copy > space) to_copy = space;

        memcpy(&sai_tx_buffer[current_half * HALF_SIZE + half_fill_pos],
               &sai_temp_buffer[idx],
               to_copy * sizeof(uint32_t));

        idx += to_copy;
        half_fill_pos += to_copy;

        if (half_fill_pos >= HALF_SIZE)
        {
        	half_ready[current_half] = 0; // Mark this half as full (DMA will play it soon)
        	current_half = 1U - current_half; // Switch to other half
        	half_fill_pos = 0;
        }
    }

    return USBD_OK;
}

After making these changes, it worked properly. I will post the usbd_audio_if.c so you can see everything in detail. NOTE THAT THIS ONLY WORKS FOR 24 BIT AUDIO. IF YOU WANT MORE INFORMATION TO CHANGE THE DEFAULT GENERATED 16 BIT USB AUDIO DEVICE CLASS TO 24 BIT I CAN POST ANOTHER GUIDE EXPLAINING EVERY DETAIL.