Timer1 interrupt debuging

hi all.. i have problem with my code here. i try to set the interrupt to trigger every 200us, but when i do some checking it show 400us instead of 200us as the timer1 is setting.. can anyone help me what is wrong with my setting.

here my code:

volatile int val;
volatile int isNewVal;
volatile unsigned long time;
unsigned long timer;


void setup() {
  
Serial.begin(115200); //setup serial comm.
delay(500); 

noInterrupts(); //disable interrupt first
TCCR1A =0;    // clear timer setting
TCCR1B =0;    // ~~
TCNT1=0;       //~~
OCR1A= 400;         // 16M/8/5000 = 400. Target Freq is 5kHz
TCCR1B |= (1<<WGM12);     //enable ctc mode
TCCR1B |= (1<<CS11);      //setup prescaler by 8.
TIMSK1 |= (1<< OCIE1A);       //enable timer interrupts
interrupts();                  //enable interrupt
}

ISR(TIMER1_COMPA_vect){
 // val = analogRead(A0); 
 
  isNewVal=1; 
  }  
  
  
void loop() {
    if (isNewVal == 1){
  timer = micros()-time;
  time = micros();
  Serial.println(timer);
    isNewVal =0;
    
    
  }
}

here result that i get from serial monitor

Printing four characters (three digits plus newline) at 115200 baud will take about 347µs.

The timer setup looks OK to me, though. Technically OCR1A should be 399, as 0-399 is 400 counts.

If you want to check the frequency of rapidly-firing interrupts, you'll likely get better results by counting the number of interrupts that occur during a fixed interval, and calculating the rate. If you use delay() to time the interval, the results won't be precise, due to the lack of synchronization between the Timer1 interrupt and the delay() function, but they'll be close. With your setup(), I got about 250 interrupts per 50 milliseconds, or 1 every 200 microseconds, as expected.

It looks like the intent is to get an analog reading every 200 microseconds. If that's so, the code will work, but the readings will have some jitter due to interrupt latency. Also, analogRead() takes a little more than 100 microseconds at the default ADC clock speed - over half the time available between Timer1 interrupts. There won't be much time for other code to run while this interrupt is enabled.

You may find more success by setting the ADC to trigger on Timer1 Compare Match B, and let the ADC interrupt save the analog, bump the pointer and counter, check to see if its done and maybe turn itself off, and return. You'll need to set OCR1B to a value that Timer1 actually gets to - something between zero and OCR1A. My memory tells me that you'll have to enable the Compare Match B interrupt to get the ADC to trigger, and that you'll need a TIMER1_COMPB_vect ISR, even if it doesn't do anything, but that's just my memory - it may work without it. Unless you need it for something else, you can turn off the Compare Match A interrupt. Benefits: the program spends a lot less time stuck in ISRs, it eliminates the blocking call to analogRead(), and the ADC is triggered by a hardware clock, eliminating jitter.

@Jack Christensen thank for correcting it. So my setting for the timer1 1 is correct right except for OCR1A must be 399. if serial.print take long timer to executed what other method to see timing in my code?

@tmd3 thanks for idea. i want to is to take analog reading every 200us. How to set the adc trigger with timer interrupt, can give link for example, im not really understand how to set it.

Strictly for validating that things are working as planned, I'd save several values in an array, then print them out later, e.g.

volatile byte isNewVal;
volatile unsigned long time;
unsigned long timer[20];

void setup()
{
    Serial.begin(115200); //setup serial comm.
    delay(500); 

    noInterrupts();      //disable interrupt first
    TCCR1A = 0;          // clear timer setting
    TCCR1B = 0;          // ~~
    TCNT1 = 0;           //~~
    OCR1A = 399;         // 16M/8/5000 = 400. Target Freq is 5kHz
    TIMSK1 |= (1<< OCIE1A);        //enable timer interrupts
    interrupts();                  //enable interrupt
    TCCR1B = (1<<WGM12) | (1<<CS11);     //enable ctc mode, prescaler 8, start the timer
    
    for (byte i=0; i<20; i++) {
        isNewVal = 0;
        time = micros();
        while (isNewVal == 0);
        timer[i] = micros() - time;
    }
    
    for (byte i=0; i<20; i++) {
        Serial.println(timer[i]);
    }        
}

ISR(TIMER1_COMPA_vect)
{
    isNewVal=1; 
}  


