Introduction: Handle Lots of Relays With Only 2 Wires

I recently worked on a project that needed to control a lot of relays. There are these nice boards with 8 or 16 relays that are well suited for my purpose. Both boards have a male pin header with GND and 5Volt pins on the outside and a pin for every relays (the 8 relays board uses a single row of ten pins, the 16 relays board uses two rows of ten pins). The logic is low for closing (activate) a relay and high for opening a relay.

This post was first published here!

The only problem is that for controlling so many relays with a micro processor you need a lot of wires and a micro processor with a lot of GPIO pins. For this project the ESP8266 is the micro processor of choice due to it ability to connect to a local WiFi network, the speed and the amount of flash memory. The only drawback is: The ESP8266 does not have enough GPIO pins for the task ahead. The solution, of-course, is to interface with the relays board over I2C which only need two GPIO pins plus GND and Vlogic. But then we need a way to drive the relays board with I2C which, natively, it can’t! Therefor I designed a dedicated I2C multiplexer board to connect as a piggyback to these relays boards to make them I2C compliant!

Design Goals

The board should fit on both 8 and 16 relays boards with as little hassle as possible. The I2C bus must comply to both 3v3 and 5 volt logic so it can be used with, for instance, an Arduino UNO (5 volt) and modern 3v3 boards like the ESP family. For convenience it must be possible to program the ATmega328 by a standard ISP connector. Opening and closing of a specific relay should be as easy as digitalWrite() to GPIO pins (we need an accompanying library for this).

How did I do it?

Well, I designed a board with an ATmega328P-PU micro processor which has 28 pins. Two of these pins are for Vcc (7 and 20) two for GND (8 and 22) two for the external oscillator (9 and 10) AREF (21) is connected to GND by a 100nF cap and RESET (1) is connected to a Reset circuit. That leaves us with 20 free pins. To be able to program the ATmega with an ISP we need three more pins: MOSI(17), MISO(18) and SCK(19) and of-course the I2C lines SDA(27) and SCL(28). But that’s 13 used pins out of 28 which leaves us with only 15 free pins! One pin short if we want a single pin for every single relay!! Luckily, SCK is only used for programming when the Multiplexer is not connected to the relay board (to program the ATmega328 you must remove the I2C Relay Multiplexer Board from the Relay Board!). This means that pin 19 can be used for both SCK and for controlling a relay. Great!

To be able to use the 5Volt I2C logic of the ATmega with the 3v3 I2C logic of modern boards we need two so called “Bi Directional Logic Level Shifters”. As the name implies, these devices can convert a logic (zero or one) signal from one voltage to an other and they can do that both ways. A Level Shifter has a Low side and a High side. We will connect the high side to the ATmega (5Vin) and the low side to the device that will eventually control the I2C bus. So how do we make this I2C Relay Multiplexer Board suited for both 3v3 and 5Volt? Thats simple. You can make the Low side of the Level Shifter as high as the High side. So if we connect the Vlogic (being 3v3 or 5volt) of the controlling micro processor to Vext (the Low side of the Level Shifter) it will work for both 3v3 and 5volt! Complete Schematic of the I2C Relay Multiplexer.

For the ATmega328P I wrote firmware so the ATmega can understand I2C commands and act accordantly to the commands given. As every I2C device it has registers you can read from or write to. The register map looks like this:

registerLayout:
	  byte      status;         // 0x00 (RO)
	  byte      majorRelease;   // 0x01 (RO)
	  byte      minorRelease;   // 0x02 (RO)
	  byte      lastGpioState;  // 0x03 (RO)
	  byte      whoAmI;         // 0x04 (R/W)
	  byte      numberOfRelays; // 0x05 (R/W)
	  byte      Command;        // 0xF0 (W) 

