The curious case of the missing Volt

This is a weird one. I'm reading the voltages of individual cells in a 4S battery using a voltage divider connected to a single analogue pin and a series of optoisolators controlled by digital pins to feed the voltages to the divider in turns and read them in sequence.

The problem is, the readings are all correct up to the 4th cell, which is always off.

The weirdest thing is, the reading is always off to more or less the value of 1V (that's the calculated voltage, not the voltage on the analogue pin), so where other cells are giving (correct) readings in increments of about 4V (the battery is fully charged), the fourth cell increases the reading by only 3V. (I have tested all cells using a multimeter, the cell is also 4V, and the total voltage of the pack is 16V).

I've tried different resistance configurations for the voltage divider, tried bypassing the optoisolator and connecting directly, but still have the same issue. It seems to read correctly up to about 12V, and then instead of 16V, it's reading 15V!

Just wondered if anyone has had any similar experiences...

hptic:
Just wondered if anyone has had any similar experiences...

No, because we simply have no idea whatsoever how you have connected anything or what code you are using. :cold_sweat:

Optoisolator outputs are photo-transistors, not perfect switches, they have significant leakage current, on-resistance and voltage drop which may be affecting your circuit, which we cannot see...

Sorry, here’s the code. In essence, arduino is reading the voltage divider correctly up to a certain voltage, and then seems to lose 1V, regardless of the resistor configuration of the voltage divider.
I’ve originally had this running on a Pro Mini 3.3V, but have since tried on a Nano and the result it the same. It does make me also think the maths is off somewhere, but can’t see where!
Don’t know how much use the photo will be in terms of wiring, I can draw up a diagram if that will help?

main.cpp:

#include <Arduino.h>
#include "InternalVoltage.h"
#define VOLTAGE_SENSOR A0
#define R1Value 21850
#define R2Value 4670

float voltages[4];
uint8_t switches[4];
uint8_t counter = 0;

InternalVoltage internalVoltage;

void setup() {
  internalVoltage.getInternalVoltage();
  Serial.begin(9600);
  Serial.println("START");
  Serial.print("Internal Voltage: ");
  Serial.println(internalVoltage.internalVoltage);
  for (uint8_t i = 0; i < 4; i++){
    switches[i] = i + 2;
    pinMode(i + 2, OUTPUT);
  }
  pinMode(A0, INPUT);
}

void loop() {
  digitalWrite(switches[counter], HIGH);
  // delay(50);
  
  float previous_cells = 0;
  
  for (uint8_t i = 0; i < counter; i++)
    previous_cells += voltages[i];

  float reading = internalVoltage.readVoltageDivider(VOLTAGE_SENSOR, R1Value, R2Value);
  
  voltages[counter] = reading - previous_cells;
  
  Serial.print(counter);
  Serial.print(" : ");
  Serial.print(internalVoltage.voltageDividerReading);
  Serial.print(" : ");
  Serial.print(reading);
  Serial.print(" = ");
  Serial.print(voltages[counter]);
  Serial.println();
  digitalWrite(switches[counter], LOW);
  counter++;
  if (counter > 3)
    counter = 0;
  delay(600);
}

internalVoltage.h:

#ifndef _InternalVoltage_h
#define _InternalVoltage_h

#include <Arduino.h>

class InternalVoltage{
	public:
		void getInternalVoltage();
		float readVoltageDivider(uint8_t pin, float R1, float R2);
		void setOffset();
		float internalVoltage = 5.0;
		int voltageDividerReading = 0;
	private:
		uint32_t readVcc();
		float decimalVoltage(float x, float in_min, float in_max, float out_min, float out_max);
		float offset = 0.0;
};

#endif

internalVoltage.cpp:

#include <Arduino.h>
#include "InternalVoltage.h"

/**
 * Public
 */
void InternalVoltage::getInternalVoltage(){
    internalVoltage = decimalVoltage(readVcc(), 0, 6000, 0, 6);
}

float InternalVoltage::readVoltageDivider(uint8_t pin, float R1, float R2){
  analogRead(pin); // 1st read switches pin to ADC but reading is unstable, esp. with high impedance voltage sources
  delay(50); // wait for the voltage to settle and then do the actual reading
  //float voltageReading = (getInternalVoltage() / 1023) * analogRead(pin);
  voltageDividerReading = analogRead(pin);
  float voltageReading = (internalVoltage / 1023) * voltageDividerReading;
  delay(10);
  return voltageReading / (R2 / (R1 + R2));
}

void InternalVoltage::setOffset(){
	
}

/**
 * Private
 */
uint32_t InternalVoltage::readVcc() {
	// 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__)
	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
	
	delay(10); // 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
	
	uint32_t result = (high<<8) | low;
	
	result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
	return result; // Vcc in millivolts
}

float InternalVoltage::decimalVoltage(float x, float in_min, float in_max, float out_min, float out_max)
{
	return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

MarkT:
Optoisolator outputs are photo-transistors, not perfect switches, they have significant leakage current, on-resistance and voltage drop which may be affecting your circuit, which we cannot see...

I am aware of that, and I have introduced delays in the code before reading the values to try and counteract that to a degree, however, the result is the same when I remove the optoisolators and connect the 4S (16V) to the voltage divider directly...

I’ve cleaned up the wiring and attached a new image to this post.

White is cell 1
Yellow is cell 2
Orange is cell 3
Red is cell 4

Which Arduino? What is your ADC value at 16V? What is your AREF voltage?

OK, here we go:
20200624_152258.jpg
A schematic might be easier to follow. :grinning:

JCA34F:
Which Arduino? What is your ADC value at 16V? What is your AREF voltage?

Pro Mini, but getting same result on Nano.
ADC reading depends on the voltage divider config used, but for a 22k/4k7 split using internally read supply voltage of 3.32V as reference it is reading as follows:
on cell 1 @ ~4.12 V => 226
on cell 2 @ ~8.24 V => 452
on cell 3 @ ~12.35 V => 677
on cell 4 @ ~16.46 V => 851

Paul__B:
OK, here we go:
20200624_152258.jpg
A schematic might be easier to follow. :grinning:

Thank you, I guess I should have figured out how to inline that image myself :grinning:

Converting your numbers I get
226/4.12 * 226 = 4.12

  • 452 = 8.24
  • 677 = 12.34
  • 851 = 15.51

Also looking at your divider 16.5 * 4.7/26.7 = 2.90V so well within the allowed input voltage range.

Without spending too long looking at your code, could there be an issue of number value exceeding its allowed range?