Note: All photographs appearing on this page are freely usable for any purpose. Links to high-resolution versions of the pictures appear below each picture.

Digital Data Acquisition & Control System


16-Channel solid-state relay/digital output board

High-resolution image shot with a Canon EOS 5D MII


The PPSSR-16 board is a 16-channel digital output board featuring solid-state relays. It normally connects to the DAQ_IF board via the PPDO bus. The PPSSR-16 has the following features:

  • Open Hardware design based on Creative Commons 4.0 license
  • Single 5V power supply input connector (screw terminals)
  • LED indicates when power is applied
  • 16 independent digital output channels featuring solid-state relays (rated at 2 amps each, though the board allows 1A per relay)
  • 16 LEDs indicate the current state of each output channel
  • 16 two-pin screw terminal output connectors with open/closed contacts
  • Each channel is completely isolated from the output control circuit
  • (2) PPDO-compatible ribbon cable connectors (10-pin) -- one for input and one for output (to support daisy-chaning)
  • Schematic and board layout are available in Eagle format
  • DIN rail mounts allow the board to be optionally mounted in a horizontal orientation on a 35mm DIN rail
  • DIN rail brackets are available in .STL format for 3D-printing
  • Full documentation including System Requirements Specifications (SyRS), Hardware Requirements Specifications (HRS), Hardware Inspection list (HI), Hardware Test Cases (HTC), Hardware Test Procedures (HTP), Hardware Design Description (HDD), and (reverse) Traceability Matrix (RTM) are available for the DAQ system, including this board.

Bill of Materials (BOM) for the PPSSR-16 board:

  • (17) 5mm LEDs
  • (16) AQZ10 Solid-State relays
  • (17) 470 Ω resistors
  • (4) 0.1 µF decoupling capacitors
  • (1) 74HC125 quad tri-state buffer
  • (2) TPIC6B595 8-bit serial-in/parallel out high-current shift registers
  • (17) 2-pin screw terminals (5mm/0.2" centers)
  • (2) 10-pin (2x5) male headers
  • (1) PPSSR-16 PCB
  • Optional: (14) test pins
  • Optional: (16) AQZ10 sockets
  • Optional: one set of horizontal 35mm DIN rail mounts for DAQ boards

Note: If you only want a few PPSSR-16 PCBs, contact Plantation Productions ( to see if there are any in stock. Bare boards are $25 each plus shipping. Fully assembled and tested boards with DIN rails and AQZ sockets are $599 each, plus shipping.

If you need more than a couple and you're not in a huge hurry, it costs about $150 (plus about 4-6 weeks) to have a set of 10 manufactured and shipped to you from China. I use Seeed Studio Fusion PCD service ( The PPSSR-16 PCBs are four-layer boards. Here are the Gerber files for them (provide these files to Seeed Studio or your personal PCB manufacturer).

PPSSR-16 Gerber Files for PCB

If you want to modify or enhance the PPSSR-16 design, or re-layout the PCB using Eagle, here are the Eagle files:

PPSSR-16 Eagle files (Schematic and board layout)

If you simply want to view the schematic on-line, you'll find that here:

PPSSR-16 Schematic (PDF)

The DIN rails were created using AutoDesk's Fusion 360 (to produce STL files) and I personally print the results on a Lulzbot Taz6 3D printer using ABS filament (ABS is recommended for this job, PLA and PETG are a bit brittle). The STL files can be found here:

PPSSR-16 DIN Rail Brackets 3D printer files


PPSSR-16 Board Layout

Connecting a PPSSR-16 board to a DAQ system

Up to 10 PPRelay-12 and/or PPSSR-16 (solid-state relay) boards can be daisy-chained together and connected to a single DAQ_IF board. You connect the first board in the daisy-chain directly to the PPDO connection on the DAQ_IF board:


You daisy-chain additional boards (PPRelay-12 or PPSSR-16) by connecting the PPDO Out connector on one board to the PPDO In connector on the next board:



The PPSSR-16 presents one TTL load to all the SPI bus signals. The DAQ_IF board buffers all SPI output signals put on the PPDO bus. Therefore, you can realistically expect a limit of about 10 daisy-chained boards on the PPDO bus. In theory, you could put as many boards in the daisy-chain as you like; however, TTL fanout and trace/ribbon cable length limit you to about 10 boards.

Because the PPDO connector carries high-frequency SPI bus signals, you should try to keep the connecting ribbon cables as short as possible. Excessively long cables may reduce the maximum number of boards you can add to the daisy chain.

Relay Connections

The contacts on each of the 16 relays on the PPSSR-16 board are routed to a 2-pin screw terminal block. These pins are polarized and have (+) and (-) terminals (the relays are DC solid state relays). The relays themselves (AQZ10) are rated for 2A operation. In practice, of course, you do not operate an electronic device at its absolute maximum rated value. Good engineering practice suggests that you derate a device by 50%. Therefore 1A is a more reasonable choice the maximum current you should put through one of these relays. The PPSSR-16 board has 100 mil traces (top and bottom layers) from the relay contacts to the associated screw terminal pins. Therefore, it is probably safe to run these guys with as much as 1 amp without worrying about the PCB heating up too much.

By their design, solid-state relays offer a fair amount of isolation beween the relay circuit and the rest of the circuit (opto-isolation is used by the AQZ10 SSR). However, PPSSR-16 boards are rated for 30VDC at the relay contacts. While the rest of the DAQ system may be spared if a high-voltage surge occurs across the control contacts on the relay, you'll likely damage the (expensive) AQZ10 device in this situation. Note that the AQZ10 SSR has an optional socket. If you anticipate blowing up the SSRs, you may want to install the sockets.



The PPDO (digital output) bus is a 10-pin connector. It has the following pinout:


Note that the PPDO bus is an instance of the DAQ_IF SPI bus with a single chip/board select line (BS0). The PPDO bus presumes that there is a single shift register device hooked up to it (e.g., PPSSR-16 looks like a 16-bit shift regster). The size (in bits) of the shift register is arbitrary. In the DAQ system, each PPRelay-12 or PPSSR-16 board you daisy chain to the PPDO connector adds 16 bits to the size of the shift register. For example, if you have two PPRelay-12 and one PPSSR-16 boards daisy-chained to the PPDO connection, the shift register length is 48 bits (16 bits per board).

If you look at the design of the PPRelay-12 (or PPSSR-16) board, you'll discover that it has two TPIC6B595 serial-in/parallel-out shift registers. The MOSI (Master Out, Slave In -- the PPSSR-16 is a slave) signal coming in on the PPDO In connector is fed to the serial in pin on the first TPIC6B595 shift register. The serial out pin on the first TPIC6B595 shift register goes to the serial in pin on the second TPIC6B595. Finally, the serial out pin on the second TPIC6B595 shift register goes to the MOSI pin on the PPDO Out connector. Therefore, if you shift 48 bits onto the PPDO bus, the last 16 bits you shift will wind up on the first board in the daisy chain; the second 16 bits (the middle 16 bits) you shift in will wind up on the second board in the daisy chain; the first set of 16 bits you shift in wind up on the third board in the daisy chain.

Note that the PPRelay-12 and PPSSR-16 boards do not use the MISO, J238, and IRQ signals. The PPRelay-12 and PPSSR-16 boards are output-only devices, so the MISO (Master input, slave output) pin has no use. Likewise, these two boards don't generate any interrupts, so these boards don't use the IRQ line. The DAQ system reserves the J238 line for end-user use, so the PPRelay-12 and PPSSR-16 boards don't use this pin.

The PPSSR-16 board uses the SCK and MOSI lines to shift data into the shift registers. Although the TPIC6B595 high-current shift registers the PPSSR-16 board uses is not, technically speaking, an SPI device, it turns out that the timing of the SPI clock (SCK) and MOSI (serial output data) signals are compatible with the serial shift clock and serial data in pins on the TPIC6B595 shift register.


Programming PPSSR-16 (PPDO Bus)

As mentioned earlier, the boards daisy-chained to the PPDO connector look like a single shift register to the DAQ_IF board. Adding more PPSSR-16 boards to the daisy chain extends the size of the shift register by 16 bits for each board you add.

To program this "shift register" you must first pull the BS0 line low on the DAQ_IF board. You accomplish this by programming the A0, A1, and A2 output pins (on the DAQ_IF) with zeros and then activate the SPI chip select line. Next, you transmit the specified number of bytes (two bytes for each board in the daisy chain) via the SPI libraries (note: the bit on MOSI gets shifted in on the rising edge of SCK -- this is handled by the SPI libraries or hardware). Finally, you set the BS0 line high (by setting the SPI chip select line high or by writing all ones to the A0, A1, and A2 output lines). On the rising edge of the BS0 signal line, the TPIC6B595 shift registers transfer the data from the internal shift registers to the output latches. Note that during the shift operation, the data on the TPIC6B595 output pins (controlling the relays) does not change until the rising edge of the BS0 signal.

Note: here is some sample code (written for the Netburner) to control the A0, A1, and A2 lines that control BS0:

// disable and enable the board select lines
// for the MCP23s17 chips.
//  Note:   board zero is reserved for the
//          DO24 board (serial shift
//          register board).

void disableDIO( void )
    // Disable by selecting Y7 on the 74HC138.
    // Y7 is unused by the DIO96 and DO24 boards.
    A0Pin()         = 1;    // A pin on '138
    A1Pin()         = 1;    // B pin on '138
    A2Pin()         = 1;    // C pin on '138

// enableDIO-
//  Enables the SPI CS line specified by the argument.
//  board:  0 for PPDIO bus, 1-6 for PPDIO96 buss,
//          7 for disabled

void enableDIO( BYTE board )

    switch( board )
        case 0:
            A0Pin() = 0;    // DOUT boards
            A1Pin() = 0;
            A2Pin() = 0;
        case 1:
            A0Pin() = 1;    // Board 1
            A1Pin() = 0;
            A2Pin() = 0;
        case 2:
            A0Pin() = 0;    // Board 2
            A1Pin() = 1;
            A2Pin() = 0;
        case 3:
            A0Pin() = 1;    // Board 3
            A1Pin() = 1;
            A2Pin() = 0;
        case 4:
            A0Pin() = 0;    // Board 4
            A1Pin() = 0;
            A2Pin() = 1;
        case 5:
            A0Pin() = 1;    // Board 5
            A1Pin() = 0;
            A2Pin() = 1;
        case 6:
            A0Pin() = 0;    // Board 6
            A1Pin() = 1;
            A2Pin() = 1;
            // If bad address, disable the chip selects:
    } // switch
} // enableDIO

Here is some comparable code running on a Teensy 3.2 (Arduino style programming):

 void setBSAdrs( int adrs )
    if( adrs <0 || adrs > 6 )
        adrs = 7;
    digitalWrite( a0, adrs & 1 );
    digitalWrite( a1, (adrs >> 1) & 1 );
    digitalWrite( a2, (adrs >> 2) & 1 );
    if( adrs == 7 )
        digitalWrite( spics, 1 );
        digitalWrite( spics, 0 );
} // setBSAdrs

void disableAdrs( void )
    digitalWrite( spics, 1 );
    digitalWrite( a0, 1 );      // ADRS 7 is unused on DAQ
    digitalWrite( a1, 1 );
    digitalWrite( a2, 1 );
} // setBSAdrs


Here is some Teensy 3.2 library code that writes 16 bits to the SPI bus (the exact code varies by SBC). Note that "RelaySettings" is the SPISettings object created in the Teensy code to communicate with the SPI bus.

SPISettings RelaySettings; //( 2000000, MSBFIRST, SPI_MODE1 );

void transferWord( uint16_t word )
        setBSAdrs( 0 );
        SPI.beginTransaction( RelaySettings );
        SPI.transfer( word >> 8 );
        SPI.transfer( word & 0xff );


So if you wanted to write the 48 bits "0x0000, 0x1f00, 0x8000" to three PPSSR-16 boards attached to the DAQ_IF, you'd use the following Teensy 3.2 code

        setBSAdrs( 0 );
        SPI.beginTransaction( RelaySettings );
        SPI.transfer( 0x80 );       // H.O. byte of 0x8000
        SPI.transfer( 0x0 );        // L.O. byte of 0x8000
        SPI.transfer( 0x1f );       // H.O. byte of 0x1f00
        SPI.transfer( 0x0 );        // L.O. byte of 0x1f00
        SPI.transfer( 0x0 );        // H.O. byte of 0x0
        SPI.transfer( 0x0 );        // L.O. byte of 0x0

