Delay in A/D convert code - necessary?

Hi all,

Here is a piece of code that I use to read an A/D port on a M328P chip. I am using the internal 1.1v reference and I wonder if the delay between setting the reference source and doing a conversion is necessary:

---code-------------------------------------------------------------------------------
uint16_t readAnalog(uint8_t port)
{
uint8_t hi;
uint8_t lo;

// a/d prescaler = sysclk/128, a/d enable
ADCSRA |= ((1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADEN));

ADMUX = port;
ADMUX |= (_BV(7) | _BV(6)); // use internal 1.1v ref

_delay_us(100); // let it settle (necessary?)

ADCSRA |= (1 << ADSC); // Start ADC conversion

while (ADCSRA & (1 << ADSC)); // wait for a/d complete

lo = ADCL; // must read ADCL first
hi = ADCH;

return (hi << 8) | lo;
}
---code-------------------------------------------------------------------------------

The line in red..... is this necessary? And if so, what's a good delay value? It seems to me that switching an analog reference source would need a finite time to settle given the inherent internal capacitance of the silicon. I couldn't find anything in the 328P datasheet about it.

Opinions will be appreciated.

(note I didn't use (code) tags so that I could color highlight the line in question).

Thanks!

-- Roger

I don't know, but I would guess that the problem is similar to the issue of switching the ADC MUX to a different input and trying to take a sample immediately.

If the analog reference voltage is set once and then remains constant, I would have thought that setting the analog reference repeatedly to the same value was unnecessary but harmless, and I not have thought it was necessary to allow any extra 'settling time' before taking a sample.

Here is a piece of code that I use to read an A/D port on a M328P chip. I am using the internal 1.1v reference and I wonder if the delay between setting the reference source and doing a conversion is necessary:

I suspect so. Coding Badly and I were working on a sketch way back when that would allow one to read the chips AVcc voltage indirectly by manipulating the MUX register directly and we both found that the first 1 to 3 reading were not 'stabilized', so I think a delay after writing to the mux register before issuing a ADC conversion is worth while. I used the below delay value but the 'wasted' time was not a factor for this particular application so I didn't play around with how short I could make it and still get a stable first ADC conversion value:

delay(50);  // Let mux settle a little to get a more stable A/D conversion
        // Start a conversion  
     ADCSRA |= _BV( ADSC );
        // Wait for it to complete
     while( ( (ADCSRA & (1<<ADSC)) != 0 ) );
        // Scale the value
     int results = (((InternalReferenceVoltage * 1024L) / ADC) + 5L) / 10L; // calculates for straight line value 
     return results;

Lefty

PeterH:
I don't know, but I would guess that the problem is similar to the issue of switching the ADC MUX to a different input and trying to take a sample immediately.

If the analog reference voltage is set once and then remains constant, I would have thought that setting the analog reference repeatedly to the same value was unnecessary but harmless, and I not have thought it was necessary to allow any extra 'settling time' before taking a sample.

I don't like the idea of setting ADMUX over and over again, but since the code is a function designed to read whatever port is specified, I don't see how to get around it.

From the datasheet:

23.5.2 ADC Voltage Reference
"... The first ADC conversion result after switching reference voltage source may be inaccurate, and the user is advised to discard this result."

23.9.1 ADMUX – ADC Multiplexer Selection Register

"• Bit 7:6 – REFS[1:0]: Reference Selection Bits
These bits select the voltage reference for the ADC, as shown in Table 23-3. If these bits are changed during a conversion, the change will not go in effect until this conversion is complete (ADIF in ADCSRA is set)."

retrolefty:

Here is a piece of code that I use to read an A/D port on a M328P chip. I am using the internal 1.1v reference and I wonder if the delay between setting the reference source and doing a conversion is necessary:

I suspect so. Coding Badly and I were working on a sketch way back when that would allow one to read the chips AVcc voltage indirectly by manipulating the MUX register directly and we both found that the first 1 to 3 reading were not 'stabilized', so I think a delay after writing to the mux register before issuing a ADC conversion is worth while. I used the below delay value but the 'wasted' time was not a factor for this particular application so I didn't play around with how short I could make it and still get a stable first ADC conversion value:

delay(50);  // Let mux settle a little to get a more stable A/D conversion

// Start a conversion 
     ADCSRA |= _BV( ADSC );
        // Wait for it to complete
     while( ( (ADCSRA & (1<<ADSC)) != 0 ) );
        // Scale the value
     int results = (((InternalReferenceVoltage * 1024L) / ADC) + 5L) / 10L; // calculates for straight line value
     return results;




Lefty

Thanks for the input. 50 milliseconds is way too long for my application. I have an LED display routine that runs in an ISR, plus I read the A/D 1000 times and average it to get a clean reading. I also use floating point in the averaging code so that I get the "between" values for the times when the A/D value is sitting on the fence (like blinking between "123" and 124").

The value I am using is as large as I can get away with without crashing the stack due to the code taking longer than the ISR interval.

johnwasser:
From the datasheet:

23.5.2 ADC Voltage Reference
"... The first ADC conversion result after switching reference voltage source may be inaccurate, and the user is advised to discard this result."

23.9.1 ADMUX – ADC Multiplexer Selection Register

"• Bit 7:6 – REFS[1:0]: Reference Selection Bits
These bits select the voltage reference for the ADC, as shown in Table 23-3. If these bits are changed during a conversion, the change will not go in effect until this conversion is complete (ADIF in ADCSRA is set)."

The part of your quote that I highlighted is exactly the reason I thought a delay would be necessary.

As far as the last part of your quote... I change the reference voltage BEFORE doing a conversion, so that's not a problem.

I change the reference voltage BEFORE doing a conversion, so that's not a problem.

So, the conversion is done AFTER the reference voltage change, which the data sheet says may produce incorrect output.

So, what is the problem when this actually does happen?

PaulS:

I change the reference voltage BEFORE doing a conversion, so that's not a problem.

So, the conversion is done AFTER the reference voltage change, which the data sheet says may produce incorrect output.

So, what is the problem when this actually does happen?

The problem is that without the delay, the A/D results are too low. I read the voltage source (an LM-35 temperature sensor) and figured what the approximate A/D result should be. It was way low. After adding the delay, the result got closer and closer to correct. At around 80 uSec it stopped "improving" and 100 uSec is as far as I can reliably go without crashing the stack.

"... The first ADC conversion result after switching reference voltage source may be inaccurate, and the user is advised to discard this result."

Waiting 100 uS, and taking a reading and discarding it, are two totally different things.

Krupski:
The value I am using is as large as I can get away with without crashing the stack due to the code taking longer than the ISR interval.

Crashing the stack? How does this happen? Do you turn interrupts on inside the ISR?

No. The code that the ISR calls takes longer to run than the ISR repeat rate. Hence saved states on the stack build up rather quickly and bang it crashes.

I've also used my oscilloscope to see how much time I have left. I send a single short pulse at the start of the ISR code and a double pulse at the end. HOPEFULLY there is some space between the end of one and the start of the next.

You know... as I'm typing this I just realized that I'm doing it all wrong. There's no need to call the A/D read code from the ISR. All the ISR has to do is update the display with whatever the current value of the A/D reading is.

Why didn't I think of this before?????

It always seems that the answer comes to me when I'm describing the problem to someone else...

Glad I could help.

However inside the ISR it can't re-enter because you can't interrupt an ISR (unless you turn interrupts back on, which you said you weren't).

It's good you are keeping the ISR processing short, that's the best way.

I'm re-arranging my code right now. I could kick myself for being so stupid as to put everything in the ISR.

All the ISR is supposed to do is update a 4 digit 7 segment LED display. All digits are tied together in the display (that is, all segment "A" are tied together, all segment "B", etc...) but of course the digit selects are separate.

I drive it without any resistors (using short on times) and I light each segment at a time so that all digits are the same brightness and so that I don't exceed the total pin current spec of the 328P.

Here's a little piece of the code to explain what I mean:

void display(uint8_t digit, uint8_t number)
{
        switch(digit) {
        case 0:
                PORTB = DIG_1;
                break;
        case 1:
                PORTB = DIG_2;
                break;
        case 2:
                PORTB = DIG_3;
                break;
        case 3:
                PORTB = DIG_4;
                break;
        default:
                break;
        }

        switch(number) {

        case 0:
                PORTD &= SEG_A; _delay_us(16); PORTD = 0b11111111;
                PORTD &= SEG_B; _delay_us(16); PORTD = 0b11111111;
                PORTD &= SEG_C; _delay_us(16); PORTD = 0b11111111;
                PORTD &= SEG_D; _delay_us(16); PORTD = 0b11111111;
                PORTD &= SEG_E; _delay_us(16); PORTD = 0b11111111;
                PORTD &= SEG_F; _delay_us(16); PORTD = 0b11111111;
                break;

        case 1:
                PORTD &= SEG_B; _delay_us(16); PORTD = 0b11111111;
                PORTD &= SEG_C; _delay_us(16); PORTD = 0b11111111;
                break;

        case 2:
                PORTD &= SEG_A; _delay_us(16); PORTD = 0b11111111;
                PORTD &= SEG_B; _delay_us(16); PORTD = 0b11111111;
                PORTD &= SEG_D; _delay_us(16); PORTD = 0b11111111;
                PORTD &= SEG_E; _delay_us(16); PORTD = 0b11111111;
                PORTD &= SEG_G; _delay_us(16); PORTD = 0b11111111;
                break;
///// etc.... there's a lot more......

The "new" version of the program will ONLY call this code (and a small function to get the individual digit values from the "value" variable) from the ISR. Now the ISR will have lots of free time between interrupts and I won't have to worry about timing!

Thanks for your help! Somehow, you helped me see the problem!

-- Roger

Krupski:
The line in red..... is this necessary? And if so, what's a good delay value?

I did some experiments on this a while go. It depends on the source resistance driving the ADC input. With 10K or less source resistance, you can get away with no delay. If the source resistance is higher than 10K, then you need around 1 microsecond minimum per 10K source resistance if you want to achieve the maximum accuracy.

EDIT: I was not changing voltage reference when I did these experiments. What I was doing instead was changing the multiplexer between different input pins with very different voltages on them. The datasheet says that if you select the 1.1V reference and the brown out detector is not enabled, then the reference takes some time to start up. Also, if you have a capacitor connected between Aref and ground, this will slow down changes in voltage reference.

I drive it without any resistors (using short on times) and I light each segment at a time so that all digits are the same brightness and so that I don't exceed the total pin current spec of the 328P.

You are still destroying your Arduino. You need resistors, even if the pins are only on for short durations. The current draw doesn't ramp up slowly.

Krupski:
You know... as I'm typing this I just realized that I'm doing it all wrong. There's no need to call the A/D read code from the ISR. All the ISR has to do is update the display with whatever the current value of the A/D reading is.

I'm intrigued to know why you're using an interrupt to trigger a display update. Given that the value you're displaying has been derived from multiple analog reads and not synchronised with or triggered by the interrupt, I don't see anything especially time-critical about updating the display.

You mentioned that you are using floating point calculations for the averages. That will slow things down a lot, and I would suggest you look at fixed point calculations instead. Obviously there is a limit to the resolution that you are going to display, so define the fixed point accordingly.