Posted on

How to connect the Tentacle Mini to a Raspberry Pi

No Arduino needed!

What you need:

Enable I2C on the Pi

  1. Run
    sudo aspi-config.
  2. Navigate to
    Advanced Options-> I2C.
  3. Select yes when it asks you to enable I2C
  4. Select yes when it tasks about automatically loading the kernel module.
  5. Apply settings and reboot
  6. After reboot, type ls /dev/*i2c*
    The Pi should list its I2C bus(es), e.g.
    /dev/i2c-1
  7. Install the I2C utilities:
    sudo apt-get install -y i2c-tools

Enable I2C on the Atlas Scientific EZO circuits

Manually switch each of your EZO circuits to I2C. Step-by-step tutorial for this is here. TL;DR Use your breadboard, a 5V DC power supply and some jumper wires to short PGND and TX before powering the circuit with 5V. Each power cycle with shorted PGND/TX will switch from UART to I2C and back.

Connect the Tentacle Mini and the Raspberry Pi

Pins to connect are 5V, IOREF, GND, SDA, SCL. In case you don’t intend to use your Tentacle Mini with an Arduino at all, you should consider ordering the Kit version. It comes without the Arduino headers pre-soldered and you can mount your own custom connector just for the required pins.

  •  Tentacle 5V -> RPi 5V
    (used to power the 2 isolated power circuits on the Tentacle)
  • Tentacle IOREF -> RPi 3.3V
    (used to power the isolated I2C communications – the Pi wants 3.3V on the I/O, so we apply 3.3V)
  • Tentacle GND -> RPi GND
  • Tentacle SCL -> RPi SCL
  • Tentacle SDA -> RPi SDA

Once the two boards are connected, plug the EZO circuits into the Tentacle Mini and power up the Pi

Talking to the sensors

You can verify the circuits work with the i2cdetect utility: i2cdetect -y 1

pi@raspberrypi:~/$ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- 61 -- 63 -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

In this example, a pH and a DO circuit are plugged in. Te factory settings for the EZO circuits are as follows:

  • DO: 0x60 (97)
  • ORP: 0x61 (98)
  • pH: 0x63 (99)
  • EC: 0x64 (100)
  • RTD: 0x66 (102)

If you’re using multiple of the same type of EZO circuit (e.g. 2x pH), both will have the address 0x63. I2C must be unique. You’ll want to connect only one of the circuits at first and change it’s address. You can do this in the interactive example code below by typing i2c,105. This will give this circuit the address of 0x69 (105). You can now plug in the next circuit of the same type.

The following code is provided by Atlas Scientific. You can download it here.

#!/usr/bin/python 

import io # used to create file streams
import fcntl # used to access I2C parameters like addresses

import time # used for sleep delay and timestamps
import string # helps parse strings


class atlas_i2c:
    long_timeout = 1.5 # the timeout needed to query readings and calibrations
    short_timeout = .5 # timeout for regular commands
    default_bus = 1 # the default bus for I2C on the newer Raspberry Pis, certain older boards use bus 0
    default_address = 99 # the default address for the pH sensor
    
    def __init__(self, address = default_address, bus = default_bus):
        # open two file streams, one for reading and one for writing
        # the specific I2C channel is selected with bus
        # it is usually 1, except for older revisions where its 0
        # wb and rb indicate binary read and write
        self.file_read = io.open("/dev/i2c-"+str(bus), "rb", buffering = 0)
        self.file_write = io.open("/dev/i2c-"+str(bus), "wb", buffering = 0)
        
        # initializes I2C to either a user specified or default address
        self.set_i2c_address(address)
    
    def set_i2c_address(self, addr):
        # set the I2C communications to the slave specified by the address
        # The commands for I2C dev using the ioctl functions are specified in
        # the i2c-dev.h file from i2c-tools
        I2C_SLAVE = 0x703
        fcntl.ioctl(self.file_read, I2C_SLAVE, addr)
        fcntl.ioctl(self.file_write, I2C_SLAVE, addr)
            
    def write(self, string):
        # appends the null character and sends the string over I2C
        string += "\00"
        self.file_write.write(string)
        
    def read(self, num_of_bytes = 31):
        # reads a specified number of bytes from I2C, then parses and displays the result
        res = self.file_read.read(num_of_bytes) # read from the board
        response = filter(lambda x: x != '\x00', res) # remove the null characters to get the response
        if(ord(response[0]) == 1): # if the response isnt an error
            char_list = map(lambda x: chr(ord(x) & ~0x80), list(response[1:])) # change MSB to 0 for all received characters except the first and get a list of characters 
            # NOTE: having to change the MSB to 0 is a glitch in the raspberry pi, and you shouldn't have to do this!
            return "Command succeeded " + ''.join(char_list) # convert the char list to a string and returns it
        else:
            return "Error " + str(ord(response[0]))
    
    def query(self, string):
        # write a command to the board, wait the correct timeout, and read the response
        self.write(string)
        
        # the read and calibration commands require a longer timeout
        if((string.upper().startswith("R")) or 
           (string.upper().startswith("CAL"))):
            time.sleep(self.long_timeout)
        elif((string.upper().startswith("SLEEP"))):
            return "sleep mode"
        else:
            time.sleep(self.short_timeout)
            
        return self.read()
            
    def close(self):
        self.file_read.close()
        self.file_write.close()

def main():
    device = atlas_i2c() # creates the I2C port object, specify the address or bus if necessary
    
    print(">> Atlas Scientific sample code")
    print(">> Any commands entered are passed to the board via I2C except:")
    print(">> Address,xx changes the I2C address the Raspberry Pi communicates with.") 
    print(">> Poll,xx.x command continuously polls the board every xx.x seconds")
    print(" where xx.x is longer than the %0.2f second timeout." %  atlas_i2c.long_timeout)
    print(" Pressing ctrl-c will stop the polling")
    
    # main loop
    while True:
        input = raw_input("Enter command: ")
        
        # address command lets you change which address the Raspberry Pi will poll
        if(input.upper().startswith("ADDRESS")):
            addr = int(string.split(input, ',')[1])
            device.set_i2c_address(addr)
            print("I2C address set to " + str(addr))
        
        # contiuous polling command automatically polls the board
        elif(input.upper().startswith("POLL")):
            delaytime = float(string.split(input, ',')[1])
            
            # check for polling time being too short, change it to the minimum timeout if too short
            if(delaytime < atlas_i2c.long_timeout):
                print("Polling time is shorter than timeout, setting polling time to %0.2f" %  atlas_i2c.long_timeout)
                delaytime =  atlas_i2c.long_timeout

            # get the information of the board you're polling
            info = string.split(device.query("I"), ",")[1]
            print("Polling %s sensor every %0.2f seconds, press ctrl-c to stop polling" % (info, delaytime))
            
            try:
                while True:
                    print(device.query("R"))
                    time.sleep(delaytime - atlas_i2c.long_timeout)
            except KeyboardInterrupt: # catches the ctrl-c command, which breaks the loop above
                print("Continuous polling stopped")
        
        # if not a special keyword, pass commands straight to board
        else:
            try:
                print(device.query(input))
            except IOError:
                print("Query failed")
        
        
if __name__ == '__main__':
    main()

Run the code above and you’ll be presented with an interactive prompt. You can use this to setup and configure your circuits and to read values from the sensors:
>> Atlas Scientific sample code
>> Any commands entered are passed to the board via I2C except:
>> Address,xx changes the I2C address the Raspberry Pi communicates with.
>> Poll,xx.x command continuously polls the board every xx.x seconds
where xx.x is longer than the 1.50 second timeout.
Pressing ctrl-c will stop the polling
Enter command:

To connect to a circuit, use the command Address,xx. xx is address of the circuit in decimal form. For the ph circuit with address 0x63, that’s 99.
Enter command: Address,99
I2C address set to 99

Use r to read the current sensor reading
Enter command: r
Command succeeded 6.997

Check out the datasheet of your respective type of EZO circuit for a list of all commands. To find out what type of circuit you have attached, use the command i
>Enter command: i
Command succeeded ?I,pH,1.0
Leave a Reply

Your email address will not be published. Required fields are marked *