status is a read-only register (0x00) that holds the status of the multiplexer. The major- and minorRelease registers (0x01 and 0x02) gives you information on the firmware release of the multiplexer. The lastGpioState register (0x03) provides information on the state (HIGH/LOW) of the last used relay. whoAmI is the register (0x04) that holds the I2C address of the multiplexer. The default address is 0x48 but you can change it to everything between 0x01 (1dec) and 0x7F (127dec). Once changed the new address is saved in EEPROM and that will be the new device address from then on (until you change it again). numberOfRelays is the register (0x05) that holds, well, the number of relays on the used board. You can set this to 16 (for a 16 relays board) or 8 (for an 8 relays board). This value will also be saved in EEPROM. To interface between your program (sketch) and the I2C Relay Multiplexer I made a library (I2C_RelaysMux). This library has the following methods:

  bool    begin(Wire, deviceAddress);
  bool    isConnected();
  byte    getMajorRelease();
  byte    getMinorRelease();
  byte    getWhoAmI();
  byte    getNumRelays();
  byte    getStatus();
  bool    writeCommand(COMMAND);
  bool    digitalRead(relayNr); 
  bool    digitalWrite(relayNr, {HIGH|LOW}); 
  bool    setI2Caddress(newAddress);  // 1 .. 127 (dec)        
  bool    setNumRelays(numRelays);    // 8 or 16 (dec)        
  void    showRegister(regSize, *regPtr, &Stream); 

To use this library you first have to install it. From github download the library as a .ZIP file. Then, in the Arduino IDE goto [Sketch] and then to “Include Library” -> “Add .ZIP library…” In the popup window select the “I2C_RelaysMux-master.zip” file and click on the [Choose] button. In your program you have to include the library with the following code:

 #include I2CMUX          relay;     //Create instance of the I2CMUX object

In the setup() function you make the connection to the multiplexer:

Wire.begin();
if (!relay.begin(Wire, 0x48))
{
Serial.println(“No device found. Abort!”);
while (true) {}
}

You can now start using the multiplexer by sending instructions like this:

relay.digitalWrite(2, HIGH); // close relay 2
state = relay.digitalRead(2); // state should now be 1
relay.digitalWrite(5, HIGH); // close relay 5
relay.digitalWrite(2, LOW); // open relay 2
for (int r=1; r<=16; r++) // switch all relays HIGH
{
relay.digitalWrite(r, HIGH);
}

The I2C_RelaysMux library with the ATmega firmware and two example programs are on github.

Step 1:

Example Programs

The I2C_RelaysMux library comes with two example programs. The first one is for an Arduino UNO board, the second, more elaborate example, is for an ESP8266 board.

I2C UNO RelaysMux Test

The I2C_UNO_RelaysMux_Test is for an Arduino UNO board. After initialization (the setup() function) it loops over a readCommand(); function. This function reads command from the Serial Monitor and, if the command is recognized, it will execute it. The following commands are valid:

n=1;set relay n to ‘closed’ (activate) [n= 1 to 16]n=0;set relay n to ‘open’ (de-activate)all=1;set all relays to ‘closed’all=0;set all relays to ‘open’address48;set I2C address to 0x48 (default)address24;set I2C address to 0x24board8;set board type to 8-relays boardboard16;set board type to 16-relays board (default)status;I2C Relay Multiplexer statuspinstate;List the state of all the relayslooptest;Chasing relays until a new commend is enteredmuxtest;test on relays board (this test is performed local on the I2C Relay Multiplexer Board)whoami;returns I2C address of the I2C Relay Multiplexer Boardwriteconfig;write config to EEPROM (I2C address and board-type)reboot;reboot the I2C Relay Multiplexer Boardrescan;re-scan for I2C Relay Multiplexer Board addresshelp;Shows valid commands

You can enter multiple commands at one line, separating them by a semicolon (;). After a period of in-activity the looptest will automatically start.

looptest (Chasing Relays)

I2C ESP8266 RelaysMux Test

The I2C_ESP8266_RelaysMux_Test is for an ESP8266 board. The functionality of this test program is the same as that of the I2C_UNO_RelaysMux_Test program with some added functionality. For this program to work you have to add the credentials of your WiFi network (lines 61 and 62). 

#ifndef STASSID #define STASSID "your-ssid" #define STAPSK "your-password"
#endif

  #endif#ifndef STASSID
  #define STASSID "your-ssid"
  #define STAPSK  "your-password"
  #endif

telnet

You can connect to the ESP8266 by the telnet protocol. This gives you the ability to remotely control the Relays Board! The commands are the same as from the Serial Console. A typical telnet session could look like this:

