help with ADC auto triggering

I am programming a standalone ATMEGA328p. My goal is to display on PORTD and PORTC the realtime values of the ADCH and ADCL registers from sampling a voltage on input pin A0. I want to have the ADC triggered when Timer1 reaches the value set in OCR1B and then start over again using CTC mode.

I think I am not getting the auto trigger working right. Also, I’m not sure I need to set up the Timer1 interrupt or not. Here is my code. I basically gets a value at startup and never moves… not the behavior I desire.

Below is my code. Any help is appreciated

#include <avr/interrupt.h>

void setup() {
  // Data direction registers (0 = input)
  // Outputs =1, inputs =0
  DDRD = 0xFF;
  DDRC = 0x3E;

  PINC= 0xFF;
  PRR |= (1<<PRTWI)|(1<<PRTIM0)|(1<<PRTIM2)|(1<<PRSPI)|(1<<PRUSART0); //5.7mA with no Displays on
  TCCR1A = 0;     // set entire TCCR1A register to 0
  TCCR1B =0; 
  OCR1B =0;
  TCNT1 =0; // Wise to force the count in range
  ICR1 = 200; // output compare register used for timer1 sets freq
  OCR1B = 200; // controls when ADC sample begins
  // ctc, IR1 sets top
  TCCR1B |= (1 << WGM12)|(1<<WGM13)|(1<<ICNC1);
  TIMSK1 |= (1<<OCIE1B); 
  TCCR1B &= ~(1<<CS12)&~(1<<CS10);
  TCCR1B |= (1<<CS11);
  ADCSRA &= ~(1<<ADPS0);
  ADCSRA |= (1<<ADPS2)|(1<<ADPS1);
  //set reference voltage
  ADMUX &= ~(1 << REFS0) & ~(1 << REFS1);

  // Right adjust for reading 10 bit value
  ADMUX &= ~(1<<ADLAR);
  // 0000 selects ADC0 channel
  ADMUX &= ~(1<<MUX3) & ~(1<<MUX2) & ~(1<<MUX1) & ~(1<<MUX0);

  // Quiet ADC inputs
  // Check ATmega328p datasheet
  // for more information.
  DIDR0 |= (1<<ADC5D)|(1<<ADC4D)|(1<<ADC3D)|(1<<ADC2D)|(1<<ADC1D)|(1<<ADC0D);

  // auto trigger on compare match B.
  ADCSRB |= (1 << ADTS2)|(1 << ADTS0);
  ADCSRA |= (1 << ADEN)|(1<<ADATE)|(1<<ADSC);
}

ISR(TIMER1_COMPB_vect) {
}

void loop() {
  while (!(bit_is_set(ADCSRA, ADIF))){
    // Getting Data.  Sit here and wait
  }

  PORTD  = ADCL;
  PORTC = ADCH;
  ADCSRA |= (1<<ADIF);
}

i find the following changes work -

ADCSRB =0x07;
TIMSK1 = 0x20;

then use

ISR(TIMER1_CAPT_vect)

instead of the COMP vector

with auto-triggering you shouldn't really need to busy wait for the ADC to finish or manually restart it, which appears to be what you are doing in the ISR. you just need to set timer1 duration to guarantee the ADC will have finished sampling before the ISR is called. the data will then just be sitting there in ADCH and ADCL ready for you to grab.

hope this helps

Thank you for that reply. It isn't clear to me from the datasheet what a "timer1 capture event" actually is. Please explain

Here is my settings to autotrigger ADC via Timer1:

	/* Setup ADC */
        ADMUX    = 0x45;        // PIN 5 Analog. 

	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)|
		  (1<<ADPS1)|	// ADC Prescaler : ADS0=0 Selects 250 kHz. // ADS0=1 Selects 125 kHz.
		  (1<<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.
//        TCCR1B &= ~((1<<CS12) | (1<<CS10));        
        OCR1A  = smpl_Time;
        OCR1B  = smpl_Time;

        TCNT1  = 0;
        TIFR1   |= (1<<OCF1B); 
        TIMSK1  |= (1<<OCIE1B);

Full version of the sketch available for downloading here: http://coolarduino.wordpress.com/2013/01/04/power-quality-meter/
The trick is, you need to run both channels of the timer1, I explained it in another blog:

For some unknown for me reason, Atmel designed TIMER 1 channel B to be an auto trigger source of the ADC, the same time to run TIMER 1 itself in CTC mode, channel A must be set. I simply “bind” two channels A and B in “parallel”, so both of them rise interrupt flag at the same moment, only A re-starts a TIMER 1, and B generates “start new conversion” event and calling ISR for “maintenance” – take a new sample waiting in the line and switch a MUX to another channel of the oscilloscope.

http://coolarduino.wordpress.com/2012/09/19/quasi-real-time-oscilloscope-remix/
Second post devoted to AtMega32u4, but functionality ADC_Timer1 pretty much same as AtMega328
Other things, as pointed above, ADC should have enough time to complete conversion, if you need sampling rate >9.6 kHz, you have to change ADC prescaller to run it faster

memotick:
Thank you for that reply. It isn’t clear to me from the datasheet what a “timer1 capture event” actually is. Please explain

Um… i can’t… really. The code was “borrowed”. In search of a deeper understanding I also checked the data sheets but very confusing. I contacted the author with the same question. Other than “it causes the capture interrupt to be called when timer1 restarts”. As far as I remember, I think I avoided the COMP vector as I was using phase-correct mode and didn’t want the interrupt to happen until the timer counted down again. It’s all a bit of a mess really :wink: Maybe someone will correct me if I’ve got it wrong. If you’re using fast/ctc timer mode maybe best to stick with COMP vector as above.

Hope you manage to get it working - autotriggering ADC off timer = very powerful feature. thankyou mr arduino!

Thanks for that reply - I think I’ll stick with CTC mode as I am more familiar with it.

I believe the problem I am having is only one ADC conversion is taking place… as if the auto triggering isn’t actually happening, or possibly if I read the ADCH/ADCL registers, it doesn’t clear a flag.

void loop() {

  ADCSRA |= (1 << ADEN)|(1<<ADATE);
  ADCSRA |= (1<<ADIF);
  // wait until ADC is done obtaining a sample
  while(ADIF==0); // ADIF goes high when a conversion is done

  PORTC=ADCL;
PORTD=ADCH;
  // ADCSRA &= ~(1<<ADEN);// could turn off ADC to save power
  // clear ADC done flag
  ADCSRA |= (1<<ADIF);

  delay(1000);
 
}