2024-09-30 10:18 AM
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
}
Solved! Go to Solution.
2024-09-30 10:40 AM
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
2024-09-30 10:40 AM
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
2024-09-30 11:14 PM - edited 2024-09-30 11:17 PM
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
}
2024-10-01 01:21 AM - edited 2024-10-01 01:23 AM
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.
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