ADC triggered by Timer0 atmega328

Hi, I'm new on Arduino and the forum, but not on programming microcontrollers
I've an Arduino mini and I want to trigger the adc and read the value when the Timer0 reaches the compare value.
I've found one way which is:

  • interrupt generated by Timer0
  • inside the ISR of timer 0 read the value of the adc and do my operations
    the thing I don't like is that I need to wait (stop the processor) until the ADC finishes the conversion (with something like a while(true) )

but I think there is another way which is:

  • trigger from timer0 (set by default on the register of the adc)
  • interrupt generated by the adc when finishes the conversion
  • read the value inside the ISR of the adc

first of all, is it possible?
last but not least, how do I write code inside the post?

See the datasheet - you'll have to do this manually (ie, writing your own ISR), but it doesn't look hard.

You can make it auto-trigger off of Timer0 or Timer1 overflow or compare matches - I recommend Timer1 so you don't have to break millis() and delay(). This also doesn't require touching the ISR for the timers.

You write the ADC ISR (I forget what the vector is named) and read the ADC values in there. You would set up the auto trigger source to be the overflow/compare match event you want in ADCSRB, and set the ADIE bit in ADCSRA to enable the ADC interrupt, which fires on completed conversion.

This what I use in a project for setting up the ADC in timer triggered mode for a few AVR types, but using timer1. Maybe you can adapt it for your purpose.

. . . 

ISR(ADC_vect) {

    // handle register ADC here

}

. . .

// initialisation

	cli() ;
	// Initialise Timer1
	TCCR1A = 0;
	TCCR1B = _BV(CS10) |     // Bit 2:0 – CS12:0: Clock Select =  no prescaler
			_BV(WGM13) |    // WGM 12 = CTC ICR1 Immediate MAX
			_BV(WGM12);     // WGM 12 ditto

	ICR1 = (((F_CPU + FREQUENCY_CORRECTION)) / 19200) - 1;    // was 9600


#if defined(_xATTINY84)
	{
		ADMUX = 0x00 ;     // input ADC0 (PA0) Vref=Vcc
		DIDR0 |= _BV(0);             // DIDR0  Digital Input Disable Register 0

		ADCSRB =  _BV(ADTS2) |     // Bit 2:0 – ADTS[2:0]: ADC Auto Trigger Source
				_BV(ADTS1) |     //   Timer/Counter1 Capture Event
				_BV(ADTS0) |     //
        _BV(ADLAR) ;     //   Left adjust
	}
 #elif defined(_xATTINY841)
 {
    
    ADMUXA = 0x00 ;     // input ADC0 (PA0)
    ADMUXB = 0x00 ;     // Vref = Vcc, gain = 1
    DIDR0 |= _BV(0);             // DIDR0  Digital Input Disable Register 0

    ADCSRB =  _BV(ADTS2) |     // Bit 2:0 – ADTS[2:0]: ADC Auto Trigger Source
        _BV(ADTS1) |     //   Timer/Counter1 Capture Event
        _BV(ADTS0) |     //
        _BV(ADLAR) ;     //   Left adjust
  }
#elif defined(_xATMEGA328)
	{
		// Analog Port PC0 (pin A0 )
		// V0_02 ADLAR and prepare also for reading A1
		ADMUX = _BV(REFS0) | _BV(ADLAR) ;     // Fixed AVcc reference voltage for ATMega328P !!
		DIDR0 |= _BV(ADC0D);             // DIDR0  Digital Input Disable Register 0
		DIDR0 |= _BV(ADC1D);             // DIDR0  Digital Input Disable Register 1
		ADCSRB =  _BV(ADTS2) |     // Bit 2:0  ADTS[2:0]: ADC Auto Trigger Source
				_BV(ADTS1) |     //   Timer/Counter1 Capture Event
				_BV(ADTS0);      //
	}

#else
#   error unknown processor type
#endif

	ADCSRA =    _BV(ADEN) |      // Bit 7   ADEN: ADC Enable
			_BV(ADSC) |      // Bit 6   ADSC: ADC Start Conversion
			_BV(ADATE) |     // Bit 5   ADATE: ADC Auto Trigger Enable
			_BV(ADIE) |      //
			_BV(ADPS1);      // Bits 2:0  ADPS[2:0]: ADC Prescaler Select Bits  (div 4 )  // V0_02 div2 NOK, div4 OK, div16 OK


	sei() ;

