Skip to main content
Visitor II
August 15, 2025
Solved

Cannot make SPDIF TX work

  • August 15, 2025
  • 12 replies
  • 2309 views

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! :)

    This topic has been closed for replies.
    Best answer by nt2ds

    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.

    12 replies

    Graduate II
    August 29, 2025

    Buffering - surprise! :D

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

    nt2dsAuthor
    Visitor II
    August 30, 2025

    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.

    Graduate II
    September 1, 2025

    Thanks!

    My take on Cube & HAL:

    I have learned that I cannot trust it (Cube), and / or that it's programmed too resource-intensively / complicated (HAL).
    For everything vital to my application, I mostly only use it for basic start code or a quick start / check.
    Then I get into the peripherals and do everything by direct register access, sometimes checking how HAL solves things.

    I need to understand everything 100% that's happening in my devices, this makes changes and trouble-shooting much easier.

    nt2dsAuthor
    Visitor II
    September 1, 2025

    Yeah I have read the same thing about HAL. I just don't make projects big enough or so much resource intensive that requires me to program in registers. I am also not an engineer, I do this for hobby projects so I don't know a lot either, if you asked me now to setup a UART with DMA only with Registers I wouldn't be able to :\

    Technical Moderator
    September 1, 2025

    Hi @nt2ds 

    Internal ticket (216620) is submitted to CubeMX team for adding 

    hsai_BlockB2.Init.NoDivider =