Assigning ADC Sampling times to hardware timers.

I'm working on a project based on accurate sampling of analog signals. I have general experience working with the Arduino environment, but have never worked with registers before, so im having a pretty hard time with them.

I'm basically trying to configure 2 ADC's triggered by 3 timers. This has to be very time consistent and without any jitter. So that's why im choosing to do it on a lower level with register mapping.

Im open to use several microcontrollers: atmega 328p, 32u4, 2560

The picture is related to the timings and how it all should work.

Your help will be greatly appreciated.

I'm basically trying to configure 2 ADC's triggered by 3 timers.

Sounds the wrong way to go about things, what is tour thinking behind that?

so im having a pretty hard time with them

Can you be more specific about what is giving you a hard time.

Note this will still not give you jitter free sampling because Timer 0, the one used for millis and micros is still running. You have to disable that as well.

My thinking behind this is to trigger ADC 1 with one timer and trigger ADC 2 with two timers so I can trigger it at 2 different times in 1 cycle(~1000us), but as you say this really might be a wrong way to approach it.
From what im seeing the datasheet(328p) there is a saw wave being used for the counter and it can trigger things at
different values of that wave with the compare unit. So I can use one timer for triggering the two ADC's at different values of the counter. Let me know where im getting it wrong here.

I have no idea how to even go about assigning ADC's to a timer, but im sure it's doable. I went through the register sections in the datasheets many times, but I still can't fully wrap my head around it.

Nick Gammon has a tutorial that includes an example of triggering the ADC using a timer: https://www.gammon.com.au/adc

You know of course that there is only one ADC per microprocessor on AVR-based Arduinos, and it is not possible to sample two channels at the same time. So, your timing cannot allow channel sampling to overlap.

Just looking at your picture, you want to start sampling ADC1 at time=0, and another at time=125 us? ADC2 is sampled at time=70 us? Then this goes on once every ms?

What is the 20us pulse for?

hzrnbgy:
Just looking at your picture, you want to start sampling ADC1 at time=0, and another at time=125 us? ADC2 is sampled at time=70 us? Then this goes on once every ms?

Yes that is true. It doesn't need to be exactly 1ms it can vary a bit that's not a problem. Just the timings themselves need to be exact.

hzrnbgy:
What is the 20us pulse for?

My bad, I wrote this in the middle of thinking how it all works. Since it just samples at beginning of the pulse there is no need to specify it like I did. Still need to figure out how to trigger it with a timer.

jremington:
You know of course that there is only one ADC per microprocessor on AVR-based Arduinos, and it is not possible to sample two channels at the same time. So, your timing cannot allow channel sampling to overlap.

I know that the ADC is multiplexed between the analog pins. I just need to sample two analog signals: the first one needs to be sampled one time in the cycle and the second one needs to be sampled at two different times in the cycle. No need to sample two signals at the same time.
Also thanks for the mentioned tutorial.

So are the two ADCs the one internal ADC or do you have external ones?

Try this one

void setup(void)
{
	/*********************************************
	CTC TIMER1
	ICR1 TOP
	*********************************************/
	// output test
	DDRB |= (1<<PORTB2 | 1<<PORTB1);

	// CTC with ICR1
	TCCR1B = 1<<WGM13 | 1<<WGM12;
	TCCR1A = 0<<WGM11 | 0<<WGM10;

	// enable interrupts
	TIMSK1 = 1<<ICIE1 | 1<<OCIE1B | 1<<OCIE1A | 0<<TOIE1;

	// TOP at 1 ms
	ICR1 = 16000 - 1;

	// ADC1 at 20 usec
	OCR1A = 320 - 1;

	// ADC2 at 90 usec
	OCR1B = 1440 - 1;

	// let it rip, run TIMER1 at 16 MHz
	TCCR1B = 1<<WGM13 | 1<<WGM12 | 0<<CS12 | 0<<CS11 | 1<<CS10;

    while (1) 
    {
    }
}

And your ISRs

ISR(TIMER1_COMPA_vect)
{
	// set next interrupt after 125 usec
	OCR1A = 2320 - 1;

	// set D9 HIGH
	PORTB |= (1<<PORTB1);

	asm("NOP");
	asm("NOP");

	// set D9 LOW
	PORTB &= ~(1<<PORTB1);
}

