cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F103 SPI DMA receive not receiving

joe2399
Associate II

I have an STM32F103 (blue pill) connected to an NRF24L01. I have the interrupt from the NRF triggering an external interrupt, and I'm reading 16 bytes in to the STM32 using polling. All of that works dandy, but I would very much like to interrupt, quickly set up a DMA transfer in the ISR, then be on my way to do other things, then process the data once the DMA transfer is complete.

I have posted my SPI initialization below. That works fine with polling and getting the NRF set up. I then call "enable_DMA_SPI_RX_with_interrupts", also posted below, and what I envision it doing is doing an automatic SPI read of 16 bytes, and moving it to the buffer "payload".

I have scrubbed through the reference manual RM0008, page 278, where it describes the channel configuration procedure. I think I am doing everything it lists.

I have also went over and over page 719 (SPI communication using DMA). It says a DMA access is requested when the enable bit in the SPI_CR2 register is set - I am setting RXDMAEN (bit 0 of SPI_CR2) page 745, verified in debugger.

Looking at Figure 248 on page 720, for reception using DMA, it depicts "software configures the DMA SPI Rx channel to receive 3 data items and enables the SPI". I think I have configured the DMA SPI channel (channel 2) to receive 16 data items, and I have enabled the SPI. The picture shows the DMA transfer commencing after those conditions have been met.

However nothing happens. With the logic analyzer hooked up, there is no activitiy on any of the SPI lines. And I don't think you can debug it step by step, because in theory it's supposed to happen in the background without code being executed. I feel like I am missing a step. Like there is some trigger to initiate the DMA transfer that I am not doing. I guess my question is after calling "enable_DMA_SPI_RX_with_interrupts", what else do I need to commence the transfer? Thank you for your time.

void spi_init(void){
RCC->APB2ENR |= 1; 			//Enable AFIO function
 
RCC->APB2ENR |= 0x1000; 	//Enabling SPI-1 periph
// Set up the pins:
init_GP(PA,4,OUT50,O_GP_PP);
init_GP(PA,5,OUT50,O_AF_PP);
init_GP(PA,6,IN,I_PP);
init_GP(PA,7,OUT50,O_AF_PP);
 
//*******Setup SPI peripherals*****
SPI1->CR1 |= 0x4; 	// Master Mode
SPI1->CR1 |= 0x08;	// fclk / 4
SPI1->CR2 |= 0x4;	// SSOE set
SPI1->CR1 |= 0x40; 	// Enabling SPI SPI periph
 
W_GP(PA,4,HIGH);
}

void enable_DMA_SPI_RX_with_interrupts(char payload[]){
 
	RCC->AHBENR |= 0x1;		//enable clock to AHB for DMA
	SPI1->CR1 &=~ 0x40; 	//disable SPI for now
 
	//per pg 279 of ref manual:
	DMA1_Channel2->CPAR = (uint32_t)&SPI1->DR;  //1. load CPAR with base address of SPI_DR
	DMA1_Channel2->CMAR = payload;			    //2. load CMAR with starting location of the data
	DMA1_Channel2->CNDTR = 0x0F;				//3. set to 16 bytes of data
												//4/5. set up CCR register:
	DMA1_Channel2->CCR |= 0x80; 				//   MINC (bit 7) memory increment set
												//   CIRC (bit 5) circular mode leave disabled for now
	DMA1_Channel2->CCR &=~ 0x10; 				//   DIR (bit 4) direction not set
	DMA1_Channel2->CCR |= 0x02;					//	 TCIE (bit 1) set for transfer complete interrupt
	DMA1_Channel2->CCR |= 0x01;  				//6. EN (bit 0) set
 
	SPI1->CR2 |= 0x1; 							//set RXDMAEN (bit 0) of SPI_CR2
	DMA1->IFCR = 0x0000;						//clear all pending interrupts
	NVIC_EnableIRQ(DMA1_Channel2_IRQn);
 
	SPI1->CR1 |= 0x40; 							//reenable SPI SPI periph
}

6 REPLIES 6
gbm
Lead III

