cancel
Showing results for 
Search instead for 
Did you mean: 

SPDIF Tx clock config: CubeMX tool is wrong, HAL drivers have an issue - MCLKDIV can be wrong

tjaekel
Lead

I think, using SAI for SPDIF Tx, can go wrong (in terms of clock config, the MCLKDIV setting):

  1. The CubeMX tool, used to configure SAI as SPDIF Tx, generates completely wrong PLL setting and code
  2. the HAL driver, doing the config, in file "stm32xxx_hal_sai.c" can set a wrong MCLKDIV divider:
    resulting in half of the needed frequency for SPDIF, even you trim manually the C-code - it can go wrong

Debugging SAI SPDIF Tx config on a STM32U5A5 MCU - I saw this: it seems to me, this bug (in HAL drivers) is there for years: in my old projects, years ago to get it working: I had to set SPDIF audio sampling rate to 96KHz - even it is 48KHz (but now obvious why after debugging the HAL code on this MCU).

In order to get the right frequencies on SPDIF Tx, on a STM32U5A5, I have to set this PLL config:

 

    /* 48 KHz SPDIF            with scope/debug 	CubeMX cfg      correct */
    PeriphClkInit.PLL3.PLL3N = 36;					//36;			36;
    PeriphClkInit.PLL3.PLL3P = 24;					//96; 			24;
    PeriphClkInit.PLL3.PLL3Q = 2;
    PeriphClkInit.PLL3.PLL3R = 2;
    PeriphClkInit.PLL3.PLL3RGE = RCC_PLLVCIRANGE_1;
    PeriphClkInit.PLL3.PLL3FRACN = 7077;			//7080			7078; -->
    // it results in 12,288.004 KHz in FW - MCLKDIV should be 2 at the end - but it fails!
    /* but: above 12,288 KHz results in MCLKDIV +1, too large! - so, we have to get a bit below "nominal", resulting in a larger clock offset error:
     * -36Hz, compared to value 7078 with just 4 Hz error!
     * why I cannot use 7078 with 12,288.004 KHz which would be better? The MCLKDIV is wrong by +1!
     */
    PeriphClkInit.PLL3.PLL3ClockOut = RCC_PLL3_DIVP;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
    {
          Error_Handler();
    }

 

ATTENTION: see the difference between the values generated by CubeMX tool and what works at the end really: CubeMX was trimmed for 0% error for audio via SPDIF - but the generated code is "soooo" wrong.

The HAL driver (stm32xxx_hal_sai.c) does this:

 

#if defined(SAI2)
    if ((hsai->Instance == SAI1_Block_A) || (hsai->Instance == SAI1_Block_B))
    {
      freq = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SAI1);
    }
    else
    {
      freq = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SAI2);
    }
#else /* SAI2 */
    freq = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SAI1);
#endif /* SAI2 */

    /* Configure Master Clock Divider using the following formula :
       - If NODIV = 1 :
         MCKDIV[5:0] = SAI_CK_x / (FS * (FRL + 1))
       - If NODIV = 0 :
         MCKDIV[5:0] = SAI_CK_x / (FS * (OSR + 1) * 256) */
    if (hsai->Init.NoDivider == SAI_MASTERDIVIDER_DISABLE)
    {
      /* NODIV = 1 */
      uint32_t tmpframelength;

      if (hsai->Init.Protocol == SAI_SPDIF_PROTOCOL)
      {
        /* For SPDIF protocol, frame length is set by hardware to 64 */
        tmpframelength = SAI_SPDIF_FRAME_LENGTH;
      }
      else if (hsai->Init.Protocol == SAI_AC97_PROTOCOL)
      {
        /* For AC97 protocol, frame length is set by hardware to 256 */
        tmpframelength = SAI_AC97_FRAME_LENGTH;
      }
      else
      {
        /* For free protocol, frame length is set by user */
        tmpframelength = hsai->FrameInit.FrameLength;
      }

      /* (freq x 10) to keep Significant digits */
      tmpval = (freq * 10U) / (hsai->Init.AudioFrequency * tmpframelength);
    }
    else
    {
      /* NODIV = 0 */
      uint32_t tmposr;
      tmposr = (hsai->Init.MckOverSampling == SAI_MCK_OVERSAMPLING_ENABLE) ? 2U : 1U;
      /* (freq x 10) to keep Significant digits */
      tmpval = (freq * 10U) / (hsai->Init.AudioFrequency * tmposr * 256U);
    }
    hsai->Init.Mckdiv = tmpval / 10U;