ISR(TIMER1_COMPB_vect)
{
	// set D9 HIGH
	PORTB |= (1<<PORTB2);

	asm("NOP");
	asm("NOP");

	// set D9 LOW
	PORTB &= ~(1<<PORTB2);
}

ISR(TIMER1_CAPT_vect)
{
	// reset OCR1A to trigger after 20 usec
	OCR1A = 320 - 1;
}

Basic Idea

  • Use timer1 in CTC mode. You probably need to disable TIMER0 or any interrupt source to be jitter free
  • Use the output compares to generate the interrupts for each timing required
  • Since you only have two output compares, you have to re-use one. OCR1A in my example. This is possible because OCR registers are not double buffered in CTC mode. Allows you to modify the OCR registers in the ISR and it will update it immediately
  • You ran the timer at 16 MHz, and set the TOP to 16000 which will give you a 1 msec period
  • My example is 20 usec delayed, meaning all your timing is shifted 20 usec to the right. This is to give the timer time to update values, especially in the CAPT interrupt.
  • After the timer is started, after 20 usec, your first ISR is triggered ISR(TIMER1_COMPA_vect)
  • Inside the interrupt, you change the value of OCR1A to define the next trigger at 145 usec (20 + 125 in your drawing)
  • After the timer is started, after 90 usec, ISR(TIMER1_COMPB_vect) is triggered
  • After 1 msec (the TOP value), inside the CAPT interrupt, you change back the OCR1A to trigger within the next 20 usec
  • Rinse and repeat

I don't have an oscilloscope to verify but if you connect one at Arduino D9 and D10, you should see the pulses on these pins (assuming I got everything right)

Not sure if any of the Atmega variants has more than two output compares but the Xmegas has four per timer which would make it easier since you don't have to re-use the OCRs, you can just define the three output compares

hzrnbgy:

ISR(TIMER1_COMPB_vect)

