16-Channel relay multiplexer shield for Arduino Nano

This device makes use of the shiftOut() function and two 8-bit serial-in, parallel-out shift registers (74HC595). A useful shift register discussion can be found here. The relay coils are driven by two Darlington transistor arrays (ULN2803).

Completed PCB Assembly – 07/21/2018


  • I’ve worked on multiple test automation projects in which a low cost relay mux would have been useful.
  • I was never able to find one and so I would just use a general purpose relay module to perform the multiplexing.
  • While it’s possible to make this work there are some drawbacks:
    • Requires lots of wiring external to the relay module. This complicates the control panel or test fixture and also the panel drawing or test fixture documentation.
    • The multiplexing must be handled by whatever program is controlling the relay module (e.g., your PC-based program, PLC, etc.), which means more software effort and longer debugging times.
  • The relay multiplexer approach presented here has lots of advantages:
    • All multiplexing/demultiplexing routing is handled on the PCB.
    • Most of this routing is done on internal copper planes for better signal quality.
    • The multiplexing does not need to be written into the PC-based test program. The PC-based program can just tell the relay mux which channel it wants to read, and the relay mux will handle it.
3D Model of a Partially Completed Assembly


  • Automated signal acquisition
  • Production test systems, V&V testing, R&D
  • Low cost alternative to high end relay multiplexers
  • Useful for programmatically routing many test points on a given device to a single DMM or oscilloscope. (It multiplexes test points to an instrument.)
  • The measurements can be active (voltage or current) or passive (resistance, capacitance, etc.)
  • It could also be used for demultiplexing (meaning it could route one signal generation instrument to many devices).

How does it work?

The idea behind relay multiplexing is similar to other kinds of multiplexing in that it allows multiple signals to be sent over the same medium, facilitating the sharing of what is usually a scarce resource (like an expensive digital multimeter with only 1 channel).

Consider a simple example in which a 3 channel multiplexer and one voltmeter are used to measure three voltage test points.

In the first state no channels are connected, and the voltmeter reads 0 V. In the second state channel 1 is closed and the meter reads 7.4 mV. The process continues until the mux has stepped through each test point, allowing all test points to be measured by the same instrument.

This simple implementation is actually very practical when you consider that most DMMs these days have reliable autoranging features. Relay muxes are commonly used to measure voltage, current, resistance, and capacitance. FETs, SSRs, and other types of switching devices can be used in place of relays.


  • 16 channel relay multiplexer/demultiplexer
    • Max current per channel: 5 A (limited by relay contacts)
  • Relay specifications:
    • Contact rating (current): 5 A
    • Switching voltage: 277 VAC, 125 VDC MAX
    • Operate time: 10 ms
    • Release time: 5 ms
  • Power:
    • Input: 5-15 V or 14.5-36 V
    • Output: Provides 3.3 V, 5 V, and 12 V power rails
  • Communication:
    • RS-232
    • USB virtual COM
    • serial TTL
    • SPI (supported by ATMEGA328P, but not by Arduino library)
  • 7 digital I/O pins, including 2 PWM
  • 4 analog inputs (10 bit, 0-5 V)
  • DIN rail or panel mount
  • Working Arduino code is shown below, or you can write your own
  • Open source hardware


Note: Vin on Arduino Nano becomes the 12 V rail when the mux is powered by the 14.5-26 VDC input.

Pin Descriptions – 16 Channel Relay Multiplexer

Electrical block diagram

Shift registers and relay drivers

Relay multiplexing

RS-232 transceiver

Board layout

  • Layers: 4
  • Finished copper: 2 oz
  • Inner copper: 1.5 oz
  • Silk screen color: white
  • Solder mask color: green
  • The two internal planes are used for routing Input and Com to the instrument.
  • The top and bottom copper layers are used for everything else

In the above screenshot, red traces are on the top copper and blue traces are on the bottom copper. Notice there are 16 traces that are significantly thicker than the rest of the traces (eight in the top right, eight in the bottom left). These traces are 100 mils thick and represent the portion of the multiplexing routing that can’t be included on one of the internal planes. To ensure these traces can handle more than 5 A, I used the following equations from IPC-2221:


  • w = minimum trace width (mils)
  • A = cross-sectional area (mils^2)
  • H = trace thickness (oz/ft^2)
  • ΔT = max permissible rise in temperature (deg C) of the conductor above ambient
  • internal layers: k = 0.024, b = 0.44, c = 0.725
  • external layers: k = 0.048, b = 0.44, c = 0.725

