[solved] Drive 4-Channel SPDT Relay via I2C/smbus2 python

I believe I misunderstand the communication protocol for the 4-channel SPDT Relay. The relays are connected to +5; SDA on pin 3; SCL on pin 5, and ground. So that is fine.
</s>i2cdetect -y 1<e> gives me the address </s>0x11<e>
I had anticipated, that I could simply send data binary to switch on and off the channels one by one.
</s><i> </i>from smbus2 import SMBus with SMBus(1) as bus: # Write a byte to address 80, offset 0 data = 0x01 bus.write_byte_data(0x11, 1, data) <e>
the above - in my mind would switch on channel 1, if I had chosen data to be F, all channels should be switched.

The code executed, but none of the relais reacts (measured with multimeter)

Can anybody help? Many thanks!

ah sorry. Reverse engineered it from the cpp example linked. Almost as I thought, but we need to send 0x10 to first stell that we want to control channels. So this does the job for me.

</s>i2cset -y 1 0x11 0x10 0x0f <e>

closed.

Hi fal414, did you solve the issue? im trying to use the same relay dev board and find the address to be 0x11. do you have any sample code or library i could take a look at?

Thanks

I have one of these and will clarify additional information for controlling this 4-channel relay in python.

The Quick Answer

In python, we can do this using the smbus2 module and the write_byte_data() function in the SMBus class.

from smbus2 import SMBus

with SMBus(1) as bus:
    bus.write_byte_data(0x11, 0x10, 0x0b)
    bus.write_byte_data(0x11, 0x10, 0x0f)

The Long Answer

I experimented with writing data using the i2cset command:
i2cset -y 1 0x11 0x10 <DATA>

When looking at the side with the I2C connector, with the left-most relay as #1 (COM1), here is the relay state when sent the hex DATA value to the device. Note that this is the coil state (relay coil ON and red LED ON).


       1   2   3   4
       ---------------
0x00:  OFF OFF OFF OFF
0x01:  ON  OFF OFF OFF
0x02:  OFF ON  OFF OFF
0x03:  ON  ON  OFF OFF
0x04:  OFF OFF ON  OFF
0x05:  ON  OFF ON  OFF
0x06:  OFF ON  ON  OFF
0x07:  ON  ON  ON  OFF
0x08:  OFF OFF OFF ON
0x09:  ON  OFF OFF ON
0x0a:  OFF ON  OFF ON
0x0b:  ON  ON  OFF ON
0x0c:  OFF OFF ON  ON
0x0d:  ON  OFF ON  ON
0x0e:  OFF ON  ON  ON
0x0f:  ON  ON  ON  ON

Unfortunately I could not find a way to get the state of a relay. Reading 0x11 0x10 always locked up my whole I2C bus requiring a cold power cycle of my GrovePi+.

Let us say everything ON is our default state which is 0x0f and I want to power cycle relay 3. I would set state 0x0b then set 0x0f. Using i2cset:

i2cset -y 1 0x11 0x10 0x0b
i2cset -y 1 0x11 0x10 0x0f

In python, we can do this using the smbus2 module and the write_byte_data() function in the SMBus class.

from smbus2 import SMBus

with SMBus(1) as bus:
    bus.write_byte_data(0x11, 0x10, 0x0b)
    bus.write_byte_data(0x11, 0x10, 0x0f)

SMBus(1) is I2C bus 1 (/dev/i2c-1)
0x11 is the I2C address of the relay
0x10 is (I think but not sure) the register we’re sending data to
0x0b is the data from the table above that defines the state of all relays

1 Like

I’m hitting the complete lock up problem when trying to use the 4-Channel SPDP Relay. Happened upon this post. I’ll give my feedback when done.

First, that tale will make a bit more sense if you reverse the registers

       4   3   2   1
       ---------------
0x00:  OFF OFF OFF OFF
0x01:  OFF OFF OFF ON 
0x02:  OFF OFF ON   OFF
0x03:  OFF OFF ON   ON 
0x04:  OFF ON OFF OFF
0x05:  OFF ON OFF ON 
0x06:  OFF ON ON OFF
0x07:  OFF ON ON OFF ON 
0x08:  ON OFF OFF OFF
0x09:  ON OFF OFF ON 
0x0a:  ON OFF ON OFF
0x0b:  ON OFF ON ON 
0x0c:  ON ON OFF OFF
0x0d:  ON ON OFF ON 
0x0e:  ON ON ON OFF
0x0f:  ON ON ON ON 

Thanks for the hints.

I put together a quick python script to to some bitwise or’ing with arguments.

This is like the second python program I ever wrote so it was
google python arguments
google python bitwise or
google python byte type
etc

There is a lot of printf() debugging in here :slight_smile:

#!/usr/bin/python

import sys

# I tried to mess with the byte objext but quit
Regs_to_turn_on = 0

# we should make sure that the arguments are numbers between 1 and 4.
print ('Number of arguments:', len(sys.argv), 'arguments.')
print ('Argument List:', str(sys.argv))

