First part of this blog (you can find it here) was about connecting and controlling single PoRelay8 board with Raspberry Pi. Relay board and Raspberry Pi were communicating over I2C bus. Setup in this example has two limitations: First, I2C bus was developed for communication between devices on same PCB and can be used only by devices position close together. Second, single PoRelay8 board features 8 relay outputs. But what to do if you need more relay outputs?

Answer to both limitations is simple: connect together up to 10 PoRelay8 boards over CAN bus in daisy-chain. In this blog we will present example hot to connect 3 PoRelay8 boards to Raspberry Pi and how to communicate with them. Up to 10 boards can be connected to a single master device (Raspberry Pi in our case, but this can be done with any device that supports I2C protocol).

 

Connecting PoRelay8 boards with CAN bus

For detailed information how to connect first PoRelay board to Raspberry Pi, please look at first part of this blog, which can be accessed here.

In our setup, first relay board is connected to Raspberry Pi over I2C bus and functions as a bridge. Additional PoRelay boards should be connected to bridge relay board in daisy-chain fashion with use of a CAN bus.

CAN bus connection can be made with flat cable with Micro-MaTch connectors (red connector on the relay board) or with use of screw terminal connectors as our case. All connections (picture 1) marked CAN-L and CAN-H must be connected together in serial fashion.  If you want to use long cables, shielded twisted pair cables are recommended.

CAN bus line must be terminated with resistor at both ends. PoRelay boards have termination resistors already integrated. They can be enabled with use of jumper as is shown on picture 1. Watch out for correct jumper orientation. Note that jumpers must be removed on relay boards that are not at the end of the CAN bus (only second board in our csae).

Picture 1: CAN connections and termination jumpers

Picture 1: CAN connections and termination jumpers

Setting the outputs on PoRelay8 boards

Primarily, the PoRelay8 devices were designed to receive data sent over PoExtBus (serial communication protocol used by PoKeys devices for external outputs). PoRelay8 devices also enable the communications over I2C or CAN buses. Communication with bridge board is quite simple as is presented in previous blog. This board can be controlled directly over I2C. Communication to other boards connected to bridge board with CAN bus is a little bit trickier.

Picture 2: Raspberry Pi and 3 PoRelay8 boards

Picture 2: Raspberry Pi and 3 PoRelay8 boards

In following python example 3 boards are connected to Raspberry Pi. All communication between Pi and bridge board happens over I2C bus. Bridge board forwards all communication with other boards over CAN bus.

To send data over CAN bus we must start with sending 0x40 value over I2C. This is interpreted as send over CAN command. Follows CAN message ID, which consist of two bytes: 0x01 and 0x08. Next byte is number of data bytes we want to send, followed by all data bytes.  Last byte is checksum of all sent bytes.

Reading data that was received from CAN bus is simple. It starts with writing 0x41 byte to I2C bus and reading from I2C expected number of received bytes from CAN bus. First read byte should have value 0x1A, as marker that data was received and is valid.

In our case and python example, we have 2 PoRelay boards connected to bridge board over CAN. At beginning I2C address of the bridge board is known and we can set its outputs simply as was described in previous blog. Now we have to find out addresses of two boards connected to bridge. This can be done by issuing CAN identify command. To do this, we write to I2C bus next bytes:

Send over CAN Data length CAN message ID Data Checksum
0x40 0x01 0x08 0x01 0x10 0x5A

 

In this case, data part with value 0x10 is interpreted as request for identification. Each board connected to bridge will respond with 12 bytes long CAN message containing information about it. Received bytes from 8 to 12 represent boards unique address, that must be used when setting outputs on desired board.

To set the outputs of desired board connected over CAN bus, following sequence must be written to I2C:

Send over CAN Data length CAN message ID Data Checksum
Set outputs Board address Outputs
0x40 0x01 0x08 0x06 0x20 0xXX 0xXX 0xXX 0xXX 0x55 0xXX

This will set outputs targeted board to pattern 01010101. Checksum is sum of all sent bytes masked with 0xFF, as its value must not be greater than 255.

Following python example will do next:

  • Identify bridge board and write out boards address and firmware version
  • Set outputs of bridge board to 0x55
  • Identify all boards connected to can bus and print out their addresses
  • Set outputs of every board to lowest byte of its address

Do not forget, for this example to work, you must have pigpio library installed on your Raspberry Pi and daemon running.

Python example

 

import struct
import time
import pigpio
import random

############################
# Read data from CAN
############################
def CAN_read():

    request = [ 0x41 ] # Read from CAN command
    pi.i2c_write_device(h, request)
    (count, response) = pi.i2c_read_device(h, 13)

    # Check if received data is OK
    if ( response[0] == 0x1A ):
        return response
    else:
        return 0

#############################
# CAN identify
#############################
def CAN_identify():

    command = [ 0x40 ] # Send over CAN
    CAN_ID = [ 0x08, 0x01 ] # CAN message ID 
    data = [ 0x10 ] # Identify command
    data_length = [ len(data) ]

    request = command + data_length + CAN_ID + data
    request.append( sum(request) & 0xFF ) # append checksum

    pi.i2c_write_device(h, request)
    (count, response) = pi.i2c_read_device(h, 1)

    print("Response after Identify command:")
    print(response[0])

    # Wait for 400 ms for replays from all boards
    time.sleep(.400)

    device_num = 1
    found_devices = []
    while True:
        response = CAN_read()

        if response:
            found_devices.append( list(response[8:12]) )

            (device_id,) = struct.unpack_from("I", response[8:12], 0)
            print( 'Device {} ID: {:02x}'.format(device_num, device_id) )
        else:
            break

        device_num += 1

    return found_devices

#############################
# Set outputs on CAN board
#############################
def CAN_set_outputs(board_id, output):

    command1 = [ 0x40 ] # Send over CAN
    command2 = [ 0x20 ] # Set outputs command
    CAN_ID = [ 0x08, 0x01 ]
    data = [ output ] # Identify command
    data_length = [ len(command2) + len(data) + len(board_id) ]

    request = command1 + data_length + CAN_ID + command2 + board_id + data
    request.append( sum(request) & 0xFF ) # append checksum

    pi.i2c_write_device(h, request)

    return

#############################
# Main program
#############################

pi = pigpio.pi()

DEVICE_ADDRESS = 0x7b # 7 bit I2C address of relay board

h = pi.i2c_open(1, DEVICE_ADDRESS)

request = [ 0x10 ] # Identify board command, to which bridge beard will respond
pi.i2c_write_device(h, request) # Send first command
(count, response) = pi.i2c_read_device(h, 9) # Read bridge board response (9 bytes)

# Print bridge board address (in hex format) and version
print( 'Version: {}.{}'.format(response[3], response[4]) ) # 
(device_id,) = struct.unpack_from("I", response, 5)
print( 'Bridge Device ID: {:02x}'.format(device_id) )

# Set outputs on Bridge relay board
outputs = 0x22
request = [ 0x20, outputs, (outputs + 0x20) & 0xFF]
pi.i2c_write_device(h, request)

# Identify devices connected to CAN bus
can_boards = CAN_identify()

# CAN Set outputs
for device in can_boards:
    CAN_set_outputs(device, device[0]);

pi.i2c_close(h)