The Mega Moteino is nice, because it would offer access to the 2.56V internal voltage reference for calibrating the ADC. But for regular 328p Moteino's, what's the best way to ensure accurate readings of battery voltage?
I've read that the gap reference 1.1v is only accurate to 10%. I'd prefer something more accurate, like 1% or 0.1%.
Are folks using an external voltage reference, such as a zener diode or maybe a temperature compensated precision voltage reference? Or are you using a dedicated battery monitoring chip?
I'm looking for an ultra low current way of monitoring my battery voltage. Most likely my power source will be 3 alkaline AA's, so I want them to last as long as possible, but I will also need to check their voltage level from time to time and plot the measured voltages so that I can verify my power budget and Moteino setup are working as planned. So, I'd prefer to have more than just a low battery indicator (if that's all I needed, it looks as though the HDC1008 actually includes one, which triggers at 2.8v). This is such a universal problem for battery operated devices that I've got to believe there must be a really good solution.
You can do it many ways, including what you mentioned.
I've found that just using a high impedance set of resistors to divide the voltage in a range readable by the ADC works great, is highly accurate and super cheap. It's used on my products like: WeatherShield, PowerShield, MotionMote, SonarMote.
Quote from: WhiteHare on September 21, 2015, 01:56:43 PM
I've read that the gap reference 1.1v is only accurate to 10%. I'd prefer something more accurate, like 1% or 0.1%.
I suggest reading the WHOLE 328P spec. If you do, you will find the table attached below which shows over full usable voltage and temperature range, the Bandgap voltage varies by 0.88%. It is an unfortunate truth that the datasheet, appearing earlier in the spec DOES, in fact, say that the range is 1.0 to 1.2Volts. I have to believe there is an errata sheet somewhere that corrects this.
The first clue that the 10% didn't make sense was that it is a 'bandgap' reference, ie, a voltage that is established by the physics of the junction - this is pretty solid... The second clue is that it is very unlikely that the designers of the ATMega1284P were more 'with it' than the 328P in terms of producing a substantially more accurate reference. What they had were more transistors to add more ADCMUX selections.
Tom
Interesting! It's the first time I've heard of that. I wouldn't have even known to scour the datasheet regarding this, because the unanimous opinion seemed to be the 10% number. Koudos for catching that one.
I do get the impression you've read the WHOLE 328p spec sheet, so riddle me this. The title on that chart refers to a "calibrated" bandgap voltage. Does that mean factory calibrated, or is there a calibration procedure, or does it somehow self-calibrate transparently? Or do you basically just measure it once across AREF and ground and then use that number (perhaps adjusting for temperature, if so inclined)? Obviously, I'm hoping it means factory calibrated. I've also read there's a temperature sensor within the 328p, so maybe it would be good for calculating a temperature correction factor as I would think that it must be in extremely close proximity to the bandgap (as they're both on the same chip).
QuoteI'm looking for an ultra low current way of monitoring my battery voltage
In my recent battery based motes I've always used 2 battery cells without the voltage regulator. This gives you more energy per cell, a nicer form factor and you can use the calibrated internal band gap reference which I've found to be fairly accurate to measure Vcc.
Works well if you don't need 3.3v for your sensors.
Quote from: WhiteHare on September 21, 2015, 03:40:26 PM
I've also read there's a temperature sensor within the 328p, so maybe it would be good for calculating a correction factor as I would think that it must be in very close proximity to the bandgap.
Just one of the many pieces of useful information waiting for you as you pour through this wealth of information...
Tom
I'd just quickly like to drop these links here. All you need to know is the voltage reference is a bandgap one, so it stays sufficiently precise with varying temperatures. Not a precision reference, though, but I guess for measuring battery voltage, it's more than adequate. In retrospective, I have no idea why they state that 1.0-1.2V range.
https://en.wikipedia.org/wiki/Bandgap_voltage_reference
https://en.wikipedia.org/wiki/Band_gap
Just now had a look at voltage references on digikey. It seems 0.88% isn't bad, especially considering it's free and I don't have to order extra parts. :)
This youtube video sums up how to read bandgap voltage and use it to derive your battery voltage.
https://www.youtube.com/watch?v=SpDXrhjwbVo
His bottom line is this:
battery voltage = (1.1/analogRead(14))*1023.
Done! With 3x AA I won't even need a voltage divider. :) :) :)
Quote from: WhiteHare on September 22, 2015, 10:09:44 AM
This youtube video sums up how to read bandgap voltage and use it to derive your battery voltage.
Or you could just use the code that's been posted a million places around the web:
/******************************************************************************
*
* readVcc()
*
******************************************************************************/
unsigned int readVcc(bool restoreMux) {
unsigned long result;
byte saveADMUX;
saveADMUX = ADMUX;
// Read 1.1V reference against AVcc
// set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1284P__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
#if defined(__AVR_ATmega2560__)
/****
it took me a while to figure-out the problem, but on MEGA 2560, immediately after analogRead(A8), ADCL started returning zero.
So every attempt to read from A8-A16 on Arduino MEGA will damage the functionality of readVcc().
I've resolved the problem by adding:
ADCSRB = 0;
just before
delay(2);
**********************************/
//ADCSRB &= ~_BV(MUX5); // Without this the function always returns -1 on the ATmega2560
ADCSRB = 0;
#endif
delay(20); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Start conversion
while (bit_is_set(ADCSRA,ADSC)); // measuring
uint8_t low = ADCL; // must read ADCL first - it then locks ADCH
uint8_t high = ADCH; // unlocks both
result = (high<<8) | low;
// Serial.print("BatteryResult="); Serial.print(result);
result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000 (because...= 3300*1023/3 since 1.1 is exactly 1/3 of 3.3V)
// Serial.print(", after calculation:"); Serial.println(result);
if (restoreMux) ADMUX = saveADMUX;
return result; // Vcc in millivolts
}
Tom
UPDATED: removed PRINT macros and added restoreMux.
Looks to me as though both ways of doing it are assuming the bandgap voltage is 1.1v. From looking at your chart, though, a better pick would be 1.13v, which is more in the middle of the range between super hot and super cold. Or, if it's for indoor use only, then the 25C curve would be a good pick.
Or, maybe just report the raw value of analogRead(14). Then the gateway, if it happens to know the ambient temperature, could pick an appropriate constant based on your graph above (a pity it's not more complete). If done in the dead of night, when ambient temperatues are more settled, that might yield another slight improvement for outdoor sensors. It would be better than the onchip temp sensor, which the datasheet says is only accurate to +/- 10C.
The ultimate might be measuring the bandgap voltage on the chip under test. Running a chip the full temperature range and recording the bandgap voltages at each point, over a range of Vcc's, should yield a pretty accurate lookup table for that chip, and maybe all chips depending on whether there's meaningful variation or not. That lookup table could sit on flash memory in the gateway. If anyone ever does those measurements, please post them! It would fill in the gaps missing from the chart.
BTW, after just now comparing the self discharge rates (see attachment), I think I'll go with lithium rather than Alkaline batteries.
What might change my mind is if the Duracell Quantum version of the Alkaline battery has a much lower self discharge rate than typical alkaline batteries, because I've read that Costco sells the Quantums under their Kirkland brand name and so the price wouldn't be too bad. However, so far I haven't found any data regarding Quantum self discharge rate. Anyone happen to know?
Otherwise, what's the best brand (least self discharge rate) for Lithium AA's? The Energizer Ultimate Lithium's claim they "hold power" for 20 years:
UPDATE TomWS: Unnecessary photo removed.
Even assuming marketing spin, that sounds tough to beat. On Amazon they'd be about $1.50 each, if purchased in quantity of 20 or more.
You're spouting a lot of information commonly available on the net after like 5 minutes of googling. I'm not saying it's not useful at all, but I think your time is better spent looking at readily available information. It's vast, really. As I've said, the bandgap reference is not a precision one in the 328. If you want precision, you pay the price. Calibrating it at the full temperature range is good, but calibration values tend to skew over time, so it's best repeating them every half a year (IIRC it's the norm for most calibrated instruments). You will find that many folks advice a measurement of the bandgap itself chip by chip to achieve more accurate results for AD sampling. You can go that route if you want.
OTOH, choosing a suitable battery is not all about the least self-discharge rate. For small, several mA and sub-mA loads, Alkalines are better suited. They're the fraction of the price of a Lithium one and for that kind of usage they probably last the same. Lithium however is better suited for applications when a higher discarge rate is expected, like in digital cameras, high-performance flashlights, etc. Of course, if your aim is achieving a consumption rate similar to the battery packs' self-discharge rate, Lithium might be a better fit, and the 20 years shelf life might play a role.
Problem with the alkalines is the discharge curve is not flat at all - which is why 9V batteries work so great with Moteinos. Lithium is much better in that regard. Also speaking from experience: if you want to power your outdoor Mote during winter do yourself a favor: Lithium, not Alkaline.
Yeah, of course. On a case-by-case basis, usage differences help deciding for a given project or even placement. The curve not being flat is not necessarily a problem, though. In case of the (unmodified) Moteino, all needed is they provide at least 3.3V till they run out. According to my calculations, even 3xAA Alkalines can provide 1.5-2 years of operaion for a properly programmed mote. And - for me - it's good enough :)
Quote from: joelucid on September 22, 2015, 03:40:34 PM
Also speaking from experience: if you want to power your outdoor Mote during winter do yourself a favor: Lithium, not Alkaline.
I totally agree with Joe here and add, if you want to keep the voltage above 1.5V/battery for the longest amount of time, use Lithium Li-FeS2 primary cells. Alkaline lose too much of their voltage earlier in their lifecycle.
Tom
Yes, for 5x the price they're bound to be better in at least one or 2 aspects. I have nothing against them (on the contrary, they're one of the best of their kind, apparently) and on the long term those extra few bucks are nothing even for hobbyists like many of us, I just don't feel their use justified for every case. With simple optimisations on the Moteino HW you admittedly used (8 MHz, regulator removed, etc) they last even longer, and in that case a cell can be used down to about 0.8V, which is pretty much their EOL. In that aspect, I can't see why I would want to keep their voltage above 1.5V, when what I need is > 3.3V (default case) for a 3-pack (temperature effects aside).
To add to my last post, here is the difference between an Alkaline and a Lithium discharge curve (both Eveready brand). Unfortunately they don't really spec at low drain levels, but the key takeaway should be, Lithium have a much sharper knee than Alkaline and Alkalines are practically useless below freezing temperatures.
Tom
Yeah, I'm aware of these figures. data.energizer.com has the DS for many of their battery products, too. But again: why do I care about the curve, when all I need is > 3.3V (or less, about 2.4V, speaking specifically of mentioned Moteino mods), using 3 batteries in series? Naturally, I'd only bring the Lithium batteries out in the cold winter. But indoors, and in my case, I don't see obvious benefits.
I've just browsed this yesterday, very useful (there are lots of these on that forum), albeit they're at higher loads, compared to our needs (but not so unusual in practice, in small consumer devices): http://www.candlepowerforums.com/vb/showthread.php?64660-Alkaline-Battery-Shoot-Out
If it helps frame the objective at all, my most challenging use case is buried soil moisture sensors. I'll probably have a lot of them, as my preliminary data indicates they will pay for themselves in water savings and a healthier landscape. I really don't want to be digging these things up every 1.5-2 years to change the batteries. If they could go 10 years or even longer, then that would be great. The limiting factor for long timeframes seems to be the self discharge rate.
I asked about the quantum's because they are a lot cheaper and, according to Duracell (http://www.duracell.com/en-us/products/all-purpose-batteries/duracell-quantum), they have a "10 year guarantee in storage," which suggests a low self discharge rate. I haven't yet found the hard data on that....
Also, thanks for the heads-up about freezing temperatures, as I'm planning other things (such as perimeter motion sensors) that would be above ground, and so I guess those should be lithium for at least that reason alone.
As I've noted, it's different for everyone, and for each case. Your sensors are going to be underground, very inconvenient to maintain - naturally you'll try to have them running without maintenance as long as possible. Battery however is only one of the components. You probably have a lot of work ahead of you, writing the firmware, the low power parts and design, etc. Our current design goals are very different, that's all.
If you want to bury your entire sensing nodes you will severely compromise the range of your radio signal.
I have a network of Vegetronix sensors with Jeenodes, but I keep the controller and battery above ground. With a 10 minute sampling interval, i get about 2 years from 3x AA alkaline cells.
Epilog: I found an interesting thread which outlines how to measure the bandgap voltage on a particular atmega328p rather than use an averaged figure: http://forum.arduino.cc/index.php?topic=38119.0
Also, I'm thinking that since the best time to measure voltage is when it's under load, maybe measuring it sometime during an RFM69HW transmit would be a "free" way to generate a high load. Not sure if that would introduce extra noise though that would undermine the result. Many have posted that running the ADC while sleeping the rest of the atmega328p does help somewhat.
The same technique is documented here http://jeelabs.org/2012/05/12/improved-vcc-measurement/ (http://jeelabs.org/2012/05/12/improved-vcc-measurement/) with useful graphics and less cruft.
Quote from: emjay on April 12, 2016, 03:35:46 AM
The same technique is documented here http://jeelabs.org/2012/05/12/improved-vcc-measurement/ (http://jeelabs.org/2012/05/12/improved-vcc-measurement/) with useful graphics and less cruft.
Thanks for the link, emjay. It's curious that he takes 4 measurements but only uses the last one. Are the first three to stabilize the charge on the input?
Tom
@Tom,
Exactly - when feeding the ADC input from a higher impedance than the usual recommended, it takes a while to get the (tiny) sampling cap charged up. Even from a lower impedance, you can see a noticeable difference between the "first" sample (say after just switching to that channel) and the "second".
Quote from: emjay on April 12, 2016, 08:54:46 AM
@Tom,
Exactly - when feeding the ADC input from a higher impedance than the usual recommended, it takes a while to get the (tiny) sampling cap charged up. Even from a lower impedance, you can see a noticeable difference between the "first" sample (say after just switching to that channel) and the "second".
But in this case the sources (Vref==Vdd & Mux source==bandgap) are internal and presumably not high impedance. ISTM that this algorithm might be faster if he changed the loop test slightly to stop on max of 4 times OR current reading == last reading (should be safe since presumably any change is monotonic).
@Tom,
Sure, I hope Vdd is low impedance ;) But just set up for Vref to appear on that pin and try drawing a sip of current....
Certainly the successive readings could be logged and perhaps trimmed back to optimize, some scope for improvement if chasing microcoulombs is the target.
Quote from: emjay on April 12, 2016, 10:59:24 AM
... some scope for improvement if chasing microcoulombs is the target.
Good point, but I was chasing microseconds ;)
Good discussion, thanks!
In addition to being sometimes erroneous, I notice the first reading of the ADC also seems to take quite a bit longer than subsequent readings. On an atmega328p running at 8Mhz, what I get is:
0. raw relative bandgap voltage=486 Elapsed time=0uS
1. raw relative bandgap voltage=397 Elapsed time=216uS deltaT=216uS
2. raw relative bandgap voltage=360 Elapsed time=336uS deltaT=120uS
3. raw relative bandgap voltage=348 Elapsed time=456uS deltaT=120uS
4. raw relative bandgap voltage=344 Elapsed time=576uS deltaT=120uS
5. raw relative bandgap voltage=343 Elapsed time=696uS deltaT=120uS
6. raw relative bandgap voltage=343 Elapsed time=816uS deltaT=120uS
7. raw relative bandgap voltage=343 Elapsed time=936uS deltaT=120uS
8. raw relative bandgap voltage=343 Elapsed time=1056uS deltaT=120uS
9. raw relative bandgap voltage=343 Elapsed time=1176uS deltaT=120uS
from this sketch:
// number of microseconds for ADC to setttle before taking a measurement
#define ADC_SETTLE_MICROSECONDS 1000
#define NUM_ADC_SAMPLES 10
uint16_t getRelativeBandgapVoltage() {
uint16_t rawBandgapMeasurement;
// Read bandgap voltage reference (~1.1V) against AVcc
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); //need to do this for every sample or just the first?
//delayMicroseconds(ADC_SETTLE_MICROSECONDS); // Settle before taking ADC measurement
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA,ADSC));
rawBandgapMeasurement = ADCL; //get the low order bits of ADC
rawBandgapMeasurement |= ADCH<<8; //combine with high order bits of ADC
return rawBandgapMeasurement;
}
void setup() {
long timeInMicroseconds[NUM_ADC_SAMPLES];
uint16_t rbgv[NUM_ADC_SAMPLES]; // relative bandgap voltage
Serial.begin(115200);
for (int i=0;i<NUM_ADC_SAMPLES;i++) {
timeInMicroseconds[i] = micros();
rbgv[i] = getRelativeBandgapVoltage();
}
for (int i=0;i<NUM_ADC_SAMPLES;i++) {
Serial.print(i);
Serial.print(F(". raw relative bandgap voltage="));
Serial.print( rbgv[i]);
Serial.print(F(" Elapsed time="));
Serial.print(timeInMicroseconds[i] - timeInMicroseconds[0]);
Serial.print(F("uS"));
if (i>0) {
Serial.print(F(" deltaT="));
Serial.print(timeInMicroseconds[i]-timeInMicroseconds[i-1]);
Serial.print(F("uS"));
}
Serial.println();
Serial.flush();
}
}
void loop() {
}
With just minimal testing so far, I've already noticed a variation of anywhere from one to seven samples needed before arriving at the final number, so Tom's idea of only waiting for the same number to repeat two times in a row sounds nicely frugal while remaining easy to implement. Not sure if there are conditions under which more than two in a row would be preferable.
The number of cycles can also be reduced just by adding some delay (ADC_SETTLE_MICROSECONDS, which is presently commented out in the above sketch). So, maybe adding some sleep time (or simply attending to other matters) in place of a pure delay would be even more energy efficient. Some of the common sketches for readVcc do sleep during the samples (to minimize noise and/or save energy), but I don't know of any that purposely sleep extra long so as to reduce the number of samples needed.
As illustrated by the above example in this post, the amount of elapsed time before converging on a particular number can definitely add-up to become non-trivial. With that in mind, I'm curious to know whether freezing temperatures and/or high ambient heat and/or low VCC and/or a weak battery might affect the convergence time, and so I'll eventually run some tests to try to characterize that. For instance, a weak battery might easily affect the settle time and/or samples required because AVcc might be falling from one sample to the next due to load. For that reason, perhaps the number of samples and/or settle time should be reported and monitored as yet another possible indicator that a battery may need to be changed. In the worse case, a weak battery might become even weaker just from chasing after an accurate measure of how weak it is! With BOD turned off, it might even run itself into the ground. So, for really weak batteries that are just barely holding on, maybe it might even make sense to halt reading the Vcc until it can be replaced...
Can anyone think of any other concerns that might play a role? If so, I can maybe run some tests for those as well and post the results afterward.
@Whitehare, if you're running this test as soon as your system turns on, the culprit might be the cap on ARef settling into its final charge. What happens if you don't run the test (leaving the ADC ref at its power on default) for a few milliseconds?
Measuring voltages at first application of power are likely to be wrong and in most cases unnecessary. Measuring voltage after reset is valid if the proc is in a stopped state and reset via RTC, for example, should be ok.
Tom
Quote from: TomWS on April 14, 2016, 06:43:08 PM
@Whitehare, if you're running this test as soon as your system turns on, the culprit might be the cap on ARef settling into its final charge. What happens if you don't run the test (leaving the ADC ref at its power on default) for a few milliseconds?
I had a similar thought, so to test that I inserted a "delay(1000)" (surely plenty of overkill, right?) at the very start of the setup() procedure on a stock Moteino R4 RFM69HW board (the earlier results I posted were from just an 8Mhz Pro Mini running at 3.3v). On the Moteino, it turns out that with or without the added setup() delay, the first ADC sample takes the same 212uS either way:
0. raw relative bandgap voltage=386 Elapsed time=0uS
1. raw relative bandgap voltage=363 Elapsed time=212uS deltaT=212uS
2. raw relative bandgap voltage=353 Elapsed time=324uS deltaT=112uS
3. raw relative bandgap voltage=349 Elapsed time=436uS deltaT=112uS
4. raw relative bandgap voltage=348 Elapsed time=548uS deltaT=112uS
5. raw relative bandgap voltage=347 Elapsed time=660uS deltaT=112uS
6. raw relative bandgap voltage=347 Elapsed time=772uS deltaT=112uS
7. raw relative bandgap voltage=347 Elapsed time=884uS deltaT=112uS
8. raw relative bandgap voltage=347 Elapsed time=996uS deltaT=112uS
9. raw relative bandgap voltage=347 Elapsed time=1108uS deltaT=112uS
So, the first surprise was that it made no difference. The second surprise was that running the same code on a 16Mhz Moteino was barely any faster than on an 8Mhz Pro Mini. I haven't yet checked the datasheet to confirm, but it would seem the ADC moves at its own pace (?).
[Edit1: That said, it is just a one time thing after power-up or reboot, at least while awake. I confirmed that by putting the whole thing in a loop with a 30 second delay separating each batch of ADC reads. However, I expect there will nonetheless be a similar price to pay each time the atmega328p wakes up from a full powerdown sleep.]
[Edit2: Anyhow, the main upshot from doing these time measurements was to motivate me to do an ADC noise reduction sleep whlie taking the ADC readings. Noise doesn't presently seem to be an issue, but if doing that, then I'm hopeful (though haven't confirmed) that the somewhat lengthy ADC sampling will not tally up over time into a significant drain on battery life.]
[Edit3: By the way, the approach I'm taking to measuring bandgap voltage for calibration purposes is simply to power VCC during calibration with a known voltage source (whose value I input just once during the calibration using the serial console) and then automatically impute the true bandgap voltage by measuring the bandgap against it. The above is part of the code which is leading to that. Like others have done, I'm planning to store this validated bandgap voltage constant somewhere TBD in the eeprom memory and use that instead of just a "typical" value for bandgap voltage. I expect that doing this will probably improve the accuracy of reported voltage measurements.]
I think I found a workable solution. :) According to the datasheet, "When the ADC is turned off and on again, the next conversion will be an extended conversion." So, I simply start a conversion as the very first step in the setup() function by adding two lines of code at its very beginning:
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
ADCSRA |= _BV(ADSC); // Convert
I don't have the atmega328p waste time waiting for the conversion to complete, but rather just have it move on from there as per usual. At least on the 8Mhz Pro Mini, doing that gets the first "extended conversion" speed bump entirely out of the way before the regular sampling starts:
0. raw relative bandgap voltage=312 Elapsed time=0uS
1. raw relative bandgap voltage=325 Elapsed time=120uS deltaT=120uS
2. raw relative bandgap voltage=328 Elapsed time=240uS deltaT=120uS
3. raw relative bandgap voltage=329 Elapsed time=360uS deltaT=120uS
4. raw relative bandgap voltage=330 Elapsed time=480uS deltaT=120uS
5. raw relative bandgap voltage=330 Elapsed time=600uS deltaT=120uS
6. raw relative bandgap voltage=330 Elapsed time=720uS deltaT=120uS
7. raw relative bandgap voltage=330 Elapsed time=840uS deltaT=120uS
8. raw relative bandgap voltage=330 Elapsed time=960uS deltaT=120uS
9. raw relative bandgap voltage=330 Elapsed time=1080uS deltaT=120uS
On the 16Mhz Moteino, the results at first don't appear to be as good:
0. raw relative bandgap voltage=388 Elapsed time=0uS
1. raw relative bandgap voltage=363 Elapsed time=156uS deltaT=156uS
2. raw relative bandgap voltage=352 Elapsed time=268uS deltaT=112uS
3. raw relative bandgap voltage=348 Elapsed time=380uS deltaT=112uS
4. raw relative bandgap voltage=347 Elapsed time=492uS deltaT=112uS
5. raw relative bandgap voltage=346 Elapsed time=604uS deltaT=112uS
6. raw relative bandgap voltage=346 Elapsed time=716uS deltaT=112uS
7. raw relative bandgap voltage=346 Elapsed time=828uS deltaT=112uS
8. raw relative bandgap voltage=346 Elapsed time=940uS deltaT=112uS
9. raw relative bandgap voltage=346 Elapsed time=1052uS deltaT=112uS
but I think that's probably because at 16Mhz it finishes with the rest of setup() before the conversion initiated at the start of setup completes. So, rather than resetting the "extended conversion" that's already in progress, I'm hypothesizing that the ADC makes the mcu wait (156-112=44uS) until the "extended conversion" completes before initiating a new sample. In real life, using the method of running on its 8Mhz internal resonator to gain the benefits of a 3.8uS wake-up, I expect the Moteino results will be the same as the 8Mhz Pro Mini above.
It's interesting that in the 8MHz case the bandgap reading is now increasing for the first few, rather than dropping as I would have expected with the charging cap scenario. This still appears to be the case in the 16MHz case so seems consistent with my expectations.
Tom
Good catch. It does seem strange, especially when you consider that the same pro mini in the earlier sketch (reply #28) had a falling voltage.
As a cross-check, I plugged a different pro mini into the same usb-to-ttl adapter as used by the Moteino, and it also demonstrates a rising voltage when running the most recent sketch. So, I don't know why these Pro Mini's are behaving differently in that respect, when running the latest version of the sketch.
Changing the subject, here's some food for thought: if on the Moteino I add a 330uSec delay before starting the regular sampling loop (but still having launched the extended conversion as before at the start of the setup() function), then over several trial runs the ADC appears to nail the final bandgap voltage on its first try every time:
0. raw relative bandgap voltage=346 Elapsed time=0uS
1. raw relative bandgap voltage=346 Elapsed time=116uS deltaT=116uS
2. raw relative bandgap voltage=346 Elapsed time=228uS deltaT=112uS
3. raw relative bandgap voltage=346 Elapsed time=340uS deltaT=112uS
4. raw relative bandgap voltage=346 Elapsed time=452uS deltaT=112uS
5. raw relative bandgap voltage=346 Elapsed time=564uS deltaT=112uS
6. raw relative bandgap voltage=346 Elapsed time=676uS deltaT=112uS
7. raw relative bandgap voltage=346 Elapsed time=788uS deltaT=112uS
8. raw relative bandgap voltage=346 Elapsed time=900uS deltaT=112uS
9. raw relative bandgap voltage=346 Elapsed time=1012uS deltaT=112uS
That leads me to wonder whether running the multiple samples without any pause at the beginning, as before, is of any benefit, or worse yet, may actually slow down the sampling capacitor from reaching its final settled voltage level.
[Edit: I don't know that 330uSec is the magic number that would work on all Moteino's in all conditions, but perhaps it is, or perhaps there is a more conservative number like, say, 400uSec, that is such a magic number, and where you come out ahead (at least on average) by using it. I really don't know, as I just now stumbled across this.]
Quote from: WhiteHare on April 15, 2016, 04:30:34 PM
It does seem strange, especially when you consider that the same pro mini in the earlier sketch (reply #28) had a falling voltage.
It's not a falling 'voltage', it's a falling reading on the bandgap due to a rising voltage on the ARef pin. That the reading is now rising on the first few at 8MHz is odd.
Quote
Changing the subject, here's some food for thought: if on the Moteino I add a 330uSec delay before starting the regular sampling loop (but still having launched the extended conversion as before at the start of the setup() function), then over several trial runs the ADC appears to nail the final bandgap voltage on its first try every time:
I don't understand where this 330uS delay is. It's between what and what?
Here's the sketch:
// number of microseconds for ADC to setttle before taking a measurement
#define ADC_SETTLE_MICROSECONDS 330
#define NUM_ADC_SAMPLES 10
uint16_t getRelativeBandgapVoltage() {
uint16_t rawBandgapMeasurement;
// Read bandgap voltage reference (~1.1V) against AVcc
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); //need to do this for every sample or just the first?
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA,ADSC));
rawBandgapMeasurement = ADCL; //get the low order bits of ADC
rawBandgapMeasurement |= ADCH<<8; //combine with high order bits of ADC
return rawBandgapMeasurement;
}
void setup() {
long timeInMicroseconds[NUM_ADC_SAMPLES];
uint16_t rbgv[NUM_ADC_SAMPLES]; // relative bandgap voltage
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
ADCSRA |= _BV(ADSC); // Convert
Serial.begin(115200);
delayMicroseconds(ADC_SETTLE_MICROSECONDS);
for (int i=0;i<NUM_ADC_SAMPLES;i++) {
timeInMicroseconds[i] = micros();
rbgv[i] = getRelativeBandgapVoltage();
}
for (int i=0;i<NUM_ADC_SAMPLES;i++) {
Serial.print(i);
Serial.print(F(". raw relative bandgap voltage="));
Serial.print( rbgv[i]);
Serial.print(F(" Elapsed time="));
Serial.print(timeInMicroseconds[i] - timeInMicroseconds[0]);
Serial.print(F("uS"));
if (i>0) {
Serial.print(F(" deltaT="));
Serial.print(timeInMicroseconds[i]-timeInMicroseconds[i-1]);
Serial.print(F("uS"));
}
Serial.println();
Serial.flush();
}
}
void loop() {
}
Forgive me for necro'ing this thread but picking up on WhiteHare's work, I've been able to get pretty good readings much faster by cranking up the ADC from 125kHz to 1Mhz. The datasheet says not to run it faster than 200kHz for 10-bit precision and then basically says if you run it at 1Mhz you don't lose much precision. Much more information here: http://www.microsmart.co.za/technical/2014/03/01/advanced-arduino-adc/
Actually, on my TH Nodes, I've been able to take battery voltage readings in 0 extra time! I wake up and start a conversion and rather than twiddling my thumbs waiting for the ADC to finish, I start issuing commands to my HTU21D. Right before going to sleep to wait for my first no hold temperature measurement I grab the results from the ADC. Issuing a command on the fastest possible I2C bus takes around 92us which is longer than the ADC needs for an extended measurement when the prescaler is cranked up to 32 instead of the default 128. Net result, I get my battery voltage for free except the extra current to keep the ADC on for 100us.
startT = micros(); // ==================== START THE CLOCK ====================
sbi(ADCSRA, ADSC); // start a conversion
issueCommand(WRITE_USER_REGISTER, ELEVEN_BIT_TEMP); // this conversation takes 88uS - plenty long enough for the ADC
issueCommand(TRIGGER_TEMP_MEASURE_NOHOLD,0);
Vcc = ADCL;
Vcc |= ADCH<<8;
//Vcc = 112296/Vcc; // 16-bit math takes way too long adding around 40us to this timed loop
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
readRaw(&data[0]);
issueCommand(WRITE_USER_REGISTER, EIGHT_BIT_RH);
issueCommand(TRIGGER_HUMD_MEASURE_NOHOLD,0);
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
readRaw(&data[2]);
stopTWI();
elapsed = micros()-startT; // ==================== STOP THE CLOCK ====================
Nice! Thank you for your follow-up post.
Maybe it's thanks to the atmega328p's now very fast 4usec wake-up from sleep, but I was finding the ADC a bit slow at getting up to speed.
Prior to sleeping the atmega, I am turning off the ADC with this:
ADCSRA = 0; // disable ADC
which is also how Nick Gammon does it. (https://www.gammon.com.au/forum/?id=11497)
Presently I'm using TomWS's method of reading the ADC until I get two of the same values in a row. It seems like a good approach. However, I'm finding that if the atmega328p doesn't do one or the other of the the below code snippets to reanimate the ADC immediately after the 4usec wake-up, then TomWS's method often seems to yield erroneous values.
I noticed that ADCSRA has the value 144 just prior to turning it off, so now I wake the ADC up with:
ADCSRA=144; //restore ADC settings from just prior to sleeping
as soon as the atmega328p awakens. That does seem to help.
Instead of that, I've seen code where some people do:
ADCSRA |= (1<<ADEN); //Power up the ADC
ADCSRA |= (1<<ADSC); //Start converting
At the moment, I'm not sure which is better.
What are you all doing in this respect?
144 is setting bits 7 (ADEN) and 4 (ADIF) which according to the datasheet is cleared by writing a logical 1 to this bit:
• Bit 4 – ADIF: ADC Interrupt Flag
This bit is set when an ADC conversion completes and the Data Registers are updated. The ADC Conversion
Complete Interrupt is executed if the ADIE bit and the I-bit in SREG are set. ADIF is cleared by hardware when
executing the corresponding interrupt handling vector. Alternatively, ADIF is cleared by writing a logical one to
the flag. Beware that if doing a Read-Modify-Write on ADCSRA, a pending interrupt can be disabled. This also
applies if the SBI and CBI instructions are used.
You are also setting the ADC clock div to 2 by zeroing out bits 2:0 so you are running your ADC at 8MHz assuming you are running the 328p at 16MHz anyway.
To make your code more readable I'd change your wakeup to:
ADCSRA |= (1<<ADEN) | (1<<ADIF); // Power up the ADC and reset the interrupt flag
EDIT: Thinking about it a little further, you are reading 144 just prior to sleeping because a conversion was finished. Setting the ADIF bit doesn't actually set it so just setting the ADEN bit should be completely equivalent to setting ADCSRA = 144.
Sorry, I failed to answer how I do it.
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define ENABLE_ADC cbi(PRR, PRADC); sbi(ADCSRA, ADEN); // Enable the ADC
#define DISABLE_ADC cbi(ADCSRA, ADEN); sbi(PRR, PRADC); // Disable the ADC to save power
Quote from: ChemE on February 07, 2017, 03:50:44 PM
You are also setting the ADC clock div to 2 by zeroing out bits 2:0 so you are running your ADC at 8MHz assuming you are running the 328p at 16MHz anyway.
I'm running the atmega328p at 8mhz on its internal resonator, because that's the only way I know of to wake it up from a deep sleep in less than 4usec. So, from your comment, I guess that means I'm running the ADC at 4Mhz then. However, that's still 4x faster than the 1Mhz maximum you seem to be recommending in your earlier post. Should I increase the prescaler and reduce it down to 1Mhz?
If you are getting accurate or at least repeatable results at 4MHz then I wouldn't bother slowing down. I ran at that speed because it was still fast enough to get a conversion started and completed in the time it takes to issue a command on the I2C bus. In my application I didn't have a need to clock the ADC faster since it was no longer rate limiting for me.
Just gave this a try and my readings all seem to take double the amount I see from WhiteHare,
Any ideas why this could be?
(I'm using WhiteHare's code on a Moteino modded to 8mhz, mixed with some other radio code that doesn't touch the ADC or anything)
0. raw relative bandgap voltage=333 Elapsed time=0uS
1. raw relative bandgap voltage=333 Elapsed time=416uS deltaT=416uS
2. raw relative bandgap voltage=333 Elapsed time=640uS deltaT=224uS
3. raw relative bandgap voltage=333 Elapsed time=864uS deltaT=224uS
4. raw relative bandgap voltage=333 Elapsed time=1088uS deltaT=224uS
5. raw relative bandgap voltage=333 Elapsed time=1312uS deltaT=224uS
6. raw relative bandgap voltage=332 Elapsed time=1536uS deltaT=224uS
7. raw relative bandgap voltage=333 Elapsed time=1760uS deltaT=224uS
8. raw relative bandgap voltage=333 Elapsed time=1984uS deltaT=224uS
9. raw relative bandgap voltage=333 Elapsed time=2208uS deltaT=224uS
When I made that post I was running the code on a 16Mhz Moteino. ;)
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.
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.
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
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
}
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
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.
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?
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.
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.
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.
@ChemE,
What is the cap value hung on Aref?
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.
@ChemE,
Isn't this a standard Moteino? Sorry, the thread is so long to plough all the way through.
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.
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?
@ChemE,
Ok, then if you have a look at the Moteino schematic you will see a cap hung off Aref for noise reduction.
Perky is correct - when switching on the internal reference, it can only rise as fast as that cap allows.
You can reduce the cap value (and hence the lag) somewhat without noise on Vref becoming too much of a problem.
Ah interesting. Another instance where a chemical engineering education is unhelpful :) So a smaller cap *might* be a means of further reducing the power consumption in that a battery voltage could be read faster provided it wasn't so much smaller as to make the line noisy. Interesting, thanks gents.
There's also the bandgap startup time to consider (40us typical, 70us max), that'll combine with the RC constant and could explain your 100us settling time.
Mark.
This Aref settling time may well have implications for low power. Those wishing to do fast ADC reads on wakeup and then go back to sleep straight away may have to do a 2 stage wakeup and use a timer to time the 100us settling time:
1) Wake up from deep sleep
2) Start bandgap and mux onto Aref
3) Start timer to interrupt after 100us
4) Go to sleep with only timer clock running
5) Wake up from timer interrupt and read ADC
6) Go back to deep sleep
Mark.
I follow you. This strategy vs. cooling my heels for 100us may well be testable via my ultracapacitor. Given that sensing battery voltage is a pretty fundamental part of most battery powered nodes, I plan to do some further investigating to see if some extra coulombs can be squeezed out.