void loop()
{
}

after i see my timer1 is correct setting, i try to implement with ADC to timer1. Here my code after i digging around for a few day. I try to set up timer1 to 200us work along with ADC. the adc will read analogvalue when timer1 is trigger. But sadly it wont work. What did i make wrong?

volatile int readValue;


void setup() {
	Serial.begin(115200);
		delay(100) ; //wait for serial com to ready
	
	//Int timer counter 5kHz
	TCCR1A = 0;	  //
	TCCR1B = 0;   // reset the timer control register
	TCNT1 = 0;	  // clear counter.
	OCR1A = 399;  //compare value for timer
	//(frequency speed at 5000Hz with 8 pre scale).
	OCR1B = 399; // compare timer to trigger ADC at 5kHz.
	TCCR1B |= (1<<CS11); //pre scale set to 8.
	TCCR1B |= (1<<WGM12); //turn on CTC mode.
	TIMSK1 |= (1<<OCIE1B);  //Timer/Counter Compare Match B interrupt is enabled
	
	//ADC setup
	// Since we are using ADC0 here, which corresponds
	// with all 5 MUX bits being zero, we don't need to set anything here. 
	
	ADMUX  |= (1<< REFS0) ; //ADC ref Volt to AVCC.
	ADCSRA |= (1<<ADPS2) | (1<<ADPS1)|(1<<ADPS0); //set pre scale to 128. Speed 125kHz
	ADCSRB |= (1<<ADTS2) | (1<<ADTS0); //select timer/counter1 compare match B
	ADCSRA |= (1<<ADEN)  //Enable ADC
			   |(1<<ADIE) //Enable interrupt
			   |(1<< ADATE); // enable auto trigger
			  	
	sei(); //enable global interrupt.
	
}

ISR(ADC_vect){
	
	 ADCSRA |= (1<<ADSC) ; //to start new conversion.
	 while(ADCSRA & (1<<ADSC)); //wait for conversion is done.
	 readValue =ADC; // read the full 16-bit ADC value.
	 
	
}
void loop() {
	Serial.println(readValue);
}

But sadly it wont work. What did i make wrong?

That's so sad.

The code does something, but you haven't described what, nor is it clear how that differs from what you want.

dut:
after i see my timer1 is correct setting, i try to implement with ADC to timer1. Here my code after i digging around for a few day. I try to set up timer1 to 200us work along with ADC. the adc will read analogvalue when timer1 is trigger. But sadly it wont work. What did i make wrong?

Where is the timer ISR? The ADC ISR should not wait for the conversion to complete (after all, that's the whole idea of the ISR, not having to wait, the ISR is invoked when conversion is complete). Indeed, in general, ISRs should not wait for anything as they need to execute quickly.

The timer ISR should start the conversion.

The ADC ISR should read the ADC result and set a flag so that the main loop knows it's complete.

The main loop should monitor the flag and process the ADC result when it's available.

Printing the ADC result at 5000 SPS is not a good idea as discussed earlier, and printing it as fast as possible is even less of a good idea, which is what loop() will do here.

Edit: Missed that Timer1 Compare Match B was used to trigger the ADC, struck out a couple comments above that don't apply with that situation.

Here's what I see:

  • Don't trigger the ADC in software. The ADC is set up for automatic hardware triggering. If your code triggers it again, the best that will happen is that the ADC will ignore it, which is what my tests reveal suggest that it does. At worst, the ADC will do exactly what you asked - abort the current conversion, and start over. The datasheet doesn't say what happens when you trigger the ADC during a conversion.
  • Don't wait for the conversion to be complete. When the ADC interrupts, it means that its last conversion is complete. If you wait for the flag to be set again, you'll skip a reading. Read the ADC immediately, do what you need to do with the data, and exit.
  • Don't print every reading. You're outrunning your serial port if you do that - you'll print roughly 200 kbps on a 115.2 kbps channel - something's sure to fail.

Finally, the reason that you're not getting anything at all: You've enabled the Timer1 compare Match B interrupt, but you don't define an ISR for that interrupt. When the COMPB interrupt happens, the processor has no well-defined place to go, so it's practically certain to go to the wrong place. Try adding this: ISR(TIMER1_COMPB_vect) {}I suggest testing things by setting OCR1A to something big - like maybe 65535, to pick something totally random. You'll still print a lot of numbers, maybe 30 lines per second, but the serial port should be up to it. To see things more clearly, you could pick a slower clock setting for Timer1.

To improve your code, you'll want to create a variable to store the value of readValue, maybe called readValueCopy. Inside loop, disable interrupts, store readValue in readValueCopy, and reenable interrupts. The purpose of disabling interrupts is to ensure that the value of readValue doesn't change while you're fetching it. The purpose of making a copy of readValue is to let you reenable interrupts quickly, and then go do something with the copy - like, maybe, printing it.

If you want to see what the analog data looks like at 5 kHz, establish an array, save the analog readings into the array, and let loop() do something with them afterward. Be sure to kep the ADC ISR short - Fetch the data, store it, bump the counter, check and see if you're done, and if so, turn off the ADC interrupt, and maybe turn off the autotrigger, too.

[Edit: spelling and punctuation]

I've been playing with this because I may be able to use it for a project I'm working on. My code captures ten samples, prints them, then repeats. There are two things that I don't understand: (1) I get inconsistent timings (shorter!) on the first two interrupts after a reset (but they are consistently the same from one reset to another), and (2) For subsequent sets of ten samples, the first in each is 112-116µs longer.

The datasheet says

A normal conversion takes 13 ADC clock cycles. The first conversion after the ADC is switched on (ADEN in ADCSRA
is set) takes 25 ADC clock cycles in order to initialize the analog circuitry.

so I would expect the very first conversion in the first set only to take longer. The difference of ~112µs is about 14 ADC clock cycles, so that seems fairly consistent with the datasheet.

Typical output from the sketch (first number is time between interrupts in µs, the second number is the analog reading, but the pin is just floating, so it means nothing):

204 446
912 447
1000 460
1000 478
1000 497
1000 513
1000 525
1000 533
1000 535
1000 533

1116 312
1000 325
1000 343
1000 362
1000 378
1000 391
1000 399
1000 401
1000 399
1000 388

1112 302
1000 321
1000 339
1000 355
1000 367
1000 374
1000 375
1000 372
1000 360
1000 343

There is a symbol ENABLE_ADC which can be used to remove all the ADC code from the compile. I used this to convince myself that the timer interrupt was functioning as expected. With ENABLE_ADC set to 0, below is typical output, so I conclude that the timer interrupt is fine. I attribute the extra 4µs in the first reading of each set of ten to set-up time for the do loop.

1004 0
1000 0
1000 0
1000 0
1000 0
1000 0
1000 0
1000 0
1000 0
1000 0

1004 0
1000 0
1000 0
1000 0
1000 0
1000 0
1000 0
1000 0
1000 0
1000 0

1004 0
1000 0
1000 0
1000 0
1000 0
1000 0
1000 0
1000 0
1000 0
1000 0

Finally, the sketch. I'm using an AFI Boarduino with Optiboot and a 16MHz ceramic resonator. Nothing is connected to the Boarduino except an FTDI interface board.

#define SAMPLE_SIZE 10
#define ENABLE_ADC 1           //0 == disable ADC functionality, and show timer interrupts only
                               //1 == enable ADC functionality

volatile boolean adcFlag;
volatile int adcVal;

void setup(void)
{
    delay(1000);

    //set up the timer
    TCCR1B = 0;                //stop the timer
    TCCR1A = 0;
    OCR1A = 1999;              //timer runs at 16MHz / 2000 / 8 (prescaler) = 1kHz
    OCR1B = 1999;
    
    //set up the adc
    #if ENABLE_ADC == 1
    ADMUX = _BV(REFS0);                                //use AVcc as reference
    ADCSRA = _BV(ADEN)  | _BV(ADATE) | _BV(ADIE);      //enable ADC, auto trigger, interrupt when conversion complete
    ADCSRA |= _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);    //ADC prescaler: divide by 128
    ADCSRB = _BV(ADTS2) | _BV(ADTS0);                  //trigger ADC on Timer/Counter1 Compare Match B
    #endif
}