telnet 192.168.2.16                     <<< User input
Trying 192.168.2.16...
Connected to 192.168.2.16.
Escape character is '^]'.
command[]
help <<< User input
command[help]
Commands are:
n=1; -> sets relay n to 'closed'
n=0; -> sets relay n to 'open'
all=1; -> sets all relay's to 'closed'
all=0; -> sets all relay's to 'open'
address48; -> sets I2C address to 0x48
address24; -> sets I2C address to 0x24
board8; -> set's board to 8 relay's
board16; -> set's board to 16 relay's
status; -> I2C mux status
pinstate; -> List's state of all relay's
looptest; -> Chasing Relays test
muxtest; -> On board test
whoami; -> shows I2C address Slave MUX
writeconfig; -> write config to eeprom
reboot; -> reboot I2C Mux
reScan; -> re-scan I2C devices
help; -> shows all commands
pinstate <<< User input
command[pinstate]
Pin: 6543210987654321
State: LLLLLLLLLLLLLLLL
1=1;3=1;5=1;6=1;8=1;13=1;pinstate; <<< User input
command[1=1]
command[3=1]
command[5=1]
command[6=1]
command[8=1]
command[13=1]
command[pinstate]
Pin: 6543210987654321
State: LLLHLLLLHLHHLHLH
whoami <<< User input
command[whoami]
>>> I am 0x48 *-- response from I2C Relay Multiplexer
address24 <<< User input
command[address24]
*-- I2C Relay Multiplexer board reboots
*-- with the new I2C address
*-- The test software will rescan to find
*-- this new address
pinstate <<< User input
command[pinstate]
Pin: 6543210987654321
State: LLLLLLLLLLLLLLLL
whoami <<< User input
command[whoami]
>>> I am 0x24
board8 <<< User input
command[board8]
pinstate <<< User input
command[pinstate]
Pin: 87654321
State: LLLLLLLL
all=1;pinstate <<< User input
command[all=1]
command[pinstate]
Pin: 87654321
State: HHHHHHHH
all=0;pinstate <<< User input
command[all=0]
command[pinstate]
Pin: 87654321
State: LLLLLLLL
^] <<< User enters [Ctrl]+]
telnet> q <<< User enters “q”
Connection closed.

Webserver

This test program also has a webserver to which you can connect to with a web-browser. In your browser type the following URL:

  http://testMux.local/

or

 http://IPaddress-esp8266/

the program presents a webpage.

By clicking on the green “Off” or the red “ON” button you can activate or de-activate the corresponding relay (on a 8 relays board you will only see 8 buttons). The refresh rate of the page is about 2 seconds, so it will lag a bit from the actual relay state.

I2C ATmega RelaysMux

This is the firmware for the ATmega328P-PU micro processor. The principle of this firmware is quite simple. After setting all the GPIO pins as OUTPUT and HIGH (remember, the relay board uses HIGH input to de-activate the relays and a LOW input to activate a relay) it will read the configuration from EEPROM to determine the type of board (8 or 16 relays) and the I2C address to listen to. Then it initializes the I2C bus with the correct I2C address and two callback functions

 Wire.onReceive(receiveEvent);
 Wire.onRequest(requestEvent);

If the I2C Master sends a byte of data to the I2C Slave the onRequest() event is triggered and that will call the receiveEvent() function. This function will then handle the incoming data. If the I2C Master wants to know what is inside a register it will first send the register number it want to know the value of and then it waits for the I2C Slave to send the requested data (handled in the requestEvent() function). The I2C firmware is event-driven so there is no processing in the loop() function except for resetting the Watch Dog Timer. To program the ATmega328P you can place it in an Arduino UNO board and program it the way you would normally program an Arduino UNO. But for the firmware to work we first need to set some fuses of the ATmega328P before you can flash the firmware. It is absolutely necessary to do this or the firmware will not work! You can set the fuses with avrdude (part of the Arduino IDE) with the following flags:

[...]/bin/avrdude -P usb -c usbtiny -b 9600 -B 100 -p ATmega328P
            -U lfuse:w:0xff:m -U hfuse:w:0xd6:m -U efuse:w:0xff:m

everything needs to be in one line! "[...]" is the path that the Arduino IDE uses to program the Arduino Boards with an Atmel processor.The “-P usb” and ”-c usbtiny” flags may be different if you don’t use an USB programmer.

For makers interested I do have some PCB’s to sell. I can even make a kit with all the parts, including a programmed ATmega328