Given the relay contacts are limited to 5 A, I just wanted to make sure that the traces can handle more than 5 A. Using MATLAB or Scilab, the following one-liner finds the minimum trace width for I = 5 A to be 54.5 mils:

>> I=5; k=0.048; b=0.44; c=0.725; dT=10; T=2; A = (I/(k*(dT)^b))^(1/c); w=A/(T*1.378)

w =


This assumes a conservative value of 10 deg C for the max permissible rise in temperature. If a larger temperature rise is permitted, the trace width can be less:

Given w=54.4 mil for ΔT=10 deg C, I decided to go with a 100 mil thickness, which is approximately double this amount.

If I had used 1 oz copper, then a 100 mil thickness probably wouldn’t have been enough:

With w=100 mil and H=2 oz/ft^2, the 5 A relay contacts will probably fail before the traces do:

Parts list

REFMFG P/NMFGDigi-Key P/NMouser P/NDescriptionPrice (ea) (DigiKey)Price (ea) (Mouser)QTYPrice (ext) (DigiKey)Price (ext) (Mouser)
Arduino NanoDFR0010DFRobot1738-1026-ND426-DFR0010DFRDUINO NANO V3.120.0919.51$20.0919.5
U1, U3SN74HC595DWRTI296-14858-1-ND595-SN74HC595DWRIC 8BIT SHIFT REG 3ST-OUT 16SOIC 0.550.532$1.101.06
U2, U4ULN2803ADWRTI296-35964-1-ND TRANS 8NPN DARL 50V 0.5A 18SO 1.531.412$3.062.82
U5MAX3232CDRTI296-13094-2-ND595-MAX3232CDRIC DRVR/RCVR MULTCH RS232 16SOIC1.891.831$1.891.83
KnG6DN-1A DC5OmronZ5439-ND 653-G6DN1ADC5General Purpose Relay SPST-NO (1 Form A) 5VDC Coil Through Hole1.6651.5216$26.6424.32
C2, C3, C4, C5, C6, C7, C8CGA3E2X7R1H104K080AETDK445-15957-1-ND581-06033C104K4CAP CER 0.1UF 50V X7R 06030.1210.10410$1.211.04
C1CL10B105KO8VPNCSamsung1276-6613-1-ND81-GCJ188R71E105KA1DCAP CER 1UF 16V X7R 06030.1410.18510$1.411.85
C9CGA3E3X5R1H334K080ABTDK445-12504-1-ND810-CGA3E3X5R1H334KBCAP CER 0.33UF 50V X5R 06030.2320.29210$2.322.92
C10GMK212BJ105KGHTTaiyo587-3365-1-ND963-GMK212BJ105KGHTCAP CER 1UF 35V X5R 08050.30.273$0.900.81
LEDLTST-C191TGKTLite-On160-1888-1-ND859-LTST-C191TGKTStandard LEDs – SMD Green Clear 525nm0.4360.3216$6.985.12
LEDLTST-C190KRKTLite-On160-1436-1-ND859-LTST-C190KRKTLED RED CLEAR 0603 SMD0.270.252$0.540.5
RnERJ-PA3J221VPanasonicP220BZCT-ND660-RK73B1HTTC221JRES SMD 220 OHM 5% 1/4W 06030.110.03820$2.200.76
RnERJ-PA3J471VPanasonicP470BZCT-ND660-RK73B1HTTC471JRES SMD 470 OHM 5% 1/4W 06030.110.03820$2.200.76
RnERJ-PA3J681VPanasonicP680BZCT-ND660-RK73B1HTTC681JRES SMD 680 OHM 5% 1/4W 06030.110.03820$2.200.76
J3, J4YO0201500000GAmphenol609-4725-ND 350 TB FXD 180 TMT0.36920.4926$9.6012.74
J1, J2M20-7821546Harwin 571-6-534237-3Headers & Wire Housings 15 PIN SIL VERTICAL SOCKET TIN1.511.512$3.023.02
N/A2970015Phoenix277-2803-ND651-2970015Mounting base, 72x45mm, din rail5.655.231$5.655.23
N/A2970031Phoenix277-7310-ND651-2970031Din rail clip for mounting base2.842.621$2.842.62
N/A2970002Phoenix277-2808-ND651-2970002Side stopper thing2.542.541$2.542.54

Arduino code

