Go Down

Topic: Software I2C with Adafruit ADS1115 (Read 220 times) previous topic - next topic


I am building a control center for my RV, and I have a main Arduino Mega running almost everything.  And its I2C bus is almost maxed out, and I don't want to use more multiplexers.  Besides I'm trying to offload work to submodules, (for example: solar regulators, inverters, chargers).  And these Arduino Nano submodules are meant to gather info and control (determine inverter status, and control temp, etc).

I need my Nanos to run software I2c for various duties.  I'm trying out the SoftWire on Digital pins 2 and 3.  I have some success with an I2C scanner on the software I2C.  It discovers my ADS1115 at address 0x48.   But when I'm using the Adafruit library (ADS1015.h) to read from the ADS1115 on the bus, I get the reading "-0.01" which is evidence to me that I have not received the data. (Switching the SDA and SCL of ADS1115 over to the hardware I2C (A4, A5) immediately receives data.)

This is all my code for the Nano.  It's pretty simple right now because I'll add code, only after I have the basic software I2C running.

Code: [Select]

#include <SoftWire.h>
#include <AsyncDelay.h>

SoftWire swI2C(2, 3);   //(sdaPin, sclPin);

#include "Adafruit_ADS1015.h"
Adafruit_ADS1115 InverterCurrentADS1115(0x48);               // ADDRESS 0x48 ADS1115 (adc)

float inverterAmps;

