3.3V Arduino to measure LiPo Voltage

Can the ADC of a 3.3V Arduino measure the full range of a single cell LiPo battery back (2.5V-4.4V)?

Out of the box, no. The Arduino can handle voltages up to Vcc.

And if you power the Arduino from the same battery you have another problem because the ADC is by default referenced to Vcc. So if you power the Arduino with 3,3V an ADC reading of 1023 means 3,3V. If you power the Arduino with 3,0V an ADC reading of 1023 means 3,0V. You can overcome this by referencing the ADC to the internal 1,1V. But that also means you need to drop the battery voltage to max 1,1V because that will then read 1023. But just use a simple voltage divider for that :slight_smile:

That was the info I was looking for. Very well written. You have highlighted a few aspects I did not/take into account.

The fuller picture is the Vcc is unregulated and the ADC would always return 1023. I will have to use the internal 1.1V reference and put a resistor divider in place. Since the LiPo is recharged in place, I will have to account for the charger voltage in the divider. I'm thinking 4:1, so for the case of 5.5V in, it will be 1.1V out.

The charge voltage of a LiPo should not be higher then the fully charged voltage... So a max of 4,4V or 4,5V will do. And, while 1,1V is the max voltage it can read, the max allowable voltage to the Arduino is still Vcc :slight_smile:

The charger for the LiPo works like the power source on the Arduino UNO. It is either the charger input voltage or the LiPo voltage. The charge voltage to the battery is not the charger input voltage.

I understand. But during charging the battery voltage is never higher then the max battery voltage aka 4,4V.

How do I set the arduino to use the internal 1.1V reference? I'm not finding an examples (or I'm missing it)

septillion:
I understand. But during charging the battery voltage is never higher then the max battery voltage aka 4,4V.

Most LiPo batteries have AFAIK a limit if 4.2volt.

adwsystems:
How do I set the arduino to use the internal 1.1V reference? I'm not finding an examples (or I'm missing it)

The Arduino can also measure VCC internally.

Leo..

Mm, you might want to boost your Google skills a bit... It's the first hint when I google for "arduino analog reference" or "arduino 1,1v reference"....

@Wawa, you're right. Just (mentally) copied the 4,4V @adwsystems mentioned.

septillion:
Mm, you might want to boost your Google skills a bit... It's the first hint when I google for "arduino analog reference" or "arduino 1,1v reference"....

Google is all relative. Easier to search for what you know you are looking for. Any how. Question. If I am using the internal 1.1V reference and forget and apply a 0-5V signal, what happens? Rail (1023) or smoke?

The analogue input can still take up to VCC+0.5volt (not the same as 5volt).
Anything higher than ~1.1volt will just return 1023.
Leo..

I have a sketch that was working using the default analogReference. I took the sketch, add the analogReference(INTERNAL) statement to the setup() routine. The input for testing was and is a 10k po to ground. Before I added the statement the voltage out was 0-5. After adding the statement it is still 0-5V. Based on the previous posts, I was expecting 0-1.1 and to reach 1.1V on just over 20% of the 270 degree pot rotation (the pot in both cases was wired to 5.0V Vcc from the arduino).

What gives?

How do I tell which reference the program is using? I don't mean by looking at the code, I mean by tying pin x to pin y I should expect z result from the same program.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

#define DEBUG_MODE 4

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

#define DEBOUNCE_TIME 100
volatile boolean digital_input_state[14] = {0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0};
boolean invert_digital_input_state[14] = {1,0, 0,0, 0,0, 0,0, 0,0, 0,1, 1,0};

#define START_STOP_PIN 11
#define BATTERY_PIN 0
#define LOAD_PIN 1

#define STOPPED 0
#define COMPLETE 1
#define RUNNING 2
volatile byte Run_Status;

#define adc_sens 1.0*5.0/1024.0 // voltage value of one ADC step
#define BATTERY_PIN 0
#define CURRENT_PIN 1
int adcReading;
boolean adcStarted=false;
boolean adcDone=false;
byte adc_pin;

volatile byte sec_pulse;

char line_text_0[21], line_text_1[21], line_text_2[21], line_text_3[21];
float vin;

unsigned int battery_voltage, current_draw;
float load = 1.0;

typedef struct elapsed_time_struct
 {
  unsigned int hour;
  unsigned int minute;
  unsigned int second;
  unsigned int msec;
 } Elapsed_Time;
Elapsed_Time run_time;

boolean debounce_task(byte pin_number, byte port_status, unsigned long time_now)
 {
  static unsigned long last_time[14] = {0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0};
  boolean pin_reading;
  boolean state_changed = false;
  byte tpin_number;
  boolean pin_status=0;

  tpin_number = pin_number;
  if (tpin_number >= 8)
   { tpin_number -= 8; }

  if (bitRead(port_status, tpin_number))
   { pin_status = true; }
 
  if (invert_digital_input_state[pin_number])
   { pin_reading = !pin_status; }
   else
    { pin_reading = pin_status; }

  if (pin_reading != digital_input_state[pin_number])
   {
    if ((time_now - last_time[pin_number]) > DEBOUNCE_TIME)
     {
      digital_input_state[pin_number] = pin_reading;
      state_changed = true;
      last_time[pin_number] = time_now;
     }
   }
  return (state_changed); 
 }