void loop(void)
{
    unsigned int deltaT[SAMPLE_SIZE];    //times between interrupts
    unsigned int adcVals[SAMPLE_SIZE];   //values from the ADC
    unsigned long time;                  //interrupt time from micros()
    unsigned long lastTime;              //previous interrupt time
    byte i;                              //array index
    
    cli();
    TCNT1 = 0;                           //clear the timer
    TIMSK1 = _BV(OCIE1B);                //enable timer interrupts
    TIFR1 = _BV(OCF1B);                  //clear the interrupt flag bit (enabling timer interrupts sets it)
    sei();
    lastTime = micros();
    i = 0;
    TCCR1B = _BV(WGM12) | _BV(CS11);     //start the timer, ctc mode, prescaler 8
    
    do {
        while (!adcFlag);                //wait for ADC conversion to complete
        time = micros();                 //capture the time
        deltaT[i] = time - lastTime;     //calculate and save the difference from the last conversion
        lastTime = time;
        cli();
        adcFlag = false;                 //reset the ISR flag
        #if ENABLE_ADC == 1
        adcVals[i] = adcVal;             //save the analog reading
        #else
        adcVals[i] = 0;
        #endif
        sei();
    } while (++i < SAMPLE_SIZE);
    
    cli();
    TIMSK1 = 0;                          //disable timer interrupts
    TCCR1B = 0;                          //stop the timer
    sei();
    
    //print the data
    Serial.begin(115200);
    Serial.println();
    for (byte i=0; i<SAMPLE_SIZE; ++i) {
        Serial.print(deltaT[i]);
        Serial.print(' ');
        Serial.println(adcVals[i]);
    }
    Serial.flush();
    Serial.end();
    delay(5000);
}

#if ENABLE_ADC == 1
ISR(ADC_vect)
{
    adcFlag = true;
    adcVal = ADC;
}
#endif

ISR(TIMER1_COMPB_vect)
{
    #if ENABLE_ADC != 1
    adcFlag = true;
    #endif
}

Edit: Added a few comments to the code.

Sorry guy, im confusing with two ISR right now. If i want adc take a reading every 200us, so i must put code to take sample on ISR(TIMER1_COMPB_vect) or in ISR(ADC_vect). which one should work to take a reading. For timer and ADC initialize, did it correct?
what im try to do is like this.

my pseudo code

void setup(){
#do time int
#do adc int
}

ISR (){        //trigger when== 200us
#  take adcreading[i]; //save in array
  i++
  }