In order to receive anything via SPI you must first send data (even if it is ignored). Use two DMA channels, one for sending, another one for receiving. You may send a single byte (no memory address increment).

First check teh SPI operation without DMA (write a simple send-receive byte routine), then fiddle with DMA.

Yes, I do have SPI set up and functioning properly using polling. I can interact with all the registers on the NRF24L01, and do a burst read of a full payload of 32 bytes. Are you saying do the DMA configuration, then send a dummy byte? I tried that, sent the dummy byte, then put a 1ms delay to observe what happens, and nothing. However, now it is reaching the DMA ISR, the TCIF2 (transfer complete) flag in the DMA_ISR is showing set. So it thinks it completed the transfer, but the logic analyzer begs to differ.

gbm
Lead III

If you want to receive N bytes, you must send N bytes - this is how SPI works. In your code I cannot see the part responsible for sending N bytes. The received data appears in the data register without any delay right after anything is sent.

Also, use bit names instead of magic number and don't overuse logic operators while setting config registers - this will make you code more readable and error-proof - example below.

// init in main()
	// RX - circular mode
	LIS_SPI_RX_DMACH->CPAR = (uint32_t)&SPI1->DR;
	LIS_SPI_RX_DMACH->CMAR = (uint32_t)&lis35_data;
	LIS_SPI_RX_DMACH->CNDTR = sizeof(lis35_data);
	LIS_SPI_RX_DMACH->CCR = DMA_CCR_DIR_P2M | DMA_CCR_MINC | DMA_CCR_CIRC | DMA_CCR_TCIE | DMA_CCR_EN;
	LIS_SPI_TX_DMACH->CPAR = (uint32_t)&SPI1->DR;
	NVIC_EnableIRQ(LIS_SPI_RX_DMACH_IRQn);
 
void LIS35_GetPositionDMA(void)
{
	SPI_CS_Enable();
	DMA1->IFCR = DMA_IFCR_CGIF2 | DMA_IFCR_CGIF3;
	LIS_SPI_TX_DMACH->CCR = 0;
	LIS_SPI_TX_DMACH->CMAR = (uint32_t)lis35_cmd;
	LIS_SPI_TX_DMACH->CNDTR = sizeof(lis35_cmd);
	LIS_SPI_TX_DMACH->CCR = DMA_CCR_DIR_M2P | DMA_CCR_MINC | DMA_CCR_EN;
}
 
void LIS_SPI_RX_DMACH_IRQHandler(void)
{
	DMA1->IFCR = DMA_IFCR_CGIF2 | DMA_IFCR_CGIF3;
	SPI_CS_Disable();
	LIS_SPI_TX_DMACH->CCR = 0;
}

Ok, that helps a lot, thank you. So your LIS_SPI_RX_DMACH is the receive channel (or stream depending on which device), and the LIS_SPI_TX_DMACH is the transmit channel/stream. Then you call LIS35_Get_position_DMA and it will automatically transmit sizeof(lis35_cmd) bytes after the DMA_CCR_EN is set in line 17. Then the receive portion should fill up the circular buffer associated with the RX portion. I will get back to working on it this weekend and post results.

The only thing I'm foggy on is both of them using the address &SPI1->DR (lines 3 and 7). It just seems like there would be a collision/conflict with both the TX and RX using the same physical chunk of silicon.

gbm
Lead III

There are two physical registers being an interface to SPI send/receive engine: one is read-only, another write-only, both at the same address, visible as DR.

Another important piece of STM32 SPI magic is the famous FRXTH bit, which, if present in a given SPI implementation, must be set for 8-bit frames. SSM and SSI are also quite funny.

gbm, thank you, you have been a great help. That's some pretty non-intuitive stuff right there, the two DRs at the same address, one read and one write.

I see it now on page 746: "The data register is split into 2 buffers - one for writing (Transmit Buffer) and another one for reading (Receive buffer). A write to the data register will write into the Tx buffer and a read from the data register will return the value held in the Rx buffer." But that doesn't exactly jump out at you...your version is far more clear!

I'll continue working on it and update when I have it working. Hopefully it will be a benefit to others too.