#if 0
    /* ===== this seems to be going wrong! ===== */
    /* Round result to the nearest integer */
    /* I get remainder 9 - so, it ends up with a wrong Mckdiv setting (just half the frequency we need) */
    if ((tmpval % 10U) > 8U)			/* why 8?? should it be 5! - but it would be still wrong at the end! */
    {
      hsai->Init.Mckdiv += 1U;
    }
#endif

    /* For SPDIF protocol, SAI shall provide a bit clock twice faster the symbol-rate */
    if (hsai->Init.Protocol == SAI_SPDIF_PROTOCOL)
    {
      hsai->Init.Mckdiv = hsai->Init.Mckdiv >> 1;
    }
  }

 

The #if 0 there is my "temporary bug fix" (when I am very close to the correct nominal PLL clock out).

  • it gets the PLL config for the SPDIF clock source
  • it tries to round up a bit (to match the next possible integer MCLKDIV divider)

But it goes wrong!

  • You have to "reach" the nominal clock speed needed from the "lower" value range, never exceed the "nominal" value - above "nominal" - even a tiny bit - wrong divider!
  • if you are "too close", "too perfect" on the nominal clock speed set by the PLL - your MCLKDIV is off by +1: you get just half the speed needed (therefore my "old" trick to set SPDIF to 96KHz audio when it is actually 48KHz)

If you see this code, or you debug this code... what happens is:

  • 48KHz, 2x32bit results in 3,072 KHz, for SPDIF the MCLKDIV is divided by two (>>1) because we need the doubled frequency for the Manchester Code
  • So, the PLL can provide a clock as 4 times faster so that MCLKDIV should become 2 at the end: 12,288.000 KHz would be the perfect value we need out of PLL.
  • But if you set PLL for 12,288.004 KHz - it is a bit above the "threshold" (due to the code in HAL driver) - the MCLKDIV is wrong by +1
  • Even if you are very close to this 12,288.000 KHz, a bit below (12,287.964 KHz) - the MCLKDIV is wrong by +1! Because, this code is done!:

 

if ((tmpval % 10U) > 8U)			/* why 8?? should it be 5! - but it would be still wrong at the end! */
    {
      hsai->Init.Mckdiv += 1U;
    }​

 

When I am very close to the "correct" frequency of PLL - I get remainder 9 and it increments the Mckdiv! - it is WRONG!

  • So, it is almost impossible with this HAL driver code to set the correct divider and to get the correct frequency for SPDIF Tx! (except: you are much more off on clock speed)
    You have to use always a bit lower frequency! Never above the "nominal" frequency.
    And it must be a bit more "off" so that you are not hitting this "round up feature" (which is actually a wrong way to do)

It seems to be wrong for me, because:

  • using PLL setting resulting in 12,288.004 KHz gives me just an offset/error as 4 Hz! - but not possible to use!
  • using the next lower possible frequency, as 12,287.964 KHz, gives me an offset/error as 36 Hz! But even this results in a wrong divider value set! I have to set PLL with a much larger offset/error to "avoid" this bug.

No wonder why all my old projects (since years) work just with 96KHz as SPDIF audio frequency and why I see/hear periodic audio artefacts on SPDIF Tx.

Dear STM team:

please, can you check if this code is really "correct", esp. if it could be better (e.g. using floating point and rounding not via simple integer, instead to look for the smallest offset error with two neighboring settings). Please, make sure the code works if I set a "perfect" PLL clock for SPDIF (and the MCLKDIV is correct, not off by +1, and just half of the needed clock speed at the end, just possible to fix by setting 96KHz audio rate). Thank you.

4 REPLIES 4
Imen.D
ST Employee

Hello @tjaekel ,

Thank you for having reported the issue. Your detailed report and collaboration are much appreciated.

I escalated this internally for investigate via the internal tickets number: 177434 and 177449.

 (PS: The internal tickets number are only for reference, not available outside of ST)

 

When your question is answered, please close this topic by clicking "Accept as Solution".
Thanks
Imen
Imen.D
ST Employee

Hi @tjaekel ,

Could please provide the ioc file for reproducing the issue with the same configuration as you.

When your question is answered, please close this topic by clicking "Accept as Solution".
Thanks
Imen

How to attach the IOC file here?