//  Name    : Relay-mux-control                                                               //
//  Author  : Mike Burdis                                                                     //
//  Date    : 07-20-2018                                                                      //
//  Notes   : shiftOut written by Carlyn Maw and Tom Igoe                                     //
//          : https://www.arduino.cc/reference/en/language/functions/advanced-io/shiftout/    // 

//Pin connected to ST_CP of 74HC595
int latchPin = 3;
//Pin connected to SH_CP of 74HC595
int clockPin = 6;
////Pin connected to DS of 74HC595
int dataPin = 5;
//holder for infromation you're going to pass to shifting function
byte data = 0;
//define mux channel array
byte muxChannel[9];
//incoming byte
byte recByte = 0;

void setup() {
  //set pins to output because they are addressed in the main loop
  pinMode(latchPin, OUTPUT);
  Serial.println("Relay multiplexer program. Enter ? for a list of commands.");

  muxChannel[0] = 0x00;  //00000000
  muxChannel[1] = 0x01;  //00000001
  muxChannel[2] = 0x02;  //00000010
  muxChannel[3] = 0x04;  //00000100
  muxChannel[4] = 0x08;  //00001000
  muxChannel[5] = 0x10;  //00010000
  muxChannel[6] = 0x20;  //00100000
  muxChannel[7] = 0x40;  //01000000
  muxChannel[8] = 0x80;  //10000000

void loop() {

   if (Serial.available()>0) { //If the buffer is not empty
      while(Serial.available()>0) { //When a byte is read it is expunged from buffer
      // Keep reading the buffer until all the bytes have been read
         recByte = Serial.read(); // read in the next byte
      if((recByte>96)&(recByte<114)){//if you get a character between a and q
        Serial.print(":"); //Colon used to make parsing the reply easier
        Serial.println(recByte); //echo the char that was sent
        handleChanRequest(recByte-97); //handle the channel request
      if(recByte==63){//if user requests help
        help();// print list of commands

// the heart of the program
void shiftOut(int myDataPin, int myClockPin, byte myDataOut) {
  // This shifts 8 bits out MSB first, 
  //on the rising edge of the clock,
  //clock idles low

  //internal function setup
  int i=0;
  int pinState;
  pinMode(myClockPin, OUTPUT);
  pinMode(myDataPin, OUTPUT);

  //clear everything out just in case to
  //prepare shift register for bit shifting
  digitalWrite(myDataPin, 0);
  digitalWrite(myClockPin, 0);

  //for each bit in the byte myDataOut
  //This means that %00000001 or "1" will go through such
  //that it will be pin Q0 that lights. 
  for (i=7; i>=0; i--)  {
    digitalWrite(myClockPin, 0);

    //if the value passed to myDataOut and a bitmask result 
    // true then... so if we are at i=6 and our value is
    // %11010100 it would the code compares it to %01000000 
    // and proceeds to set pinState to 1.
    if ( myDataOut & (1< 0) & (request < 9) ){
  }else if(( (request > 8) & (request < 17) )){
    //handle exception
    Serial.println("Exception: Invalid relay channel");

void help(void){
  Serial.println("The following commands are available:");
  Serial.println("a - Clear all channels");
  Serial.println("b - Set channel 1");
  Serial.println("c - Set channel 2");
  Serial.println("d - Set channel 3");
  Serial.println("e - Set channel 4");
  Serial.println("f - Set channel 5");
  Serial.println("g - Set channel 6");
  Serial.println("h - Set channel 7");
  Serial.println("i - Set channel 8");
  Serial.println("j - Set channel 9");
  Serial.println("k - Set channel 10");
  Serial.println("l - Set channel 11");
  Serial.println("m - Set channel 12");
  Serial.println("n - Set channel 13");
  Serial.println("o - Set channel 14");
  Serial.println("p - Set channel 15");
  Serial.println("q - Set channel 16");
  Serial.println("? - Print list of commands");

Serial commands

At the serial terminal, type a question mark and press enter to see a list of commands.

PCB project and fabrication files

I’ve created a GitHub repository here for the PCB project and Gerber files.

A few notes:

  • A schematic (PDF) is included in the top level directory.
  • The original project format is Altium 14.1. Altium can (supposedly) convert to OrCAD and ASCII formats, so I’ve included these formats as well. Just look in the directory “PCB Project.”
  • If for some reason you just need the Gerber files, look in the directory “PCB_Production_Package.”


About the author

Leave a Reply

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