Yesterday I spent around 3 hours putting together the pH and EC circuits on a breadboard.


The pieces of the prototype circuit consist of some sections of the circuit that I milled on the Othermill, a few components on SMT-> DIP boards, and Adafruit’s ADS1015 BoB.  All…gulp…wired together….if I don’t breathe…the circuit works.

I then set up a DMM, a scope and an I2C logic analyzer to test.

I hard code the EC probe’s resistance value by using a 200Ω resistor.  To be more exact, the resistor actually measures 198Ω.  This way I’ll know if my tests can get reasonably close to the resistor value.  Recall from previous EC tests that to calculate the EC:

  • measure VIN and VOUT in order to calculate the Gain from the EC probe’s resistance.  I.e.: Gain = 1+ VOUT/VIN
  • calculate the resistance and conductance read from the EC probe:  The layout specifies a 1K feedback resistor.  So R(measured) = 1K/Gain-1 and the EC value in Siemens is 1/R(measured)

to test, I put together an Eclipse project.  The project is located at this GitHub location (the ADS1015_Test.Zip).  I decided zipping up Eclipse project at different working states is an easy way to start testing since all the include files, code, makefile, debugging options… changes are there. 


Test 1: Measure VIN and VOUT using ADS1015_Test Project

Recall VIN is the DC peak value determined from the Wien Bridge Oscillator.  VOUT is the DC peak value determined from the gain loop in the EC circuit. The VOUT is amplified by how much the EC Probe (in this case the 200Ω resistor) amplifies the VIN signal.

For this test, I will “hard code” the MUX.  


Measuring VIN

When the MUX pin = GND, the signal to the ADS1015 is the VIN.  When the MUX pin = power source (in this case I am testing with a 3.3V power source), the signal sent to the ADS1015 is VIN.

I start with the MUX pin connected to GND.



I use the debugger and open an Jlinkrttclient session within terminal.  As shown above, the value I got from the ADS1015 for VIN = 2012.  I had set the ADS1015’s resolution (also called perhaps confusingly the gan) to GAIN_FOUR, which means each LSB of the ADC = .5 mV.  So VIN = 2012*.5 = 1006mV.  Hmmm…I was expecting a value of around 200mV.  Something is not right….

Measuring VOUT

Moving the MUX pin to VDD, I rerun the test program and get VOUT = 2028.


The value of 2028*.5 = 1.014V is pretty much what I was expecting for VOUT.  


Debugging VIN

I should be seeing VIN close to 200mV and VOUT close to 1000mV.  What’s going on with VIN?  The first debug test I’ll do is to look at the values going into the ADS1015 AIN1 pin.  This is the pin for VIN and VOUT after rectification (I discuss all this in detail in previous posts.  It takes me a bit longer than I want at this point to back link to these older posts…).  So the question I am answering is: What values does the ADS1015 receive as input prior to sending them over to the nRF51822 via I2C?







Scope measurements:

  • VGND = 1.8
  • VIN = 1.92 – 1.8 = .12V
  • VOUT = 2.8 – 1.8 = 1V
The scope values are closer to what I expected.  Before I unpack the I2C traffic, I’m going to check the code.  Perhaps the data type that I use within the ADS1015 library is not the right one to return ADC values.  These need to match.  First looking at Adafruit’s Arduino library for the differential, I note the function returns an int16_t:
int16_t Adafruit_ADS1015::readADC_Differential_0_1()
In my library, I’m returning an int16_t: 
int16_t readADC_Differential_VGND(nrf_drv_twi_t const * const  p_instance,int P)
In main.c, I set the variable to receive the reading to uint16_t:
 uint16_t rawADC = readADC_Differential_VGND(&twi,1);
Changing this to int_16_t, I get the following readings:
  • VIN = 318*.5 = 159mV
  • VOUT = 318*.5 = 159mV
