2025-06-12 6:48 AM - edited 2025-06-13 6:40 AM
Hi,
I am trying to establish a successful transfer using SPI in master full duplex DMA mode using a NUCLEO-H7S3L8 board (STM32H7RSxx family).
For this purpose I wrote the following functions:
#define CDC_ITF_TX_DMA_CH LL_DMA_CHANNEL_2
#define CDC_ITF_RX_DMA_CH LL_DMA_CHANNEL_3
#define SPI_CS_MAX_IDX_v 4
const u2agpio_st SPI_CS [SPI_CS_MAX_IDX_v] = {
{GPIOF, LL_GPIO_PIN_3},
{GPIOE, LL_GPIO_PIN_11},
{GPIOE, LL_GPIO_PIN_9},
{GPIOF, LL_GPIO_PIN_14},
};
void StartTrfM2PDma(uint32_t p_channel,
uint32_t p_req, uint32_t p_srcaddr, uint32_t p_destaddr,
uint32_t p_trfsize) {
LL_DMA_DisableChannel(GPDMA1, p_channel);
LL_DMA_ConfigAddresses(GPDMA1, p_channel, p_srcaddr,
p_destaddr);
LL_DMA_SetPeriphRequest(GPDMA1, p_channel, p_req);
LL_DMA_SetDataTransferDirection(GPDMA1, p_channel, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
LL_DMA_SetTransferMode(GPDMA1, p_channel, LL_DMA_PFCTRL);
LL_DMA_SetBlkHWRequest(GPDMA1, p_channel, LL_DMA_HWREQUEST_BLK);
//LL_DMA_SetTriggerMode(GPDMA1, p_channel, LL_DMA_TRIGM_BLK_TRANSFER);
LL_DMA_SetBlkDataLength(GPDMA1, p_channel, p_trfsize);
LL_DMA_SetSrcDataWidth(GPDMA1, p_channel, LL_DMA_SRC_DATAWIDTH_BYTE);
LL_DMA_SetDestDataWidth(GPDMA1, p_channel, LL_DMA_DEST_DATAWIDTH_BYTE);
LL_DMA_SetSrcIncMode(GPDMA1, p_channel, LL_DMA_SRC_INCREMENT);
LL_DMA_SetDestIncMode(GPDMA1, p_channel, LL_DMA_DEST_FIXED);
LL_DMA_EnableIT_TC(GPDMA1, p_channel);
LL_DMA_EnableIT_HT(GPDMA1, p_channel);
LL_DMA_EnableIT_DTE(GPDMA1, p_channel);
LL_DMA_EnableIT_USE(GPDMA1, p_channel);
LL_DMA_EnableIT_ULE(GPDMA1, p_channel);
}
void U3aLlStartTrfP2MDma(uint32_t p_channel,
uint33_t p_req, uint32_t p_srcaddr, uint32_t p_destaddr,
uint33_t p_trfsize) {
LL_DMA_DisableChannel(GPDMA1, p_channel);
LL_DMA_ConfigAddresses(GPDMA1, p_channel, p_srcaddr,
p_destaddr);
LL_DMA_SetPeriphRequest(GPDMA1, p_channel, p_req);
LL_DMA_SetDataTransferDirection(GPDMA1, p_channel, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
LL_DMA_SetTransferMode(GPDMA1, p_channel, LL_DMA_PFCTRL);
LL_DMA_SetBlkHWRequest(GPDMA1, p_channel, LL_DMA_HWREQUEST_BLK);
// LL_DMA_SetTriggerMode(GPDMA1, p_channel, LL_DMA_TRIGM_BLK_TRANSFER);
LL_DMA_SetBlkDataLength(GPDMA1, p_channel, p_trfsize);
LL_DMA_SetSrcDataWidth(GPDMA1, p_channel, LL_DMA_SRC_DATAWIDTH_BYTE);
LL_DMA_SetDestDataWidth(GPDMA1, p_channel, LL_DMA_DEST_DATAWIDTH_BYTE);
LL_DMA_SetSrcIncMode(GPDMA1, p_channel, LL_DMA_SRC_FIXED);
LL_DMA_SetDestIncMode(GPDMA1, p_channel, LL_DMA_DEST_INCREMENT);
LL_DMA_EnableIT_TC(GPDMA1, p_channel);
LL_DMA_EnableIT_HT(GPDMA1, p_channel);
LL_DMA_EnableIT_DTE(GPDMA1, p_channel);
LL_DMA_EnableIT_USE(GPDMA1, p_channel);
LL_DMA_EnableIT_ULE(GPDMA1, p_channel);
}
void StartTrfDmaSpi(uint32_t p_txsrcaddr,
uint32_t p_rxdstaddr, uint32_t p_trfsize, uint8_t p_rw, uint8_t p_str,
uint8_t p_cs) {
LL_SPI_Disable(SPI6);
LL_SPI_DisableDMAReq_TX(SPI6);
LL_SPI_DisableDMAReq_RX(SPI6);
if (LL_SPI_GetTransferDirection(SPI6) == LL_SPI_FULL_DUPLEX) {
// config Mem to Phi transfer
StartTrfM2PDma(GPDMA1, CDC_ITF_TX_DMA_CH,
LL_GPDMA1_REQUEST_SPI6_TX, p_txsrcaddr, LL_SPI_DMA_GetTxRegAddr(SPI6),
p_trfsize);
// config Phi to Mem transfer
StartTrfP2MDma(GPDMA1, CDC_ITF_RX_DMA_CH,
LL_GPDMA1_REQUEST_SPI6_RX, LL_SPI_DMA_GetRxRegAddr(SPI6), p_rxdstaddr,
p_trfsize);
LL_SPI_EnableIT_TXP(SPI6);
LL_SPI_EnableIT_RXP(SPI6);
LL_SPI_EnableDMAReq_TX(SPI6);
LL_SPI_EnableDMAReq_RX(SPI6);
LL_DMA_EnableChannel(GPDMA1, CDC_ITF_TX_DMA_CH);
LL_DMA_EnableChannel(GPDMA1, CDC_ITF_RX_DMA_CH);
} else {
// LL_SPI_SetDataWidth(SPI6, LL_SPI_DATAWIDTH_32BIT);
// LL_SPI_SetFIFOThreshold(SPI6, LL_SPI_FIFO_TH_01DATA);
// Half duplex 3W case
// RW = 0: write
// RW = 1: read
if (!p_rw) {
LL_SPI_SetTransferDirection(SPI6, LL_SPI_HALF_DUPLEX_TX);
StartTrfM2PDma(GPDMA1, CDC_ITF_TX_DMA_CH, LL_GPDMA1_REQUEST_SPI6_TX,
p_txsrcaddr, LL_SPI_DMA_GetTxRegAddr(SPI6), p_trfsize);
LL_SPI_DisableIT_RXP(SPI6);
LL_SPI_EnableIT_TXP(SPI6);
LL_SPI_EnableDMAReq_TX(SPI6);
LL_SPI_DisableDMAReq_RX(SPI6);
LL_DMA_EnableChannel(GPDMA1, CDC_ITF_TX_DMA_CH);
} else {
// configure half duplex transmitter
LL_SPI_SetTransferDirection(SPI6, LL_SPI_HALF_DUPLEX_RX);
StartTrfP2MDma(GPDMA1, CDC_ITF_RX_DMA_CH, LL_GPDMA1_REQUEST_SPI6_RX,
LL_SPI_DMA_GetRxRegAddr(SPI6), p_rxdstaddr, p_trfsize);
LL_SPI_EnableIT_RXP(SPI6);
LL_SPI_DisableIT_TXP(SPI6);
LL_SPI_EnableDMAReq_RX(SPI6);
LL_SPI_DisableDMAReq_TX(SPI6);
LL_DMA_EnableChannel(GPDMA1, CDC_ITF_RX_DMA_CH);
}
}
LL_SPI_SetTransferSize(SPI6, p_trfsize);
if (p_str) {
LL_GPIO_ResetOutputPin(SPI_CS[p_cs].gpioPort, SPI_CS[p_cs].gpioPin);
}
LL_SPI_Enable(SPI6);
LL_SPI_StartMasterTransfer(SPI6);
}
void StopTrfSpiDma(uint8_t p_stp, uint8_t p_cs) {
LL_SPI_Disable(SPI6);
LL_SPI_DisableDMAReq_TX(SPI6);
LL_SPI_DisableDMAReq_RX(SPI6);
if (p_stp) {
LL_GPIO_SetOutputPin(SPI_CS[p_cs].gpioPort, SPI_CS[p_cs].gpioPin);
}
}
The runs and SPI data can be seen from the MOSI pins, but with one problem: data is not successfully sent.
In the following photo:
504B @50MHz are being transmitted (CH1 trace - yellow one - is SCK, CH2 trace is CS):
total time of transmission is:
20us * 3.1 = 62us
vs
(504*8/50e6) = 80.64us
which is the time required for such a full transmission, meaning that approx. 116 B (according to these numbers) are missing on transmission.
However, when I read the GPDMA_CxBR1.BNDT[15:0] (previously programmed to 504) after GPDMA TC Irq, its value is 0, meaning that all the 504 should have been transmitted... But that's not true according to oscilloscope analysis.
So ... what is the missing piece? Maybe an LLI setup?
Thanks,
s.
Solved! Go to Solution.
2025-06-18 2:20 AM - edited 2025-06-18 2:22 AM
The problem is related with data widths and FIFO settings. The TRF says the following regarding SPI and DMA data packing:
Data packing with DMA
If the transfers are managed by DMA (TXDMAEN and RXDMAEN set in the SPI_CFG1 register) the packing mode is enabled/disabled automatically depending on the PSIZE value configured for SPI TX and the SPI RX DMA channel.
The packing mode is enabled if the DMA channel PSIZE value is a multiple of the data size.
Then the DMA automatically manages the sequences of write and read operations to/from the SPI data registers, based on FIFO occupancy flags, and depending on the FIFO threshold and data size configurations.
The DMA completes the transfer automatically according to the TSIZE field setting, whatever the data packing mode used, and even if the number of data to transfer is not a multiple of the DMA data size (16 bits or 32 bits) while the frame size is smaller.
Alternatively, the last data frames can be written by software, in the single/unpacked mode. Configuring any DMA data access to less than the configured data size is forbidden. One complete data frame must be always accessed at minimum.
Also, for SPI RXPLVL[1:0]
00: no next frame is available at RxFIFO
01: 1 frame is available
10: 2 frames are available*
11: 3 frames are available*
Note: (*): Possible value when data size is set up to 8 bits only.
When SPI DSIZE and DMA access width are set to both 8b, the DMA cannot read the the whole RxFIFO packing level, so missing at least always one B, resulting in:
t = (512*3/4*8)/50e6 = 61.44 us
which is approximately what is seen on oscilloscope using the above settings.
Setting SPI DSIZE = 16bits, FTHLV to 02_DATA, DMA Peripheral access to WORD and Mem access to 1B and SPI TSIZE according to DSIZE, results in correct whole 512 B transfer, SPI EOT IRQ also notified:
2025-06-12 6:59 AM - edited 2025-06-12 7:00 AM
Probably the SPI/DMA is doing what it's been told to do. Can you show the contents of the SPI and DMA registers just before transmission starts (LL_SPI_StartMasterTransfer)?
> (504*8/50e6) = 8.064us
I believe it's actually 80.64 us.
2025-06-13 1:02 AM - edited 2025-06-13 2:53 AM
@TDK wrote:I believe it's actually 80.64 us.
Yes, this was obviously a typo. Fixed. Thanks.
The following is just before starting the transfer:
There is a strange p_txsrcaddr=<optimized out> that it might could be part of the issue....
s.
PS: the issue happens also with lower GPDMA_CxBR1.BNDT[15:0] values (e.g. 256 instead of 504).
2025-06-13 6:37 AM
Looks correct as far as I can see.
How do you know not all data is getting sent? Just based off of the width of the transfer? Is the measured bus speed 50 MHz? Does it work with 2 bytes? 1 byte? Lower clock speed?
When is U2aLlStopTrfSpiDma called? Need to wait for transmission to end, not just DMA completion.
2025-06-13 7:00 AM - edited 2025-06-13 7:09 AM
@TDK wrote:How do you know not all data is getting sent? Just based off of the width of the transfer? Is the measured bus speed 50 MHz?
The clock frequency is 50 MHz measured on oscilloscope and yes, based on the width of the transfer, not all data is sent, simply math. I don't understand how could 504B could be transmitted in less than 80.64us at 50MHz.
I have tried with 256B and the same happens. I have to sit a while and try with far lower transfer sizes and lower clock speeds but I do not expect lot of better results.
16 bits of BNDT should allow enough size transfer unless otherwise stated..
I also noticed that setting SPI's duplex to half as transmitter, the time width of the transfer is the half of a full duplex like in the oscilloscope capture. In this specific test it was 256B. This is a non sense right now.
@TDK wrote:When is U2aLlStopTrfSpiDma called? Need to wait for transmission to end, not just DMA completion.
StopTrfSpiDma is called AFTER DMA Transfer Complete IRQ. Do you mean should be SPI EOT ?
Of course this could be the problem. Ouch!
s.
2025-06-16 3:27 AM - edited 2025-06-16 2:55 PM
So, I tried to simply do not stopping the DMA channels in its IRQ of Transfer complete leaving this job for SPI, and these are the results:
2025-06-16 5:59 AM
I think it's just general code bugs. We're only getting snippets here. Try to create a minimal but complete working example and attach that as a zipped project.
2025-06-16 6:00 AM
Yes, I agree it's the better way to proceed.
2025-06-17 8:04 AM - edited 2025-06-17 11:02 PM
Deleted
2025-06-18 2:20 AM - edited 2025-06-18 2:22 AM
The problem is related with data widths and FIFO settings. The TRF says the following regarding SPI and DMA data packing:
Data packing with DMA
If the transfers are managed by DMA (TXDMAEN and RXDMAEN set in the SPI_CFG1 register) the packing mode is enabled/disabled automatically depending on the PSIZE value configured for SPI TX and the SPI RX DMA channel.
The packing mode is enabled if the DMA channel PSIZE value is a multiple of the data size.
Then the DMA automatically manages the sequences of write and read operations to/from the SPI data registers, based on FIFO occupancy flags, and depending on the FIFO threshold and data size configurations.
The DMA completes the transfer automatically according to the TSIZE field setting, whatever the data packing mode used, and even if the number of data to transfer is not a multiple of the DMA data size (16 bits or 32 bits) while the frame size is smaller.
Alternatively, the last data frames can be written by software, in the single/unpacked mode. Configuring any DMA data access to less than the configured data size is forbidden. One complete data frame must be always accessed at minimum.
Also, for SPI RXPLVL[1:0]
00: no next frame is available at RxFIFO
01: 1 frame is available
10: 2 frames are available*
11: 3 frames are available*
Note: (*): Possible value when data size is set up to 8 bits only.
When SPI DSIZE and DMA access width are set to both 8b, the DMA cannot read the the whole RxFIFO packing level, so missing at least always one B, resulting in:
t = (512*3/4*8)/50e6 = 61.44 us
which is approximately what is seen on oscilloscope using the above settings.
Setting SPI DSIZE = 16bits, FTHLV to 02_DATA, DMA Peripheral access to WORD and Mem access to 1B and SPI TSIZE according to DSIZE, results in correct whole 512 B transfer, SPI EOT IRQ also notified: