accurate Moteino battery voltage monitoring

Started by WhiteHare, September 21, 2015, 01:56:43 PM

Felix

Quote from: WhiteHare on February 09, 2017, 03:26:01 PM
When I made that post I was running the code on a 16Mhz Moteino.   ;)
Makes sense, I was under the impression you were running 8mhz as well.
I just included calibration math for the reading and readings now take 384us (after settling; first reading takes 500us). I could do the math afterwards but I think the overall time is the same, turning off the ADC sooner would save a little juice.

ChemE

Quote from: Felix on February 09, 2017, 07:15:51 PM
Makes sense, I was under the impression you were running 8mhz as well.
I just included calibration math for the reading and readings now take 384us (after settling; first reading takes 500us). I could do the math afterwards but I think the overall time is the same, turning off the ADC sooner would save a little juice.

Have you tried cranking up your ADC bus speed yet?  I settled at 1MHz and WhiteHare is at 4MHz and we both appear to be getting super-consistent results.

Felix

No, just with defaults, which I'm not sure what those are.
If you have handy code that would be great, otherwise I will dig the DS or web when I get some time...
Thanks

ChemE

Quote from: Felix on February 13, 2017, 03:26:31 PM
No, just with defaults, which I'm not sure what those are.
If you have handy code that would be great, otherwise I will dig the DS or web when I get some time...
Thanks

The default ADC speed set by init() which is called in Setup() is 125kHz.  Ask and ye shall receive...

#include "Arduino.h"

// Define various ADC prescaler
#define       ADC_PS_16               (1 << ADPS2)
#define       ADC_PS_32               (1 << ADPS2) |                (1 << ADPS0)
#define       ADC_PS_64               (1 << ADPS2) | (1 << ADPS1)
#define       ADC_PS_128              (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0)
#define       sbi(sfr, bit)           (_SFR_BYTE(sfr) |= _BV(bit))
#define       cbi(sfr, bit)           (_SFR_BYTE(sfr) &= ~_BV(bit))
#define       START_ADC_CONVERSION    sbi(ADCSRA, ADSC);  // start a conversion
#define       ENABLE_ADC              cbi(PRR, PRADC); sbi(ADCSRA, ADEN);  //ADCSRA |= bit(ADEN); // Enable the ADC
#define       DISABLE_ADC             cbi(ADCSRA, ADEN); sbi(PRR, PRADC); // Disable the ADC to save power

static inline void Fast_ADC_Init(void) {
  sei();
  // Timer 0 initialization from wiring.c for a ATmega 328P (Arduino Uno rev 3) + 12 bytes to sketch size
  TCCR0A = _BV(WGM01) | _BV(WGM00);      // set timer 0 prescale factor to 64
  TCCR0B = _BV(CS01) | _BV(CS00);        // set timer 0 prescale factor to 64
  TIMSK0 = _BV(TOIE0);                 // enable timer 0 overflow interrupt
  
  // Timer 2 initialization from wiring.c for an ATmega 328P (Arduino Uno rev 3) + 20 bytes to sketch size
  TCCR2A |= _BV(COM2A1) | _BV(WGM20);    // Enable timer 2 to _delay_ms() works properly
  TCCR2B |= CS22;                        // set clkT2S/64 (From prescaler)
  
  // ADC Housekeeping
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);    // Set the multiplexer to read the internal bandgap voltage
  //ADCSRA &= ~ADC_PS_128;    // Unset the bits set by init()
  ADCSRA |= ADC_PS_32;    // set our own prescaler to 32 
}


Just call Fast_Init_ADC() in your setup code and you'll be running at 1MHz and comparing to the internal 1.1V bandgap.  I never invoke init() in my release code so I have to do those things in my Fast_Init_ADC.  If you are calling init() either directly or by using Arduino's Setup() routine, then you can change Fast_Init_ADC() to just:

static inline void Fast_ADC_Init(void) {
  // ADC Housekeeping
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);    // Set the multiplexer to read the internal bandgap voltage
  ADCSRA &= ~ADC_PS_128;    // Unset the bits set by init()
  ADCSRA |= ADC_PS_32;    // set our own prescaler to 32 
}

Felix

Thanks ChemE,
I'm running 8mhz so to get 1mhz I think the setting has to be:

ADCSRA |= ADC_PS_16


The power-on ADC prescaler is 128 (62.5khz at FCPU=8mhz).

I seem to get flat readings at 500khz though so I think i'll stick with that. I have to first clear the prescaler bits before setting a new value.
Results:

ADCSRA = 10000100
0. raw relative bandgap voltage=296  Elapsed time=0uS
1. raw relative bandgap voltage=297  Elapsed time=200uS  deltaT=200uS
2. raw relative bandgap voltage=296  Elapsed time=392uS  deltaT=192uS
3. raw relative bandgap voltage=296  Elapsed time=592uS  deltaT=200uS
4. raw relative bandgap voltage=296  Elapsed time=784uS  deltaT=192uS

perky

According to the datasheet max sample rate for full resolution is 15kS/s, which is 150kHz for 10 bit successive approximation. For 8 bit that increases to 76.9kS/s, or around 615kHz. So it seems 8 bit would be OK, but 10 bit might not be as far as I can tell.
Mark.

WhiteHare

Does running the ADC at a faster rate lead to a much faster convergence (i.e. TomWS's method of waiting for two successive voltage readings in a row that match), or does it take about the same amount of time for that even if the sample rate is slower?

perky

I think the dummy reads are necessary because of 'common mode' settling times, it requires a ceratin number of ADC clocks to stabalize the internal references before a sample can be taken accurately. Whether that can be done at a much faster rate that normal sampling and still be accurate I don't know.
Mark.

ChemE

My first reading is the only one that is throw away.  The second and subsequent readings are all within one integer of each other.  I'll check later this morning if the same is true at faster and faster ADC clocks.  But for certain I am getting a faster true reading at 1MHz than I was at 125kHz.

ChemE

I'm running into a hard limit it seems.  No matter what I try, I cannot get an accurate measurement within 100us of powering up the ADC.  Once that 100us has elapsed, I can sample at 4MHz and get accurate results every time in around 4us per measurement, but measurements taken inside that 100us are inaccurate.

emjay


ChemE

Quote from: emjay on February 16, 2017, 02:55:54 PM
@ChemE,

What is the cap value hung on Aref?

I'm not sure I understand the question but I don't have anything connected to Aref.  I'm measuring against the internal bandgap voltage.

emjay

@ChemE,

Isn't this a standard Moteino?  Sorry, the thread is so long to plough all the way through.

perky

#58
Quote from: ChemE on February 16, 2017, 04:21:34 PM
I'm not sure I understand the question but I don't have anything connected to Aref.  I'm measuring against the internal bandgap voltage.
I'm not sure about the 328P, but the bandgap voltage may well be multiplexed onto Aref first. If that's the case Aref will have an output impedance which will combine with the 100nF cap on Aref to form an RC constant. If by not settling for 100us you mean values you get appear to be much higher than they should be during that time then this could well be the cause.

Edit: According to the datasheet block diagram, the bandgap voltage is multiplexed onto Aref. Also there is a bandgap reference startup time as well which is 40us typical.

Mark.

ChemE

Quote from: emjay on February 17, 2017, 07:15:35 AM
@ChemE,

Isn't this a standard Moteino?  Sorry, the thread is so long to plough all the way through.

Yes, I was doing this testing on a standard Moteino so that it would be useful to everyone here.  I've never attached anything to Aref except a multimeter probe but that could just be my own ignorance.  Is it expected that Aref is attached to a capacitor?