void loop(){
if (i =100){
for(j=0;j<100;j++){
#  print result of adcreading[];
}

Using the Christensen code, I added a statement to read TIFR1 as the first line in Setup, and print it while the program was waiting for something to happen. The value was 7, indicating that all three TIFR1 flags were active at startup. If I move the line that clears OCF1B ahead of the line that selects Timer1 Compare Match B as the ADC trigger, I get expected result - the first sample appears to trigger after 1000 microseconds, and it appears to take about twice as long as the canonical 108 microseconds. [Edit: corrected duration]

With the flag already set, I suspect that the processor throws a Timer1 COMPB interrupt as soon as the interrupt is enabled. The "204" that shows up represents the time it takes for the first conversion - about what we'd expect - but the ADC didn't have to wait for the compare match in order to trigger. It fired as soon as the interrupt was enabled.

[Edit: added text below]
Looking now at the "dut" post, here's my take:
The Timer1 COMPB interrupt has to be enabled to trigger the ADC, but that interrupt doesn't have any other purpose. Because it's enabled, and it occurs, the processor must have an ISR to execute. That ISR would typically look like this:ISR(TIMER1_COMPB_vect) {}It doesn't do anything; it just has to be in the code. When I try this without an ISR(TIMER1_COMPB_vect), I get nothing - the processor apparently either loses its program counter, or it executes some default code from which it never returns. I suspect that somebody on this forum knows what happens when an interrupt without an ISR occurs; I hope that they pipe up and tell us.

The proper place to fetch analog data is inside the ISR for the ADC. The ADC ISR is executed when ADC data is ready to be fetched.

Thanks @Jack Christensen will take Christensen code and an experiment on it.

but the ADC didn't have to wait for the compare match in order to trigger. It fired as soon as the interrupt was enabled.

Sorry for asking 1 more about adc. if adc fire as soon interrupt was enable, did the value i will get is in time interval 200us or it just the value at random time when it finish conversion?

The ADC triggered immediately on the first conversion only. The delay between the trigger and the ADC interrupt is not random, it's very well defined. You can see the conversion times under different conditions in Section 24.4 of the ATmega328 datasheet.

With the ADC prescaler set at 128, the first conversion will take 200 microseconds. Subsequent conversions, if you use TImer1 as a trigger source, will take 108 microseconds.

I believe that the ADC triggers as soon as the Timer1 COMPB interrupt is enabled only if the OCIE1B flag is set at the time that interrupts are enabled. Otherwise, the ADC doesn't trigger until the Timer1 COMPB interrupt occurs.

@tmd3, thanks a million, I was really beating my head into the wall! I owe you one! Works perfectly now. The business about the first in each group of ten being longer was due to faulty logic on my part. Took a slightly different approach and everything looks as it should:

adcStart_us, adcLapse_us, adcValue

1001444, 200, 450
1002440, 112, 443
1003440, 108, 434
1004440, 112, 427
1005440, 108, 420
1006440, 108, 416
1007440, 108, 414
1008440, 108, 413
1009440, 108, 417
1010440, 108, 424

6028080, 108, 315
6029080, 108, 322
6030080, 108, 329
6031080, 108, 335
6032080, 108, 339
6033080, 108, 340
6034080, 108, 340
6035080, 108, 336
6036080, 108, 329
6037080, 108, 321

11054720, 108, 307
11055720, 108, 300
11056720, 108, 293
11057720, 108, 289
11058720, 108, 288
11059720, 108, 288
11060720, 108, 293
11061720, 108, 300
11062720, 108, 307
11063720, 108, 314

16082248, 108, 324
16083248, 108, 327
16084248, 108, 328
16085248, 108, 326
16086248, 108, 321
16087248, 108, 314
16088248, 108, 306
16089248, 108, 299
16090248, 108, 292
16091248, 108, 288

21109776, 108, 288
21110776, 108, 290
21111776, 108, 295
21112776, 108, 302
21113776, 108, 310
21114776, 112, 317
21115776, 108, 322
21116776, 108, 326
21117776, 108, 327
21118776, 108, 327

Here's the final sketch:

#define SAMPLE_SIZE 10

volatile boolean adcFlag;
volatile boolean timerFlag;
volatile int adcVal;

void setup(void)
{
    delay(1000);
    Serial.begin(115200);
    Serial.println(F("adcStart_us, adcLapse_us, adcValue"));

    //set up the timer
    TCCR1B = 0;                //stop the timer
    TCCR1A = 0;
    TIFR1 = 0xFF;              //ensure all interrupt flags are cleared
    OCR1A = 1999;              //timer runs at 16MHz / 2000 / 8 (prescaler) = 1kHz
    OCR1B = 1999;
    
    //set up the adc
    ADMUX = _BV(REFS0);                                //use AVcc as reference
    ADCSRA  = _BV(ADEN)  | _BV(ADATE) | _BV(ADIE);     //enable ADC, auto trigger, interrupt when conversion complete
    ADCSRA |= _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);    //ADC prescaler: divide by 128
    ADCSRB = _BV(ADTS2) | _BV(ADTS0);                  //trigger ADC on Timer/Counter1 Compare Match B
}

void loop(void)
{
    unsigned long startTimes[SAMPLE_SIZE];    //adc start time (timer interrupt)
    unsigned long endTimes[SAMPLE_SIZE];      //adc complete time (adc interrupt)
    unsigned int adcVals[SAMPLE_SIZE];        //values from the ADC
    unsigned long tStart;                     //timer interrupt time from micros()
    unsigned long tEnd;                       //adc interrupt time from micros()
    byte i;                                   //array index
    
    cli();
    TCNT1 = 0;                           //clear the timer
    TIMSK1 = _BV(OCIE1B);                //enable timer interrupts
    sei();
    i = 0;
    TCCR1B = _BV(WGM12) | _BV(CS11);     //start the timer, ctc mode, prescaler 8
    
    do {
        while (!timerFlag);              //wait for timer interrupt which starts adc conversion
        tStart = micros();               //capture the time
        while (!adcFlag);                //wait for ADC conversion to complete
        tEnd = micros();                 //capture the time
        startTimes[i] = tStart;          //save the times
        endTimes[i] = tEnd;
        cli();
        adcFlag = false;                 //reset the ISR flags
        timerFlag = false;
        adcVals[i] = adcVal;             //save the analog reading
        sei();
    } while (++i < SAMPLE_SIZE);
    
    TIMSK1 = 0;                          //disable timer interrupts
    TCCR1B = 0;                          //stop the timer
    
    //print the data
    Serial.println();
    for (byte i=0; i<SAMPLE_SIZE; ++i) {
        Serial.print(startTimes[i]);
        Serial.print(", ");
        Serial.print(endTimes[i] - startTimes[i]);
        Serial.print(", ");
        Serial.println(adcVals[i]);
    }
    Serial.flush();
    delay(5000);
}

ISR(ADC_vect)
{
    adcFlag = true;
    adcVal = ADC;
}

ISR(TIMER1_COMPB_vect)
{
    timerFlag = true;
}

nice work :slight_smile: @Jack Christensen. i got some Q in your code, why are the timer interrupt enable is in loop function not in setup func? is there any effect if we just initialize all timer setting in void setup function.

void loop(void)
{
        
    cli();
    TCNT1 = 0;                           //clear the timer
    TIMSK1 = _BV(OCIE1B);                //enable timer interrupts
    sei();
    i = 0;
    TCCR1B = _BV(WGM12) | _BV(CS11);     //start the timer, ctc mode, prescaler 8

why must disable timer when to print the data. if timer stop, then the next 10 sample taken is continue from the stop point or it will reset to new point.

 } while (++i < SAMPLE_SIZE);
    
    TIMSK1 = 0;                          //disable timer interrupts
    TCCR1B = 0;                          //stop the timer
    
    //print the data

dut:
nice work :slight_smile: @Jack Christensen. i got some Q in your code, why are the timer interrupt enable is in loop function not in setup func? is there any effect if we just initialize all timer setting in void setup function.

why must disable timer when to print the data. if timer stop, then the next 10 sample taken is continue from the stop point or it will reset to new point.

It goes back to the problem of not being able to print the data as fast as the ADC can generate it. I decided to just stop the sampling and restart it, but it could just as well have been left running, and the main code could just collect samples in the array until it's full, then ignore the interrupts (and the associated data) while printing the array. But either way, some data will be missed at higher sampling rates.

As we saw earlier, the ADC can generate data quite rapidly, faster than it can be printed (or otherwise processed) so some planning and understanding of the timing is necessary, especially at higher sampling rates.

@dut, try this sketch. I moved all initialization of the timer and ADC to setup() and changed it back to your original 200µs sampling interval. Sampling is in sets of ten, using Serial.flush() to ensure all the output is sent before starting the next set. The ADC continues sampling while the printing happens but those samples are lost. One set of ten samples takes 2ms but note that it's about 20ms between printing each set, so it's sampling about ten times faster than it can print the data.

adcStart_us, adcLapse_us, adcValue

1002 ms
1000636, 204, 114
1001036, 108, 117
1001236, 108, 116
1001436, 108, 116
1001636, 108, 115
1001836, 108, 114
1002040, 104, 115
1002236, 108, 115
1002436, 112, 115
1002636, 108, 115

1021 ms
1020236, 108, 116
1020436, 108, 116
1020636, 108, 116
1020836, 108, 116
1021036, 108, 116
1021236, 108, 116
1021436, 108, 116
1021636, 108, 116
1021836, 108, 116
1022036, 108, 116

1040 ms
1039436, 108, 116
1039636, 108, 116
1039836, 108, 116
1040036, 108, 116
1040236, 108, 116
1040436, 108, 116
1040636, 108, 116
1040836, 108, 115
1041036, 108, 116
1041236, 108, 116

1059 ms
1058636, 108, 116
1058836, 108, 116
1059036, 108, 116
1059236, 108, 116
1059436, 108, 116
1059636, 108, 116
1059836, 108, 116
1060036, 108, 116
1060236, 108, 116
1060436, 108, 116

1079 ms
1077836, 108, 116
1078036, 108, 116
1078236, 108, 116
1078436, 108, 116
1078636, 108, 116
1078836, 108, 116
1079036, 108, 116
1079236, 108, 116
1079436, 108, 116
1079636, 108, 116
//Timer-driven ADC sampling.
//Jack Christensen 13Jun2013

#define SAMPLE_SIZE 10
#define PAUSE_BETWEEN_SAMPLES 0    //milliseconds
#define BAUD_RATE 115200

volatile boolean adcBusy;
volatile int adcVal;

void setup(void)
{
    delay(1000);
    Serial.begin(BAUD_RATE);
    Serial.println(F("adcStart_us, adcLapse_us, adcValue"));

    //set up the timer
    TCCR1B = 0;                //stop the timer
    TCCR1A = 0;
    TIFR1 = 0xFF;              //ensure all interrupt flags are cleared
    OCR1A = 399;               //timer runs at 16MHz / 400 / 8 (prescaler) = 5kHz (200µs between samples)
    OCR1B = 399;
    cli();
    TCNT1 = 0;                 //clear the timer
    TIMSK1 = _BV(OCIE1B);      //enable timer interrupts
    sei();
    TCCR1B = _BV(WGM12) | _BV(CS11);    //start the timer, ctc mode, prescaler 8

    //set up the adc
    ADMUX = _BV(REFS0);                                //use AVcc as reference
    ADCSRA  = _BV(ADEN)  | _BV(ADATE) | _BV(ADIE);     //enable ADC, auto trigger, interrupt when conversion complete
    ADCSRA |= _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);    //ADC prescaler: divide by 128
    ADCSRB = _BV(ADTS2) | _BV(ADTS0);                  //trigger ADC on Timer/Counter1 Compare Match B
}