print("\nArguments passed:\n", end = " ")
for i in range(1, len(sys.argv)):
    print(sys.argv[i], end = " ")
    Regs_to_turn_on = Regs_to_turn_on | ( 1 << int(sys.argv[i])-1 )

    print("\n")
    print("Interim Byte to write is = (in hex)" + hex(Regs_to_turn_on))
    print("Interim Byte to write is = (in binary)" + bin(Regs_to_turn_on))
    print("\n")

print("\n")
print("Final Byte to write is = (in hex)" + hex(Regs_to_turn_on))
print("Final Byte to write is = (in binary)" + bin(Regs_to_turn_on))
print("\n")

from smbus2 import SMBus

with SMBus(1) as bus:
    #                                 4321
    bus.write_byte_data(0x11, 0x10, Regs_to_turn_on)

Thnx, this was very helpfull for me!

I just got one of these boards and came across this so figured I would share. See below for a python example where I have replicated the Adruino C++ example so that you can leverage it more easily.

Hope this helps.

import time
import smbus

class GroveRelay(object):
    def __init__(self):
        self.channel_state      = 0
        self.cmd_channel_ctrl   = None
        self.save_i2c_addr      = None
        self.read_i2c_addr      = None
        self.read_firmware_ver  = None
        
    def begin(self, i2c_port, cmd_channel_ctrl, save_i2c_addr, read_i2c_addr, read_firmware_ver):
        self.channel_state      = 0
        self.cmd_channel_ctrl   = cmd_channel_ctrl
        self.save_i2c_addr      = save_i2c_addr
        self.read_i2c_addr      = read_i2c_addr
        self.read_firmware_ver  = read_firmware_ver
        
        self.bus = smbus.SMBus(i2c_port)
        self.bus.write_byte_data(self.save_i2c_addr, self.cmd_channel_ctrl, self.channel_state)
        
    def change_i2c_address(self, new_addr):
        self.bus.write_byte_data(self.save_i2c_addr, self.save_i2c_addr, new_addr)
        self.save_i2c_addr = new_addr;
        
    def get_channel_state(self):
        return self.channel_state
    
    def get_firmware_version(self):
        self.bus.write_byte(self.save_i2c_addr, self.read_firmware_ver)
        return self.bus.read_byte(self.save_i2c_addr)
        
    def channel_ctrl(self, state):
        self.channel_state = state
        self.bus.write_byte_data(self.save_i2c_addr, self.cmd_channel_ctrl, self.channel_state)
        
    def turn_on_channel(self, channel):
        self.channel_state |= (1 << (channel - 1))
        self.bus.write_byte_data(self.save_i2c_addr, self.cmd_channel_ctrl, self.channel_state)

    def turn_off_channel(self, channel):
        self.channel_state &= ~(1 << (channel - 1))
        self.bus.write_byte_data(self.save_i2c_addr, self.cmd_channel_ctrl, self.channel_state)

if __name__ == "__main__":
    I2C_PORT = 1

    CMD_CHANNEL_CTRL        = 0x10
    CMD_SAVE_I2C_ADDR		= 0x11
    CMD_READ_I2C_ADDR		= 0x12
    CMD_READ_FIRMWARE_VER	= 0x13
    
    CHANNLE1_BIT = 0x01
    CHANNLE2_BIT = 0x02
    CHANNLE3_BIT = 0x04
    CHANNLE4_BIT = 0x08
    CHANNLE5_BIT = 0x10
    CHANNLE6_BIT = 0x20
    CHANNLE7_BIT = 0x40
    CHANNLE8_BIT = 0x80

    relay = GroveRelay()
    relay.begin(1, 0x10, 0x11, 0x12, 0x13)
    
    print ("Firmware version:" + str(relay.get_firmware_version()))

    #/* Begin Controlling Relay */
    print("Channel 1 on");
    relay.turn_on_channel(1);
    time.sleep(1);
    print("Channel 2 on");
    relay.turn_off_channel(1);
    relay.turn_on_channel(2);
    time.sleep(1);
    print("Channel 3 on");
    relay.turn_off_channel(2);
    relay.turn_on_channel(3);
    time.sleep(1);
    print("Channel 4 on");
    relay.turn_off_channel(3);
    relay.turn_on_channel(4);
    time.sleep(1);
    relay.turn_off_channel(4);

    relay.channel_ctrl(CHANNLE1_BIT |
                      CHANNLE2_BIT |
                      CHANNLE3_BIT |
                      CHANNLE4_BIT);
    print("Turn all channels on, State: ");
    print(relay.get_channel_state());

    time.sleep(1);

    relay.channel_ctrl(CHANNLE1_BIT |
                      CHANNLE3_BIT);
    print("Turn 1 3 channels on, State: ");
    print(relay.get_channel_state());

    time.sleep(1);

    relay.channel_ctrl(CHANNLE2_BIT |
                      CHANNLE4_BIT);
    print("Turn 2 4 channels on, State: ");
    print(relay.get_channel_state());

    time.sleep(1);

    relay.channel_ctrl(0);
    print("Turn off all channels, State: ");
    print(relay.get_channel_state());

    time.sleep(1);
1 Like