cancel
Showing results for 
Search instead for 
Did you mean: 

STM32G4 trigger DMA request from SPI to Memory with timer

mariankeller
Associate II

Hello,

I'm trying to get SPI to receive data from an ADC in a periodic interval, and must not use interrupts, as the whole application is already pushing the upper limit of what the MCU can handle. The code below is just a rough test, adapted from what I found in another thread in this forum. However, I still can't get it to work. Timer runs, DMA and DMAMUX seem to get configured, but nothing happens on the SPI CLK. Any ideas?

 

void acq_configure_adc_transfer()
{
	uint32_t circular_buffer[10];

	__HAL_RCC_DMAMUX1_CLK_ENABLE();
	__HAL_RCC_DMA1_CLK_ENABLE();
	__HAL_RCC_SPI1_CLK_ENABLE();
	__HAL_RCC_TIM1_CLK_ENABLE();

	DMA1_Channel1->CCR = 0x0;	//Disable DMA stream

	DMA1_Channel1->CPAR = (uint32_t)&SPI1->DR;   	                    //Set source memory region
	DMA1_Channel1->CMAR = (uint32_t)&circular_buffer;	                //Set peripheral data register as destination
	DMA1_Channel1->CNDTR = sizeof(circular_buffer) ; 		//Number of items to transfer

	//NVIC_EnableIRQ(DMA1_Channel2_IRQn);	     	//Enable interrupt for DMA Channel 2, to deal with finished transfers

	DMA1_Channel1->CCR =
			DMA_CCR_EN | 			    //Enable
			DMA_CCR_HTIE | 			    //half transfer interrupt enable
			DMA_CCR_TCIE | 			    //transfer complete interrupt enable
			DMA_CCR_CIRC | 		     	//DMA Circular mode
			DMA_PINC_DISABLE | 		    //Do not increment peripheral
			DMA_MINC_DISABLE | 		    //Do not increment memory  //DMA_MINC_DISABLE
			DMA_CCR_PSIZE_1 | 		    //Peripheral data size 0 = 16bit , 1 = 32bit
			DMA_CCR_MSIZE_1 | 		    //Memory data size 0 = 16bit , 1 = 32bit
			DMA_PRIORITY_VERY_HIGH;  	//DMA Priority

	DMAMUX1_Channel0->CCR = 0;
	DMAMUX1_Channel0->CCR |= DMA_REQUEST_TIM1_UP | DMAMUX_CxCR_EGE;

	HAL_TIM_Base_Start(&htim1);	//Start Timer

	//Reset SPI1 Configuration Register (-> this will disable the SPI1)
	SPI1->CR1 = 0b00000000;
	//SPI1->CR2 = SPI_CR2_TXEIE | SPI_CR2_TXDMAEN | SPI_CR2_SSOE;	//TXEIE:Txbuffer empty interrupt enable, TXDMAEN: Enable TX dma, SSOE:SSoutputenable
	SPI1->CR2 = SPI_CR2_RXNEIE | SPI_CR2_RXDMAEN;	//TXEIE:Txbuffer empty interrupt enable, TXDMAEN: Enable TX dma, SSOE:SSoutputenable

	SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_SPE | SPI_DATASIZE_16BIT; //Enable SPI, configuration as master, Data frame format 16bit
	//Reset Timer Configuration Registers (-> this will disable the timer)
	TIM1->CR1 = 0b00000000;
	TIM1->CR2 = 0b00000000;

	TIM1->DIER = TIM_DIER_UDE; //UDE Update DMA request enable

	TIM1->PSC = 500; 			//Set the prescaler
	TIM1->ARR = 1000; 			//Set the autoreload value

	TIM1->CR1 = TIM_CR1_CEN | TIM_CR1_ARPE;	//the CEN is set (Counter enable) and ARPE:Auto-reloadpreloadenable is buffered (TIMx_ARR register is buffered)
	//Timer is now started and triggers SPI at each overflow

}

 

1 ACCEPTED SOLUTION

Accepted Solutions

SPI won't clock if you read out its data register (using DMA or processor, it doesn't matter).

You need to set up two DMAs: one, upon trigger from the timer, transfers a dummy value to SPI data register to start the SPI clocking, and then if SPI finishes the clocking, there's a data read in in the SPI's data register, and the second DMA, upon trigger from SPI, transfers the read data from that SPI data register to memory.

You don't want the buffer to be a local variable, and you want to increment DMA MINC.

JW

View solution in original post

3 REPLIES 3

SPI won't clock if you read out its data register (using DMA or processor, it doesn't matter).

You need to set up two DMAs: one, upon trigger from the timer, transfers a dummy value to SPI data register to start the SPI clocking, and then if SPI finishes the clocking, there's a data read in in the SPI's data register, and the second DMA, upon trigger from SPI, transfers the read data from that SPI data register to memory.

You don't want the buffer to be a local variable, and you want to increment DMA MINC.

JW