antoniominighin:
last but not least, how do I write code inside the post?

Create your program (sketch) in the Arduino IDE, and then carry out the following steps:
(1) Select the entire codes of the IDE by pressing Cntrl+A.
(2) Copy the selected codes of Step-1.
(3) Click on the symbol </> (called code tags) of the Toolbar of your posting window. Press Cntrl+V.

void setup() 
{
  // put your setup code here, to run once:

}

void loop() 
{
  // put your main code here, to run repeatedly:

}

DrAzzy:
See the datasheet - you'll have to do this manually (ie, writing your own ISR), but it doesn't look hard.

You can make it auto-trigger off of Timer0 or Timer1 overflow or compare matches - I recommend Timer1 so you don't have to break millis() and delay(). This also doesn't require touching the ISR for the timers.

You write the ADC ISR (I forget what the vector is named) and read the ADC values in there. You would set up the auto trigger source to be the overflow/compare match event you want in ADCSRB, and set the ADIE bit in ADCSRA to enable the ADC interrupt, which fires on completed conversion.

void adc_setup()
{
  cli();
  ADMUX |= (1 << REFS0);    // use AVcc as the reference
  ADMUX |= (1 << ADLAR);    // left aligned, better for 8 bit resolution
    
  ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);    // 128 prescale for 16Mhz
  ADCSRA |= (1 << ADATE);    // Set this bit to enable trigger source
  ADCSRB |= (1 << ADTS0) | (1 << ADTS1);   // Triggered by Timer0 compare match A
  PRR |= (0 << PRADC);      // Power Reduction Register's PRADC bit must be disabled before starting the conversion
  ADCSRA |= (1 << ADEN);    // Enable the ADC
  ADCSRA |= (1 << ADIE);    // Enable Interrupts 
  sei();
}

void timer0_setup()
{
  TCCR0A |= (1 << WGM01);   // Set the Timer Mode to CTC
  OCR0A = 0xF9;             // Set the value that you want to count to

  TIMSK0 |= (1 << OCIE0A);  // Set the ISR COMPA_vect. "NOTE!"
  sei();                    // Enable interrupts

  TCCR0B |= (1 << CS01);    // Set prescaler to 8 and start the timer
}

Fisrt:
(inside the ISR of the ADC_vect) do i have to clear both the interrupt flags OCF0A of the TIFR and the ADIF of the adc?
Second:
I've marked a line in the timer_setup() with "NOTE!" because I'm a bit confuse on the use of the 2 register TIFR and TIMSK
You said that I don't need to enable the interrupt of the timer, but does the adc trigger recive the OCF0A edge also if the OCEI0A of TIMSK is not 1?
does this mean that the TIMSK only enable the interrupt for the processor?

hope are not silly doubts and thanks to everyone for the time spent helping me

You have to clear only the timer interrupt flag in ISR(ADC_vect). Something like this from an old example of mine:

ISR(ADC_vect) {

 . . . 
// handle register ADCH here (you are left adjusting the result)
 . . .

#if defined(_xATTINY85)
  {
    TIFR = _BV(OCF0B) ;  // reset interrupt flag
  }
#elif defined(_xATMEGA328)  || defined(_xATTINY84)
  {
    TIFR1 = _BV(ICF1);
  }
#else
#   error unknown processor type
#endif

}

I struggled for some time using timer0 for this (actually with ATTiny85 where I was forced to use timer 0 for auto triggering the ADC) before moving millis() etc. to timer 1 and echo what DrAzzy said about not recommending it. Although you are using an ATMega328p you'll face similar issues.
This may also help if you continue to use timer 0: Reseting ATTiny85 timer so millis and delay will work properly - Programming Questions - Arduino Forum

Other than that, just try it and see what happens.

6v6gt:
You have to clear only the timer interrupt flag in ISR(ADC_vect).

Other than that, just try it and see what happens.

Okay I'll set up timer1
but do you know if I have to set TIMSK1 |= (1 << OCIE1A) to enable the trigger for the ADC??

As I read the datasheet, no, you don't need to enable the interrupt, unless you're doing something in an ISR tied to it. And you're not.

DrAzzy:
As I read the datasheet, no, you don't need to enable the interrupt, unless you're doing something in an ISR tied to it. And you're not.

