Using Adafruit_ADS1115/Wire object in a custom class

Hello, I am trying to prototype a Battery Management System using the Arduino and an ADS1115. I am using the Adafruit_ADS1x15 library to interface with the T.I ADS1115 IC.

My code is set up into 3 files, CurrentController.ino (main file), Battery.h and Battery.cpp the last two which are my Battery class. I will now show my code below.
CurrentController.ino:

/*
Name: CurrentController.ino
Created: 5/23/2018 5:14:18 PM
Author: tomea
*/
#include <Servo.h>
#include "Battery.h"

// global variables
const float PD_FACTOR = 3.504943957968f; // Uses resistor divider in ratio ~3.55. This allows for error in resistors and ADC.
bool shouldSpin = 0;
int incomingByte;
int escVal = 5;
// Current lookup table. First column is current, second is ESC value.
float iLUT[22] = {
 0, 10,
 2.37, 1100,
 7.11, 1200,
 12.71, 1300,
 17.95, 1400,
 23.04, 1500,
 27.56, 1600,
 31.76, 1700,
 37.42, 1800,
 47.42, 1900,
 49.06, 2000
};

// function declarations
void readSerial();
void sendData();
int getESC(float current);
int setESC(int value);

// Class declaration
Servo esc;
Battery battery(3.504943957968f, 1000, A1, A0);

// the setup function runs once when you press reset or power the board
void setup() {
 Serial.begin(9600);
 Serial.println("Started");
 delay(5000); // RUNAWAY TIME!!! :p
}

// the loop function runs over and over again until power down or reset
void loop() {
 readSerial();
 setESC(escVal);
 battery.update();
 sendData();
}

// Returns an ESC PWM value which draw approximately the current in the function parameter.
// Use the LUT and interpolates between those values in the LUT.
int getESC(float current) {
 int min = 0;
 int max = 18;
 for (int i = 0; i < 19; i += 2) {
 if (iLUT[i] == current) {
 return iLUT[i + 1];
 }
 else if (iLUT[i] > current) {
 max = i;
 min = i - 2;
 return map(current, iLUT[min], iLUT[max], iLUT[min + 1], iLUT[max + 1]);
 }
 else if (current < iLUT[0]) {
 max = 2;
 min = 0;
 return map(current, iLUT[min], iLUT[max], iLUT[min + 1], iLUT[max + 1]);
 }
 }
}

// Outputs the requested PWM signal on pin 9 to the ESC.
int setESC(int value) {
 if (shouldSpin) {
 esc.writeMicroseconds(value);
 }
 else {
 esc.writeMicroseconds(1000); // 1000 = off / idle
 }
}

// Sends voltage and current data via UART
// TODO: Send value being written to ESC with this data
void sendData() {
 Serial.print(battery.voltage, 4);
 Serial.print(",");
 Serial.print(battery.current, 4);
}

// Reads byte stored in UART buffer and if it corresponds to a command does an action.
// 83  = S (Start the motor)
// 115 = s (Stop the motor)
// 43  = + (Increase ESC output by 5%
// 45  = - (Decrease ESC output by 5%
void readSerial() {
 // check is Serial data available
 if (Serial.available() > 0) {
 // read incoming byte
 incomingByte = Serial.read();
 if (incomingByte == 83)
 shouldSpin = 1;
 if (incomingByte == 115)
 shouldSpin = 0;
 if (incomingByte == 43) {
 escVal += 5;
 //Serial.println(val);
 if (escVal > 100)
 escVal = 100;
 }
 if (incomingByte == 45) {
 escVal -= 5;
 if (escVal < 0)
 escVal = 0;
 }
 }
}

Battery.h:

#pragma once
#if defined(ARDUINO) && ARDUINO >= 100
 #include "arduino.h"
#else
 #include "WProgram.h"
#endif
#include <Adafruit_ADS1015.h>

class Battery
{
public:
 const float pdFactor;
 float voltage, current;
 int averagingWindow, voltagePin, currentPin;
 Battery(float pdFactor, int averagingWindow, int voltagePin, int currentPin);
 void update();
private:
 Adafruit_ADS1115 ads1115;
 int voltage_adc, current_adc, current_v;
 float currentScale;
 void getData();
};

Battery.cpp:

#include "Battery.h"


// Initilisation function for battery class.
// Params:
// float pdFactor      - factor to convert divided voltage to actual voltage. Needs to be measured manually.
// int averagingWindow - How many samples to use when averaging
// int voltagePin      - Pin number for reading voltage (Must be an analog pin)
// int currentPin      - Pin number for reading current (Must be an analog pin)
Battery::Battery(float pdFactor, int averagingWindow, int voltagePin, int currentPin) : pdFactor(pdFactor), averagingWindow(averagingWindow), voltagePin(voltagePin), currentPin(currentPin) {
 update();
 currentScale = 0.5f / current_v;
 ads1115.begin();
}

// Updates voltage and current variables.
void Battery::update() {
 getData();
}