void loop(void)
{
    unsigned long startTimes[SAMPLE_SIZE];    //adc start time (timer interrupt)
    unsigned long endTimes[SAMPLE_SIZE];      //adc complete time (adc interrupt)
    unsigned int adcVals[SAMPLE_SIZE];        //values from the ADC
    unsigned long tStart;                     //timer interrupt time from micros()
    unsigned long tEnd;                       //adc interrupt time from micros()
    byte i;                                   //array index
    
    i = 0;
    while (adcBusy);                     //if a conversion is in progress, wait for it to complete
    
    do {
        while (!adcBusy);                //wait for timer interrupt which starts adc conversion
        tStart = micros();               //capture the time
        while (adcBusy);                 //wait for ADC conversion to complete
        tEnd = micros();                 //capture the time
        startTimes[i] = tStart;          //save the times
        endTimes[i] = tEnd;
        cli();
        adcVals[i] = adcVal;             //save the analog reading
        sei();
    } while (++i < SAMPLE_SIZE);    
    
    //print the data
    Serial.println();
    Serial.print(millis());
    Serial.println(" ms");
    for (byte i=0; i<SAMPLE_SIZE; ++i) {
        Serial.print(startTimes[i]);
        Serial.print(", ");
        Serial.print(endTimes[i] - startTimes[i]);
        Serial.print(", ");
        Serial.println(adcVals[i]);
    }
    Serial.flush();
    #if PAUSE_BETWEEN_SAMPLES > 0
    delay(PAUSE_BETWEEN_SAMPLES);
    #endif
}