void setup() {


  InverterCurrentADS1115.setGain(GAIN_SIXTEEN);    // SET input range +/- 0.256V (shunt has +/- 0.075 range)
  InverterCurrentADS1115.begin();                  // MPPT

  digitalWrite(LED_BUILTIN, LOW);

  // Set how long we are willing to wait for a device to respond

  const uint8_t firstAddr = 1;
  const uint8_t lastAddr = 0x7F;
  Serial.print("Searching addresses in range 0x");
  Serial.print(firstAddr, HEX);
  Serial.print(" - 0x");
  Serial.print(lastAddr, HEX);
  Serial.println(" (inclusive) ...");

  for (uint8_t addr = firstAddr; addr <= lastAddr; addr++) {
    digitalWrite(LED_BUILTIN, HIGH);

    uint8_t startResult = swI2C.llStart((addr << 1) + 1); // Signal a read

    if (startResult == 0) {
      Serial.print("\rDevice found at 0x");
      Serial.println(addr, HEX);
    digitalWrite(LED_BUILTIN, LOW);

  inverterAmps = (InverterCurrentADS1115.readADC_Differential_0_1() / 187.00);   //need decimal places
  Serial.print("amps: ");


void loop()

From my research so far, it seems I'm going to need help changing the Adafruit ADS1015 Library since it is built for the hardware Wire Library.  I have no idea how to add a class, or what that means.  And I have actually tried going through the entire library, changing every instance of "Wire." to "SoftWire."  but that's just me groping around in the dark.  Am I going to have to make my own registry reads and writes, manually from the ADS1115, from the specs in its datasheet?


Feb 16, 2020, 04:16 am Last Edit: Feb 16, 2020, 04:25 am by jremington
My approach would be to look at the code and documentation for SoftWire, and compare it to the same for Wire, before blindly making substitutions.

For example the SoftWire GitHub page explains:

In addition high-level functions provide almost direct compatibility with the Wire library. However, the user must first declare transmit and receive buffers, and configure SoftWire to use them before the high-level functions beginTransmission(), endTransmission(), read(), write() and requestFrom () can be used.
Nothing beats actually understanding the software you want to use, and fortunately I2C is a pretty simple and flexible protocol, so SoftWire should work perfectly.


Hint: you have to make your SoftWire object (swI2C) available to the ADS1X15 library.


Thanks very much for replying.
I have a lot of enthusiasm for this project, but I must be coming to the furthermost reaches of what Google can do for me.  Arduino's reference library is tapped out; I doubt that others find themselves in such esoteric territory. I bet it is exactly as you say: I have to do something with an object or a class or whatever.   And this is it: I want to know how to make my SoftWire object available to the ADS1X15 Library (or any library for that matter).  Please (if it's not too much trouble) show me some line(s) that would do that, or show me someone else who has had to make his object available for a library, or give me an expression to Google.
Thanks so much, in advance,


Feb 18, 2020, 04:14 am Last Edit: Feb 18, 2020, 04:51 am by jremington
You don't need a "library" to do I2C, a few very short routines are all that is required. But you do have to have a basic understanding of the I2C protocol in order to use them. I've attached a collection that I use all the time (I hardly ever use Wire.h). These nevertheless use the buit in I2C hardware; they are not "software I2C", rather, they replace Wire.h.

The latter couple of functions show how to read and write multibyte values from specific sensors, and of course you don't need ShowDevices() -- that is essentially the I2C Address Scanner.

setup() should call I2C_Init(), later you use the primitive routines as demonstrated in the last two functions.

Code: [Select]

Simple I2C routines
ATmega328 @ 16 MHz  Atmel Studio IV/ avr-gcc /Arduino IDE


#define F_CPU 16000000UL
#include <math.h>
#include <util/delay.h>
#include <avr/io.h>

// ---------------------------------------------------------------------------
// The standard clock rate is 100 KHz, and set by I2C_Init
// FIXED I2C_Stop() sjr

#define F_SCL 100000L // I2C clock speed 100 KHz
#define READBIT 1  //low bit of device address for read
#define TW_START 0xA4 // send start condition (TWINT,TWSTA,TWEN)
#define TW_STOP 0x94 // send stop condition (TWINT,TWSTO,TWEN)
#define TW_ACK 0xC4 // return ACK to slave
#define TW_NACK 0x84 // return NACK to slave
#define TW_SEND 0x84 // send data (TWINT,TWEN)
#define TW_READY (TWCR & 0x80) // ready when TWINT returns to logic 1.
#define TW_STATUS (TWSR & 0xF8) // returns value of status register

// I2C register definitions

void I2C_Init(){

// at 16 MHz, the SCL frequency will be 16/(16+2(TWBR)), assuming prescalar of 0.
// so for 100KHz SCL, TWBR = ((F_CPU/F_SCL)-16)/2 = ((16/0.1)-16)/2 = 72
    TWSR = 0; // prescalar to zero
    TWBR = ((F_CPU/F_SCL)-16)/2; // set SCL frequency in TWI bit register

void I2C_Stop(void) {

 // wait for stop condition to be executed on bus
   // TWINT is not set after a stop condition!
 while(TWCR & _BV(TWSTO));

unsigned char I2C_Detect(unsigned char addr){

// look for device at specified address; return 1=found, 0=not found
    TWCR = TW_START; // send start condition
    while (!TW_READY); //wait
 TWDR = addr; // load device's bus address
    TWCR = TW_SEND; // and send it
    while (!TW_READY);
    return (TW_STATUS==0x18); // return 1 if found; 0 otherwise

void ShowDevices(void){

// search all 127 addresses, report those present on the I2C bus

    for (unsigned char addr=1; addr<128; addr++) {
        if (I2C_Detect(addr<<1)) // I2C detected?
           printf(" .%02X",addr<<1);

void I2C_Start (unsigned char slaveAddr) {


unsigned char I2C_Write (unsigned char data) {

// sends a byte to slave

    TWDR = data; // load data to be sent
    TWCR = TW_SEND; // and send it
    while (!TW_READY); // wait
    return (TW_STATUS!=0x28); //0 if successful, 1 if not

unsigned char I2C_ReadACK () {

// reads a byte from slave

    TWCR = TW_ACK; // ack = will read more data
    while (!TW_READY); // wait
    return TWDR;
    //return (TW_STATUS!=0x28);

unsigned char I2C_ReadNACK () {

// reads a byte from slave

    TWCR = TW_NACK; // nack = not reading more data
    while (!TW_READY); // wait
    return TWDR;
 //return (TW_STATUS!=0x28);

void I2C_WriteByte (unsigned char busAddr, unsigned char data) {

// write byte to slave

    I2C_Start(busAddr); // send bus address
    I2C_Write(data); // then send the byte

void I2C_WriteRegister(unsigned char busAddr, unsigned char deviceRegister, unsigned char data){

    I2C_Start(busAddr); // send bus address
    I2C_Write(deviceRegister); // first unsigned char = device register address
    I2C_Write(data); // second unsigned char = data for device register

unsigned char I2C_ReadRegister(unsigned char busAddr, unsigned char deviceRegister) {

// read single byte of data in register

    unsigned char data = 0;
    I2C_Start(busAddr); // send device address
    I2C_Write(deviceRegister); // set register pointer
    I2C_Start(busAddr+READBIT); // restart as a read operation
    data = I2C_ReadNACK(); // read the register data
    I2C_Stop(); // stop
    return data;

// Read a two-byte word, low order first

signed int I2C_ReadWord(unsigned char busAddr, unsigned char deviceRegister) {

    unsigned int data = 0;
 unsigned char l;
    I2C_Start(busAddr); // send device address
    I2C_Write(deviceRegister | 0x80); // set register pointer, autoincrement
    I2C_Start(busAddr+READBIT); // restart as a read operation
    l = I2C_ReadACK(); // read the register data
 data |= I2C_ReadNACK(); //read next unsigned char
    I2C_Stop(); // stop
    return (signed int) ((data<<8)|l);

// read a 3 byte value, lowest order byte first

signed long I2C_ReadPressureRaw(unsigned char busAddr, unsigned char deviceRegister) {

    unsigned char pxl,pl,ph;
    I2C_Start(busAddr); // send device address
    I2C_Write(deviceRegister | 0x80); // set register pointer, autoincrement
    I2C_Start(busAddr+READBIT); // restart as a read operation
    pxl = I2C_ReadACK(); // read ls byte
    pl = I2C_ReadACK(); // read middle
    ph = I2C_ReadNACK(); // read high
    I2C_Stop(); // stop
    return (int32_t)ph << 16 | (uint16_t)pl << 8 | pxl;


I want to know how to make my SoftWire object available to the ADS1X15 Library (or any library for that matter).
One way is to make it a parameter of the constructor, another way is to provide it to the begin() method.

If that makes no sense to you, you should start with an easier project where you can learn to program. Or use the hardware I2C interface as the available library expects you to do that. Don't expect us to do your job completely.

Go Up