Thank you very much, that already got me in the right direction. Now, there only remains one issue, it won't work when I set the SPI CR1 data size bits. No matter if I use SPI_DATASIZE_8BIT or SPI_DATASIZE_16BIT, nothing happens at the SPI. When I just don't set them, it looks like it transfers 2x8 bits (haven't validated that, just looking at the clk) with an about one cycle pause in-between. That seems to result from the data packing. Is there some other configuration issue that could cause that? 16 bit mode works just fine otherwise when configured using the HAL, which I want to avoid having in the final code.

As a side node, opposed to what I've read in some other threads on this forum, it seems like the CS signal generated by an STM32G4 series SPI peripheral can be configured to be just as one would expect, suitable to control an SPI ADC. 

 

 

uint16_t dummy_val = 0;

uint16_t circular_buffer[10];

void acq_configure_adc_transfer()
{
	__HAL_RCC_DMAMUX1_CLK_ENABLE();
	__HAL_RCC_DMA1_CLK_ENABLE();
	__HAL_RCC_SPI1_CLK_ENABLE();
	__HAL_RCC_TIM1_CLK_ENABLE();
	__HAL_RCC_GPIOA_CLK_ENABLE();


	// Transfer dummy data to SPI using DMA1 Channel 1
	DMA1_Channel1->CCR = 0x0;	//Disable DMA stream
	DMA1_Channel1->CPAR = (uint32_t)&SPI1->DR;   	                    //Set peripheral data register as destination
	DMA1_Channel1->CMAR = (uint32_t)&dummy_val;	                    //Set source memory region
	DMA1_Channel1->CNDTR=10; 					//Number of items to transfer


	// Read SPI1 data after rx to buffer
	DMA1_Channel2->CCR = 0x0;	//Disable DMA stream
	DMA1_Channel2->CPAR = (uint32_t)&SPI1->DR;   	                    //Set peripheral data register as destination
	DMA1_Channel2->CMAR = (uint32_t)&circular_buffer;	                //Set source memory region
	DMA1_Channel2->CNDTR = sizeof(circular_buffer) ; 					//Number of items to transfer



	DMA1_Channel1->CCR =
			DMA_CCR_EN | 			    //Enable
			DMA_CCR_HTIE | 			    //half transfer interrupt enable
			DMA_CCR_TCIE | 			    //transfer complete interrupt enable
			DMA_CCR_CIRC | 		     	//DMA Circular mode
			DMA_PINC_DISABLE | 		    //Do not increment peripheral
			DMA_MINC_DISABLE | 		    //Do not increment memory  //DMA_MINC_DISABLE
			DMA_CCR_PSIZE_0 | 		    //Peripheral data size 0 = 16bit , 1 = 32bit
			DMA_CCR_MSIZE_0 | 		    //Memory data size 0 = 16bit , 1 = 32bit
			DMA_CCR_DIR |
			DMA_PRIORITY_VERY_HIGH;  	//DMA Priority

	DMA1_Channel2->CCR =
				DMA_CCR_EN | 			    //Enable
				DMA_CCR_HTIE | 			    //half transfer interrupt enable
				DMA_CCR_TCIE | 			    //transfer complete interrupt enable
				DMA_CCR_CIRC | 		     	//DMA Circular mode
				DMA_PINC_DISABLE | 		    //Do not increment peripheral
				DMA_MINC_ENABLE | 		    //Do increment memory
				DMA_CCR_PSIZE_0 | 		    //Peripheral data size 0 = 16bit , 1 = 32bit
				DMA_CCR_MSIZE_0 | 		    //Memory data size 0 = 16bit , 1 = 32bit
				DMA_PRIORITY_VERY_HIGH;  	//DMA Priority


	DMAMUX1_Channel0->CCR = 0;
	DMAMUX1_Channel0->CCR |= DMA_REQUEST_TIM1_UP | DMAMUX_CxCR_EGE;

	DMAMUX1_Channel1->CCR = 0;
	DMAMUX1_Channel1->CCR |= DMA_REQUEST_SPI1_RX | DMAMUX_CxCR_EGE;

	HAL_TIM_Base_Start(&htim1);	//Start Timer

	__HAL_RCC_GPIOA_CLK_ENABLE();

	GPIO_InitTypeDef GPIO_InitStruct = {0};


	GPIO_InitStruct.Pin = GPIO_PIN_4;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

	GPIO_InitStruct.Pin = GPIO_PIN_5;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
	GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

	GPIO_InitStruct.Pin = GPIO_PIN_6;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
	GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

	GPIO_InitStruct.Pin = GPIO_PIN_7;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
	GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

	//Reset SPI1 Configuration Register
	SPI1->CR1 = 0;
	SPI1->CR2 =  SPI_CR2_RXDMAEN | SPI_CR2_TXDMAEN | SPI_CR2_NSSP | SPI_CR2_SSOE;	// TXDMAEN: Enable TX dma, SSOE:SSoutputenable
	SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_SPE; //Enable SPI, configuration as master, Data frame format 16bit
	//SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_SPE | SPI_DATASIZE_16BIT; //Enable SPI, configuration as master, Data frame format 16bit

	//Reset Timer Configuration Registers
	TIM1->CR1 = 0;
	TIM1->CR2 = 0;

	TIM1->DIER = TIM_DIER_UDE; //UDE Update DMA request enable

	TIM1->PSC = 500; 			//Set the prescaler
	TIM1->ARR = 1000; 			//Set the autoreload value (timer interval)

	TIM1->CR1 = TIM_CR1_CEN | TIM_CR1_ARPE;	//the CEN is set (Counter enable) and ARPE:Auto-reloadpreloadenable is buffered (TIMx_ARR register is buffered)
	//Timer is now started and triggers SPI at each overflow
}

 

 

If something does not work as expected, always start with reading out and checking content of registers (with having in mind that debugging is intrusive).

Data size is in CR2, not in CR1.

waclawekjan_0-1727770574300.png

 

Generally, never use Cube-defined symbols (constants) as they may be different than what you think; use exclusively those defined in the CMSIS-mandated device header. This has the added benefit that they contain the register name, so it's less likely you apply them to inappropriate register. E.g. here:

SPI1->CR |= (0b0111 << SPI_CR2_DS_Pos)  // 8-bit

JW