ADC Result Ready Trigger ISR (SAMD21/ MKR1000)

Hi everyone,

I'm working on a data acquisition system that samples at a relatively low frequency (500Hz). I would like to use the ADC's conversion complete/ result ready flag to trigger an interrupt service routine which processes the data (an adaptive FIR filter).

I haven't done too much programming in the Arduino environment so I'm a little confused about using the Atmel Sotware Framework (ASF) code with "normal" Arduino C/C++ code. It doesn't seem like there is a native Arduino function for this.

I've been looking at the SAM ADC Driver. I suppose the Arduino compiler knows all the register names/locations of the SAMD21 uC? For example, one enumeration is

ADC_INTERRUPT_RESULT_READY = ADC_INTFLAG_RESRDY

Does Arduino knows how to handle that natively? Or do I need to include some other file?

To configure the ADC, the reference guide says to first pass the configuration structure &config_adc to the function adc_get_config_defauts

adc_get_config_defaults(&config_adc)

But I don't see any further definition of the above function. Will Arduino know what this means? Does it know what the defaults are for the SAMD21 device?

Sorry for the chaotic post. I'm just trying to figure out the relationship between the lower level functions defined by Atmel and Arduino.

Any advice is appreciated. Thank you!

The code below uses the ADC conversion complete flag to run an interrupt routine. I took some code
out because I was using my own routines for serial and setting up the serial port etc...
I use the _BV macro but you can use the Arduino routines to set up the ports and set bits and such.

 void setup() {
  //DDRB|= _BV(PINB5);  //sets up pin 5 portb (led) as an output
     TCCR1B=_BV(CS12)|_BV(CS10);//dived by 1024 timer counter 1
  
  sendstring("serial port working \n\r");	//verify serial port
  ADCSRA = (_BV(ADATE) |_BV(ADIE)|_BV(ADPS1)| _BV(ADPS0)); //divide by 8, ADC prescale bits, divides system clock to get adc clock
  ADCSRB|=_BV(ADTS2)|_BV(ADTS1);     //auto trigger on TC1 overflow
  ADMUX |= _BV(REFS0)| _BV(MUX1);			//select AVcc & channel 0 (PA1) Optical light sensor on ACE Shield
  ADCSRA |= _BV(ADEN);					   	//enable ADC
  sei();
}

void loop() {
    
  	//ADCSRA |= _BV(ADSC);	//start conversion : will not need this if autotriggering is used	
	//PINB =_BV(PINB5);   //this should toggle led, write to PINX register to toggle pin 
	//while(!(ADCSRA & _BV(ADIF)));					//use this for polling ADC complete intrupt flag 
	if (ADC_complete){
		    
		if (ADMUX & _BV(MUX0)){   //if chan 0 is selected
				ADMUX |=_BV(MUX1);    //turn on chan 1
				ADMUX &=~_BV(MUX0);   //turn off chan 0
			}
			else{
				ADMUX |=_BV(MUX0) ;  //
				ADMUX &= ~_BV(MUX1);
			}
			
		    //stop timer
			ADC_low = ADCL;                                     //read low order byte first, it's a must
			ADC_high = ADCH;	                               //read high order byte
			ADC_datareg=ADC_high<<8|ADC_low;		//save conversion result
			//ADC_datareg=ADCH<<8|ADCL;			//saves 2 instructions
			sprintf(str,"%i counts\r\n", ADC_datareg);	//display conversion result & channel
			sendstring(str);
			_delay_ms(1000);	   			
			ADC_complete=LOW;
			TIFR1|=_BV(TOV1);                             //clear tc1 ovf flag for next measurment
			
	}
	
}
/***************************************************************
     When ADC if finished converting this interrupt routine is run
********************************************************************/
ISR (ADC_vect){
	ADC_complete=HIGH;
	//ADCSRA |= _BV(ADIF);	//reset int. flag note: auto cleared by interrupt routine
}

Thanks! I'm a little bit confused with the _BV macro (byte value?) but nevertheless I think I basically get it.

My question about your code is: Is this a true hardware triggered interrupt? It seems that the software is continuously polling the conversion complete register and when it is complete, the ADC register is read.

Correct me if I am wrong please. I think the SAMD21 has an event called result ready/conversion complete that can trigger an interrupt without the use of polling. Any idea?

Thank you again.

The while loop would do the polling but that is commented out ... \

If you look at the interrupt routine it just sets a ADC_complete high so that the IF statement is exectuted which reads the ADC results register. You could read the ADC results register in the interrupt routine also for a more quicker response.

Thank you.

I found some great code courtesy of ForceTronics: ForceTronics: Utilizing Advanced ADC Capabilities on Arduino’s with the SAMD21 (Zero, MKR1000, etc) Part 1

