Reading ADC on 2 inputs

Go To Last Post
26 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Chip = Tiny84
I'm trying to read voltages on 2 ADC inputs (ADC2 on PA2 and ADC3 on PA3).

For ADC2 I have...

void initADC()	// initialize and prepare ADC2 for reading
{
	ADMUX = _BV(MUX1);
	// REFS1 and REFS2 --> Not set, Vcc used as Vref
	// ADLAR --> Not set because I want the ADCH and ADCL registers left where they are.
	// MUX1 -->  leaving MUX3,MUX2,MUX0=0 Sets ADC2 on PA2
	DIDR0 = _BV(ADC2D)+_BV(ADC3D);	// disable binary input ADC2/PA2 and ADC3/PA3 for power saving
	ADCSRA = _BV(ADEN)+_BV(ADSC)+_BV(ADPS2);
	// ADEN --> Enable ADC
	// ADSC --> Start conversion
	// ADPS2 --> Sets ADC prescaler to 16
}

The to read the ADC2 value, I have...

advval = ADC;

After ADC2 is done being read...
To read ADC3, do I have to set ADMUX=_BV(MUX1)+_BV(MUX0)
before I set the ADCSRA = _BV(ADSC) to start a conversion to get the ADC3 result into ADC?
Since the differece between ADC2 and ADC3 is the setting of MUX0, can I just change MUX0 from 0 to 1 or 1 to 0 depending on which ADC I want to read?
To set MUX0 to 1, I would use _BV(MUX0)
Instead of using (0 << MUX0) to set MUX0 to 0, what is the "unset" equivalent of "_BV()".

Thanks for the help.[/code]

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You can get away with just flipping the odd bit to change the MUX but for a more generic routine consider something like:

void set_MUX_value(uint8_t new_value) {
 ADMUX &= 0xC0; // retain the two REFS bits
 ADMUX |= (new_value & 0x3F); // set the new MUX value
}

If you want to reduce the number of bits that can be changed then set more bits in 0xC0 and reduce the number in 0x3F

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
You can get away with just flipping the odd bit to change the MUX but for a more generic routine consider something like:

void set_MUX_value(uint8_t new_value) {
 ADMUX &= 0xC0; // retain the two REFS bits
 ADMUX |= (new_value & 0x3F); // set the new MUX value
}

If you want to reduce the number of bits that can be changed then set more bits in 0xC0 and reduce the number in 0x3F


Thanks for the feedback. After I had posted, I was thinking about doing the exact thing you suggested.
Well, maybe not exactly how. But I was thinking about passing some values to a subroutine.
Except the 0xC0, 0x3F notation is not intuituve to me yet. That's why I'm using _BV notation. It helps me read what I have written. I understand that 0xC0 = 11000000 and sets the bits 7..0 accordingly. But 0x3F = 00111111, that would set MUX5..0 all to 1. What value would be passed as "new_value"? Or does 0X3F simply define the bits that can be set by "new_value", and "new_value" is a number like 0x03 to set MUX5..0 = 000011 and 0x02 would set 000010?
I'm really new to this. I'm trying to read all the tutorials I can, but they're so arbitrary.
Thanks, again, for your help.

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What the code I showed above does is:

ADMUX &= 0xC0;

is the same as:

ADMUX &= 0b11000000;

and when you AND with a 1 bit then the previous setting is maintained but when you AND with a 0 bit the existing bits are ignored and the bits are forced to 0. So this will leave bits 6 and 7 intact but force all the six MUXn bits to be set back to 0, clearing any existing setting.

Then:

(new_value & 0x3F)

is just making sure that even if the caller of this function made a mistake and specified a 'new_value' which even had bits 6 and 7 set then this, which is the same as:

(new_value & 0b00111111)

ensures that only the bottom six bits of new_value are retained and anything in the upper 2 bits is dropped. Finally the:

ADMUX |=

part of that line takes whatever is already in ADMUX (which can only currently be some setting in bits 6 and 7 because of the first line) and merges the new_value into the lower 6 bits, knowing that it won't have anything in bit positions 6 and 7 that might clash with the existing state of REFSn bits.

Like I say, if you only planned for the routine to be able to modify the bottom 3 bits of ADMUX (say) then the values would be 0xF8 and 0x07 instead. That is:

0b11111000
0b00000111

If you cannot yet "see" bit patterns in something like 0xF8 then in GCC (though this is non-standard) you might prefer to use 0bNNNNNNNN instead so you can truly visualise the bits you are talking about.

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I want to read both ADC2 and ADC3 during the high state of the OC1A output pin (I'm also using PWM). I see that the TOV1 interrupt is generated when the counter reaches BOTTOM If I'm using Phase Correct PWM. Reading the ADCs when the TOV1 interrupt is generated would place the ADC reads in the middle of each PWM pulse.

So reading the 2 ADCs using your example above would be.

set_MUX_value(0x02);
 ADCSRA = _BV(ADSC);
 adc2val = ADC;
set_MUX_value(0x03);
 ADCSRA = _BV(ADSC);
 adc3val = ADC;

Could I trigger the above code by the TOV1 interrupt?
I know I'm deviating from the original intent of this post. Just let me know if it would be more proper to start another thread.

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
ADCSRA = _BV(ADSC); 
 adc2val = ADC;

Will NOT work - you set ADSC but you aren't waiting for the conversion to complete. Try something like:

ADCSRA = _BV(ADSC);
 while(ADCSRA & _BV(ADSC)); 
 adc2val = ADC;

which waits until ADSC drops back to 0 which is one way to check for conversion completion - this can actually take some time.

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

ADCSRA = _BV(ADSC); 
 adc2val = ADC;

Will NOT work - you set ADSC but you aren't waiting for the conversion to complete. Try something like:

ADCSRA = _BV(ADSC);
 while(ADCSRA & _BV(ADSC)); 
 adc2val = ADC;

which waits until ADSC drops back to 0 which is one way to check for conversion completion - this can actually take some time.

Cliff


Duh. Yeah, of course you're right. I missed that.
You said it can take some time. I'm using the 8MHz clock with the prescale set to 2 for a 4MHz CPUclk. If I set the ADCclk divider to 32 (4MHz/32) = 125kHz. How long does the conversion take at that frequency? and how many samples are done? It says it uses Successive Approximation. My PWM frequency is only going to be 244Hz. If that matters.

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

If you cannot yet "see" bit patterns in something like 0xF8 then in GCC (though this is non-standard) you might prefer to use 0bNNNNNNNN instead so you can truly visualise the bits you are talking about.

Cliff

What you said makes perfect sense. Thanks for the explanation. It's just a matter of getting used to the hex versus binary notation and being able to "see" it without using my Windoze calulator to convert hex to binary.

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
You can get away with just flipping the odd bit to change the MUX but for a more generic routine consider something like:

void set_MUX_value(uint8_t new_value) {
 ADMUX &= 0xC0; // retain the two REFS bits
 ADMUX |= (new_value & 0x3F); // set the new MUX value
}

If you want to reduce the number of bits that can be changed then set more bits in 0xC0 and reduce the number in 0x3F


clawson,
If I do want to change the REFS bits along with the MUX bits, can I just specify a completely new set of bits for the ADMUX register?

ADMUX = 0x03; // 0b00000011 ADC3 with Vcc Vref
ADCSRA = _BV(ADSC);
 while(ADCSRA & _BV(ADSC));
adc3ct = ADC;

ADMUX = 0x82; /0b10000010 ADC2 with 1.1V Vref
ADCSRA = _BV(ADSC);
 while(ADCSRA & _BV(ADSC)); 
adc2ct = ADC;

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Absolutely but why would you ever want to change the reference?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you were asking me I would say to measure supply voltage but as you didn't ask me, I'll keep quiet. :)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Absolutely but why would you ever want to change the reference?

Quote:

If you were asking me I would say to measure supply voltage but as you didn't ask me, I'll keep quiet.

;) Steve, if I were asked I could mention something about measuring the internal reference for calibration (given that Vcc is "known" as the output of a linear regulator, and much closer % to nominal than the internal 1.1V).

And then once that is done, I could mention something about high range/low range conversions.

Lee

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Absolutely but why would you ever want to change the reference?

I'm using the 1.1V reference to measure the power supply voltage via a resistor divider (360K/10K). This will allow me to calculate the PWM duty cycle required to output an specific RMS voltage. The data sheet recommends a 10K resistor to ground for single ended ADCs. The 1.1V reference allows me to use a large upper half of the divider to reduce current through the divider. This is for a battery powered device. This design is a voltage regulator that will use unfiltered PWM to drive an incandescent bulb. The Vcc reference will be used for another ADC that will read the wiper of a pot connected between Vcc and GND to tell the AVR what specific RMS voltage to calculate. The target voltage ADC is scaled such that the pot can "request" an output voltage between 0 and 40V.

I figured out what I was doing wrong. Before I go into the regualtion loop, I set ADMUX=0x03 and ADCSRA=_BV(ADEN)+_BV(ADSC)+_BV(ADPS2)+_BV(ADPS0) and wait for the conversion to complete. I hadn't been starting a conversion before.
Then I go into the loop. In the loop I just reset ADMUX to 0x03 or 0x82 accordingly then initiate a conversion using ADCSRA &= _BV(ADSC). Since the ADC is already enabled each conversion within the loop takes only 13 cycles instead of the 25 it takes for the first conversion done before the loop begins.

See what I mean? Does this make sense?

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JimmyM wrote:

ADMUX = 0x03; // 0b00000011 ADC3 with Vcc Vref
ADCSRA = _BV(ADSC);
 while(ADCSRA & _BV(ADSC));
adc3ct = ADC;

ADMUX = 0x82; /0b10000010 ADC2 with 1.1V Vref
ADCSRA = _BV(ADSC);
 while(ADCSRA & _BV(ADSC)); 
adc2ct = ADC;


Just one remark: You said you are running PWM and this code is executed during the PWM interrupt. This way your interrupt routing is quite slow (2x65us waiting for the ADC in the interrupt). If you can afford to measure only once per PWM interrupt then I suggest you alternate, once you measure channel 1 and the next time channel 2. This way you could get away with no waiting for the ADC.

Markus

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

markus_b wrote:
JimmyM wrote:

ADMUX = 0x03; // 0b00000011 ADC3 with Vcc Vref
ADCSRA = _BV(ADSC);
 while(ADCSRA & _BV(ADSC));
adc3ct = ADC;

ADMUX = 0x82; /0b10000010 ADC2 with 1.1V Vref
ADCSRA = _BV(ADSC);
 while(ADCSRA & _BV(ADSC)); 
adc2ct = ADC;


Just one remark: You said you are running PWM and this code is executed during the PWM interrupt. This way your interrupt routing is quite slow (2x65us waiting for the ADC in the interrupt). If you can afford to measure only once per PWM interrupt then I suggest you alternate, once you measure channel 1 and the next time channel 2. This way you could get away with no waiting for the ADC.

I only need to sync the ADC2 conversion with the PWM on cycle. I was thinking about using an interrupt to trigger the conversion, but I'm open to suggestions.
Reading ADC3 can be done every 10-100 PWM cycles if necessary and it doesn't have to be sync'd with the PWM.

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Forgive me for what might seem like a stupid question.
What does the ADC clock divider (set by CS12..10 bits in the TCCR1B register) do? What does that ADC clock have to do with? Why would one ever need to slow down or speed up the ADC clock? I can't find a reference in the Tiny84 documentation for what the clock effects.

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The CS1[0-2] bits in TCCR1B select the the clock source of timer/counter 1. They have nothing to do with the ADC, but influence the speed of the timer.

There are the ADCS[0-2] bits in ADCSRA who are similar, but for the ADC. They should be configured that the ADC clock runs as fast as possible, but slower then 200kHz.

Markus

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

markus_b wrote:
The CS1[0-2] bits in TCCR1B select the the clock source of timer/counter 1. They have nothing to do with the ADC, but influence the speed of the timer.

There are the ADCS[0-2] bits in ADCSRA who are similar, but for the ADC. They should be configured that the ADC clock runs as fast as possible, but slower then 200kHz.


OK. Thanks. I see that those bits set the divider to control the ADC frequency. I had no idea what the frequency affected. Higher ADC frequency means faster conversions. If a conversion takes 13 cycles. It will complete sooner if I run it at 125kHz rather than a lower frequency. Why 200kHz? I can't find that in the documentation.

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Why 200kHz? I can't find that in the documentation.

How did you look? Search about any old AVR datasheet for "200" and you will find something like
Quote:
By default, the successive approximation circuitry requires an input clock frequency between 50 kHz and 200 kHz to get maximum resolution...

Quote:

Higher ADC frequency means faster conversions. If a conversion takes 13 cycles. It will complete sooner if I run it at 125kHz rather than a lower frequency.

Well, yeah, but it is a don't-care situation in nearly all apps. You can get about 10ksps out of the AVR ADC. You have a requirement to do the conversion during the high part of the PWM for some reason. We only get a tiny piece of information each request. We finally know about the PWM, and we finally know about the PWM frequency, but we still have no idea of how long those high times might be.

