7 years, 6 months ago.

Why does attaching an ADC interrupt clobber the mbed builtin pc.printf?

Greetings. After getting the ADC on a Nucleo F302R8 properly configured, I found that enabling the ADC interrupt makes printf() statements stop working (line 46). If I comment out that line, of course the ADC interrupt handler is no longer executed, but the printf() works again. Why might this be?

Thank you :)

calling NVIC_EnableIRQ(ADC1_IRQn); makes serial not work

void config(){
	/* Enable ADC, GPIOA, B, C, and DMA clocks */
	RCC->AHBENR |= RCC_AHBENR_ADC1EN; 
	RCC->AHBENR |= RCC_AHBENR_GPIOAEN; 
	RCC->AHBENR |= RCC_AHBENR_GPIOBEN; 
	RCC->AHBENR |= RCC_AHBENR_GPIOCEN; 
	RCC->AHBENR |= RCC_AHBENR_DMA1EN; 
	RCC->APB1ENR |= RCC_APB1ENR_DAC1EN;
	
	DAC->CR |= DAC_CR_EN1;  //Enable the DAC
	RCC->CFGR2 |= RCC_CFGR2_ADC1PRES_DIV2; //Divide some clock by 2 

	/* Configure PA_0, PC_1, PC_0, and PB_1 GPIOs as analog inputs*/
	GPIOA->MODER |= (GPIO_MODER_MODER0);  
	GPIOC->MODER |= (GPIO_MODER_MODER0 << GPIO_MODER_MODER1_Pos); 
	GPIOC->MODER |= (GPIO_MODER_MODER0);  
	GPIOB->MODER |= (GPIO_MODER_MODER0 << GPIO_MODER_MODER1_Pos);  
	
	GPIOA->MODER |= GPIO_MODER_MODER8_0; //PA_8 configured as digital out
  
	ADC1->CFGR |= ADC_CFGR_OVRMOD;  // The data register is overwritten with the last conversion result and the previous unread data is lost
	ADC1->SQR1 |= 3U; //number of channels -1 to convert.  The "Length" field is 4 bits long and starts at the zeroth bit of the SQR "Regular Sequence" register
	
	/* Populate the sequence of ADC inputs to be read */
	ADC1->SQR1 |= ADC_SQR1_SQ1_0; //ADC1_IN1
	ADC1->SQR1 |= 7U << ADC_SQR1_SQ2_Pos; //ADC1_IN7  (bits (0,1,2) = 7)
	ADC1->SQR1 |= 6U << ADC_SQR1_SQ3_Pos; //ADC1_IN6
	ADC1->SQR1 |= 12U << ADC_SQR1_SQ4_Pos; //ADC1_IN12

	// Calibration procedure 
	ADC1->CR &= ~ADC_CR_ADVREGEN;
	ADC1->CR |= ADC_CR_ADVREGEN_0; // 01: ADC Voltage regulator enabled
	ADC1->CR &= ~ADC_CR_ADCALDIF; // calibration in Single-ended inputs Mode.
	ADC1->CR |= ADC_CR_ADCAL; // Start ADC calibration
	// Read at 1 means that a calibration in progress.
	while (ADC1->CR & ADC_CR_ADCAL); // wait until calibration done
	calibration_value = ADC1->CALFACT; // perhaps this will be useful later...
	 
	ADC1->CFGR &= ~ADC_CFGR_RES; // 12-bit data resolution
	ADC1->CFGR &= ~ADC_CFGR_ALIGN; // Right data alignment
		
	ADC1->CR |= ADC_CR_ADEN; //ADEN=1 enables the ADC. The flag ADRDY will be set once the ADC is ready for operation.
	ADC1->IER |= ADC_IER_EOCIE; //enable end of regular conversion interrupt.  There are multiple CONVERSIONS per SEQUENCE
	ADC1->CFGR |= ADC_CFGR_CONT; //enable continuous conversion
	NVIC_SetPriority(ADC1_IRQn, 3U); // set priority level
	NVIC_EnableIRQ(ADC1_IRQn); //Attach the ADC interrupt 

	ADC1->CR |= ADC_CR_ADEN; // Enable ADC1

	while(!ADC1->ISR & ADC_ISR_ADRD); // wait for ADRDY
	ADC1->CR |= ADC_CR_ADSTART; // Start ADC1 Software Conversion 

	DAC->CR |= DAC_CR_EN1; //Enable the DAC
}

