Bit Bang Software Shift Registers

In this tutorial I will go over the basics of using a 74hc595 shift register with the pic16f18323. As the title suggests, this will be done using a software hack to bit bang the data into the shift register. This is basically a port from the Arduino ShiftOut function. In the next tutorial I will discuss how to used the built in hardware SPI that comes as one of the peripherals inside the pic16f18323. Below is a small schematic showing how to connect your shift register to the pic.

shiftRegisterBitBang

Again I will post the whole program and then go over it piece by piece.

/* 
 * File:   main.c
 * Author: Bill Heaster
 *SR testing 
 * Created on October 10, 2015, 2:46 PM
 */

#include <stdio.h>
#include <stdlib.h>
#include <xc.h>

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

// CONFIG1
#pragma config FEXTOSC = HS     // FEXTOSC External Oscillator mode Selection bits (HS (crystal oscillator) above 4 MHz)
#pragma config RSTOSC = EXT1X   // Power-up default value for COSC bits (EXTOSC operating per FEXTOSC bits)
#pragma config CLKOUTEN = OFF   // Clock Out Enable bit (CLKOUT function is disabled; I/O or oscillator function on OSC2)
#pragma config CSWEN = ON       // Clock Switch Enable bit (Writing to NOSC and NDIV is allowed)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
#pragma config MCLRE = ON       // Master Clear Enable bit (MCLR/VPP pin function is MCLR; Weak pull-up enabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config WDTE = OFF       // Watchdog Timer Enable bits (WDT disabled; SWDTEN is ignored)
#pragma config LPBOREN = OFF    // Low-power BOR enable bit (ULPBOR disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable bits (Brown-out Reset enabled, SBOREN bit ignored)
#pragma config BORV = LOW       // Brown-out Reset Voltage selection bit (Brown-out voltage (Vbor) set to 2.45V)
#pragma config PPS1WAY = ON     // PPSLOCK bit One-Way Set Enable bit (The PPSLOCK bit can be cleared and set only once; PPS registers remain locked after one clear/set cycle)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will cause a Reset)
#pragma config DEBUG = OFF      // Debugger enable bit (Background debugger disabled)

// CONFIG3
#pragma config WRT = OFF        // User NVM self-write protection bits (Write protection off)
#pragma config LVP = OFF        // Low Voltage Programming Enable bit (High Voltage on MCLR/VPP must be used for programming.)

// CONFIG4
#pragma config CP = OFF         // User NVM Program Memory Code Protection bit (User NVM code protection disabled)
#pragma config CPD = OFF        // Data NVM Memory Code Protection bit (Data NVM code protection disabled)

#define _XTAL_FREQ 8000000 //let the compiler know what our crystal is so our _delay functions properly.

//used for output macros
#define CLKPIN LATCbits.LATC3
#define LATCHPIN LATCbits.LATC4
#define DATAPIN LATCbits.LATC5



//setup
void setup()
{
    //disable analog
    ANSELA =0x00;
    ANSELC =0x00;
    
    //set pin direction
    TRISCbits.TRISC3=0;
    TRISCbits.TRISC4=0;
    TRISCbits.TRISC5=0;
    
    
    //set all the pins low to start
    CLKPIN=0;
    LATCHPIN=0;
    DATAPIN=0;
    
       
}



//function for bit banging to the shift register. 
//takes in data and sends to the shift register MSB first
//shifts on the positive edge of the clock

void shiftOut(unsigned char dataOut)
{
    int i=0;
    unsigned char pinstate=0;
    
    CLKPIN =0;
    DATAPIN=0;
    
    for (i=7; i>=0; i--)
    {
        CLKPIN =0;
        
        if(dataOut &(1<<i))
        {
            pinstate = 1;
        }
        else
        {
            pinstate=0;
        }
        
        //set the state of the data pin
        DATAPIN = pinstate;
        
        //clock out the data
        CLKPIN = 1;
        
        //zero out the data pin after the clock in
        DATAPIN = 0;
          
    }
   
    //finished shifting
    CLKPIN =0;
}







int main(int argc, char** argv) {

    unsigned char cnt = 0;
    
    //setup everything first
    setup();
    
    //go into main loop
    while(1)
    {
       //cycle through 8 bit chars and push the binary out via the shift register
       for(cnt = 0; cnt<256; cnt++)
       {
        //pull the latch low to write to register
        //if using daisy chain config just send another shiftout after the first call.
        LATCHPIN =0;
        
        //send to first register
        shiftOut(cnt);
        
        LATCHPIN = 1;
        
       //hold the lights for 1 second before we loop
        for(int i=0; i<100; i++)
        {
            __delay_ms(10);
            
        }
        
        
       }     
        
    }
    
    
  
    return (EXIT_SUCCESS);
}