This ain't brain surgery.
-- ADC idle, set up for interrupt.
-- Start a conversion on the "important" channel when the pin goes high. Either use the actual autotrigger, or do it in the ISR on the event.
-- When that conversion completes, save the value in the ADC ISR, set the mux for the secondary channel, and start that conversion.
-- When that conversion completes, save the value in the ADC ISR, and flag "ready to process". ADC is again idle.

Lee

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
[;) Steve, if I were asked I could mention something about measuring the internal reference for calibration (given that Vcc is "known" as the output of a linear regulator, and much closer % to nominal than the internal 1.1V).
Lee

Well, I could argue with that, if I was inclined to argue.

I thought this was battery operated, and I didn't notice the use of a voltage regulator.

The first fixed voltage regulator specs I looked at shows an accuracy of 2%. Of the three AVR internal references I checked, 1 was off by 2% and the other two were within 0.5 percent.

I won't argue with the suggestion of measuring the internal voltage reference though. That seems like a good idea to me.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:

Well, yeah, but it is a don't-care situation in nearly all apps. You can get about 10ksps out of the AVR ADC. You have a requirement to do the conversion during the high part of the PWM for some reason. We only get a tiny piece of information each request. We finally know about the PWM, and we finally know about the PWM frequency, but we still have no idea of how long those high times might be.

This ain't brain surgery.
-- ADC idle, set up for interrupt.
-- Start a conversion on the "important" channel when the pin goes high. Either use the actual autotrigger, or do it in the ISR on the event.
-- When that conversion completes, save the value in the ADC ISR, set the mux for the secondary channel, and start that conversion.
-- When that conversion completes, save the value in the ADC ISR, and flag "ready to process". ADC is again idle.

Lee


It wasn't my intent to just give little additional bits of information as I go. I just didn't want to muddy my posting about ADCs with too much other stuff.
I need to sample when the PWM is high because the load placed on the battery by the bulb will pull down voltage. So I want to measure the battery voltage that the bulb actually sees when the PWM is high. The PWM duty will increase from 0 to what ever the calculation requires. Typically though, the duty will be greater than 10%. So at 244 Hz that means that the ADC needs to convert during a PWM on period of ~0.0004 seconds. The readings during the time while the duty is ramping are not quite as important since the program has not reached the desited duty cycle yet, so some additional error caused by variations in battery voltage between PWM ON and PWM OFF. If possible I'd like to perform the conversion during the middle of the PWM on cycle. Since I'm using Phase Correct PWM, the TOV1 interrupt is generated when the counter reaches the BOTTOM.

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You'll have to start studying your data sheet. The ADC timing is covered in chapter 16.5 (doc8006.pdf). The data sheets are large and tedious, but cover all details. You should read once one cover to cover to get a grip on all aspects, when you get used to AVR's you can just drill down to the part you need to get the snippet you need, but is very useful to have had read everything once.

Back to your ADC with PWM problem: At 125kHz your conversion will take 13/125kHz = 104uS. Your PWM at 244Hz runs at 4ms/cycle, even at a 1% cycle you'll have plenty of time for the conversion (or even two).

One thing you could do is to initiate the output ADC channel conversion during the PWM int, read the output result and initiate the battery ADC conversion during the 1st ADC complete and just read the battery result and stop during the second ADC complete. You need to keep around some status but it's light on CPU and synchronous with the PWM.

Don't worry about the details coming up piecemeal that much. Many posters don't even tell which AVR they are using. It is a good habit to describe the essentials initially, the PWM part is pretty important, even if we are discussing ADC.

Markus

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:

Well, yeah, but it is a don't-care situation in nearly all apps. You can get about 10ksps out of the AVR ADC. You have a requirement to do the conversion during the high part of the PWM for some reason. We only get a tiny piece of information each request. We finally know about the PWM, and we finally know about the PWM frequency, but we still have no idea of how long those high times might be.

This ain't brain surgery.
-- ADC idle, set up for interrupt.
-- Start a conversion on the "important" channel when the pin goes high. Either use the actual autotrigger, or do it in the ISR on the event.
-- When that conversion completes, save the value in the ADC ISR, set the mux for the secondary channel, and start that conversion.
-- When that conversion completes, save the value in the ADC ISR, and flag "ready to process". ADC is again idle.

Lee