ISR (PCINT0_vect) // Pins D8 to D13
 {
  unsigned long current_time;
  current_time = millis();

  if (debounce_task(START_STOP_PIN, PINB, current_time) && (digital_input_state[START_STOP_PIN] ==1))
   {
    if (Run_Status == STOPPED)
     {
      Reset_Counts();
      Run_Status = RUNNING;
     }
     else if (Run_Status == RUNNING)
     {
      Run_Status = STOPPED;
     }
   }
 }

ISR (ADC_vect)
{
  byte low, high;

  low = ADCL;
  high = ADCH;

  adcReading = (high << 8) | low;
  adcDone = true;
}  // end of ADC_vect

ISR(TIMER2_COMPA_vect)
 {
  static unsigned int msec_count = 0;
  static byte count=0;

  msec_count++;
  if (msec_count >= 100)
   {
    count++;
    sec_pulse=count;
    if (count>=4)
     { count = 0; }
    msec_count -= 100;
   }
 }

void clear_display()
 {
  char line_text[17];
  sprintf(line_text, "%16s", "");
  lcd.setCursor(0, 0);            // move to the begining of the first line
  lcd.print(line_text);
  lcd.setCursor(0, 1);            // move to the begining of the first line
  lcd.print(line_text);
 }

void Reset_Counts()
 {
  run_time.hour=0;
  run_time.minute=0;
  run_time.second=0;
  run_time.msec=0;
 }

void DAC_Write(float Output_Value)
 {
  int DAC_Value;
  byte b1, b2;
 
  DAC_Value = (Output_Value/5.0)*4096;
  if (DAC_Value>4096) 
   { DAC_Value =4096; }
   
//  DAC_Value=2048;
  b1 = byte(DAC_Value / 16);
  b2 = byte(DAC_Value % 16);

  if (DEBUG_MODE == 4)
   {
    Serial.print ("Output Value:");
    Serial.print (Output_Value);
    Serial.println();

    Serial.print ("DAC Value:");
    Serial.print (DAC_Value);
    Serial.println();
   }

  Wire.beginTransmission(0x60);
  Wire.write(64);
  Wire.write(b1);
  Wire.write(b2 << 4); // Needed twice, since the 4 lowest bits (of 12) are in the fourth byte
  Wire.endTransmission();
 }

void display_status(char line_text[])
 {
  char temp_text[21];
  
  switch (Run_Status)
   {
    case STOPPED :
                   sprintf(temp_text, "STOPPED");
                  break;
    case COMPLETE :
                   sprintf(temp_text, "COMPLETE");
                  break;
    case RUNNING :
                   sprintf(temp_text, "RUNNING");
                  break;
    default :
                  break;
   }
  sprintf(line_text, "%-9s %02d:%02d:%02d", temp_text, int(run_time.hour), int(run_time.minute), int(run_time.second));
  sprintf(line_text, "%-20s", line_text);
 }
 
void display_voltage(char line_text[])
 {
  sprintf(line_text, "Voltage: %2d.%02d V", int(battery_voltage/100), int(battery_voltage%100));
  sprintf(line_text, "%-20s", line_text);
 }
 
void display_current(char line_text[])
 {
  sprintf(line_text, "Current: %2d.%02d mA", int(current_draw/100), int(current_draw%100));
  sprintf(line_text, "%-20s", line_text);
 }

void display_capacity(char line_text[])
 {
  sprintf(line_text, "Rating : %2d.%02d mA-Hr", int(run_time.minute), int(run_time.second));
  sprintf(line_text, "%-20s", line_text);
 }

void setup()
 {
  unsigned long current_time; 
  current_time = millis();

  Serial.begin (115200);

  lcd.begin(20,4);               // start the library
  lcd.setCursor(0,0);             // set the LCD cursor position 
  lcd.print("Test Sketch");
  lcd.setCursor(0,1);             // set the LCD cursor position 
  lcd.print("19Feb2017");

 // Setup Timer 2 interrupt at 1mSec 
  TCCR2A = 0;          // normal operation
  TCCR2B = bit(WGM12) | bit(CS10) | bit (CS11);   // CTC, scale to clock / 1024
  OCR2A =  249;       // compare A register value (1000 * clock speed / 1024)
  TIMSK2 = bit (OCIE1A);             // interrupt on Compare A Match

  Reset_Counts();
  Run_Status=STOPPED;
  vin = 0; 
  sec_pulse = 0;

  Wire.begin();
  adc_pin = BATTERY_PIN;

  DAC_Write(2.0);

  analogReference(INTERNAL);
  
  do
   { } while (millis()-current_time < 5000);
  clear_display();
  lcd.setCursor(0,0);             // set the LCD cursor position 
 }

