Measuring Arduino Battery voltage Low Power Mode

Hello. I am using an ArduinoMega 328 using the 8MHz internal clock powered by an 18650 lithium battery.

I want to monitor swimming pool temperature and levels and am trying to monitor voltage. I am sending these values to a monitorng station via radio (lora) about every hour.

I am using example codes furnished by Nick Gammon which have been modified for my special application.

The various additional components are;

Distance(A02yyuw)
Temperature (DS18B20)
Transmitter Lora Reyax RYLR896
LDO Adustable MCP1727 regulator:

The Arduno is powered directly by the battery while sleeping. When it wakes up, it turns on a MOSFET to enable the components, measure, transmit the values then disable the components before going back to sleep.

All measured values are correct EXCEPT the battery voltage which, from time to time show 4.4V instead of the normal 4.17V value.

I am not able to explain this value and I would be interested in any suggestions. Below is my code.

Thank you

[sleepTest.ino|attachment](upload://eejBfXrCQddZujpzpLyBoHxMj0F.ino) (8.7 KB)
/*
	Simple, in-progress library to allow easy reading of current battery (VCC) voltage
	so that a timely warning can be given to user that it's time to get new batteries
	before these ones expire!
		
	See https://github.com/RalphBacon/Arduino-Battery-Monitor
	See https://www.youtube.com/ralphbacon video #160
	
*/

#include "Arduino.h"
#include "BatteryVoltageReader.h"

void BatteryVoltageReader::begin() {
	this->startInternal1V1VoltageReference();
}

void BatteryVoltageReader::startInternal1V1VoltageReference() {
	// Internal 1.1V reference, works instantly (compare to below)
	ADMUX = bit (REFS0) | bit(REFS1);

	// Above is the equivalent of:

	/*
	 // Request INTERNAL 1v1 reference voltage (for ATMega328P)
	 analogReference (INTERNAL);

	 // That request is not honoured until we read the analog pin
	 // so force voltage reference to be turned on
	 analogRead (A0);
	 */
}

uint16_t BatteryVoltageReader::readVCC() {
	//Partly adapted from http://www.gammon.com.au/adc (Nick Gammon)

	// Adjust this value to your boards specific internal BandGap voltage x1000
	//const long InternalReferenceVoltage = 1095L; // Here I measured 1.095 volts on pin 21
	const long InternalReferenceVoltage = 1067L; // Bill I measured 1.067 volts on pin 21	
	//Taken from https://forum.arduino.cc/index.php?topic=331178.msg2285210#msg2285210
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
     // For mega boards
        // REFS1 REFS0          --> 0 1, AVcc internal ref. -Selects AVcc reference
        // MUX4 MUX3 MUX2 MUX1 MUX0  --> 11110 1.1V (VBG)         -Selects channel 30, bandgap voltage, to measure
     ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR)| (0<<MUX5) | (1<<MUX4) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);  
#else
     // For 168/328 boards
        // REFS1 REFS0          --> 0 1, AVcc internal ref. -Selects AVcc external reference
        // MUX3 MUX2 MUX1 MUX0  --> 1110 1.1V (VBG)         -Selects channel 14, bandgap voltage, to measure
     ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
#endif

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

	// Start a conversion
	ADCSRA |= _BV(ADSC);

	// Wait for conversion to complete
	while (((ADCSRA & (1 << ADSC)) != 0)) {
		;
	}

	// Scale the value - calculates for straight line value. +5L gives us the fractional rounding.
	unsigned int results = (((InternalReferenceVoltage * 1024L) / ADC) + 5L) / 10L;
	return results;
}#ifndef BatteryVoltageReader_h_
#define BatteryVoltageReader_h_

#include "Arduino.h"
class BatteryVoltageReader {
public:
	void begin();
	uint16_t readVCC();

private:
	void startInternal1V1VoltageReference();

protected:


};
#endif

The nominal voltage for a Li-ion is 3.7V.
It will vary between 4.2V and 0 as it discharges.
What is Vref for the ADC?
Is there a voltage divider connected to the battery?

Sorry again the correct main code is here

//This code will work on standalone 3.3V ATMega328p with 8 MHZ internal oscillator, 
//loaded with program with IDE for board "MiniCore"
#define DEBUG //use ftdi 5V select jumper
#include "BatteryVoltageReader.h" //for battery monitoring
BatteryVoltageReader BVR;

#include <avr/sleep.h>
#include <avr/wdt.h>
#ifdef DEBUG
#define NSLEEPS 4 //sleeps for NSLEEPS*8 seconds.
#else
#define NSLEEPS 450 //sleeps for NSLEEPS*8 seconds. NSLEEP=450 for 1 hour nap
#endif