It wasn't my intent to just give little additional bits of information as I go. I just didn't want to muddy my posting about ADCs with too much other stuff.
I need to sample when the PWM is high because the load placed on the battery by the bulb will pull down voltage. So I want to measure the battery voltage that the bulb actually sees when the PWM is high. The PWM duty will increase from 0 to what ever the calculation requires. Typically though, the duty will be greater than 10%. So at 244 Hz that means that the ADC needs to convert during a PWM on period of ~0.0004 seconds. The readings during the time while the duty is ramping are not quite as important since the program has not reached the desited duty cycle yet, so some additional error caused by variations in battery voltage between PWM ON and PWM OFF. If possible I'd like to perform the conversion during the middle of the PWM on cycle. Since I'm using Phase Correct PWM, the TOV1 interrupt is generated when the counter reaches the BOTTOM.

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

markus_b wrote:
You'll have to start studying your data sheet. The ADC timing is covered in chapter 16.5 (doc8006.pdf). The data sheets are large and tedious, but cover all details. You should read once one cover to cover to get a grip on all aspects, when you get used to AVR's you can just drill down to the part you need to get the snippet you need, but is very useful to have had read everything once.

Yup. there it is. As plain as a punch in the face. I've been through the ADC section several time over the last few days. How I missed it I'll never know. But, thanks.
markus_b wrote:

Back to your ADC with PWM problem: At 125kHz your conversion will take 13/125kHz = 104uS. Your PWM at 244Hz runs at 4ms/cycle, even at a 1% cycle you'll have plenty of time for the conversion (or even two).

OK, cool. that's great about the timing.
markus_b wrote:

One thing you could do is to initiate the output ADC channel conversion during the PWM int, read the output result and initiate the battery ADC conversion during the 1st ADC complete and just read the battery result and stop during the second ADC complete. You need to keep around some status but it's light on CPU and synchronous with the PWM.

Bear with me here...
So, if I set up an interrupt, then init the ADC before the PWM, it will trigger an ADC read every time the TOV1 flag is set.

ISR(TIM1_OVF_vect)
{
   ADMUX = 0x03;
   ADCSRA |= _BV(ADSC);
   while(ADCSRA & _BV(ADSC));
   adc3ct = ADC;
}
void initPWM()
{
  DDRA = _BV(DDA6);
  PORTB = _BV(PORTB2)+_BV(PORTB1)+_BV(PORTB0);
  PORTA = _BV(PORTA7)+_BV(PORTA5)+_BV(PORTA4)+_BV(PORTA1)+_BV(PORTA0);
  TCCR1A = _BV(COM1A1)+_BV(WGM11)+_BV(WGM10);
  TCCR1B = _BV(CS11);
  OCR1A = 0;
}
void initADC()
{
   DIDR0 = _BV(ADC2D)+_BV(ADC3D);
   ADMUX = 0x03;
   ADCSRA = _BV(ADEN)+_BV(ADSC)+_BV(ADPS2)+_BV(ADPS0);
    while(ADCSRA & _BV(ADSC));
}
void readADC2()
{
  ADMUX = 0x82;
  ADCSRA |= _BV(ADSC);
  while(ADCSRA & _BV(ADSC));
  adc2ct = ADC;
}
int main()
{
  initADC();
  initPWM();

  // optionally call readADC2()
  // regulation code here.

}

Should I check to see if there is currently a conversion running before trying to start another via the readADC2() routine? Also, what if there is a conversion running via the readADC2 routine? Or should I just use the interrupt to start both conversions? PWM depended one first, then the second.

markus_b wrote:

Don't worry about the details coming up piecemeal that much. Many posters don't even tell which AVR they are using. It is a good habit to describe the essentials initially, the PWM part is pretty important, even if we are discussing ADC.

Jim M., Rank amateur AVR guy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

So I want to measure the battery voltage that the bulb actually sees when the PWM is high.

So, no biggie. I already outlined a scheme.
Quote:

The PWM duty will increase from 0 ...

It will be a bit tough to catch the high with a a duty cycle of 0. ;)

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
Quote:

So I want to measure the battery voltage that the bulb actually sees when the PWM is high.

So, no biggie. I already outlined a scheme.
Quote:

The PWM duty will increase from 0 ...

It will be a bit tough to catch the high with a a duty cycle of 0. ;)

Wouldn't the interrupt still get generated though? I mean, the counter is still counting up and down, there is just no compare match since OCR1A = 0. The ADC would still get the interrupt to perform a conversion. Wouldn't it?

Jim M., Rank amateur AVR guy.