okay I've tried different time to trigger ADC from Timer/Counter1.
I failed.
I didn't know if the issue was DUE to the TIMER or the ADC.
So I've tried to do some test.
The first is on Timer1 and is a simple blink led test triggered by CTC mode.
I've found something strange on TCCR1B
TCCR1B |= (1 << CS11)|(1 << CS10); this works
TCCR1B |= (1 << CS12); this doesn't!!

Can some one tries with different tests?

boolean on = 1;
int waitstates = 20;
int i = 0;

void setup() {
  // put your setup code here, to run once:
  timer1_setup();
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
}

ISR(TIMER1_COMPA_vect)
{
  TIFR1  &= ~(1 << OCF1A);
  if(i == waitstates)
  {
    i = 0;
    digitalWrite(LED_BUILTIN, on);
    on = !on;
  }
  else i++;
}

void timer1_setup()
{
    cli();                    // Disable interrups
    OCR1AH = 0xFF;           // Set the MSBvalue that you want to count to
    OCR1AL = 0xFF;           // Set the LSBvalue that you want to count to
    TCCR1B |= (1 << WGM12);   // Set the Timer Mode to CTC on OCR2A
      
    TIMSK1 |= (1 << OCIE1A);   // Set the ISR TIMER1_COMPA_vect
    sei();                    // Enable interrupts
  
    TCCR1B |= (1 << CS12);    // Set prescaler to 1024 and start the timer
}

the comments may be not accurate like the last line

by the way at Pag. 136-137 of the atmega328 should be clear what this
TCCR1B |= (1 << CS11)|(1 << CS10);
and this
TCCR1B |= (1 << CS12);
mean

I continue by my self to take update this topic, yeah!

I've found in the datasheet that Timer0 and Timer1 SHARE the same PRESCALER
maybe its a problem.. but I don't know if this could generate an error in my code.

I've tried to blink one time the led inside the ISR of TIMER1_COMPA_vect.
it seems that with cs12 bit on it does't reach the interrupt

You seem to be having some basic difficulty even with the timer configuration.

At what frequency do you want to trigger the ADC (ie samples per second) a and which channel do you want to read ? Is 8 bit resolution enough or do you need the full 10 bit ? Maybe also say what the application is.

I could quickly put something together which you can test in isolation but the code I supplied in #2 was more or less complete apart from the content of the ISR which also needs the reset of TIFR1 when you have processed the sample.

In your last example, you should declare the variables which are set in the ISR as volatile otherwise the compiler may optimise them out of existence. Also use unsigned long instead of int for 'i' if the timer is running at any speed.

For testing, it is probably better to increment counters in the ISR and occasionally print out their values in the loop() rather than using a LED.

6v6gt:
You seem to be having some basic difficulty even with the timer configuration.

At what frequency do you want to trigger the ADC (ie samples per second) a and which channel do you want to read ? Is 8 bit resolution enough or do you need the full 10 bit ? Maybe also say what the application is.

I could quickly put something together which you can test in isolation but the code I supplied in #2 was more or less complete apart from the content of the ISR which also needs the reset of TIFR1 when you have processed the sample.

In your last example, you should declare the variables which are set in the ISR as volatile otherwise the compiler may optimise them out of existence. Also use unsigned long instead of int for 'i' if the timer is running at any speed.

For testing, it is probably better to increment counters in the ISR and occasionally print out their values in the loop() rather than using a LED.

I've found that Arduino initialize the timer1 in PWM mode setting WGM1n so I need to reset TCCR1B before setting my WGM mode.
before I try and report some test on the adc can u explain me something more on when to use volatile variabile?

Does this help?

/*
 *   ADC auto trigger on Timer1 CTC compare match B
 *   Tested on atmega328p.
 */

volatile bool adcFlag;
volatile uint16_t ADCresult;

uint8_t blinker = 13;
 
uint8_t adcChan = 0;

uint16_t ticks  = 31250; // 2s   

void initialize_ADC( uint8_t adcChan )
{
  
  cli(); 
  // enable adc, auto trigger, interrupt enable, prescale=128
  ADCSRA = (( 1<<ADEN ) | ( 1<<ADATE ) | ( 1<<ADIE ) | ( 1<<ADPS2 ) | ( 1<<ADPS1 ) | ( 1<<ADPS0 )); 
  // Timer/Counter 1 Compare Match B 
  ADCSRB = (( 1<<ADTS2 ) | ( 1<<ADTS0 ));
  // ref=AVcc + adc chan   
  ADMUX  = (( 1<<REFS0 ) + adcChan );
  sei();
  
} // initialize_ADC


void TimerOneInit ( uint16_t ticks )
{
  
  TCCR1A  = 0;
  TCCR1B  = 0;
  TCNT1   = 0;
  TIMSK1  = 0;
  
  TCCR1B  = ( 1 << WGM12 ) ;  // Configure for CTC mode 4 OCR1A=TOP
  
  OCR1B   = ticks;            // compare value
  OCR1A   = ticks;            // Set CTC TOP value, must be >= OCR1B
  
  // start timer, give it a clock
  TCCR1B |= (( 1 << CS10 ) | ( 1 << CS12 )) ;  // Fcpu/1024, 64us tick @ 16 MHz
  
} // TimerOneInit

ISR( ADC_vect ) // ADC conversion complete 
{
  
  ADCresult = ADC;          // Read the ADC
  adcFlag   = true;         // set flag  
  TIFR1     = ( 1<<OCF1B ); // clear Compare Match B Flag
  
} // ISR

void setup()
{
  
  Serial.begin( 9600 );
  Serial.println( __FILE__ );
  Serial.println( "ADC auto trigger on Timer1 CTC compare match B." );
  delay( 1000 );
  pinMode( blinker,OUTPUT );
  TimerOneInit ( ticks ) ;
  initialize_ADC( adcChan );
  
} //setup

void loop() 
{

  if( adcFlag )
  {
    
    cli();                   // disable interrupts and read shared variable
    uint16_t adcPrint = ADCresult;
    sei();
    Serial.print( "ADC = " );
    Serial.println( adcPrint );
    Serial.println();
    adcFlag = false;        // clear flag
    PINB    = ( 1 << PB5 ); // Toggle the LED 
      
  }
   
} // loop

tf68:
Does this help?

/*

*  ADC auto trigger on Timer1 CTC compare match B
*  Tested on atmega328p.
*/

volatile bool adcFlag;
volatile uint16_t ADCresult;

uint8_t blinker = 13;

uint8_t adcChan = 0;

uint16_t ticks  = 31250; // 2s

void initialize_ADC( uint8_t adcChan )
{
 
  cli();
  // enable adc, auto trigger, interrupt enable, prescale=128
  ADCSRA = (( 1<<ADEN ) | ( 1<<ADATE ) | ( 1<<ADIE ) | ( 1<<ADPS2 ) | ( 1<<ADPS1 ) | ( 1<<ADPS0 ));
  // Timer/Counter 1 Compare Match B
  ADCSRB = (( 1<<ADTS2 ) | ( 1<<ADTS0 ));
  // ref=AVcc + adc chan 
  ADMUX  = (( 1<<REFS0 ) + adcChan );
  sei();
 
} // initialize_ADC

void TimerOneInit ( uint16_t ticks )
{
 
  TCCR1A  = 0;
  TCCR1B  = 0;
  TCNT1  = 0;
  TIMSK1  = 0;
 
  TCCR1B  = ( 1 << WGM12 ) ;  // Configure for CTC mode 4 OCR1A=TOP
 
  OCR1B  = ticks;            // compare value
  OCR1A  = ticks;            // Set CTC TOP value, must be >= OCR1B
 
  // start timer, give it a clock
  TCCR1B |= (( 1 << CS10 ) | ( 1 << CS12 )) ;  // Fcpu/1024, 64us tick @ 16 MHz
 
} // TimerOneInit

ISR( ADC_vect ) // ADC conversion complete
{
 
  ADCresult = ADC;          // Read the ADC
  adcFlag  = true;        // set flag 
  TIFR1    = ( 1<<OCF1B ); // clear Compare Match B Flag
 
} // ISR

Hi thanks for your help.

  1. in the ISR why do u set TIFR1 OCF1B bit to 1 instead of clear it?
  2. Thinking about what I really need to do, it's impossible to trigger the adc by the timer at high speed, because the adc must be clocked at 125 or 250 khz, it takes at least 13 clock cycles so the timer can run at a maximum frequency of 10 or 20khz.
    maybe 6v6gt was right, I should have thought about the frequency of the timer before
  1. See datasheet, that's how you clear it.

I didn't realize you had to do that if using it as a trigger source, though...

Try this:

ISR(TIMER1_COMPB_vect)
{ 
static int16_t counter = 0;
static int32_t accumul = 0;

    int32_t temp  = ADCL;  
            temp += (ADCH << 8);  
            temp -= adc_Offst;

  temp = temp * temp;
  accumul += temp;

  if(++counter > 1000) { // 20 Hz, 20 frame/sec.
    ppm_Level = accumul;
    counter = 0;
    accumul = 0;
    flag    = 1;
    }
}

void adc_init()
{ 
  ADMUX    = 0xC5;        // PIN 5 Analog. REF Internal. Bias = 0.55V
  ADCSRA = ((1<< ADEN)|   // 1 = ADC Enable
      (0<< ADSC)|         // ADC Start Conversion 
      (1<<ADATE)|         // 1 = ADC Auto Trigger Enable
      (0<< ADIF)|         // ADC Interrupt Flag
      (0<< ADIE)|         // ADC Interrupt Enable
      (1<<ADPS2)|
      (0<<ADPS1)|         // ADC Prescaler : 1 MHz.
      (0<<ADPS0));  
  ADCSRB = ((1<<ADTS2)|   // Sets Auto Trigger source - Timer/Counter1 Compare Match B
      (0<<ADTS1)|
      (1<<ADTS0));
  /* Set up TIMER 1 - ADC sampler */
  TIMSK0 = 0x00;
  TIMSK1 = 0x00;
  TIMSK2 = 0x00; 

  TCCR1A = 0;
  TCCR1B = 0;
  TCCR1C = 0;

  TCCR1A =  ((1<<WGM11) | (1<<WGM10));       // Mode 15, Fast PWM
  TCCR1B =  ((1<<WGM13) | (1<<WGM12));       // Mode 15, Fast PWM

  TCCR1B |=  (1<<CS10);                      // clk/1 prescaling.
  OCR1A  = SMP_TMR1;
  OCR1B  = SMP_TMR1;

  TCNT1  = 0;
  TIFR1   |= (1<<OCF1B); 
  TIMSK1  |= (1<<OCIE1B);
}
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_TFTLCD.h> // Hardware-specific library

#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RESET A4

#define	BLACK   0x0000
//#define	BLUE    0x001F
#define	RED     0xF800
#define	GREEN   0x07E0
//#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF
#define LIGHTGREY   0xC618
#define ORANGE  0xFD20

Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);

#define  SMP_RATE                   20       // Sampling Rate, in kHz
#define  SMP_TMR1 ((16000/SMP_RATE) -1)      // Sampling Period of Timer1

             int16_t     adc_Offst =   512;
volatile     int32_t     ppm_Level =     0;
             float       rms_Level =   0.0;

volatile     int8_t      flag      =     0;

void setup(void) {
  Serial.begin(115200);
  
           tft.reset();
  uint16_t identifier = tft.readID();
           tft.begin(identifier);
           tft.setRotation( 1); 
           tft.fillScreen(BLACK);

  adc_init();
  analogMeter(); // Draw analogue meter
}

void loop()
{
  char    incomingByte;
  int32_t temp1, temp2;

  if( flag ) { 
    temp1 = ppm_Level;      
    temp1 = sqrt(temp1);    
    rms_Level = 20.0 * log10(temp1 +1); 

    rms_Level -= 48.175; 
    temp2 = rms_Level * 3.333; 
    refresh(temp2);   
    flag = 0;
    }

  if (Serial.available() > 0) {   
    incomingByte = Serial.read();
    // "x" command - DEBUG
    if( incomingByte == 'x' ) {
      Serial.println("\n\t");
      Serial.println(ppm_Level, DEC);
      Serial.println(rms_Level, 2);
      }
   }
}

You don't need TFT LCD to run a code, read data over serial.

A_meter_simpl_AC_3a.zip (1.78 MB)

AntonioMinighinEngin:
Hi thanks for your help.

  1. in the ISR why do u set TIFR1 OCF1B bit to 1 instead of clear it?
  2. Thinking about what I really need to do, it's impossible to trigger the adc by the timer at high speed, because the adc must be clocked at 125 or 250 khz, it takes at least 13 clock cycles so the timer can run at a maximum frequency of 10 or 20khz.
    maybe 6v6gt was right, I should have thought about the frequency of the timer before

For higher speeds than are possible with the AVR inbuilt ADCs, start looking at external SPI ADCs like MCP3002 (or better) which can handle sample rates of up to 200K SPS. I have used this is in a ESP8266 project where the inbuilt ADC is too weak for my purpose.