// Averages ADC data of a window of averagingWindow size, and then updates voltage and current class variables.
void Battery::getData() {
 Serial.println("test");
 ads1115.readADC_SingleEnded(0);
 // averaging loop
 for (int i = 0; i <= averagingWindow; i++) {
 voltage_adc += analogRead(voltagePin);
 current_adc += analogRead(currentPin);
 }

 // convert ADC reading into voltage
 // voltage_adc and current_adc are being converted to floats to hopefully get better accuracy from this calculation. Untested!!
 voltage = ((float)voltage_adc / averagingWindow) * (5.0 / 1023.0) * pdFactor;
 current_v = ((float)current_adc / averagingWindow) * (5.0 / 1023.0) * currentScale;

 // reset ADC variables
 voltage_adc = 0;
 current_adc = 0;

 // Voltage range checks
 // --------------------
 // if ACS770 voltage < 0.5 something has gone wrong, so clamp to 0.5V if voltage goes below
 if (current_v < 0.5f) {
 current_v = 0.5f;
 }
 // if battery voltage goes below 10V (assuming its a 4S battery) something has gone wrong.
 if (voltage < 10.0f) {
 //Serial.println("Battery voltage below 10V. Check connections.");
 }
 // if battery voltage goes below 0V something has gone wrong, so clamp to 0V if it goes below. ADC is broken if this goes below 0.
 if (voltage < 0.0f) {
 //Serial.println("Battery voltage below 0V. Something has gone wrong!");
 voltage = 0.0f;
 }

 // convert current sense voltage into current using mV/A factor found in ACS770 datasheet.
 // Take away 0.5 from current sense voltage as 0.5V is voltage at 0A.
 current = (current_v - 0.5f) / 40e-3;
}

This code seems to break the Arduino. The Serial.println(“Started”); statement in the setup
function does not run, and the TX L.E.D on the board does not illuminated when there is no serial connection to the board. This makes me think that my code has caused a problem with the Arduino backend, potentially a bad ISR?

Please give me advise on my issue on whether it is a limitation of the Wire library, or if I have programmed this wrong.

Many thanks,

Tom

public:
 const float pdFactor;
 float voltage, current;
 int averagingWindow, voltagePin, currentPin;

NONE of those should be public. Provide getters for values that you think the public needs access to.

Battery battery(3.504943957968f, 1000, A1, A0);

Think about all the things that are supposed to happen as a result of this.

Serial.print() before Serial.begin() is silly. So is ads1115.readADC_SingleEnded() before ads1115.begin().

Your constructor is doing WAY too much, and in the wrong order.

By the way, you have a misconception about the accuracy of a value that you can store in a float. 3.504943957968f is NOT a reasonable initial value.

I have removed the Serial.println in Battery::getData() and changed the order so ads1115.begin() is being called before ads1115.readADC_SingleEnded(0). Here is my new class:

#include "Battery.h"


// Initilisation function for battery class.
// Params:
// float pdFactor      - factor to convert divided voltage to actual voltage. Needs to be measured manually.
// int averagingWindow - How many samples to use when averaging
// int voltagePin      - Pin number for reading voltage (Must be an analog pin)
// int currentPin      - Pin number for reading current (Must be an analog pin)
Battery::Battery(float pdFactor, int averagingWindow, int voltagePin, int currentPin) : pdFactor(pdFactor), averagingWindow(averagingWindow), voltagePin(voltagePin), currentPin(currentPin) {
	ads1115.begin();
	update();
	currentScale = 0.5f / current_v;
}

// Updates voltage and current variables.
void Battery::update() {
	getData();
}

// Averages ADC data of a window of averagingWindow size, and then updates voltage and current class variables.
void Battery::getData() {
	ads1115.readADC_SingleEnded(0);
	// averaging loop
	for (int i = 0; i <= averagingWindow; i++) {
		voltage_adc += analogRead(voltagePin);
		current_adc += analogRead(currentPin);
	}

	// convert ADC reading into voltage
	// voltage_adc and current_adc are being converted to floats to hopefully get better accuracy from this calculation. Untested!!
	voltage = ((float)voltage_adc / averagingWindow) * (5.0 / 1023.0) * pdFactor;
	current_v = ((float)current_adc / averagingWindow) * (5.0 / 1023.0) * currentScale;

	// reset ADC variables
	voltage_adc = 0;
	current_adc = 0;

	// Voltage range checks
	// --------------------
	// if ACS770 voltage < 0.5 something has gone wrong, so clamp to 0.5V if voltage goes below
	if (current_v < 0.5f) {
		current_v = 0.5f;
	}
	// if battery voltage goes below 10V (assuming its a 4S battery) something has gone wrong.
	if (voltage < 10.0f) {
		//Serial.println("Battery voltage below 10V. Check connections.");
	}
	// if battery voltage goes below 0V something has gone wrong, so clamp to 0V if it goes below. ADC is broken if this goes below 0.
	if (voltage < 0.0f) {
		//Serial.println("Battery voltage below 0V. Something has gone wrong!");
		voltage = 0.0f;
	}

	// convert current sense voltage into current using mV/A factor found in ACS770 datasheet.
	// Take away 0.5 from current sense voltage as 0.5V is voltage at 0A.
	current = (current_v - 0.5f) / 40e-3;
}

This however is still causing the same issue, with nothing being sent out from the Arduino. As for the public class variables I have made them private apart from voltage and current, which I trust myself only to read.

Your constructor should do nothing.

It is absolutely pointless to do hardware-specific things before the hardware is set up.

The hardware is set up AFTER your constructor is called..

Okay, creating a separate member function to do the hardware initialisation fixed it. So is the arduino hardware initialisation only complete once the code has reached the setup() function?

tomeaton17: Okay, creating a separate member function to do the hardware initialisation fixed it. So is the arduino hardware initialisation only complete once the code has reached the setup() function?

Yes. There is a function, init(), that gets called, after constructors, etc., before setup(), that gets the hardware ready.