void loop()
 {
  // put your main code here, to run repeatedly:

    switch (sec_pulse)
     {
      case 1 :
    display_status(line_text_0);
     lcd.setCursor(0, 0);            // move to the begining of the first line
     lcd.print(line_text_0);
                        sec_pulse=0;
    adcStarted = false;
     break;

      case 2 :
                       display_voltage(line_text_1);
                        lcd.setCursor(0, 1);            // move to the begining of the second line
                        lcd.print(line_text_1);
                        sec_pulse=0;
    adcStarted = false;
     break;

      case 3 :
                       display_current(line_text_2);
                        lcd.setCursor(0, 2);            // move to the begining of the second line
                        lcd.print(line_text_2);
                        sec_pulse=0;
    adcStarted = false;
     break;

      case 4 :
                       display_capacity(line_text_3);
                        lcd.setCursor(0, 3);            // move to the begining of the second line
                        lcd.print(line_text_3);
                        sec_pulse=0;
    adcStarted = false;
     break;
      default :
       break;
     }  

  // if we aren't taking a reading, start a new one
  if (!adcStarted)
   {
    adcStarted = true;
    // start the conversion
    ADMUX = bit (REFS0) | (adc_pin & 0x07);
    ADCSRA |= bit (ADSC) | bit (ADIE);
   }
  if (adcDone==true)
   {
    adcDone = false;
    vin = (adcReading * 5.0 / 1024.0);
    switch (adc_pin)
     {
      case BATTERY_PIN :
                       battery_voltage = (unsigned int) (vin *100.0);
                        adc_pin = CURRENT_PIN;
                       break;
      case CURRENT_PIN :
                       current_draw = (unsigned int) (vin / load *100.0);
                        adc_pin = BATTERY_PIN;
                       break;
      default :
              break;
     } 
   }
 }

vin = (adcReading * 5.0 / 1024.0);

I thought you had a 3.3volt Arduino (post#0).

That line will always make vin 4.995volt (if your (unknown) Arduino has a 10-bit A/D).

Using 1.1volt Aref only means that max A/D value is already reached at 1/3 of the pot rotation,
assuming you have a lin pot connected to 3.3volt and ground.
Connecting the pot to 5volt and reading it with a 3.3volt MCU might already have destroyed the pin.
Leo..

Wawa:
I thought you had a 3.3volt Arduino (post#0).

Sorry, I jumped back to a 5V Arduino UNO for breadboard testing this new (to me) concept.

Wawa:
vin = (adcReading * 5.0 / 1024.0);

That line will always make vin 4.995volt (if your (unknown) Arduino has a 10-bit A/D).

Why do you think that?

Wawa:
Using 1.1volt Aref only means that max A/D value is already reached at 1/3 of the pot rotation,
assuming you have a lin pot connected to 3.3volt and ground.

That is my understanding. The pot is only for proof of concept(s), it will be a high resistance resistor divider in the end to minimize current usage.

I found the issue myself.

ADMUX = bit (REFS0) | (adc_pin & 0x07);

clears the bits set by analogReference(). In short the interrupt ADC method is not "directly" compatible with analogReference(). (you can make it work but more steps are needed that purported in the reference section)

adwsystems:
Why do you think that?

Rather obvious.

1023 * 5.0 / 1024 = .....

Wawa:
Rather obvious.

1023 * 5.0 / 1024 = .....

Not really, since adcReading is not a fixed value and not always 1023.

adwsystems:
I have a sketch that was working using the default analogReference. I took the sketch, add the analogReference(INTERNAL) statement to the setup() routine. The input for testing was and is a 10k po to ground. Before I added the statement the voltage out was 0-5. After adding the statement it is still 0-5V. Based on the previous posts, I was expecting 0-1.1 and to reach 1.1V

Think again.

A 10-bit A/D will reach a max of 1023, but it's up to you what you want to do with that.

That formula should hold 1.1xxx (or whatever Aref is), and the inverse of the voltage divider you have used.
Leo..

Which part are you debating?

A) That the ADC doesn't run 0-5V? OK it doesn't it's maximum value is 4.995V. I'll agree.

B) That the program did not stop incrementing the value after 60deg rotation? Fact. It did not. The program posted ran from 0-5V (ok 0-4.995V if you want to be technical) in 270 degrees of rotation (it is a single turn 270 degree potentiometer). It did not stop incrementing were 1.1V should be on the pot, about 60 degrees of rotation.

or C) should you be using 1023 as the divisor or 1024? Well, that's a debate for another thread. I'm sticking with 1024 (4.995V max) so the last bit doesn't work differently from all the others. That's all I'm saying on that.

C isn't a debate, 1024, anything else is just wrong :wink: