Close

How to Interface the 74HC595 Serial Shift Register with a PIC16F877A (Drive 8+ LEDs with 3 Pins)

mozelectronicsmozelectronics wrote 10/22/2025 at 08:24 • 8 min read • Like

How to Interface the 74HC595 Serial Shift Register with a PIC16F877A (Drive 8+ LEDs with 3 Pins)

Want to drive a bunch of LEDs (or any digital outputs) but you’re running out of GPIOs on the PIC16F877A? The 74HC595 serial-in/parallel-out (SIPO) shift register is the classic fix: you clock data in using just DATA, CLOCK, and LATCH lines, and it presents eight stable outputs on Q0–Q7. You can also daisy-chain more ‘595s to get 16, 24, 32… outputs with the same three pins.

Below is a complete, hands-on guide with wiring, timing, pitfalls, and two code options (bit-banged I/O and hardware SPI) for both MikroC PRO for PIC and MPLAB XC8.

What You’ll Learn

Why use a 74HC595?

Microcontrollers have limited GPIO. The 74HC595 lets you:

Typical uses: LED bars/matrices, 7-segment displays (single or multiplexed), relays, simple digital control lines.

IC Overview (74HC595)

⚠️ Naming you’ll see in datasheets:

Pin Summary (16 pins)

PinNameFunction / Notes
14DSSerial data in
11SH_CPShift clock (data sampled on rising edge)
12ST_CPLatch clock (outputs updated on rising edge)
13OE̅Output enable (active LOW). Tie to GND to always enable.
10MR̅Master reset (active LOW). Tie to VCC for normal operation.
9Q7′Serial out to next ‘595’s DS (for chaining)
1–7,15Q0–Q7Parallel outputs
8GNDGround
16VCC+5 V
Common correction: A frequently cited PISO partner IC is 74HC165, not 74HC156.

Hardware You Need

Wiring (Single 74HC595N + 8 LEDs)

PIC16F877A → 74HC595 (recommended pins):

Outputs & LEDs:

💡 If you ever want to blank all outputs in one shot, route OE̅ to a PIC pin instead of GND and set it HIGH to disable outputs.

Timing in One Picture (conceptually)

  1. Put the next data bit on DS

  2. Pulse SH_CP (rising edge) → bit shifts in

  3. Repeat for 8 bits (MSB-first or LSB-first; just be consistent)

  4. Pulse ST_CP (rising edge) → all Q0–Q7 update at once

Bit Order (important!)

You can shift MSB-first or LSB-first. Choose one and use it everywhere (your patterns must match). In the code below, we’ll do LSB-first because it’s simple for LED sequences; I’ll show you how to flip it.

Part NumberDescriptionOrderable / PackagingFunctionTemp RangeMountingPackage & WidthPackage CodeNotes / Status
SN74HC595DBRIC SR TRI-STATE 8BIT 16-SSOPTape & Reel (TR)Serial to Parallel, Serial-40°C ~ 85°CSurface Mount16-SSOP (0.209″, 5.30mm)16-SSOP
SN74HC595ADBRIC SR TRI-STATE 8BIT 16-SSOPTape & Reel (TR)Serial to Parallel, Serial-40°C ~ 85°CSurface Mount16-SSOP (0.209″, 5.30mm)16-SSOP
CD74HC595SM96IC SR TRI-STATE 8BIT 16-SSOPTape & Reel (TR)Serial to Parallel, Serial-55°C ~ 125°CSurface Mount16-SSOP (0.209″, 5.30mm)16-SSOPExtended temp

MikroC PRO for PIC (Bit-Banged I/O)

Set your project to HS oscillator and match the crystal you use (8 MHz example below).

// MikroC PRO for PIC
// PIC16F877A @ 8 MHz (HS)
// Simple bit-bang driver for 74HC595 (LSB-first)

sbit DATA_pin  at PORTC.B1;  // DS
sbit CLOCK_pin at PORTC.B0;  // SH_CP
sbit LATCH_pin at PORTC.B2;  // ST_CP

void pulse_clock() {   CLOCK_pin = 1;   Delay_us(2);   CLOCK_pin = 0;   Delay_us(2);
}

void latch_update() {   LATCH_pin = 1;   Delay_us(2);   LATCH_pin = 0;
}

// Send one byte, LSB-first
void shiftOutLSB(unsigned char val) {   unsigned char i;   for (i = 0; i < 8; i++) {      DATA_pin = (val >> i) & 0x01;      pulse_clock();   }   latch_update();
}

