​Expanding the ATtiny85's IO Capacity with Shift Registers

A project log for One-Tiny Bot

Mobile robot controlled by one ATtiny85

C. ParkinC. Parkin 05/06/2015 at 18:440 Comments

The ATtiny85 is a great little chip, but decidedly lacking when it comes to IO pins. While excellent mobile robots can certainly be built around its innate capabilities, I decided that I needed to expand beyond 5 pins for the sorts of applications I had in mind. Figuring out different ways to do so has proven wonderfully educational.

The obvious and easiest place to start was with shift registers, as first suggested to me by the Sparkfun Tiny AVR Programmer Hookup Guide.

Accordingly, I acquired some 74HC165 (PISO: Parallel-In/Serial-Out) and 74HC595 (SIPO: Serial-In/Parallel-Out) shift register chips, and set about learning how to make the Tiny talk to one, the other, and then both of them.

There are a plethora of helpful guides on using these chips with Arduino:

These methods (with the exception of those utilizing the shiftIn() function) transfer easily to the ATtiny85. However, because it lacks sufficient IO pins and built-in SPI support, talking to more than one (non-daisy-chained) shift register at once is not so easy on the smaller chip.

The solution I found was to share a single clock line between the PISO and SIPO shift registers, with separate latch and data lines for each, holding the various enables and inhibits steady. While this approach has drawbacks, such as requiring firm temporal separation of input and output phases and the need to refresh the SIPO register for each output, it allowed me to easily control the two shift registers with only 5 pins.

As a first project, I chose to reproduce the setup demonstrated in Sparkfun's introductory Shift Register video, reading the state of an 8-position DIP switch with a 74HC165 and turning on a corresponding bank of 8 LEDs through a 74HC595.

Here's a photo of the setup (schematic forthcoming soon!):

And here's the code:

// 8xDIP --> 74HC165 --> ATtiny85 --> 74HC595 --> 8xLED
// ====================================================================================
// Updated 05May2015
// By C. Parkin

// Pins
int clockPin    = 0;    // *COMMON* connected to pin 11 (SH_CP, SRCLK) of 595, pin 2 (CLK/CP) of 165
int outLatchPin    = 1;    //connected to pin 12 (ST_CP, RCLK) of 595
int outDataPin    = 2;    //connected to pin 14 (DS, SER) of 595
int inLatchPin    = 3;    //connected to pin 1 (SH/LD, PL)("shift/load") of 165
int inDataPin    = 4;     //connected to pin 9 (QH, Q7) ("data") of 165

// Other Pin Settings:
// 165: 
//     Pin 15 (CLK INH or CE) ("clock enable") to GROUND (active low)
//      Pin 10 (SER, DS) ("serial data input") to GROUND (when not connected to another 165)
//     Pin 11-14 (A-D), 3-6 (E-H) to parallel inputs
//    Pin 16 to Vcc
//    Pin 8 to GND
//    Pin 7 (Q_H or Q_7) ("complementary output from the last stage") left unconnected?(as shown on 2+ sources)
// 595:
//    Pin 10 (MR, SRCLR) ("Master Reset", "Serial Clear") to Vcc
//    Pin 13 (OE) ("output enable input") to GROUND (active LOW)
//    Pin 16 to Vcc
//    Pin 8 to GND
//    Pin 15, 1-7 to parallel outputs
//      Pin 9 (Q7") ("Serial out") left unconnected w/only 1 chip?

// Serial storage variables
byte inByte;    //for the 8 values loaded FROM the 165 shift register
byte outByte;    //for the 8 values loaded TO the 595 shift register

// function to read in from 165

void getter()

    // take the latch pin low to move the parallel inputs into the shift register
    digitalWrite(inLatchPin, LOW);
    delayMicroseconds(5);   //some resources suggest 10 for this; try that if 5 doesn't work.
    digitalWrite(inLatchPin, HIGH);
    // populate the inByte
    for (int bitCount = 0; bitCount < 8; bitCount++)
        // set the current bit to the current value of inDataPin
        bitWrite(inByte, bitCount, digitalRead(inDataPin));

        //pulse the clock to shift to the next serial-in bit
        digitalWrite(clockPin, HIGH);
        digitalWrite(clockPin, LOW);        


// function to write out to 595

void putter()

    // output the outByte

    for (int bitCount = 0; bitCount < 8; bitCount++)
        // set outDataPin according to current bit value:
        digitalWrite(outDataPin, bitRead(outByte, bitCount));

        //pulse the clock
        digitalWrite(clockPin, HIGH);
        digitalWrite(clockPin, LOW);

    //take the latch pin high to move the new sequence to the parallel output
    digitalWrite(outLatchPin, HIGH);
    digitalWrite(outLatchPin, LOW);


void setup()

    // Common Clock Pin
    pinMode(clockPin, OUTPUT);    //sends pulses to the shift registers

    // IN specific pins
    pinMode(inLatchPin, OUTPUT);     //latch/loads the current parallel in values into the shift register for serial output to tiny
    pinMode(inDataPin, INPUT);    //serial input from shift register to tiny

    // OUT specific pins
    pinMode(outLatchPin, OUTPUT);    //shifts current serial values in register to register outputs
    pinMode(outDataPin, OUTPUT);    //serial output from tiny to shift register
    // Set pins to proper states: (Is this better done in the loop or getter/putter functions?)
    digitalWrite(inLatchPin, HIGH); // SH/LD is active low, so we set it high to avoid premature loading
    digitalWrite(clockPin, LOW);    // clock is low-to-high edge-triggered, so start low
    digitalWrite(outLatchPin, LOW); // SH_CP is positive-edge triggered, so start low
    // Initialize in/outBytes to zero
    inByte = B00000000;
    outByte = B00000000;

void loop()

    // Here is where we would, in a more complicated program, figure out what to do with the input data
    outByte = inByte;    // In this simple case, simply pass it over to the Putter function



Worked like a charm!

Up next: adapting this setup (PATS: PISO --> ATtiny85 --> SIPO) to read bump switches and control motors for a simple mobile robot.