What I did in CubeMX (and maybe you can replicate):

  • create a project for a STM32U5A5 LQFP100 package (because, this package has SAI2 pin configurations, the LQFP64 does not, even the same pins are there and potential possible to use as SAI2_B SPDIF, what I try to get working)
  • setup SAI2_B, with PC12 as SPDIF output
  • trim the clock config for SPDIF (SAI2_B) so that the error for audio frequency is minimum (48KHz, stereo): my HSE XTAL is 8MHz (not 16MHz as on Nucleo board) - check if "Real Audio Frequency" is reported as the correct one, with the smallest error

My entire FW project is here (not the CubeMX project, my real IDE project, see my clock config there):

https://github.com/tjaekel/NUCLEO-U5A5JZ-Q_QSPI 

Here the CubeMX config done (for a STM32U5A5 LQFP package, as reference):

MCU_SPDIF_CubeMX_1.png

Trim the SPDIF clock config for "best" "Real Audio Frequency" - it is wrong (even it shows me perfect 48KHz).

MCU_SPDIF_CubeMX_2.png

The clock configuration: SAI2_B, PC12, using PLL3_P, with HSE 8 MHz XTAL - resulting in correct "Real Audio Frequency"

But it is (for sure) wrong:

  • yes, 48KHz, 32bit samples (as needed for 24bit plus bits for SPDIF, always 32bit on SPDIF), two channels, results in 3,072 KHz clock for sai_clk (here)
  • but SPDIF needs the doubled frequency: so the sai_clk must be at least 2x the audio frequency, so that MCLKDIV can be set to 1 (or 0), or any N times multiplied clock would be fine
  • the reported PLL setting is not right for SPDIF

The IOC file for SPDIF config is here:

https://github.com/tjaekel/NUCLEO-U5A5JZ-Q_QSPI/blob/main/STM32U5A5_LQFP100.ioc 

I will close as solution when you (STM team) can confirm how to configure SPDIF via CubeMX.
Thank you.

Hi @tjaekel and thank you for sharing the details.

Our dev team are working to resolve the reported issue.
I confirm that in CubeMX: some parameters are not displayed on SAI configuration panel when SPDIF protocol is selected and some information concerning audio frequency should be corrected.

As the SAI clock generator described in the STM32U5 reference manual (RM0476):

ImenD_0-1713810922207.png

On HAL SAI source code, depending on the audio frequency asked, MCKDIV is computed.
Computation of MCKDIV depends on asked audio frequency (Fs), frame length (FRL), SAI kernel clock (Fk), master clock no divider parameter (NODIV) and master clock oversampling (OSR):

If NODIV = 1 :
    MCKDIV = Fk / (Fs x FRL)

If NODIV = 0 and OSR = 0 :
    MCKDIV = Fk / (Fs x 256)

If NODIV = 0 and OSR = 1 :
    MCKDIV = Fk / (Fs x 256 x 2)

For specific case of SPDIF protocol, the bit clock must be twice the symbol-rate, so MCKDIV is also divided by 2.

So, for specific SPDIF case, the previous formulas become:

If NODIV = 1 :
    MCKDIV = Fk / (Fs x 64 x 2)

If NODIV = 0 and OSR = 0 :
    MCKDIV = Fk / (Fs x 256 x 2)

If NODIV = 0 and OSR = 1 :
    MCKDIV = Fk / (Fs x 256 x 2 x 2)

It's recommended to configure the SAI kernel clock in order to have as much as possible, an integer value of MCKDIV.
It means for specific case of SPDIF, to configure SAI kernel clock as follow:

If NODIV = 1 :
    Fk = N x (Fs x 64 x 2)

If NODIV = 0 and OSR = 0 :
    Fk = N x (Fs x 256 x 2)

If NODIV = 0 and OSR = 1 :
    Fk = N x (Fs x 256 x 2 x 2)

Because master clock no divider (NODIV) and master clock oversampling (OSR) parameters are not available on SAI configuration panel. These parameters are initialized to 0. It means that you need to configure a SAI kernel clock as a multiple of (Fs x 256 x 2).
Therefore, for an audio frequency at 48KHz, you need to configure SAI kernel clock at around N x 24,576Mhz.

Nevertheless, we will work to resolve the issues on CubeMX side, to add missing parameters and correct information displayed on SAI configuration panel: 
1- Add those two parameters "Master Clock No Divider" and 'Master Clock Oversampling" in the list of Parameters settings when SPDIF protocol is selected for SAI in order to calculate the right "Real Audio Frequency".
2-"Master Clock Oversampling" parameter has also to be added when SAI mode is set as "Master" or "Master with master clock out".

Hope my answer is clear!

Do not hesitate to raise any issue and feel free to add your feedback.

When your question is answered, please close this topic by clicking "Accept as Solution".
Thanks
Imen