ISR(ADC_vect)
{
    adcBusy = false;
    adcVal = ADC;
}

ISR(TIMER1_COMPB_vect)
{
    adcBusy = true;
}

Edit: Simplified code, one flag variable for both ISRs.

1 Like

wow great work thanks@Jack Christensen. i try it and it really working for me. i manage to get sample with desired interval. i will try to work on continuous taking sample without stop the time.

i wonder, instead of taken one input channel , can we make arduino take sample with 2 or 3 more channel synchronously?

dut:
wow great work thanks@Jack Christensen. i try it and it really working for me. i manage to get sample with desired interval. i will try to work on continuous taking sample without stop the time.

Just remember, continuous samples can only be taken for so long at high rates, if there is to be sufficient time to process them.

i wonder, instead of taken one input channel , can we make arduino take sample with 2 or 3 more channel synchronously?

Funny you should ask, my application needs two analog inputs, so that's the next thing I did. This sketch reads ADC channels 0 and 1 alternately.

//Timer-driven ADC sampling.
//Jack Christensen 13Jun2013

#define SAMPLE_SIZE 20
#define PAUSE_BETWEEN_SAMPLES 100    //milliseconds
#define BAUD_RATE 115200

volatile boolean adcBusy;
volatile boolean timerFlag;
volatile int adcVal;
byte mux;

