1. Introduction
While working on the DMA_ADC routine, I encountered an issue related to interrupt configuration. According to the manual, interrupt configuration is typically required for continuous ADC conversion mode. However, I found that in practical situations, ADC continuous conversion can occur without interrupt configuration. Unfortunately, I overlooked this information during ADC sampling, and unintentionally enabled ADC interrupt sampling. This replication is based on the APM32F411 official DMA_ADC routine, which simulates the problem.
2. Replicating the Issue in the APM32F411 DMA_ADC Routine
/*!
* [url=home.php?mod=space&uid=247401]@brief[/url] ADC Init
*
* @param None
*
* @retval None
*/
void ADC_Init(void)
{
GPIO_Config_T gpioConfig;
ADC_Config_T adcConfig;
/* RCM Enable*/
RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOA);
/* GPIO Configuration */
GPIO_ConfigStructInit(&gpioConfig);
gpioConfig.pin = GPIO_PIN_0;
gpioConfig.mode = GPIO_MODE_AN;
gpioConfig.pupd = GPIO_PUPD_NOPULL;
GPIO_Config(GPIOA, &gpioConfig);
RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1);
/* ADC Configuration */
ADC_Reset();
ADC_ConfigStructInit(&adcConfig);
/* Set resolution*/
adcConfig.resolution = ADC_RESOLUTION_12BIT;
/* Set dataAlign*/
adcConfig.dataAlign = ADC_DATA_ALIGN_RIGHT;
/* Set scanDir*/
adcConfig.scanConvMode = DISABLE;
/* Set convMode continous*/
adcConfig.continuousConvMode = ENABLE;
/* Set extTrigEdge*/
adcConfig.extTrigEdge = ADC_EXT_TRIG_EDGE_NONE;
/* Set nbrOfConversion*/
ADC_Config(ADC1, &adcConfig);
ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_0, 1, ADC_SAMPLETIME_112CYCLES);
NVIC_EnableIRQRequest(ADC_IRQn,0x00,0x00);
ADC_EnableInterrupt(ADC1,ADC_INT_EOC);
ADC_EnableDMA(ADC1);
ADC_EnableDMARequest(ADC1);
/* Enable ADC*/
ADC_Enable(ADC1);
ADC_SoftwareStartConv(ADC1);
}
In the ADC_Init() configuration of the replicated code, NVIC is utilized to request an interrupt, and interrupts are enabled. However, no content processing is implemented in the interrupt service function.
3. Issue Localization and Confusion
When facing the problem, my initial debugging involved stepping through the code. As the program became stuck, the following observations were made:
When a program encounters issues, the first information I focus on is “Internal: Mode->Handler, Stack->MSP->0×20000400.” Meanwhile, in the Debug Faults, the value of the SCB->DFSR register is 0×00000001. First, the program mode transitioned from Thread to Handler, signifying a shift from normal user code execution to exception program handling. Secondly, examining the contents of the SCB register and combining it with information from the kernel manual, the situation is as follows:
The SCB->DFSR register value was 0×00000001, indicating that the CPU was halted by the debugger. However, the reason for the halt required further analysis.
Further analysis of the DFSR register revealed that the HALTED bit was set because a HALT request was made in NVIC. Reviewing the code, it was found that NVIC was requested during ADC initialization. When the program entered the ADC interrupt, it repeatedly entered the interrupt program. At this point, the issue was confirmed. The HALT request occurred because interrupts were enabled in the ADC configuration. Enabling interrupts requested HALT, and the CPU responded by executing the ADC interrupt service function. However, the ADC interrupt service function was not handled, and the ADC was configured for continuous conversion mode. Consequently, the program kept switching between Handler and Thread modes. Additionally, considering the startup file (.s), as shown below:
Default_Handler PROC
EXPORT ADC_IRQHandler [WEAK]
When interrupts are enabled through the application, the MCU utilizes the provided interrupt number to calculate the offset and loads it into the interrupt vector table. Upon enabling the corresponding interrupt and transitioning to Handler mode, the PC takes this address as the entry point, jumping into the interrupt service routine and executing it. The process is illustrated in the following diagram (for illustration purposes only):
As I didn’t define any handling for the ADC_Handler mode in my initial program, the MCU couldn’t enter the interrupt service routine after responding to the interrupt. Consequently, it gets stuck at the assembly language B line, similar to the C language while(1);, creating a deadlock.