#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 9 //yellow 15 on DIL;wires rby
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

#include <SoftwareSerial.h>
SoftwareSerial lora(2,3);//Rx,Tx;4,5 on DIL;5-->ReyaxRx
//setup lora address AT+ADDRESS=2 (piscine=2;jardin=3)
//global variables
float Celsius=00.00;
int cntr=0;
int led=5;//11 on DIL
int enbLoad=6;//12 on DIL
unsigned long rxcrc;

//---------- variables for distance measurement --------------
#define DISTMEASTIME 1000UL //could be as low as 300ms according to A02yyuw spec.
SoftwareSerial mySerial(12,11); //Rx,Tx on DIL;18,17
//DIL17Tx-->blueWire-->A02yyuwRx
//DIL18Rx-->greenWire-->A02yyuwTx
//connector layout r,b,blue,g
enum statesRX
{
    ST_RX_HEADER=0,
    ST_RX_DATA   
};
unsigned char data[4]={};
float Distance=0.0;

void setup () 
{
  pinMode(led, OUTPUT);
  pinMode(enbLoad, OUTPUT);
  digitalWrite(enbLoad,HIGH);
  sensors.begin();
#ifdef DEBUG
  Serial.begin(9600);
#endif
  lora.begin(9600);
  mySerial.begin(9600);
  BVR.begin(); //for battery monitoring
#ifdef DEBUG
  digitalWrite(led, HIGH); 
#endif
  delay(100); //for led display
  digitalWrite(led, LOW); 
  digitalWrite(enbLoad,LOW);
}

void loop () 
{
  chkCnt ();
  // disable ADC
  ADCSRA = 0;  
  // clear various "reset" flags
  MCUSR = 0;     
  // allow changes, disable reset
  WDTCSR = bit (WDCE) | bit (WDE);
  // set interrupt mode and an interval 
  WDTCSR = bit (WDIE) | bit (WDP3) | bit (WDP0);    // set WDIE, and 8 seconds delay
  wdt_reset();  // pat the dog
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);  
  noInterrupts ();           // timed sequence follows
  sleep_enable();
  // turn off brown-out enable in software
  MCUCR = bit (BODS) | bit (BODSE);
  MCUCR = bit (BODS); 
  interrupts ();             // guarantees next instruction executed
  sleep_cpu ();  
  // cancel sleep as a precaution
  sleep_disable();
} // end of loop

void chkCnt()
{
  if (cntr < NSLEEPS-1) cntr++;
  else
  {
    cntr=0;
    ADCSRA|=(1<<7);//re-enable ADC
    wakeCallback();
    ADCSRA&=~(1<<7);//disable ADC
  }
}  
void wakeCallback()
{
  char str_Celsius[8],str_Distance[8],crcinput[16],values[30],cmd[60]; 
  uint16_t batVolts;
  digitalWrite(enbLoad,HIGH);
#ifdef DEBUG
  digitalWrite(led, HIGH);
#endif   
  delay(100); //for led display           
  digitalWrite(led, LOW);  
  mySerial.listen();
  Distance=measureDistance(Distance); //delay of DISTMEASTIME time
  lora.listen(); 
  sensors.requestTemperatures();
  Celsius = sensors.getTempCByIndex(0); 
  dtostrf(Celsius,1,2,str_Celsius);
  dtostrf(Distance,1,2,str_Distance);
  sprintf(crcinput,"%s%s",str_Celsius,str_Distance);
  rxcrc=crc32b(crcinput);                 
  batVolts = BVR.readVCC(); //check battery level
  sprintf(values,"%s,%s,%lu,%d",str_Celsius,str_Distance,rxcrc,batVolts);
  sprintf(cmd,"%s%d,%s","AT+SEND=1,",strlen(values),values);
#ifdef DEBUG
  Serial.print(Celsius);
  Serial.print("dg,");
  Serial.print(Distance);
  Serial.print("cm,");
  Serial.print(batVolts);
  Serial.println("V");
//  Serial.println("cmd:");
//  Serial.println(cmd);
#endif
  for (int i=0;i<3;i++) //send several times to be sure but if loop delay <=1.5sec, not all messages are received!!
  {
    lora.println(cmd);
    delay(2000);
  }
#ifdef DEBUG
  digitalWrite(led, HIGH); 
#endif 
  delay(100);  //for led display                
  digitalWrite(led, LOW);  
  digitalWrite(enbLoad,LOW);  
}
// watchdog interrupt
ISR (WDT_vect) 
{
   wdt_disable();  // disable watchdog
}  // end of WDT_vect
unsigned long  crc32b(char message[]) 
{
   int i, j;
   unsigned int byte; 
   unsigned long crc, mask;

   i = 0;
   crc = 0xFFFFFFFF;
   while (message[i] != 0) {
      byte = message[i];            // Get next byte.
      crc = crc ^ byte;
      for (j = 7; j >= 0; j--) {    // Do eight times.
         mask = -(crc & 1);
         crc = (crc >> 1) ^ (0xEDB88320 & mask);
      }
      i = i + 1;
   }
   return ~crc;
}