Sigh…this makes me think there are cached buffers holding onto the value in between readings.  The first time I started with VOUT.  The second VIN.  Coincidence?
One more time… Here is a reading for VIN:
Looking at the I2C traffic:
Packet ID Address Data Read/Write ACK/NAK
0 0x90 0x01 Write ACK
0 0x90 0xA7 Write ACK
0 0x90 0x83 Write ACK
1 0x90 0x00 Write ACK
  0x91 0x13 Read ACK
  0x91 0xC0 Read NAK

Looking at the ADS1015 data sheet (p 8 on the data sheet I’m looking at)

  • byte 1 to Address 0x90 -> ADS1015 address (see this link for how to figure out the ADS1015 address from 0x90).  Data = 0x01-> write to the config register
  • byte 2= 0xA7 = b1010 0111->MSB of what to write to the config register
  • byte 3 -> 0x83 = b1000 0011-> LSB of what to write to the config register

p. 15 of the data sheet has the map to what the config write bytes mean:


Looking at the explanation of the MSB config bytes (p. 16)

MSB = b1010 0111

  15 14 13 12 11 10 9 8
BIT 1 0 1 0 0 1 1 1
  • Single shot reading (bit 15)
  • AINp = AIN1 and AINn = AIN3 (bits 14-12)
  • programmable gain = +/1 1.024V (GAIN_FOUR) (bits 11-9)
  • Power-down single-shot device operating mode (bit 8)

so far so good.  Now onto the LSB = b1000 0011

  7 6 5 4 3 2 1 0
BIT 1 0 0 0 0 0 1 1
  • 1600SPS datarate – the default. (bits 7-5)
  • Default comparator mode (bit 4)
  • Default comparator polarity (bit 3)
  • Default latching comparator (bit 2)
  • Disable comparator (bits 1-0) – which I assume means bits 4-2 are ignored.
So the config bytes seem to be set up correctly.  Back to the I2C traffic:
  • byte 4 -> write to the ADS1015, data = 0x00 which means write to the register that contains results (the “Conversion register”)
  • byte 5 -> 0x5F – the MSB byte of the ADC reading
  • byte 6 -> 0xE0 – the LSB byte 
So the ADC reading = 0x13C0.  This isn’t the value, because the data sheet notes the Conversion register’s first 4 bits are unused (see Table 8 on p. 15):
Shifting over 4 bits,  0X13C0 -> 0x013C = 316…which is identical to what the API returned.  I decided to include the logic behind handling an ADC reading that is negative:
Values are stored in two’s complement.  This makes Adafruit’s code that reads the ADS1015 more sense:

// Read the conversion results

  uint16_t res = readRegister(m_i2cAddress, ADS1015_REG_POINTER_CONVERT) >> m_bitShift;

  if (m_bitShift == 0)


    return (int16_t)res;




    // Shift 12-bit results right 4 bits for the ADS1015,

    // making sure we keep the sign bit intact

    if (res > 0x07FF)


      // negative number – extend the sign to 16th bit

      res |= 0xF000;


  • use a uint16_t to store the value that comes back from the ADS1015
  • check if the 12th bit is a 1 (i.e.: 0x7FF = b0111 1111 1111…anything above 0x7FF – say 0x800 – has a 1 in the most significant bit, making is a negative value.  So extend negative throughout the 16 bits and return an int16_t

After working through a few more results, I’m thinking the challenge is actually the rectifying part of the circuit.  In the prototype I take a shortcut and make a rectifier with just an opamp, diode, and cap.  I wanted to try this over the more complicated FET solution I was using.  However, I’m thinking because the MUX causes such a fluctuation between signal switching, the FET solution is what I should use.  The results I am seeing is most likely an artifact of the rectifier since the signals are switching correctly.

Well – that was a good test session.  I got a much better feel for the meaning between the I2C packets.  I also validated the API is returning the same results as what I2C is returning and the configuration settings of the ADS1015 have been set correctly.


Now I’ll rebuild the rectifier circuit to go back to the one on the Ladybug Shield.


Thanks for reading this far.  Please find many things to smile about.