This week I’d like to introduce you to two new Qwiic-compatible I2C products in our lineup: a new Qwiic-compatible, 2 channel analog to digital converter, and a re-spin of our popular I2C-RELAY16 into a Qwiic-compatible 3.3V version.
To explore how each of these can be used through a simple example, I’ll walk through an example connecting the ADC to a liquid level sensor, using Python on a Raspberry Pi to read the data, and then act on that liquid level by triggering a relay.
Introducing the I2C-MCP3427Microchip MCP3427 – a two channel differential delta-sigma ADC. With the addition of some Qwiic-compatible 3.3V I2C connectors and some terminal blocks, it became the Iowa Scaled Engineering I2C-MCP3427.
The ADC has two differential inputs which have a common-mode range of 0-3.3V and can measure up to +/-2.048V differentially. The converter has a built-in 2.048V reference, a programmable front end amplifier capable of 1x, 2x, 4x, and 8x gain, and programmable bit depth/precision. The converter will do 15 samples/second at 16 bit precision, 60 samples/second at 14 bit precision, and 240 samples/second at 12 bit precision.
Wouldn’t you know it – I’m a few weeks slow on posting about some new products, and as usual Sparkfun beats me to the punch. Oh well, story of my life. They’re pretty comparable products, honestly, each converter with a few advantages and disadvantages. Which one is right for your application is a question you’ll have to answer by looking at the datasheets for each.
Mine has a RasPi-compatible Python library – just sayin’…
Measuring a Glass of Water
Let’s take the I2C-MCP3427 for a spin with one of my favorite sensors – the Milone Technologies eTape liquid level sensors. This is actually the original application I had for the I2C-MCP3427. The goal was to monitor the liquid levels in the bowl and reservoir that keeps my
evil furry overlords cats (Apollo and Hawkeye) hydrated. The thing typically works fine, but every now and then – typically when I’m on the other side of the world – it decides to fault for no discernible reason, either overflowing the bowl or running low.
The I2C-MCP3427 actually comes with a 3-pin header that’s designed expressly to plug into these sensors. It provides power, ground, and Vin on the correct pins. All you need to do is solder in a 3-pin, 0.1″ header and solder J2 (or J3) closed to short the negative input of the channel to ground. For this example, I’ll be using a 5-inch sensor.
These sensors are wired as a standard resistor divider. The black and red wires are the power and ground connections. Between black and white is the fixed resistor, and white and red is the variable. Since we want to keep all measurements between 0-2.048V (to stay in the differential range of the converter), we’ll wire black to +3.3V, red to ground, and white to the positive input of the converter channel.
The sensors have quite a tolerance (+/- 20%), so I typically take measurements on each to determine the empty and full scale resistance values of each sensor.
Rtop = Black-White = 1090 ohms
Rbottom = Red-White = 1060 ohms
Full Scale (5″ liquid)
Rtop = Black-White = 1090 ohms
Rbottom = Red-White = 510 ohms
You might be thinking that means we have resistive gradient of -110 ohms per inch, but wait, not so fast! There’s a gotcha on these sensors. The first inch at the bottom is dead band, meaning the resistance doesn’t change in there. That really means that our gradient is -137.5 ohms/inch (550 ohms change of 4 inches of active sensor).
If you work through the standard resistor divider equations, you can determine the variable resistance of the tape from the output voltage at the center of the divider.
Rbottom = (Rtop * Vout) / (Vdd – Vout)
Rtop was measured at 1.09k-ohms above, and Vdd is within an gnat’s eyelash of 3.3V (measured at 3.2968V). Also, we can determine that the liquid depth on the sensor is the dead band (1 inch) plus difference in resistance between measured and the no-liquid state, divided by 137.5 ohms/inch.
Dinches = 1.0 + (Rempty – Rbottom) / 137.5
So, putting the two together:
Dinches = 1.0 + (Rempty – (Rtop * Vout) / (Vdd – Vout) ) / 137.5
Or, with some numbers filled in…
Dinches = 1.0 + (1060 – (1090 * Vout) / (3.3 – Vout) ) / 137.5
Let’s see if the real world matches up with our math. Here’s a small chunk of python I used on a Rasberry Pi to test the sensor:
#!/usr/bin/python import smbus2 import mcp3427 import time bus = smbus2.SMBus(1) address = 0x68 adc = mcp3427.mcp3427(bus, address) adc.configure(True, 1, 1, 16) time.sleep(0.1) while 1: print "ADC val = %d" % adc.getADCValue() vout = adc.getADCVolts() print "ADC volts = %f V" % vout depth = 1.0 + (1060.0 - (1090.0 * vout) / (3.3 - vout)) / 137.5 print "Liquid Depth = %f inches" % depth time.sleep(0.1)
Here’s the results – as you can see based on the screen behind the pint glass, the level printed pretty closely matches the submersion depth at 2 inches and 5 inches:
Adding the I2C-RELAY16-QWIIC3.3V-5V level shifter. We thought it would be convenient if you could just hook those 16-channel cheap Chinese relay boards directly to a Qwiic network. So, we’d like to introduce the I2C-RELAY16-QWIIC! It’s the same PCA9671 chip at the core and you can use the same libraries to connect to it. The PCA9671 is tolerant of 5V on its I/O pins, even when powered from a lower voltage like 3.3V. That makes the new design even simpler than the old one.
So, let’s take our liquid level sensor a bit further, and have it turn on a relay when the level drops below 2 inches, and turn off when it passes above 3 inches. This would be a very simplistic sort of way of controlling a fill valve on a tank. These sensors are essentially mechanical, triggered by the fluid pressure squeezing resistive elements, so there’s some latency in them when the fluid level drops. That’s why it kicks on a bit lower than 2 inches. If you’re doing something like this on a tank that empties quickly, keep that in mind.
Here’s the system in action:
Here’s the code that makes it work:
#!/usr/bin/python import smbus2 import mcp3427 import relay16 import time bus = smbus2.SMBus(1) adcAddress = 0x68 relay16Address = 0x20 relayBoard = relay16.relay16(bus, relay16Address) adc = mcp3427.mcp3427(bus, adcAddress) adc.configure(True, 1, 1, 16) time.sleep(0.1) while 1: vout = adc.getADCVolts() depth = 1.0 + (1060.0 - (1090.0 * vout) / (3.3 - vout)) / 137.5 print "Liquid Depth = %f inches" % depth if depth < 2.0: relayBoard.setRelay(1) elif depth > 3.0: relayBoard.clearRelay(1) time.sleep(0.1)