SPDIF Tx clock config: CubeMX tool is wrong, HAL drivers have an issue - MCLKDIV can be wrong
I think, using SAI for SPDIF Tx, can go wrong (in terms of clock config, the MCLKDIV setting):
- The CubeMX tool, used to configure SAI as SPDIF Tx, generates completely wrong PLL setting and code
- 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.