Note that you must transfer the H.O. byte of each word first.

Because of the nature of shift registers, if you shift extra data to the shift registers (more than n*2 bytes, where n is the number of daisy-chained boards) then the extra data is lost (the extra data will be the first bytes you shift out). Because of the wiring of the daisy-chain, the last two data bytes you shift out will be shifted into the first board in the daisy chain (the board connected directly to the DAQ_IF board), the two bytes shifted out before the last two bytes will be shifted into the second board of the daisy chain, etc.

Toggling the DAQ_IF reset line will immediately clear all the TPIC6B595 shift registers and output latches. While the reset line is low, the PPSSR-16 and PPSSR-16 boards will force the internal shift registers to all zero bits. On the rising edge of the reset line, the boards will transfer the data (now zeros) from the internal shift registers to the output latches. Note that if you are in the middle of shifting data into the shift registers while also pulsing the reset line (say, in a multi-threaded environment), the results are undefined. If you must shift data into the shift register from one thread and you could be toggling the reset line from a separate thread, you should use synchronization primitives to protect access to the SPI clock.

Here is the Teensy 3.2 code that will toggle the reset line:

#define reset           23

void toggleReset( void )
digitalWrite( reset, 0 );
delay( 3 );
digitalWrite( reset, 1 );
delay( 3 );


Here is the Netburner code that will toggle the reset line:

#define RESET 				(J2[44])

// Reset the DAQ system hardware (put system in fail-safe mode:

void reset( void )

    // Note: as per requirements, pulse should be low for
    // at least 200 mSec.
    RESET = 1;
    RESET = 0;
    RESET   = 1;

Note that DAQ system requirements state that the RESET line must be held low for at least 250 msec. This is why the large delays are present in this code.

If the watchdog timer times out and asserts the WD signal on the PPDO connector, the PPSSR-16 board will automatically put the output relays in a fail-safe mode by clearing the internal shift register and transferring their contents to the TPIC6B595 output latches. As for the reset line, if you're currenting shifting data into the shift register when the WD timer times out, this could create an undefined result. In theory, the software has hung up and probably isn't shifting data to the PPDO connector. In practice, this could be the result of some operation taking too long. To prevent this undefined operation, it's a good idea to always refresh the watchdog timer immediately prior to shifting out any data and checking the WD_LATCH value to ensure that the watchdog timer hasn't already timed out.

Here's some sample Teensy 3.2 code that will refresh the watchdog timer:

#define wd_rfsh         17

void refreshWD( void )
    digitalWrite(wd_rfsh, HIGH );
    delayMicroseconds( 10 );
    digitalWrite( wd_rfsh, LOW);
    delayMicroseconds( 10 );

Here is the Netburner MOD54415 code that will refresh the watchdog timer:

#define RESET 				(J2[44])

			led1Pin()	= 0;
			led1Pin()	= 1;

// shortDelay is defined thusly:
// Software delay function (arg in microseconds)

void shortDelay( unsigned int usec )
    unsigned int i;
    i = 20*usec;    // 20=1usec delay (approx)
        asm("nop"); //needed so delay not optimized out
    }while( i>0);