Does printf stop functioning, or does everything stop functioning? If you flash an LED, does that still flash?

posted by Erik - 06 May 2017

Hi Erik, I am toggling an IO pin in the main loop in addition to serial printing. The main loop does not seem to be executing when the ADC interrupt is enabled. Could the interrupt be getting called so fast that nothing else is ever called? Here are the NVIC registers as shown by Keil's debugger when ADC_IRQ1 is enabled:

ADC_IRQ1 enabled

NVIC_ISPR0 is 0x10000000
NVIC_ICPR0 is 0x10000000
NVIC_ISER0 is 0x10040000
NVIC_ICER0 is 0x10040000
NVIC_IABR0 is 0x00040000

With ADC1_IRQ un-enabled, the main loop executes (toggling the IO pin and serial printing). The NVIC registers look like this:

ADC_IRQ1 disabled

NVIC_ISPR0 is 0x10040000
NVIC_ICPR0 is 0x10040000
NVIC_ISER0 is 0x10000000
NVIC_ICER0 is 0x10000000
NVIC_IABR0 is 0

`stm32f302x8.h` defines: ADC1_IRQn = 18

posted by N K 06 May 2017

1 Answer

7 years, 6 months ago.

So the interrupts of the ADC are blocking your system. Where is your interrupt handler defined? I see you are not using NVIC_SetIRQ, so I assume you use the standard naming. Which in general I would not advice you to do (except on NRF51822, since they are only mbed target I am aware of which does not support interrupt remapping). And what does your interrupt handler do?

Option 1: It does not actually clear the interrupt, so it keeps being called.

Option 2: It takes too long, so it is continiously handling interrupts

Option 3: It is invalid and the system ends with a hardfault :).

Erik, thanks for your response. I've been coming to the mbed forums for a couple of years now, and I appreciate your dedication and willingness to approach even the most peculiarly worded problems.

I ended up sidestepping this by using DMA to pipe the continuous ADC conversions into memory.

DMA configuration

void dma_config(){
	DMA1_Channel1->CCR = 0U; //disable the DMA
	DMA1_Channel1->CCR |= DMA_CCR_CIRC;
	DMA1_Channel1->CCR &= ~DMA_CCR_DIR; // sets DMA direction to:(peripheral --> memory). techinically this is unnecessary because the CCR is initialized to 0.  
	DMA1_Channel1->CCR |= DMA_CCR_MSIZE_1; // the memory size is a 32 bit integer
	DMA1_Channel1->CCR |= DMA_CCR_PSIZE_0; // the peripheral size is a 16 bit integer
	DMA1_Channel1->CCR |= DMA_CCR_MINC;  // Memory Increment mode enabled
	DMA1_Channel1->CCR &= ~DMA_CCR_PINC; // Peripheral Increment mode disabled: the ADC1->DR is always in the same place

	DMA1_Channel1->CPAR = (uint32_t) &ADC1->DR; //point the DMA at the address of the ADC1->DR
	//The ADC1->DR is a 32 bit register, but only the least significant 16 bits are used.  The rest are 'reserved'
	DMA1_Channel1->CMAR = (uint32_t) a_in; // point the DMA at the zeroth address of the array of int
	
	DMA1_Channel1->CNDTR = NUM_ANALOG_CHANNELS; // tell the DMA how many data to transfer
	DMA1_Channel1->CCR |= DMA_CCR_EN;  //Enable the DMA
}

main loop

int main(){
	clock_config();
	adc_config();
	dac_config();
	dma_config();
	adc_start();
	
	while(1){
		DAC->DHR12R1 = a_in[3];
	}
}
posted by N K 06 May 2017

No problem, and unless you really need the data to be used directly, I think DMA indeed fits better with ADCs.

posted by Erik - 07 May 2017