void main() {   TRISC.B0 = 0;  // CLOCK   TRISC.B1 = 0;  // DATA   TRISC.B2 = 0;  // LATCH   CLOCK_pin = 0;   DATA_pin  = 0;   LATCH_pin = 0;
   while(1) {      // Walk a single '1' across Q0..Q7      for (unsigned char b = 0; b < 8; b++) {         shiftOutLSB(1 << b);         Delay_ms(150);      }      // All off      shiftOutLSB(0x00);      Delay_ms(200);      // All on      shiftOutLSB(0xFF);      Delay_ms(200);   }
}


MSB-first version (swap the loop body):

void shiftOutMSB(unsigned char val) {   char i;   for (i = 7; i >= 0; i--) {      DATA_pin = (val >> i) & 0x01;      pulse_clock();   }   latch_update();
}


MPLAB XC8 (Bit-Banged I/O)

Set configuration bits for HS crystal and match _XTAL_FREQ to your crystal (8 MHz shown). If you use 20 MHz, set _XTAL_FREQ 20000000 and delays will scale.

// MPLAB XC8, PIC16F877A @ 8 MHz (HS)
#include <xc.h>
#define _XTAL_FREQ 8000000

// CONFIG (adjust to your project as needed)
#pragma config FOSC = HS, WDTE = OFF, PWRTE = OFF, BOREN = ON, LVP = OFF, CPD = OFF, WRT = OFF, CP = OFF

#define DATA_pin   RC1  // DS
#define CLOCK_pin  RC0  // SH_CP
#define LATCH_pin  RC2  // ST_CP

void pulse_clock(void) {    CLOCK_pin = 1; __delay_us(2);    CLOCK_pin = 0; __delay_us(2);
}

void latch_update(void) {    LATCH_pin = 1; __delay_us(2);    LATCH_pin = 0;
}

// LSB-first
void shiftOutLSB(unsigned char val) {    for (unsigned char i = 0; i < 8; i++) {        DATA_pin = (val >> i) & 0x01;        pulse_clock();    }    latch_update();
}

void main(void) {    TRISC0 = 0; // CLOCK    TRISC1 = 0; // DATA    TRISC2 = 0; // LATCH    RC0 = 0; RC1 = 0; RC2 = 0;
    while(1) {        for (unsigned char b = 0; b < 8; b++) {            shiftOutLSB(1 << b);            __delay_ms(150);        }        shiftOutLSB(0x00); __delay_ms(200);        shiftOutLSB(0xFF); __delay_ms(200);    }
}


Using the PIC’s Hardware SPI (MSSP) — Faster & Cleaner

The PIC16F877A has an MSSP module that can operate as SPI Master. This lets you blast out a byte with one register write—much faster and lower CPU overhead than bit-banging.

Connections (SPI Master):

MPLAB XC8 (SPI example, MSB-first by hardware):

#include <xc.h>
#define _XTAL_FREQ 8000000
#pragma config FOSC = HS, WDTE = OFF, PWRTE = OFF, BOREN = ON, LVP = OFF, CPD = OFF, WRT = OFF, CP = OFF

#define LATCH_TRIS TRISC2
#define LATCH_LAT  RC2

void spi_init(void) {    // SDO=RC5 output, SCK=RC3 output    TRISC3 = 0; // SCK    TRISC5 = 0; // SDO    LATCH_TRIS = 0; LATCH_LAT = 0;
    // SPI Master, Fosc/16, CKP=0, CKE=1 (Mode 0,0)    SSPSTAT = 0b01000000; // CKE=1    SSPCON  = 0b00100001; // SSPEN=1, CKP=0, Fosc/16
}

void latch_update(void) {    LATCH_LAT = 1; __delay_us(2);    LATCH_LAT = 0;
}

void shiftOutSPI(unsigned char val) {    SSPBUF = val;                 // start transfer    while(!SSPSTATbits.BF);       // wait done    latch_update();               // update outputs
}

void main(void) {    spi_init();
    while(1) {        for (unsigned char b = 0; b < 8; b++) {            // If you want LSB-first behavior visually on Q0..Q7,            // you can reverse bits before sending (optional).            unsigned char v = (1 << b);            // send directly (MSB-first over the wire; 74HC595 doesn't care as long as you're consistent)            shiftOutSPI(v);            __delay_ms(120);        }        shiftOutSPI(0x00); __delay_ms(200);        shiftOutSPI(0xFF); __delay_ms(200);    }
}


📝 Notes

Daisy-Chaining Two or More 74HC595s

To get 16, 24, 32… outputs:

Example (bit-bang, 2 chips):

void shiftOut2Bytes(unsigned char b1, unsigned char b2) {    // Send the byte destined for the furthest chip first    shiftOutLSB(b2); // goes to second chip    shiftOutLSB(b1); // then first chip    // shiftOutLSB already calls latch_update().
}


Common Mistakes & Troubleshooting

Extending to 7-Segment and LED Matrices

Like

Discussions