float measureDistance(float Distance)
{
  uint16_t distance; //in cm
  uint8_t stateRX =ST_RX_HEADER,rxIdx,ch,sum,i;
  unsigned long measuremillis=DISTMEASTIME,startmillis=millis();
  float sum_Distance=0.0;
  int iter=0;
  while ((millis()-startmillis)<measuremillis)
  {       
    while ( mySerial.available() > 0 )
    {
      ch = mySerial.read(); 
      switch( stateRX )
      {
        case    ST_RX_HEADER:
          if( ch == 0xff )
          {
            rxIdx = 0;
            data[rxIdx++] = ch;
            stateRX = ST_RX_DATA;              
          }//if (ch==0xff)           
          break; 
        case    ST_RX_DATA:
          data[rxIdx++] = ch;
          if( rxIdx == 4 )
          { 
            sum = 0;
            for( i=0; i<3; i++ ) sum = sum + data[i];
            if( sum == data[3] )
            {
              distance = ((uint16_t)data[1] << 8) + data[2];
              if( distance > 30 )
              {
                //Distance=((float)distance)/10.;
                sum_Distance+=((float)distance)/10.;
                iter++;
#ifdef DEBUG
                Serial.print("distance = ");
                Serial.print(distance);
                Serial.println(" mm, ");
                //Serial.print(Distance);
                //Serial.println(" cm");
#endif                
              }//if( distance>30 )
#ifdef DEBUG
              else  Serial.println("Distance below lower limit.");
#endif             
            }//if( sum == data[3] )
#ifdef DEBUG
            else Serial.println("Msg checksum error.");
#endif
            stateRX = ST_RX_HEADER;
            while (mySerial.available()>0) mySerial.read(); //MT the buffer (if any)
          }//if( rxIdx == 4 )
          break;
      }//switch 
    }//while ( mySerial.available() > 0 )
  }//while ((millis()-startmillis)<measuremillis)
  if (iter>0) Distance=sum_Distance/(float)iter; //return the avg. Distance or 0.0
  return Distance;
}

It's a fully charged battery and I measure 4.17 with my voltmeter also

I can help if you answer my questions.

// Adjust this value to your boards specific internal BandGap voltage x1000
//const long InternalReferenceVoltage = 1095L; // Here I measured 1.095 volts on pin 21
const long InternalReferenceVoltage = 1067L; // Bill I measured 1.067 volts on pin 21

"The Arduno is powered directly by the battery"

Ok so you are using the internal 1.1v reference. So there must be a voltage divider circuit somewhere. Can you show the circuit.

I can but it's a file *.txt. I'm not very good at manipulating on the forum. How can I send it to you correctly?

Just upload it. Click the icon with the up-arrow.

schemaSleepTestTempWaterLvl.txt (3.3 KB)

I think you should wait a little after you re-enable the ADC before you call wakeCallback(). the internal reference may not be stabilized. So try a 100ms delay.
It might also be a good idea to call batVolts = BVR.readVCC() twice and just ignore the first call

ok done.
I'll do about 30 tests.
Back later.
Thanks

ok after > 30 tests. Stable value now but getting 428 from the BVR instead of measured value of 4.15 on my voltmeter. When I use my ftdi adapter (5.06V), I get 506 from the BVR too. Maybe a decoupling problem? I don't decouple pin 21(Aref) ? Only Vcc (pins 7,20). I will continue testing.


ok after 60 tests and changing components (see attached results on chip2).

Also see more results in table below:

Vcc, BVR chip1(Vref=1.067), BVR chip2(Vref=1.100)

3.70 375 371
4.15 428 416
5.06 506 503

My conclusions:

  1. Satisfying results with delay between enabling ADC and BVR.readVCC() twice.
  2. chip1 has a bad reference voltage
  3. chip1 has a non-linear behavior for BVR.readVCC()

That is a must, You need at least a 0.1uF cap there, otherwise your 1.1V reference voltage will be noisy

ok thanks

Can you please post a copy of your circuit, a picture of a hand drawn circuit in jpg, png?
Hand drawn and photographed is perfectly acceptable.
Please include ALL hardware, power supplies, component names and pin labels.

It will be much easier to draw and layout this way.

Thanks.. Tom.. :smiley: :+1: :coffee: :australia:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.