2024-01-18 07:33 PM
Hello. I am new to using STM32 MCUs and this forum has helped me a lot in getting familiar fast. I am using using NUCLEO-F756ZG board. I have tried ADC with DMA and timer trigger and it all works fine. Now, I am trying to switch timer period dynamically along with changing DMA buffer. So, after some digging on the internet, this is what I came up with.
void Restart_DMA(uint32_t ARR_val, uint32_t* new_address, uint32_t new_len)
{
//Disable ADC
hadc1.Instance->CR2 &= ~ADC_CR2_ADON;
//Disable DMA
hdma_adc1.Instance->CR &= ~DMA_SxCR_EN;
while(!(hdma_adc1.Instance->CR & DMA_SxCR_EN));
//Clear DMA flags
DMA2->LIFCR = DMA_FLAG_TCIF0_4;
//Switch timer period
htim5.Instance->CR1 &= ~(TIM_CR1_CEN);
htim5.Instance->ARR = ARR_val;
htim5.Instance->CNT = 0;
htim5.Init.Period = ARR_val;
//Set new buffer and length
hdma_adc1.Instance->CR &= ~DMA_SxCR_DBM;
hdma_adc1.Instance->NDTR = new_len;
hdma_adc1.Instance->M0AR = (uint32_t)new_address;
//Enable DMA
hdma_adc1.Instance->CR |= DMA_SxCR_EN;
//Enable ADC
hadc1.Instance->CR2 |= ADC_CR2_ADON;
//Enable Timer
htim5.Instance->CR1|=(TIM_CR1_CEN);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_8);
if(buff_flag == 1)
{
buff_flag = 2;
Restart_DMA(7, &adc_vals2[0], 5000);
}
else
{
buff_flag = 1;
Restart_DMA(3, &adc_vals1[0], 10000);
}
}
The rest is just boilerplate codes from CubeMX. The main function just initializes everything and starts the DMA stream for the first time. Since I need to switch buffer length, buffer address and sampling frequency, circular DMA is not a solution. Is this a good way to do it?
Note: This is for an application of measuring response signals of electrical system for a long time (5mintues~). It doesn't make sense to keep up 1 Msps sample rate for such a long time, 100 samples/s is enough. But the signal usually changes rapidly or has noise in the transient state. So, for a few microseconds at the beginning, I need 1 Msps sample rate. Afterwards, as the signal stabilizes, the sampling rate is gradually reduced. In the actual application, there will be 10 or so separate buffers, each to be filled with different sampling rate. I am trying to keep the transition as fast as possible.
2024-01-19 12:16 AM
Actually, setting the DMA to circular mode worked. Just moved the adc disable and dma disable calls to the interrupt callback. So, the code is like this now,
void Restart_DMA(uint32_t ARR_val, uint32_t* new_address, uint32_t new_len)
{
//Clear DMA flags
DMA2->LIFCR = DMA_FLAG_TCIF0_4;
//Switch timer period
htim1.Instance->CR1 &= ~(TIM_CR1_CEN);
htim1.Instance->ARR = ARR_val;
htim1.Instance->CNT = 0;
htim1.Init.Period = ARR_val;
//Set new buffer and length
hdma_adc1.Instance->CR &= ~DMA_SxCR_DBM;
hdma_adc1.Instance->NDTR = new_len;
hdma_adc1.Instance->M0AR = (uint32_t)new_address;
//Enable DMA
hdma_adc1.Instance->CR |= DMA_SxCR_EN;
//Enable ADC
hadc1.Instance->CR2 |= ADC_CR2_ADON;
//Enable Timer
htim1.Instance->CR1|=(TIM_CR1_CEN);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
//Disable DMA
hdma_adc1.Instance->CR &= ~DMA_SxCR_EN;
//Disable ADC
hadc1.Instance->CR2 &= ~ADC_CR2_ADON;
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_8);
if(buff_flag == 1)
{
buff_flag = 2;
Restart_DMA(8-1, &adc_vals2[0], 5000);
}
else
{
buff_flag = 1;
Restart_DMA(4-1, &adc_vals1[0], 10000);
}
}
This properly swaps the buffers. The operations seem to take about 2 microseconds to finish. It's a bit longer than I wished for but it works. Now, I just need to verify the periods with an oscilloscope. If there's any optimization for the above code to reduce the swap time, it would be very appreciated.