2025-10-08 9:36 AM
Hello all,
I have been struggling with DMA transfers from SPI transactions and would love a second opinion on what I could be doing wrong. I have confirmed that polling works and I can corroborate the data I see in the RX registers with a scope.
I do not use the HAL API for SPI receptions with DMA, I am mostly using low-level register modifications to trigger read/writes from the SPI peripheral, although I do use the initialization of the peripheral that is generated by Cube IDE.
I was looking into why the DMA destination buffer was not being filled, when I realized there was something wrong with the SPI transaction itself. According to the RM, the DMA will make a request once RXP is set and it is automatically activated according to `RXWNE` and `RXPLVL`. When inspecting these register during the transaction I see that `RXWNE` is set to 1, meaning there is, at least, 4 bytes in the RX FIFO. RXPLVL itself reads all zeroes since the data size is larger than 16 bits, so I don't see an issue there.
The transaction is completed correctly, as the EOT flag is raised and I see the data in the RXDR registry (and is cleared out once the EOT flag is set).
The transaction is a 32 bit from an AD with an 20MHz SPI clock that occurs after a GPIO interrupt and a timer trigger. These are the settings for my SPI peripheral:
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_32BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_ENABLE;
hspi1.Init.CRCPolynomial = 0x0;
hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
hspi1.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;
hspi1.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
hspi1.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
hspi1.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
hspi1.Init.MasterSthat RXWNE SIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
hspi1.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
hspi1.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
hspi1.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;
hspi1.Init.IOSwap = SPI_IO_SWAP_DISABLE;
hspi1.Init.ReadyMasterManagement = SPI_RDY_MASTER_MANAGEMENT_INTERNALLY;
hspi1.Init.ReadyPolarity = SPI_RDY_POLARITY_HIGH;
This is the simplified code I use for the transaction:
HAL_GPIO_WritePin(css_gpio.GPIO_PORT, css_gpio.GPIO_PIN, GPIO_PIN_RESET);
SCB_InvalidateDCache_by_Addr(&spi_buffer_rx[buf_index], sizeof(uint32_t));
// set up dma BEFORE doing the transfer
HAL_DMA_Start(hspi1.hdmarx, (uint32_t)&hspi1.Instance->RXDR, (uint32_t)&spi_buffer_rx[buf_index], 1);
hspi1.Instance->CR1 &= ~SPI_CR1_SPE; // disable the SPI per before amking changes
hspi1.Instance->IER |= SPI_IER_RXPIE; // enable the interrupts from RXP
// enable the DMA before starting the transfer
hspi1.Instance->CFG1 |= SPI_CFG1_RXDMAEN;
hspi1.Instance->CR1 |= SPI_CR1_SPE;
hspi1.Instance->TXDR = 0UL; // send an empty array
hspi1.Instance->CR1 |= SPI_CR1_CSTART; // start the transfer and wait until it's done
//while(!(hspi1.Instance->SR & SPI_SR_RXP)); // wait for the hardware to set the flag of an available packet in the FIFO. This SHOULD trigger the DMA transfer
uint32_t my_val=hspi1.Instance->RXDR;
while(!(hspi1.Instance->SR & SPI_SR_EOT));
hspi1.Instance->IFCR |= SPI_IFCR_EOTC | SPI_IFCR_TXTFC; // clear the flags for end of transfer and txt sr
HAL_GPIO_WritePin(css_gpio.GPIO_PORT, css_gpio.GPIO_PIN, GPIO_PIN_SET);
Does anyone have an idea why RXP is not being set even though I have a successful data reception? I suspect it has to do with data packing, but I've set the peripheral to the best of my knowledge.
Thanks!
2025-10-08 9:42 AM
Are you debugging? Are you reading the data register in the debugger? This will empty the FIFO and clear the flag.
The invalidate call should happen after the data has been received and written by DMA but before you read it with the CPU. Doing it before receiving data doesn't serve a purpose. I recommend disabling data cache while debugging the basics.
Your code has the check for RXP commented out, so how do you know it's not being set?
2025-10-08 9:45 AM
Hi!
Thanks for the comment. Yes I am debugging. The RXP check is commented out because it would enter into the loop and never exit, since the bit is never set.
I will disable the cache and try some more testing.
2025-10-08 11:24 AM
Be sure not to read data in the debugger or it will clear the RXP flag.
2025-10-08 11:32 AM
I flashed my board and let it run to see if it continued with the transactions or if it stopped at the RXP check and it continues to stop. In the debugger, without expressions, live expressions, or break points, I see the same behaviour.
2025-10-08 11:38 AM - edited 2025-10-08 11:39 AM
Probably SPI isn't set up correctly then. It can operate in transaction mode. Show the SPI register contents when it "doesn't work" and code is waiting for RXP.
You're mixing HAL and register-level instructions. Is there a reason HAL_SPI_TransmitReceive_DMA is insufficient here?
2025-10-08 11:41 AM
I believe the sequence of events might be an error here.
After I disable the SPI peripheral (CR1 - bit 0), I consistently see a change in the SR register, enabling the RXP bit.
HAL_DMA_Start(hspi1.hdmarx, (uint32_t)&hspi1.Instance->RXDR, (uint32_t)&spi_buffer_rx[buf_index], 1);
hspi1.Instance->CR1 &= ~SPI_CR1_SPE; // disable the SPI per before amking changes
// SR = 1001000000000111
hspi1.Instance->IER |= SPI_IER_RXPIE | SPI_IER_EOTIE;
While the the SPI peripheral is turned off and I issue the RXDMA, FTHLV, and interruption register, this bit continues on. Upon enabling it once more, the bit is cleared, however, at this point the transaction has yet to start since I have not set CR1 CSTART.
So, the hardware reports something in its RX FIFO, the DMA request triggers, but the RXDR register is zeroed.
2025-10-08 11:52 AM
I agree. Since I was getting the data before the DMA implementation, I oversaw that the transaction was not really being handled gracefully. I was trying to keep everything at a register level, but for testing purposes I used HAL_DMA_Start().
And I tried using the standard HAL API for my application, but I could not make it work for Flex SPI.