{
// set D9 HIGH      <-- D10 !?
PORTB |= (1<<PORTB2);

effect of copy paste

Use single autotriggeteing of the ADC. In the ISR for the triggering timer prepare the trigger for the next sampling and change MUX. In ADC ISR collect result. You will probably need low impedance source for the ADC.

Took a small break (I went away for some time)from this project and then resumed back to it, and actually decided it will be my best choice to learn registers from the ground up. Now I am at point where I figured out how to make all the timings, and also how to make the ADC sample at the timer's frequency, and all the other details.

Only roadblock right now is that I can't make the ADC trigger at the compare match A of timer/counter0. I successfully made it trigger at the overflow of timer/counter0, but that way I can't adjust where it actually triggers in the timer period(1024us), that's why I specifically need it to trigger at compare match A. I must also add that I successfully configured the timer to output a pulse using the output compare register, the pulse it's rock solid and exactly timed where it's need it to be, I did that for adjusting the compare match position.

Also I really appreciate everyone's replies and time invested in providing the suggested codes.

Now that I have a better understanding of registers, I am more aware the 328p's capabilities. And changed my way to go about this code.

Smajdalf:
Use single autotriggeteing of the ADC. In the ISR for the triggering timer prepare the trigger for the next sampling and change MUX. In ADC ISR collect result. You will probably need low impedance source for the ADC.

They understand it best. Using the ADC's autotrigger function is the only way to go for jitter free sampling. And I'm going to change the analog input each cycle by changing the MUX each cycle with a counter to 3 and after that get ADC value, the ADC conversion and other stuff will be in an interrupt vector triggered by timer2 with the same div factor as timer0 a bit after (for headroom) the ADC has finished it's thing (the best method in my opinion). Instead of previously thinking to sample all 3 analog inputs in 1 cycle (this is unnecessary).
Only thing I need help solving right now is making the ADC autotrigger work with timer/counter0 compare match A.

I have noticed when I use timer/counter0 compare match A it just takes 1 sample at startup and repeats it infinitely.

Sorry if my explanations are messy.

Let me know where im messing up.

Here is the code:

byte timing1 = 45;
byte syncpulse = 20;
int sample;

void setup() {
  Serial.begin(9600);
  ADCSRA |= (1 << ADEN);
  ADCSRA |= (1 << ADATE);
  ADCSRB = B00000011; //trigger at timer/counter0 compare match A

  DDRD = B01101000; //make pin D5, D6, D3 output //for testing purposes
  DDRB = B00001000; //make pin B3 output

//timer0
  TCCR0A = B00000011; //Timer Control Register 1, fast pwm mode
  TCCR0B = B00000011; //Timer Control Register 2, counter clock divided by 64 (1024us cycle)
  //tried using fast PWM with OCRA for TOP, but doesn't help
  OCR0A = timing1;

//timer2 //for syncing external hardware
  TCCR2A = B10100011; //Timer Control Register 1, both Compare Match Outputs on non inverting mode, fast pwm mode

  TCCR2B = B00000100; //Timer Control Register 2, counter clock divided by 128 (1024us cycle)
  OCR2A = syncpulse; //sync pulse
}

int readAdc(char chan) //reading adc
{
    ADMUX = B01000000;  //select input and ref
    while (ADCSRA & (1<<ADSC)); //wait for end of conversion
    return ADCW;
}

void loop() {
  sample = readAdc(0);
  Serial.println(sample);
  delay(50);
}

defavlttt:

int readAdc(char chan) //reading adc

{
   ADMUX = B01000000;  //select input and ref
   while (ADCSRA & (1<<ADSC)); //wait for end of conversion
   return ADCW;
}

Just a note: "char chan" in "readAdc(char chan)" is not used.

Using the ADC's autotrigger function is the only way to go for jitter free sampling.

No.
Jitter occurs mainly because of timer 0 controlling the millis and micros timers. You disable that to prevent jitter.

Sampling using that routine is not very much faster than using analogRead. In fact it is less accurate because of the lack of settling time once the multiplexer has been changed.

Erik_Baas:
Just a note: "char chan" in "readAdc(char chan)" is not used.

Just a leftover from the iterations. Shouldn't cause a problem.

Grumpy_Mike:
Sampling using that routine is not very much faster than using analogRead. In fact it is less accurate because of the lack of settling time once the multiplexer has been changed.

In my case I'm not reaching for ADC speed greater than 1khz, but for a consistent period of sampling. Also can you explain to me about the settling time of the ADC?

Grumpy_Mike:
Jitter occurs mainly because of timer 0 controlling the millis and micros timers. You disable that to prevent jitter.

I wasn't intending to say that timer 0 has jitter. I wanted to say that the only way to sample jitter free is to trigger an ADC with a timer, instead of sampling with an interrupt, or some other way.

I wasn't intending to say that timer 0 has jitter.

No you are misunderstanding what I said. I said that timer 0 IS THE CAUSE OF JITTER on the other timers.

I wanted to say that the only way to sample jitter free is to trigger an ADC with a timer, instead of sampling with an interrupt, or some other way.

And I want to tell you that is rubbish.

Grumpy_Mike:
No you are misunderstanding what I said. I said that timer 0 IS THE CAUSE OF JITTER on the other timers.

How does Timer0 cause jitter in the other timers if the other timers aren't using interrupts?

if the other timers aren't using interrupts?

But they are.

Alright, no need to argue about if there is jitter. I've done my tests to whether it's true or not. Just a simple sketch where I output the signal from timer0 compare match A to pin 6.

byte pulse = 10;

void setup() {
  DDRD = B01000000; //make port D6 output
  TCCR0A = B11110011; //Timer Control Register 1, both Compare Match Outputs on inverting mode, fast pwm mode
  TCCR0B = B00000011; //Timer Control Register 2, counter clock divided by 64 (1024us cycle)
  OCR0A = pulse;
}

void loop() {

}

Now tell me is there any jitter on timer0? It doesn't even move 2ns.

I don't expect any jitter from a hardware PWM. There is no CPU cycle involve in toggling the outputs as it is done by the timer hardware. The jitter arises when you enable the output compare interrupts and do some processing in the ISR. It's possible that when the output compare interrupt fires, the CPU is servicing another interrupt. The hardware PWM would toggle the pin exactly when it needs to, but the compare ISR itself has to wait for the current ISR to finish. Jitter arises if you have multiple ISRs as each has to wait for each other to finish.