STM32U5xx: SPDIF out - how to configure?
I am struggling since days to get "my audio" out via SPDIF (electrical).
What I see on scope (there is a voltage divider and cap for DC-free on PC12, Vpp is 0.6V, approx. 75 Ohm impedance):

It makes sense - but my SPDIF receiver (a STM32F769I-DISCO board, which works fine as SPDIF receiver with another source) does not get any audio (but it seems to sync and realize there is SPDIF signal, but completely silent).
Intention:
- any audio I have in MCU (it will be PDM MIC via CODEC to I2S, as 24bit, 48 KHz) should be forwarded via SPDIF Tx
- I want to use SAI2_B as SPDIF Tx (with PC12), also as 24bit (both are 48 KHz)
- I tried with "faking", sending buffer content for SPDIF Out as a sine wave (24bit, 48 KHz)
#ifdef SPDIF_TEST
#define SPDIF_TIME 100
#define SPDIF_WORDS 4 /* 24bit values, but 32bit word */
#define SPDIF_CHANNELS 2
#define SPDIF_AUDIOFREQ 48
#define SPDIF_DOUBLEBUF 2
int32_t SPDIF_out_test[SPDIF_CHANNELS * SPDIF_AUDIOFREQ * SPDIF_TIME * SPDIF_DOUBLEBUF] __aligned(4);
void GenerateSPDIFOut(void)
{
int i;
int32_t val = 0;
double d;
for (i = 0; i < (SPDIF_CHANNELS * SPDIF_AUDIOFREQ * SPDIF_TIME); i++)
{
d = sin(((2 * M_PI) * i) / SPDIF_AUDIOFREQ);
d *= (double)0x00700000; //volume scaling
val = (int32_t)d;
val &= 0x00FFFFFF;
val |= 0x01000000;
SPDIF_out_test[2 * i + 0] = val;
SPDIF_out_test[2 * i + 1] = val;
}
}
#endif
I play this via this code (using DMA Ch5):
ifdef SPDIF_TEST
GenerateSPDIFOut();
MX_SAI_Init();
#endif
#ifdef SPDIF_TEST
HAL_SAI_Transmit_DMA(&hsai_BlockB1, (uint8_t *)SPDIF_out_test, (uint16_t)sizeof(SPDIF_out_test) / sizeof(uint32_t));
#else
The configuration is this:
hsai_BlockB1.Instance = SAI2_Block_B;
hsai_BlockB1.Init.Protocol = SAI_SPDIF_PROTOCOL;
hsai_BlockB1.Init.AudioMode = SAI_MODEMASTER_TX;
hsai_BlockB1.Init.Synchro = SAI_ASYNCHRONOUS;
hsai_BlockB1.Init.OutputDrive = SAI_OUTPUTDRIVE_DISABLE;
hsai_BlockB1.Init.NoDivider = SAI_MASTERDIVIDER_ENABLE; //SAI_MASTERDIVIDER_ENABLE;
hsai_BlockB1.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_EMPTY;
hsai_BlockB1.Init.AudioFrequency = SAI_AUDIO_FREQUENCY_48K;
////hsai_BlockB1.Init.DataSize = SAI_DATASIZE_32;
hsai_BlockB1.Init.SynchroExt = SAI_SYNCEXT_DISABLE;
hsai_BlockB1.Init.MckOutput = SAI_MCK_OUTPUT_DISABLE;
hsai_BlockB1.Init.MonoStereoMode = SAI_STEREOMODE;
hsai_BlockB1.Init.CompandingMode = SAI_NOCOMPANDING;
hsai_BlockB1.Init.PdmInit.Activation = DISABLE;
hsai_BlockB1.Init.PdmInit.MicPairsNbr = 1;
hsai_BlockB1.Init.PdmInit.ClockEnable = SAI_PDM_CLOCK1_ENABLE;
if (HAL_SAI_Init(&hsai_BlockB1) != HAL_OK)
{
Error_Handler();
} PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_SAI2;
PeriphClkInit.Sai2ClockSelection = RCC_SAI2CLKSOURCE_PLL3;
PeriphClkInit.PLL3.PLL3Source = RCC_PLLSOURCE_HSE;
PeriphClkInit.PLL3.PLL3M = 1;
/* 48 KHz SPDIF with scope CubeMX cfg */
PeriphClkInit.PLL3.PLL3N = 18; //36;
PeriphClkInit.PLL3.PLL3P = 96; //96;
PeriphClkInit.PLL3.PLL3Q = 2;
PeriphClkInit.PLL3.PLL3R = 2;
PeriphClkInit.PLL3.PLL3RGE = RCC_PLLVCIRANGE_1;
PeriphClkInit.PLL3.PLL3FRACN = 3544; //7080
PeriphClkInit.PLL3.PLL3ClockOut = RCC_PLL3_DIVP;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}void HAL_SAI_MspInit(SAI_HandleTypeDef* hsai)
{
if(hsai->Instance==SAI2_Block_B)
{
/* Peripheral clock enable */
if (SAI2_client == 0)
{
__HAL_RCC_SAI2_CLK_ENABLE();
/* Peripheral interrupt init*/
HAL_NVIC_SetPriority(SAI2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(SAI2_IRQn);
}
SAI2_client++;
/**SAI1_B_Block_B GPIO Configuration
PB5 ------> SAI1_SD_B
*/
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF13_SAI2;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/* Peripheral DMA init*/
NodeConfig.NodeType = DMA_GPDMA_LINEAR_NODE;
NodeConfig.Init.Request = GPDMA1_REQUEST_SAI2_B;
NodeConfig.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
NodeConfig.Init.Direction = DMA_MEMORY_TO_PERIPH;
NodeConfig.Init.SrcInc = DMA_SINC_INCREMENTED;
NodeConfig.Init.DestInc = DMA_DINC_FIXED;
NodeConfig.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD;
NodeConfig.Init.DestDataWidth = DMA_SRC_DATAWIDTH_WORD;
NodeConfig.Init.SrcBurstLength = 1;
NodeConfig.Init.DestBurstLength = 1;
NodeConfig.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
NodeConfig.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
NodeConfig.Init.Mode = DMA_NORMAL;
NodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;
NodeConfig.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
NodeConfig.DataHandlingConfig.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED;
if (HAL_DMAEx_List_BuildNode(&NodeConfig, &Node_GPDMA1_Channel5) != HAL_OK)
{
Error_Handler();
}
if (HAL_DMAEx_List_InsertNode(&List_GPDMA1_Channel5, NULL, &Node_GPDMA1_Channel5) != HAL_OK)
{
Error_Handler();
}
if (HAL_DMAEx_List_SetCircularMode(&List_GPDMA1_Channel5) != HAL_OK)
{
Error_Handler();
}
handle_GPDMA1_Channel5.Instance = GPDMA1_Channel5;
handle_GPDMA1_Channel5.InitLinkedList.Priority = DMA_LOW_PRIORITY_MID_WEIGHT;
handle_GPDMA1_Channel5.InitLinkedList.LinkStepMode = DMA_LSM_FULL_EXECUTION;
handle_GPDMA1_Channel5.InitLinkedList.LinkAllocatedPort = DMA_LINK_ALLOCATED_PORT0;
handle_GPDMA1_Channel5.InitLinkedList.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
handle_GPDMA1_Channel5.InitLinkedList.LinkedListMode = DMA_LINKEDLIST_CIRCULAR;
if (HAL_DMAEx_List_Init(&handle_GPDMA1_Channel5) != HAL_OK)
{
Error_Handler();
}
if (HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel5, &List_GPDMA1_Channel5) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(hsai, hdmatx, handle_GPDMA1_Channel5);
if (HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel5, DMA_CHANNEL_NPRIV) != HAL_OK)
{
Error_Handler();
}
}
}
Besides the issue - "why I do not get the SPDIF signal on receiver", there are some major questions:
- I do not need to set the datasize "as I understand" - forced anyway), e.g. via:
////hsai_BlockB1.Init.DataSize = SAI_DATASIZE_32;
It should be forced automatically to 32bit (actually, the STM docs talk about 64bit and I see in drivers something with 64bit is used, I understand also the there is a DIV factor of 64 for SPDIF)
- So, assuming the SPDIF needs really a SAI2 clock as 48 KHz * 64 = 3,072 KHz - I decided to use PLL3 and set the correct PLL out frequency for the SAI2 as SPDIF:
Reusing PLL2, used for SAI1 and OCTOSPI cannot work, because I would not be able to set exactly this SAI_CLK value. So, PLL3 for the "nominal" SAI2 clock needed, just for SPDIF. - Not really clear how to launch the DMA (with circular buffer):
It the size the "number of words/samples" or the "number of bytes"? (I am using now "number of sample words", as seen that "size" is multiplied in driver with DMA word size)
HAL_SAI_Transmit_DMA(&hsai_BlockB1, (uint8_t *)SPDIF_out_test, (uint16_t)sizeof(SPDIF_out_test) / sizeof(uint32_t));
- The biggest confusion comes with CubeMX generated code!
If I use CubeMX and configure all (for my 8 MHz HSE) - I get this config:
PLL3P : PPL3M = 1
PLL3N = 36
PLL3P = 96
factional = 7080
//resulting in: 3.07021 MHz, audio as: 48.0 KHz (minimum error)
I tried to trim in CubeMX that audio frequency error is minimum (or 0).
But when I run this config - it seems to be completely wrong. I check with a scope the SPDIF signal and its frequency. I assume to see this:
- 48 KHz audio, two channels, 32bit samples (SPDIF uses 24bit, but a 32bit frame) - so, 3,072 KHz
- but SPDIF is "marked encoded", so that I expect to see on SPDIF the fastest frequency (for values as 1) is doubled, so that a sequence of zeros is 48 KHz, but a sequence of ones is 96 KHz
So, I tried to trim the SPDIF frequency via scope and I had to configure this:
PLL3P : PLL3M = 1
PLL3N = 18
PLL3P = 96
fractional = 3546
//resulting in 2x 48 KHz for SPDIF signal
So, what I see:
- The CubeMX config says "correct 48 KHz as audio" but I have to double the frequency for SPDIF. And the PLL frequency seems to be a little bit off, but OK to tweak via "fractional" (or my scope is a bit off).
Also not really clear for me:
- the SPDIF sample words (e.g. as 24bit) contain also the CS, U, V bits: MSB of audio sample is always at bit 23, but the upper next three bits are also transmitted via SPDIF: if, for instance, valid bit is "wrong" - receiver might ignore it.
- I tried to play with these bits: no difference
- assuming it would work (at the end) - there is a "dramatic impact" on my buffer handling:
I CANNOT simply forward the same buffer: assume I get 24bit samples from my codec and I want to send out exactly the same buffer content as SPDIF - do I have to touch in every sample the bits [26:24] just to set a valid CS, U, V bit? - It sounds to as (OK): process the audio buffer received from codec (via I2S), flip the SPDIF bits (or shift the audio sample value? ...) and send out via SPDIF as a different buffer...?
Not possible to forward a 32bit buffer (with 24bit sample words) via SPDIF...?
Even I saw the SPDIF working already (with a lot of distortion) - I cannot replicate neither send a test signal via SPDIF (from a prefilled buffer with sine wave samples).
What is wrong? (I want to see SPDIF working, esp. when I am sure, the SPDIF out via SAI2_B is generated and out there)
