2025-11-23 12:43 PM
Using Nucleo-U545RE-Q, SPI2 Full-duplex Master, default GPIO selected by STM32CubeMX v6.15.0 with LL selected for implementation.
Config clock for max 160 MHz and prescale SPI by 8 for 20 Mbps. Add GPDMA Ch2/3 for SPI2 TX/RX.
I double checked the output configuration code generated by the CubeMX against the Cube_FW_H5_V1.5.0 Examples_LL: SPI_TwoBoards_FullDuplex_DMA_Master_Init, and it matches exactly but for peripheral selection.
Jumper-wire PC1-to-PC2 (CN7.36-to-CN7.35) for simple SPI loop-back test.
With this configuration, the loop-back works when I implemented the SPI full-duplex transfer by LL blocking code. But trying to use the GPDMA channels results in no data, no DMA IRQ for TC or DTE, no SPI IRQ for anything. Below is the simple code to try to get a transfer going, there is initialization code that enables all SPI and DMA interrupt sources and handles the IRQ by clearing the flag and setting the local global booleans that you see below. Note that this is setup for Word (32-bit) size, but I've tried with simple 8-bit as well. Besides, the fact that the blocking LL code to make the transfer shows that the 32-bit size is working. I've used much slower bit rate as well just to check, but on a side note, going any faster (e.g. prescale by 4 for 40 Mbps) causes erroneous data RX, so this is a secondary concern as to why because I understood that SPI is good up to system clock DIV by 2. The code below just hangs waiting on any IRQ which never comes to prove a transfer never took place:
#define DMAx GPDMA1
#define SPIx SPI2
#define DMA_RX LL_DMA_CHANNEL_3
#define DMA_TX LL_DMA_CHANNEL_2
void spiXferDMA(void *txBuf, void *rxBuf, uint32_t cnt)
{
uint32_t dmaCnt = cnt * 4; // DMA length configuration is in total bytes even when Data Width is Word
dmaIrq = dmaErr = spiIrq = btnIrq = false;
LL_SPI_ClearFlag_EOT(SPIx);
LL_SPI_ClearFlag_TXTF(SPIx);
LL_SPI_ClearFlag_SUSP(SPIx);
LL_SPI_EnableDMAReq_RX(SPIx);
LL_SPI_EnableDMAReq_TX(SPIx);
LL_SPI_SetTransferSize(SPIx, cnt); // SPI size configuration is in count of Words to transfer
LL_SPI_Enable(SPIx);
LL_DMA_ConfigAddresses(DMAx, DMA_RX, LL_SPI_DMA_GetRxRegAddr(SPIx), (uint32_t)rxBuf);
LL_DMA_ConfigAddresses(DMAx, DMA_TX, (uint32_t)txBuf, LL_SPI_DMA_GetTxRegAddr(SPIx));
LL_DMA_SetBlkDataLength(DMAx, DMA_RX, dmaCnt);
LL_DMA_SetBlkDataLength(DMAx, DMA_TX, dmaCnt);
LL_DMA_EnableChannel(DMAx, DMA_RX);
LL_DMA_EnableChannel(DMAx, DMA_TX);
LL_SPI_StartMasterTransfer(SPIx);
while (!dmaIrq && !dmaErr && !spiIrq && !btnIrq)
{
HAL_GPIO_WritePin(LED_GRN_GPIO_Port, LED_GRN_Pin, GPIO_PIN_SET);
HAL_Delay(30);
HAL_GPIO_WritePin(LED_GRN_GPIO_Port, LED_GRN_Pin, GPIO_PIN_RESET);
HAL_Delay(969);
}
LL_SPI_Disable(SPIx);
LL_SPI_DisableDMAReq_RX(SPIx);
LL_SPI_DisableDMAReq_TX(SPIx);
LL_DMA_DisableChannel(DMAx, DMA_TX);
LL_DMA_DisableChannel(DMAx, DMA_RX);
}
Solved! Go to Solution.
2025-12-03 10:01 AM
So, this was a secure/non-secure error on my part after all - I got confused and started running my non-secure scratch project on a device that had TZEN set. The code mostly runs and you don't really notice the mis-match until something like this. So setting up the LL code in a secure project build, the SPI3 runs by DMA in 8-bit transfers and the SPI2 runs by DMA with 32-bit transfers. It still cannot run with DIV by 2, but up to DIV by 4 for 40 Mbps transfers looks good.
2025-11-23 7:17 PM - edited 2025-11-23 7:28 PM
I have been working on this for 2 days now, so this post is not made in haste. I didn't mention above that I have USART1 TX running by GPDMA using LL API as easily as I would have expected given all of my years of experience with the STM32Fxx MCU's.
Since posting, I used CubeMX to setup the SPI3 as 8-bits transfer size and GPDMA channels 0/1 for RX/TX same settings. Right away the simple loop-back is working if I use LL API commands to implement blocking full-duplex transfer logic (obviously I had to make a new function to invoke LL_SPI_TransmitData8 vice LL_SPI_TransmitData32, etc) but working nice and easy. Then I switch to try to use the GPDMA channels and no data on loop-back, BUT, now there is both a DMA TX 'TC' and DMA RX 'TC' interrupt firing - note that they had been enabled for the attempt using SPI2 with Word sized transfers and never fired.
Since at least the DMA TC interrupts are firing, I broke out the scope - I hate having to get down to this tedious level of debugging. But I dusted it off and hooked it up and when watching the full-duplex transfer by LL blocking method, signals are as expected, SPI clock and MOSI and MISO exactly overlap (but of course, they are jumper-wired together). But trying to use the DMA channels results in only SCLK, nothing is coming out of MOSI. I triple check in the CubeMX configuration, RX channel is Peri2Mem, IncSrcAddr-No, IncDestAddr-Yes, Tx is Mem2Peri, IncSrcAddr-Yes, IncDestAddr-No. I triple check the calls to LL_DMA_ConfigAddresses(), etc.
So what is happening to have SPI generate clock but no data seems like bad connection from DMA to SPI FIFO, that is what is different from the STM32Fxx parts is this deep SPI FIFO. In CubeMX configuration, I just left SPI3 Fifo Threshold at default of 01 Data.
Also, just to clarify, this is all non-secure coding with TZEN=0 - I deal with the Secure/Non-secure code hell in a parallel project where I merge stuff after it works in simple mode.
2025-12-03 10:01 AM
So, this was a secure/non-secure error on my part after all - I got confused and started running my non-secure scratch project on a device that had TZEN set. The code mostly runs and you don't really notice the mis-match until something like this. So setting up the LL code in a secure project build, the SPI3 runs by DMA in 8-bit transfers and the SPI2 runs by DMA with 32-bit transfers. It still cannot run with DIV by 2, but up to DIV by 4 for 40 Mbps transfers looks good.