I've went through all of the setup code and adapted it to my application but I'm trying to figure out how the NVIC knows what routine to jump to on an result_ready interrupt. I see how the interrupt is generated and the code he wants to run on an interrupt but I do not see how they are linked.

This is the ISR handler from ForceTronics.

void ADC_Handler() {
    digitalWrite(LED_BUILTIN, HIGH); //turn LED off
    ADC->INTFLAG.reg = ADC_INTFLAG_WINMON; //Need to reset interrupt
}

The result_ready interrupt is enabled in this function (which is part of a higher level setup function)

void setUpInterrupt(byte priority) {
  
  ADC->INTENSET.reg |= ADC_INTENSET_RESRDY; // enable Result Ready ADC interrupts
   while (ADC->STATUS.bit.SYNCBUSY);

   NVIC_EnableIRQ(ADC_IRQn); // enable ADC interrupts
   NVIC_SetPriority(ADC_IRQn, priority); //set priority of the interrupt
}

The ISR handler is beneath the main void loop(). Does the compiler interpret this as "go to the routine next in line after the current one"? If this is true, if you had multiple ISRs, how would the NVIC know which one to jump to?

Thank you again.

There should be an *.h file with the interrupt names and associated number. You have to use the name exactly in your code as it is in the *.h file. Thats how the tools know what routine to look for when an interrupt occurs.

I use stm32 and i have a *.h and a *.s (assembly file) I have to include in the project.
Heres part of the file

/****** STM32F051 specific Interrupt Numbers ************************************/
WWDG_IRQn = 0, /
!< Window WatchDog Interrupt /
PVD_IRQn = 1, /
!< PVD through EXTI Line detect Interrupt /
RTC_IRQn = 2, /
!< RTC through EXTI Line Interrupt /
FLASH_IRQn = 3, /
!< FLASH Interrupt /
RCC_IRQn = 4, /
!< RCC Interrupt /
EXTI0_1_IRQn = 5, /
!< EXTI Line 0 and 1 Interrupts /
EXTI2_3_IRQn = 6, /
!< EXTI Line 2 and 3 Interrupts /
EXTI4_15_IRQn = 7, /
!< EXTI Line 4 to 15 Interrupts /
TS_IRQn = 8, /
!< Touch sense controller Interrupt /
DMA1_Channel1_IRQn = 9, /
!< DMA1 Channel 1 Interrupt /
DMA1_Channel2_3_IRQn = 10, /
!< DMA1 Channel 2 and Channel 3 Interrupts /
DMA1_Channel4_5_IRQn = 11, /
!< DMA1 Channel 4 and Channel 5 Interrupts /
ADC1_COMP_IRQn = 12, /
!< ADC1, COMP1 and COMP2 Interrupts /
TIM1_BRK_UP_TRG_COM_IRQn = 13, /
!< TIM1 Break, Update, Trigger and Commutation Interrupts /
TIM1_CC_IRQn = 14, /
!< TIM1 Capture Compare Interrupt

and the *.s

.CPU M0
.area vectors(rom,rel)
__vectors::
; you must use the .paddr directive so the correct form of the
; function address (i.e. with the low bit ON) is used
.paddr _Default_Handler ; NMI_Handler
.paddr _Default_Handler ; HardFault_Handler
.paddr _Default_Handler ; MemManage_Handler
.paddr _Default_Handler ; BusFault_Handler
.paddr _Default_Handler ; UsageFault_Handler
.long 0 ; ARM RESERVED
.long 0
.long 0
.long 0
.paddr _Default_Handler ; SVC_Handler
.paddr _Default_Handler ; DebugMon_Handler
.long 0 ;
.paddr _Default_Handler ; PendSV_Handler
.paddr _SysTick_Handler ; SysTick_Handler
; IRQ0 to IRQ31
.paddr _Default_Handler ; 0
.paddr _Default_Handler ; 1
.paddr _Default_Handler ; 2
.paddr _Default_Handler ; 3
.paddr _Default_Handler ; 4
.paddr _Default_Handler ; 5
.paddr _Default_Handler ; 6
.paddr _EXTI4_15_IRQHandler ; 7
.paddr _Default_Handler ; 8
.paddr _Default_Handler ; 9
.paddr _Default_Handler ; 10
.paddr _Default_Handler ; 11
.paddr _Default_Handler ; 12
.paddr _Default_Handler ; 13
.paddr _Default_Handler ; 14

in the above file i just put in the names my program will actually use. Any other interrupt will go to
a default handler.

so there are probably just a file or two you may have to bring into your project and modify to include your
interrupt routine.

search around your directories and look through the files. It wil help you know what files are use for what.
it may take a bit but i always snoop around to learn stuff.

I don't use the sam I use STM32 which is a different company so I don't know how they deal with the interrupt stuff.