void setup(void)
{
    delay(1000);
    Serial.begin(BAUD_RATE);
    Serial.println(F("adcStart_us, adcLapse_us, adcValue"));

    //set up the timer
    TCCR1B = 0;                //stop the timer
    TCCR1A = 0;
    TIFR1 = 0xFF;              //ensure all interrupt flags are cleared
    OCR1A = 1999;               //timer runs at 16MHz / 2000 / 8 (prescaler) = 1kHz (1000µs between samples)
    OCR1B = 1999;
    cli();
    TCNT1 = 0;                 //clear the timer
    TIMSK1 = _BV(OCIE1B);      //enable timer interrupts
    sei();
    TCCR1B = _BV(WGM12) | _BV(CS11);    //start the timer, ctc mode, prescaler 8

    //set up the adc
    ADMUX = _BV(REFS0);                                //use AVcc as reference
    ADCSRA  = _BV(ADEN)  | _BV(ADATE) | _BV(ADIE);     //enable ADC, auto trigger, interrupt when conversion complete
    ADCSRA |= _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);    //ADC prescaler: divide by 128
    ADCSRB = _BV(ADTS2) | _BV(ADTS0);                  //trigger ADC on Timer/Counter1 Compare Match B
}

void loop(void)
{
    unsigned long startTimes[SAMPLE_SIZE];    //adc start time (timer interrupt)
    unsigned long endTimes[SAMPLE_SIZE];      //adc complete time (adc interrupt)
    unsigned int adcVals[SAMPLE_SIZE];        //values from the ADC
    unsigned long tStart;                     //timer interrupt time from micros()
    unsigned long tEnd;                       //adc interrupt time from micros()
    byte i;                                   //array index
    
    i = 0;
    while (adcBusy);                     //if a conversion is in process, wait for it to complete
    ADMUX = _BV(REFS0);                  //force mux0
    mux = 0;
    
    do {
        while (!adcBusy);                //wait for next conversion to start
        tStart = micros();               //capture the time
        while (adcBusy);                 //wait for ADC conversion to complete
        tEnd = micros();                 //capture the time
        startTimes[i] = tStart;          //save the times
        endTimes[i] = tEnd;
        cli();
        adcVals[i] = adcVal;             //save the analog reading
        sei();
    } while (++i < SAMPLE_SIZE);
    
    
    //print the data
    Serial.println();
    Serial.print(millis());
    Serial.println(" ms");
    for (byte i=0; i<SAMPLE_SIZE; ++i) {
        Serial.print(startTimes[i]);
        Serial.print(", ");
        Serial.print(endTimes[i] - startTimes[i]);
        Serial.print(", ");
        Serial.println(adcVals[i]);
    }
    Serial.flush();
    #if PAUSE_BETWEEN_SAMPLES > 0
    delay(PAUSE_BETWEEN_SAMPLES);
    #endif
}

ISR(ADC_vect)
{
    adcBusy = false;
    adcVal = ADC;
    ADMUX = _BV(REFS0) | (++mux & 1);    //flip between mux0 and mux1
}

ISR(TIMER1_COMPB_vect)
{
    adcBusy = true;
}

Output looks like this:

adcStart_us, adcLapse_us, adcValue

1019 ms
1001436, 204, 115
1002436, 116, 1018
1003436, 108, 115
1004436, 116, 1018
1005436, 108, 115
1006436, 108, 1018
1007436, 108, 115
1008436, 108, 1018
1009436, 108, 115
1010436, 108, 1018
1011436, 108, 115
1012436, 108, 1018
1013436, 108, 115
1014436, 108, 1018
1015436, 108, 115
1016436, 108, 1018
1017436, 108, 115
1018436, 108, 1018
1019436, 108, 115
1020436, 108, 1018

1174 ms
1155436, 108, 115
1156436, 108, 1018
1157436, 108, 115
1158436, 108, 1018
1159436, 108, 115
1160436, 108, 1018
1161436, 108, 115
1162436, 108, 1018
1163436, 108, 115
1164436, 108, 1018
1165436, 108, 115
1166436, 108, 1018
1167436, 108, 115
1168436, 108, 1018
1169436, 108, 115
1170440, 104, 1018
1171436, 108, 115
1172436, 108, 1018
1173436, 108, 115
1174436, 108, 1018

1328 ms
1309436, 108, 115
1310436, 108, 1018
1311436, 108, 115
1312436, 108, 1017
1313436, 108, 115
1314436, 108, 1018
1315436, 108, 115
1316436, 108, 1018
1317436, 108, 115
1318436, 108, 1018
1319436, 108, 115
1320436, 108, 1018
1321436, 108, 115
1322436, 108, 1018
1323436, 108, 115
1324436, 108, 1018
1325436, 108, 115
1326436, 108, 1018
1327436, 108, 115
1328436, 108, 1018
1 Like