As with the other tutorials there is no change needed to the configuration bits. The next few lines are the macros that will be used. RC3 will be used to create a clock source, RC4 will be the latch pin and RC5 will be the pin that is used for the serial data out.

//used for output macros
#define CLKPIN LATCbits.LATC3
#define LATCHPIN LATCbits.LATC4
#define DATAPIN LATCbits.LATC5

The setup for this program is very simple. All that is happening is the analog select registers are cleared similar to the previous tutorials. We then set the TRIS bits for the pins that are being used to output by clearing them to 0. Lastly, the pins are all set to low to start off the program.

void setup()
{
    //disable analog
    ANSELA =0x00;
    ANSELC =0x00;
    
    //set pin direction
    TRISCbits.TRISC3=0;
    TRISCbits.TRISC4=0;
    TRISCbits.TRISC5=0;
    
    
    //set all the pins low to start
    CLKPIN=0;
    LATCHPIN=0;
    DATAPIN=0;
       
}

This function is used for shifting the data into the shift register. The argument is an unsigned char (8 bit variable) which is the data that will be shifted out to the pins. This is almost identical to the Arduino Shift-Out function. This function can only do what its name implys though, shifting out. For other modules such as the RF12 wireless module the SPI data bus requires you to send and receive a bit in the same clock cycle. For this we will need to used the Hardware SPI which I will discuss in the next tutorial. For now the shift out function is simple and will work fine to make some LED’s light up on the shift register.


//function for bit banging to the shift register. 
//takes in data and sends to the shift register MSB first
//shifts on the positive edge of the clock

void shiftOut(unsigned char dataOut)
{
    int i=0;
    unsigned char pinstate=0;
    
    CLKPIN =0;
    DATAPIN=0;
    
    for (i=7; i>=0; i--)
    {
        CLKPIN =0;
        
        if(dataOut &(1<<i))//this applies a mask to the data to see if the bit we want is a binary 1 or 0
        {
            pinstate = 1;
        }
        else
        {
            pinstate=0;
        }
        
        //set the state of the data pin
        DATAPIN = pinstate;
        
        //clock out the data
        CLKPIN = 1;
        
        //zero out the data pin after the clock in
        DATAPIN = 0;
          
    }
   
    //finished shifting
    CLKPIN =0;
}

This is a very application specific function. This is because the 74hc595 requires the data to be shifted out in MSB(most significant bit) first on the rising edge of the clock cycle. If we wanted to shift the bits LSB first we could simply make the for() loop start at 0 and count up to 7.

int main(int argc, char** argv) {

    unsigned char cnt = 0;
    
    //setup everything first
    setup();

cnt is a variable that is 8 bits in length within the PIC RISC architecture. This will be used later to count from 0b00000000 to 0b11111111. As before the setup() routine is called to get all of the pins and registers configured.

while(1)
    {
       //cycle through 8 bit chars and push the binary out via the shift register
       for(cnt = 0; cnt<256; cnt++)
       {
        //pull the latch low to write to register
        //if using daisy chain config just send another shiftout after the first call.
        LATCHPIN =0;
        
        //send to first register
        shiftOut(cnt);
        
        LATCHPIN = 1;
        
       //hold the lights for 1 second before we loop
        for(int i=0; i<100; i++)
        {
            __delay_ms(10);
            
        }
        
        
       }     
        
    }

Here is where the action happens. The for() loop is where the counter variable is incremented up to the maximium for 8-bit (256). Everytime the counter is incremented the shift register is updated using the shiftOut() routine. Based on the datasheet for the 74hc595 the shift register of the device is written to when the latchpin (RCK) is held low. While that latch pin is low we can shift in the entire byte, bit by bit. After the byte is shifted in pull the latchpin high to make the shift register push the shifted bits into the storage register, which will cause the LEDS to light. A one second delay occurs so the user can see the change in the lights and the loop starts all over again.

Conclusion

This is a very simple way to write to a shift register and not have to worry about manipulating registers directly or dealing with interrupts and peripherals. As stated before, this is very application specific. The hardware SPI that is included in many PIC’s ( also called MSSP) will do the clock and data transformation for you without the need to code any functions. It is also fully configurable allowing you to select options like MSB or LSB first, Input data sample locations, rising or falling edge of clock signal. Even further the hardware SPI will send and receive bits in the same clock cycle making it easier to use modules that require 2 way communication using a true SPI protocol. This program with its simplicity allows you to update a 7segment LED or run a binary clock with easy to follow user software. See the next tutorial for using the built in Hardware SPI.