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).

Motivation
- 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.

Applications
- 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.
Features
- 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
Pinout
Note: Vin on Arduino Nano becomes the 12 V rail when the mux is powered by the 14.5-26 VDC input.

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:


where
- 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 =
54.4375
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
REF | MFG P/N | MFG | Digi-Key P/N | Mouser P/N | Description | Price (ea) (DigiKey) | Price (ea) (Mouser) | QTY | Price (ext) (DigiKey) | Price (ext) (Mouser) |
---|---|---|---|---|---|---|---|---|---|---|
Arduino Nano | DFR0010 | DFRobot | 1738-1026-ND | 426-DFR0010 | DFRDUINO NANO V3.1 | 20.09 | 19.5 | 1 | $20.09 | 19.5 |
U1, U3 | SN74HC595DWR | TI | 296-14858-1-ND | 595-SN74HC595DWR | IC 8BIT SHIFT REG 3ST-OUT 16SOIC | 0.55 | 0.53 | 2 | $1.10 | 1.06 |
U2, U4 | ULN2803ADWR | TI | 296-35964-1-ND | TRANS 8NPN DARL 50V 0.5A 18SO | 1.53 | 1.41 | 2 | $3.06 | 2.82 | |
U5 | MAX3232CDR | TI | 296-13094-2-ND | 595-MAX3232CDR | IC DRVR/RCVR MULTCH RS232 16SOIC | 1.89 | 1.83 | 1 | $1.89 | 1.83 |
U6 | MC78M12CDTRKG | On Semi | MC78M12CDTRKGOSCT-ND | 863-MC78M12CDTRKG | IC REG LINEAR 12V 500MA DPAK | 0.49 | 0.43 | 1 | $0.49 | 0.43 |
Kn | G6DN-1A DC5 | Omron | Z5439-ND | 653-G6DN1ADC5 | General Purpose Relay SPST-NO (1 Form A) 5VDC Coil Through Hole | 1.665 | 1.52 | 16 | $26.64 | 24.32 |
C2, C3, C4, C5, C6, C7, C8 | CGA3E2X7R1H104K080AE | TDK | 445-15957-1-ND | 581-06033C104K4 | CAP CER 0.1UF 50V X7R 0603 | 0.121 | 0.104 | 10 | $1.21 | 1.04 |
C1 | CL10B105KO8VPNC | Samsung | 1276-6613-1-ND | 81-GCJ188R71E105KA1D | CAP CER 1UF 16V X7R 0603 | 0.141 | 0.185 | 10 | $1.41 | 1.85 |
C9 | CGA3E3X5R1H334K080AB | TDK | 445-12504-1-ND | 810-CGA3E3X5R1H334KB | CAP CER 0.33UF 50V X5R 0603 | 0.232 | 0.292 | 10 | $2.32 | 2.92 |
C10 | GMK212BJ105KGHT | Taiyo | 587-3365-1-ND | 963-GMK212BJ105KGHT | CAP CER 1UF 35V X5R 0805 | 0.3 | 0.27 | 3 | $0.90 | 0.81 |
LED | LTST-C191TGKT | Lite-On | 160-1888-1-ND | 859-LTST-C191TGKT | Standard LEDs – SMD Green Clear 525nm | 0.436 | 0.32 | 16 | $6.98 | 5.12 |
LED | LTST-C190KRKT | Lite-On | 160-1436-1-ND | 859-LTST-C190KRKT | LED RED CLEAR 0603 SMD | 0.27 | 0.25 | 2 | $0.54 | 0.5 |
Rn | ERJ-PA3J221V | Panasonic | P220BZCT-ND | 660-RK73B1HTTC221J | RES SMD 220 OHM 5% 1/4W 0603 | 0.11 | 0.038 | 20 | $2.20 | 0.76 |
Rn | ERJ-PA3J471V | Panasonic | P470BZCT-ND | 660-RK73B1HTTC471J | RES SMD 470 OHM 5% 1/4W 0603 | 0.11 | 0.038 | 20 | $2.20 | 0.76 |
Rn | ERJ-PA3J681V | Panasonic | P680BZCT-ND | 660-RK73B1HTTC681J | RES SMD 680 OHM 5% 1/4W 0603 | 0.11 | 0.038 | 20 | $2.20 | 0.76 |
J3, J4 | YO0201500000G | Amphenol | 609-4725-ND | 350 TB FXD 180 TMT | 0.3692 | 0.49 | 26 | $9.60 | 12.74 | |
J1, J2 | M20-7821546 | Harwin | 571-6-534237-3 | Headers & Wire Housings 15 PIN SIL VERTICAL SOCKET TIN | 1.51 | 1.51 | 2 | $3.02 | 3.02 | |
N/A | 2970015 | Phoenix | 277-2803-ND | 651-2970015 | Mounting base, 72x45mm, din rail | 5.65 | 5.23 | 1 | $5.65 | 5.23 |
N/A | 2970031 | Phoenix | 277-7310-ND | 651-2970031 | Din rail clip for mounting base | 2.84 | 2.62 | 1 | $2.84 | 2.62 |
N/A | 2970002 | Phoenix | 277-2808-ND | 651-2970002 | Side stopper thing | 2.54 | 2.54 | 1 | $2.54 | 2.54 |
$96.88 | 90.63 |
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.begin(9600);
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
//NOTICE THAT WE ARE COUNTING DOWN in our for loop
//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) ){
setChannel(muxChannel[request],muxChannel[0]);
}else if(( (request > 8) & (request < 17) )){
request=request-8;
setChannel(muxChannel[0],muxChannel[request]);
}else{
//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.”