Go Down

Topic: ADC triggered by Timer0 atmega328 (Read 608 times) previous topic - next topic

antoniominighin

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?

DrAzzy

#1
Dec 12, 2018, 12:36 am Last Edit: Dec 12, 2018, 12:46 am by 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.
ATtiny core for 841+1634+828 and x313/x4/x5/x61/x7/x8 series Board Manager:
http://drazzy.com/package_drazzy.com_index.json
ATtiny breakouts (some assembled), mosfets and awesome prototyping board in my store http://tindie.com/stores/DrAzzy

6v6gt

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.
Code: [Select]


. . .

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() ;


GolamMostafa

#3
Dec 12, 2018, 03:48 pm Last Edit: Dec 12, 2018, 03:50 pm by GolamMostafa
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.
Code: [Select]
void setup()
{
  // put your setup code here, to run once:

}

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

}

antoniominighin

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.
Code: [Select]

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

6v6gt

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

Code: [Select]


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: http://forum.arduino.cc/index.php?topic=155418.0 

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

antoniominighin

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??

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.
ATtiny core for 841+1634+828 and x313/x4/x5/x61/x7/x8 series Board Manager:
http://drazzy.com/package_drazzy.com_index.json
ATtiny breakouts (some assembled), mosfets and awesome prototyping board in my store http://tindie.com/stores/DrAzzy

AntonioMinighinEngin

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?

Code: [Select]


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
}


AntonioMinighinEngin

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

AntonioMinighinEngin

#10
Dec 19, 2018, 03:06 pm Last Edit: Dec 19, 2018, 03:08 pm by AntonioMinighinEngin
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

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.

AntonioMinighinEngin

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?

tf68

Does this help?

Code: [Select]

/*
 *   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


AntonioMinighinEngin

Does this help?

Code: [Select]

/*
 *   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

Go Up