Having designed an 8 relay card for the Raspberry Pi using the IIC bus, I had to come up with a way of testing this and providing users with a way of easily using this. I am not a programmer - but as an electronics engineer I have had to work alongside programmers and even had to make minor modifications to programs. This post describes my experiences with this project using the MCP23008.
Why use IIC and not just use 8 outputs of the Raspberry Pi GPIO?
My initial thought was simply to do the latter. This would have been very easy to control and the task would have been to simply layout a PCB with 8 relays on it and an octal driver such as the ULN2801. However this would that someone wanting to use more relays for a control project would not have been able to do so.
For this reason, I set out to use the IIC bus to drive the 8 realys. The IC that I chose can have 8 possible addresses that can be set by the user. By providing 2 seperate 26 way connectors, it would be possible for a user to use upto 8 cards, all set to different addresses and daisy chain this. If required this method could be used to control upto 64 cards - surely enough for any control project.
Why not use the SPI bus?
The IIC bus just uses 2 I/O to address and pass data to and from a large number of devices, as long as it can address them. The Master device (usually the microprocessor) controls the clock(SCL) and data line (SDA) is bidirectional.
On the SPI bus, there are 3 lines and then additional outputs for eneabling the ICs. So for example, to control 2 seperate SPI bus devices, the Raspberry Pi provides 5 seperate pins. 1 clock, 1 data out, 1 data in and 2 chip enable pins.
More information on the 8 relay card for the Raspberry Pi is provided here.
Managing the IIC 2 wire bus
In general, the master device (the Raspberry PI) sends out a start signal, then addresses the device and waits for an acknowledge. Once the acknowledge is received, the master addresses a particular register in the slave and then again waits for an acknowledge. It finally sets the register to the required value and sends the stop signal.
Sending IIC Start Signal
In the default state, both the SDA (GPIO pin 3)and SCL (GPIO pin 5) lines are kept high (true). The SDA line is taken low (false) and then the SCL line is taken low for a start signal. The Python fucntion for this is shown below.
def start():
#start bit
GPIO.output(3, False)
time.sleep(0.001)
GPIO.output(5, False)
time.sleep(0.001)
Sending IIC Address or Data bytes
The Python code for this is presented below. The SDA bit is set and then the SCL bit is clocked high and low. This is done 8 times for the 8 bits of the address or the data.
def data(byte):
#sends "byte" out to I2C
for x in range (0,8):
GPIO.output(3, byte[x])
time.sleep(0.001)
GPIO.output(5, True)
time.sleep(0.001)
GPIO.output(5, False)
time.sleep(0.001)
Selecting the address
The MCP23008 IIC device used here has a possible 8 addresses that can be set by the user and this is shown below.
#I2C addresses
#use switch S1 on Custard Pi 6 to set the address
add0= [0, 1, 0, 0, 0, 0, 0, 0]
add1= [0, 1, 0, 0, 0, 0, 1, 0]
add2= [0, 1, 0, 0, 0, 1, 0, 0]
add3= [0, 1, 0, 0, 0, 1, 1, 0]
add4= [0, 1, 0, 0, 1, 0, 0, 0]
add5= [0, 1, 0, 0, 1, 0, 1, 0]
add6= [0, 1, 0, 0, 1, 1, 0, 0]
add7= [0, 1, 0, 0, 1, 1, 1, 0]
Bits 1,2,3 and 4 set the address.
The MCP23008 registers
This device is very flexible, and the pins can be set as digital outputs or inputs. The IODIR register has to be set to all 1's to set the pins as outputs which is what we want here.
Here we set the variables used for this.
#set IODIR register
iodir= [0, 0, 0, 0, 0, 0, 0, 0]
#set default to all off
allout= [0, 0, 0, 0, 0, 0, 0, 0]
And here we set the IODIR register to set all the pins as outputs.
def setasoutput(address):
#set all 8 bits as outputs
start()
byte=address
data(byte)
ack()
byte=iodir
data(byte)
ack()
byte=allout
data(byte)
ack()
stop()
Waiting for the Acknowledge signal
In the code above you can see the ack command. This is the wait for the acknowledge signal. This is a little complicated as the SDA line has to be changed from an
output to an input by the matser to read the acknowledge signal back from the slave device (MCP23008).
The Python code for this is shown below. This sets the SDA line true, then makes it an input. Just to be sure, SDA is set true again. SCL is set to true and the SDA line is read to see if it is set to false by the
slave - which is the ACK signal. In the ACK is not received after 10 reads, an error message is displayed and the program aborted.
If the ACK is received the SCL line is taken low, the SDA line is set as an output and set low as well.
def ack():
#wait for acknowledge
#set GPIO3 as input
GPIO.output(3, True)
time.sleep(0.001)
GPIO.setup(3, GPIO.IN)
time.sleep(0.001)
GPIO.output(5, True)
time.sleep(0.001)
#read GPIO3
y=GPIO.input(3)
while y is True:
for z in range (0,10):
y=GPIO.input(3)
if z==9:
print "Aborting due to fail when writing to I2C device"
print "Please check switch S1 settings"
GPIO.cleanup()
import sys
sys.exit()
GPIO.output(5, False)
time.sleep(0.001)
GPIO.setup(3, GPIO.OUT)
time.sleep(0.001)
GPIO.output(3, False)
time.sleep(0.001)
Sending the Stop signal
Here, the SDA line is taken high followed by the SCL line.
def stop():
#stop bit
GPIO.output(5, True)
time.sleep(0.001)
GPIO.output(3, True)
time.sleep(0.001)
Setting a pin High or Low
The correct data is sent to the GPIO register of the MCP23008 to do this. First we need to define the address of this.
#set GPIO register
gpio= [0, 0, 0, 0, 1, 0, 0, 1]
The status of all the outputs is held in a register and the default value of this is set.
#psuedo output latch
outstatus=[0, 0, 0, 0, 0, 0, 0, 0]
Then the setbit and clrbit functions are used to set individual pins as shown below.
def setbit(address, byte):
#sets selected port pin
sendbyte(address)
for x in range (0,8):
if byte[x] ==1:
outstatus[x]=1
byte=outstatus
data (byte)
ack()
stop()
def clrbit(address, byte):
#clears selected port pin
sendbyte(address)
for x in range (0,8):
if byte[x] ==0:
outstatus[x]=0
byte=outstatus
data (byte)
ack()
stop()
The port pins are defined below.
#set relay ON
ONrelay0= [0, 0, 0, 0, 0, 0, 0, 1]
ONrelay1= [0, 0, 0, 0, 0, 0, 1, 0]
ONrealy2= [0, 0, 0, 0, 0, 1, 0, 0]
ONrelay3= [0, 0, 0, 0, 1, 0, 0, 0]
ONrelay4= [0, 0, 0, 1, 0, 0, 0, 0]
ONrelay5= [0, 0, 1, 0, 0, 0, 0, 0]
ONrelay6= [0, 1, 0, 0, 0, 0, 0, 0]
ONrelay7= [1, 0, 0, 0, 0, 0, 0, 0]
#set relay OFF
OFFrelay0= [1, 1, 1, 1, 1, 1, 1, 0]
OFFrelay1= [1, 1, 1, 1, 1, 1, 0, 1]
OFFrealy2= [1, 1, 1, 1, 1, 0, 1, 1]
OFFrelay3= [1, 1, 1, 1, 0, 1, 1, 1]
OFFrelay4= [1, 1, 1, 0, 1, 1, 1, 1]
OFFrelay5= [1, 1, 0, 1, 1, 1, 1, 1]
OFFrelay6= [1, 0, 1, 1, 1, 1, 1, 1]
OFFrelay7= [0, 1, 1, 1, 1, 1, 1, 1]
How to use these functions for the Custard Pi 6
The functions described can be downloaded from here as cpi6.txt file. Just place this in the folder that you are developing your Python code and rename it cpi6.py
Then just call the relevant routines to control the relays on the card as shown by the sample Python code below. The "import cpi6" command makes the functions we
defined available to this program. Just remember to use cpi6. before you call these functions or vaiables.
#1/usr/bin/env python
import RPi.GPIO as GPIO
import time
import cpi6
GPIO.setmode(GPIO.BOARD)
#start program
board1=cpi6.add1
cpi6.setasoutput(board1)
while True:
cpi6.setbit(board1, cpi6.ONrelay0)
cpi6.setbit(board1, cpi6.ONrelay1)
cpi6.setbit(board1, cpi6.ONrelay7)
cpi6.clrbit(board1, cpi6.OFFrelay0)
cpi6.clrbit(board1, cpi6.OFFrelay1)
cpi6.clrbit(board1, cpi6.OFFrelay7)
GPIO.cleanup()
import sys
sys.exit()
Summary
Although I have explained the IIC bus and the MCP23008 in fair detail, the user only needs to use the following commands when controlling the Custard Pi 6 and have an understanding of how to set the address on the card which is explained furtherin this slideshow.
setasoutput - sets all the 8 pins as outputs
setbit - sets the particular pin HIGH - switch relay ON
clrbit - sets the chosen pin LOW - switch relay OFF
Why use IIC and not just use 8 outputs of the Raspberry Pi GPIO?
My initial thought was simply to do the latter. This would have been very easy to control and the task would have been to simply layout a PCB with 8 relays on it and an octal driver such as the ULN2801. However this would that someone wanting to use more relays for a control project would not have been able to do so.
For this reason, I set out to use the IIC bus to drive the 8 realys. The IC that I chose can have 8 possible addresses that can be set by the user. By providing 2 seperate 26 way connectors, it would be possible for a user to use upto 8 cards, all set to different addresses and daisy chain this. If required this method could be used to control upto 64 cards - surely enough for any control project.
Why not use the SPI bus?
The IIC bus just uses 2 I/O to address and pass data to and from a large number of devices, as long as it can address them. The Master device (usually the microprocessor) controls the clock(SCL) and data line (SDA) is bidirectional.
On the SPI bus, there are 3 lines and then additional outputs for eneabling the ICs. So for example, to control 2 seperate SPI bus devices, the Raspberry Pi provides 5 seperate pins. 1 clock, 1 data out, 1 data in and 2 chip enable pins.
More information on the 8 relay card for the Raspberry Pi is provided here.
Managing the IIC 2 wire bus
In general, the master device (the Raspberry PI) sends out a start signal, then addresses the device and waits for an acknowledge. Once the acknowledge is received, the master addresses a particular register in the slave and then again waits for an acknowledge. It finally sets the register to the required value and sends the stop signal.
Sending IIC Start Signal
In the default state, both the SDA (GPIO pin 3)and SCL (GPIO pin 5) lines are kept high (true). The SDA line is taken low (false) and then the SCL line is taken low for a start signal. The Python fucntion for this is shown below.
def start():
#start bit
GPIO.output(3, False)
time.sleep(0.001)
GPIO.output(5, False)
time.sleep(0.001)
Sending IIC Address or Data bytes
The Python code for this is presented below. The SDA bit is set and then the SCL bit is clocked high and low. This is done 8 times for the 8 bits of the address or the data.
def data(byte):
#sends "byte" out to I2C
for x in range (0,8):
GPIO.output(3, byte[x])
time.sleep(0.001)
GPIO.output(5, True)
time.sleep(0.001)
GPIO.output(5, False)
time.sleep(0.001)
Selecting the address
The MCP23008 IIC device used here has a possible 8 addresses that can be set by the user and this is shown below.
#I2C addresses
#use switch S1 on Custard Pi 6 to set the address
add0= [0, 1, 0, 0, 0, 0, 0, 0]
add1= [0, 1, 0, 0, 0, 0, 1, 0]
add2= [0, 1, 0, 0, 0, 1, 0, 0]
add3= [0, 1, 0, 0, 0, 1, 1, 0]
add4= [0, 1, 0, 0, 1, 0, 0, 0]
add5= [0, 1, 0, 0, 1, 0, 1, 0]
add6= [0, 1, 0, 0, 1, 1, 0, 0]
add7= [0, 1, 0, 0, 1, 1, 1, 0]
Bits 1,2,3 and 4 set the address.
The MCP23008 registers
This device is very flexible, and the pins can be set as digital outputs or inputs. The IODIR register has to be set to all 1's to set the pins as outputs which is what we want here.
Here we set the variables used for this.
#set IODIR register
iodir= [0, 0, 0, 0, 0, 0, 0, 0]
#set default to all off
allout= [0, 0, 0, 0, 0, 0, 0, 0]
And here we set the IODIR register to set all the pins as outputs.
def setasoutput(address):
#set all 8 bits as outputs
start()
byte=address
data(byte)
ack()
byte=iodir
data(byte)
ack()
byte=allout
data(byte)
ack()
stop()
Waiting for the Acknowledge signal
In the code above you can see the ack command. This is the wait for the acknowledge signal. This is a little complicated as the SDA line has to be changed from an
output to an input by the matser to read the acknowledge signal back from the slave device (MCP23008).
The Python code for this is shown below. This sets the SDA line true, then makes it an input. Just to be sure, SDA is set true again. SCL is set to true and the SDA line is read to see if it is set to false by the
slave - which is the ACK signal. In the ACK is not received after 10 reads, an error message is displayed and the program aborted.
If the ACK is received the SCL line is taken low, the SDA line is set as an output and set low as well.
def ack():
#wait for acknowledge
#set GPIO3 as input
GPIO.output(3, True)
time.sleep(0.001)
GPIO.setup(3, GPIO.IN)
time.sleep(0.001)
GPIO.output(5, True)
time.sleep(0.001)
#read GPIO3
y=GPIO.input(3)
while y is True:
for z in range (0,10):
y=GPIO.input(3)
if z==9:
print "Aborting due to fail when writing to I2C device"
print "Please check switch S1 settings"
GPIO.cleanup()
import sys
sys.exit()
GPIO.output(5, False)
time.sleep(0.001)
GPIO.setup(3, GPIO.OUT)
time.sleep(0.001)
GPIO.output(3, False)
time.sleep(0.001)
Sending the Stop signal
Here, the SDA line is taken high followed by the SCL line.
def stop():
#stop bit
GPIO.output(5, True)
time.sleep(0.001)
GPIO.output(3, True)
time.sleep(0.001)
Setting a pin High or Low
The correct data is sent to the GPIO register of the MCP23008 to do this. First we need to define the address of this.
#set GPIO register
gpio= [0, 0, 0, 0, 1, 0, 0, 1]
The status of all the outputs is held in a register and the default value of this is set.
#psuedo output latch
outstatus=[0, 0, 0, 0, 0, 0, 0, 0]
Then the setbit and clrbit functions are used to set individual pins as shown below.
def setbit(address, byte):
#sets selected port pin
sendbyte(address)
for x in range (0,8):
if byte[x] ==1:
outstatus[x]=1
byte=outstatus
data (byte)
ack()
stop()
def clrbit(address, byte):
#clears selected port pin
sendbyte(address)
for x in range (0,8):
if byte[x] ==0:
outstatus[x]=0
byte=outstatus
data (byte)
ack()
stop()
The port pins are defined below.
#set relay ON
ONrelay0= [0, 0, 0, 0, 0, 0, 0, 1]
ONrelay1= [0, 0, 0, 0, 0, 0, 1, 0]
ONrealy2= [0, 0, 0, 0, 0, 1, 0, 0]
ONrelay3= [0, 0, 0, 0, 1, 0, 0, 0]
ONrelay4= [0, 0, 0, 1, 0, 0, 0, 0]
ONrelay5= [0, 0, 1, 0, 0, 0, 0, 0]
ONrelay6= [0, 1, 0, 0, 0, 0, 0, 0]
ONrelay7= [1, 0, 0, 0, 0, 0, 0, 0]
#set relay OFF
OFFrelay0= [1, 1, 1, 1, 1, 1, 1, 0]
OFFrelay1= [1, 1, 1, 1, 1, 1, 0, 1]
OFFrealy2= [1, 1, 1, 1, 1, 0, 1, 1]
OFFrelay3= [1, 1, 1, 1, 0, 1, 1, 1]
OFFrelay4= [1, 1, 1, 0, 1, 1, 1, 1]
OFFrelay5= [1, 1, 0, 1, 1, 1, 1, 1]
OFFrelay6= [1, 0, 1, 1, 1, 1, 1, 1]
OFFrelay7= [0, 1, 1, 1, 1, 1, 1, 1]
How to use these functions for the Custard Pi 6
The functions described can be downloaded from here as cpi6.txt file. Just place this in the folder that you are developing your Python code and rename it cpi6.py
Then just call the relevant routines to control the relays on the card as shown by the sample Python code below. The "import cpi6" command makes the functions we
defined available to this program. Just remember to use cpi6. before you call these functions or vaiables.
#1/usr/bin/env python
import RPi.GPIO as GPIO
import time
import cpi6
GPIO.setmode(GPIO.BOARD)
#start program
board1=cpi6.add1
cpi6.setasoutput(board1)
while True:
cpi6.setbit(board1, cpi6.ONrelay0)
cpi6.setbit(board1, cpi6.ONrelay1)
cpi6.setbit(board1, cpi6.ONrelay7)
cpi6.clrbit(board1, cpi6.OFFrelay0)
cpi6.clrbit(board1, cpi6.OFFrelay1)
cpi6.clrbit(board1, cpi6.OFFrelay7)
GPIO.cleanup()
import sys
sys.exit()
Summary
Although I have explained the IIC bus and the MCP23008 in fair detail, the user only needs to use the following commands when controlling the Custard Pi 6 and have an understanding of how to set the address on the card which is explained furtherin this slideshow.
setasoutput - sets all the 8 pins as outputs
setbit - sets the particular pin HIGH - switch relay ON
clrbit - sets the chosen pin LOW - switch relay OFF
This comment has been removed by the author.
ReplyDeleteCan you tell me if there is a way of reading the current state of the relays please?
ReplyDelete