2024-10-08 12:00 AM
I have come across a rather weird problem and would like to get some direction.
I am using STM32F756 nucleo board. I set ADC1 to trigger by TIM1 (the complete project uses some other peripherals). TIM1 is set as PWM generation. All these are set using CubeMX in CubeIDE. In ADC setup, continuous DMA request is enabled, continuous conversion disabled. Trigger set to TIM1 update event (also tried with external trigger and shorting TIM1 output to EXTI pin with jumper). ADC1-DMA is set to circular mode. In the code, I start ADC with "HAL_ADC_StartDMA" before starting the timer. To start the timer, I copied "HAL_TIM_StartPWM" and made another method, let's say, "Private_TIM_StartPWM" which is almost identical to the HAL method except it doesn't have the "__HAL_TIM_ENABLE" call at the end. So, when calling this method, TIM1 is set up for PWM generation but the counter remains idle and will be enabled separately later.
Now onto the problem, in the code, before TIM1 can be enabled, the DMA transfer complete interrupt is fired. I have checked that the SR register has only TCIF flag set in the interrupt. However, checking the timer output with oscilloscope, the timer is not outputting any pulse, so, the ADC should not be triggering at all. When I checked the DMA buffer, there is no data there.
I set up a separate minimal project with only ADC1 and TIM1 set the same way to reproduce the problem but in this case, it works fine as expected. So, back in my main project, I debugged with breakpoint in every IRQHandler (only 4) and the DMA transfer complete interrupt is the first one being fired, so, it probably is not a case of another interrupt messing with things. I have repeatedly debugged the interrupt, it is always only TCIF flag, no PWM in timer output, no data in buffer memory.
I am quite stumped as to what is causing this and would like some guidance as to what should I look into. Thank you for your precious time.
Solved! Go to Solution.
2024-10-08 05:26 AM
> CR->EN bits are 0. I set these bits to 0 right after calling "HAL_ADC_StartDMA".
Why do you do that?
This is the reason for the DMA TCIF.
JW
2024-10-08 01:58 AM
Read out and check/post TIM, ADC and the relevant DMA stream registers content.
JW
2024-10-08 04:35 AM
Register contents before the interrupt
ADC | TIM1 | DMA | |||
SR | 0x0 | CR1 | 0x80 | CR | 0x3551F |
CR1 | 0x4000000 | CR2 | 0x20 | ISR | 0x0 |
CR2 | 0x39000701 | SR | 0x1 | FCR | 0xA0 |
HTR | 0xfff | CCMR1 | 0x6c | ||
SQR3 | 0x3 | BDTR | 0x200A000 | ||
DMAR | 0x80 |
At the IRQHandler breakpoint, almost none of these changes except ADC's CR2->ADON bit is 0, DMA's CR->HTIE and CR->EN bits are 0. I set these bits to 0 right after calling "HAL_ADC_StartDMA".
2024-10-08 04:43 AM
I think I have figured out what happened. Apparently, disabling DMA sets the TCIF flag and fires the interrupt. I did not know about it. Now, the problem is that in my project, I need to disable DMA to change the buffer pointer and data size and this process needs to be done fast (2us), so, there is not enough time to stop and start DMA again. And for some unknown reason, if DMA is in normal mode, it doesn't fire the interrupt after the first time even if the TCIE bit is set.
2024-10-08 05:07 AM - edited 2024-10-08 05:07 AM
If you show more of your code, the issue could likely be spotted. The problem description is a little hard to follow--you're using HAL functions, so which IRQHandler is the breakpoint in?
Showing code and screenshots of register values or variables is going to be better than a text description. Things can be lost in translation.
These are well-tested chips, there is unlikely to be a hardware bug in modules as common as ADC and DMA.
I do not believe that disabling DMA sets TCIF.
2024-10-08 05:26 AM
> CR->EN bits are 0. I set these bits to 0 right after calling "HAL_ADC_StartDMA".
Why do you do that?
This is the reason for the DMA TCIF.
JW
2024-10-08 05:35 AM - edited 2024-10-08 05:36 AM
I have been disabling the DMA after calling HAL method. This is what has been setting the interrupt. This I just tested. So, it is not a bug, I just didn't know this information. Found it from the below discussion-
https://community.st.com/t5/stm32-mcus-products/restarting-dma-with-new-configuration/td-p/455057
Another interesting thing I found is that if DMA is disabled while within interrupt routine, it doesn't fire any additional interrupt, probably because the TCIF flag is already set.
In my project and tests, I have only added code in the main function as follows-
main in project
Private_TIM_StartPWM (&htim1, TIM_CHANNEL_1);
Private_TIM_StartPWM (&htim8, TIM_CHANNEL_1);
Private_SwitchTimer_Start(&htim5);
//Start peripherals
Private_Start_Receive(&huart3, (uint8_t*)ReceiveBuffer_p, 2);
HAL_ADC_Start_DMA(&hadc1, VoltageBuffer_p, 1000);
HAL_ADC_Start_DMA(&hadc2, &TempBuffer, 1);
HAL_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_3, TimestampBuffer_p, 1000);
HAL_DAC_Start(&hdac, DAC_CHANNEL_2);
hdac.Instance->CR &= ~DAC_CR_EN2;
// Disable DMA and half transfer interrupts
hadc1.Instance->CR2 &= ~ADC_CR2_ADON;
hadc2.Instance->CR2 &= ~ADC_CR2_ADON;
hdma_adc1.Instance->CR &= ~(DMA_SxCR_EN | DMA_SxCR_HTIE);
hdma_adc2.Instance->CR &= ~(DMA_SxCR_EN | DMA_SxCR_HTIE);
htim2.Instance->DIER &= ~TIM_DIER_CC3DE;
htim2.Instance->CR1 &= ~TIM_CR1_CEN;
htim2.Instance->CNT = 0;
hdma_tim2_up_ch3.Instance->CR &= ~(DMA_SxCR_EN | DMA_SxCR_HTIE | DMA_SxCR_TCIE);
while(1)
{
}
Private_TIM_StartPWM method
HAL_StatusTypeDef Private_TriggerTimer_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
{
/* Set the TIM channel state */
TIM_CHANNEL_STATE_SET(htim, Channel, HAL_TIM_CHANNEL_STATE_BUSY);
/* Enable the Capture compare channel */
TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
if (IS_TIM_BREAK_INSTANCE(htim->Instance) != RESET)
{
/* Enable the main output */
__HAL_TIM_MOE_ENABLE(htim);
}
htim->Instance->SR = 0;
return HAL_OK;
}
For a minimal test, I set only ADC1 and TIM1 from CubeMX and added the following code in main
Private_TIM_StartPWM(&htim1, TIM_CHANNEL_1);
htim1.Instance->PSC = 5400-1;
htim1.Instance->ARR = 10000-1;
htim1.Instance->CCR1 = 5000;
HAL_ADC_Start_DMA(&hadc1, buff1, 40);
hdma_adc1.Instance->CR &= ~DMA_SxCR_HTIE;
hdma_adc1.Instance->CR &= ~DMA_SxCR_EN; //Interrupt fires here
hdma_adc1.Instance->CR |= DMA_SxCR_EN;
htim1.Instance->CR1 |= TIM_CR1_CEN;
Edit: Breakpoint in "DMA2_Stream0_IRQHandler".
2024-10-08 05:42 AM
In my project, I need to sample some analog signal at different sampling rate and store the data in different memory location. The signal is in one channel only. So, I need to get one set of data; tweak timer frequency, change DMA buffer location and size and re-enable DMA. But these operations have to be done in a very short time, 2~3 us. So, I was planning to use HAL methods to start of all the peripherals and DMA, then disable everything and only enable when it starts sampling. Now, I am thinking of other options.