electroSome

SPI Communication with PIC Microcontroller – MPLAB XC8

PIC Microcontroller - SPI

PIC Microcontroller - SPI

SPI (Serial Peripheral Interface) is a full duplex synchronous serial communication interface used for short distance communications. It is usually used for communication between different modules in a same device or PCB. SPI devices communicates each other using a master slave architecture with a single master.

SPI Communication

SPI is called as a 4-wire bus as it requires four wires for its communication as shown above. In the case of single slave communications we need only 3 wires, as slave select (SS) is not required. So SPI requires more communication lines in contrast to UART, I2C, USB etc. Multiple slaves are supported through individual slave select lines.

SPI Master and Multi Slave Connections

Please read the article SPI – Serial Peripheral Interface for more details about the working of SPI.

PIC’s MSSP Module in SPI Mode

I hope you know basic understanding of SPI communication before continuing to following sections. Then you can easily understand the following block diagram in which MSSP module of PIC is working in SPI mode.

SPI Mode MSSP Module Block Diagram – PIC 16F877A

SSPSR is the serial in serial out shift register used to transmit/receive data. It is not directly accessible to the programer. We can do all read write operations through SSPBUF register. You can configure the MSSP module by using SSPSTAT and SSPCON1 registers.

SPI Registers

SSPSTAT – MSSP Status Register

SSPSTAT Register MSSP Module PIC 16F877A

SSPCON1 – MSSP Control Register 1

SSPCON1 Register MSSP Module PIC 16F877A

SSPBUF – MSSP – Buffer Register

All data read/write operations has to happen with this register. For example we can write a data to this register to transmitting it to slave devices and we can read the received data from this register.

SPI Library for MPLAB XC8

For making the task simpler, I created a SPI library for MPLAB XC8. I tested this library only with PIC 16F877A. For other microcontrollers you might need to make some changes. Please refer the device datasheet for more information.

In the following section, I am explaining the SPI library code. You can simply use this SPI library in your program just by including the header file spi.h. Don’t forget to include spi.c file to your project. You can download these files at the end of this article.

SPI Initialization

Constants for SPI Initialization

Here I am defining some user defined enum data types which can be used for configuring or initializing the PIC’s MSSP module in SPI mode.

typedef enum
{
  SPI_MASTER_OSC_DIV4  = 0b00100000,
  SPI_MASTER_OSC_DIV16 = 0b00100001,
  SPI_MASTER_OSC_DIV64 = 0b00100010,
  SPI_MASTER_TMR2      = 0b00100011,
  SPI_SLAVE_SS_EN      = 0b00100100,
  SPI_SLAVE_SS_DIS     = 0b00100101
}Spi_Type;

typedef enum
{
  SPI_DATA_SAMPLE_MIDDLE = 0b00000000,
  SPI_DATA_SAMPLE_END    = 0b10000000
}Spi_Data_Sample;

typedef enum
{
  SPI_CLOCK_IDLE_HIGH = 0b00001000,
  SPI_CLOCK_IDLE_LOW  = 0b00000000
}Spi_Clock_Idle;

typedef enum
{
  SPI_IDLE_2_ACTIVE = 0b00000000,
  SPI_ACTIVE_2_IDLE = 0b01000000
}Spi_Transmit_Edge;

Please find the usage of above constants below.

SPI Initialization Function

Following function initializes the MSSP module in SPI module as per the input parameters.

void spiInit(Spi_Type sType, Spi_Data_Sample sDataSample, Spi_Clock_Idle sClockIdle, Spi_Transmit_Edge sTransmitEdge)
{
  TRISC5 = 0;
  if(sType & 0b00000100) //If Slave Mode
  {
    SSPSTAT = sTransmitEdge;
    TRISC3 = 1;
  }
  else //If Master Mode
  {
    SSPSTAT = sDataSample | sTransmitEdge;
    TRISC3 = 0;
  }
  SSPCON = sType | sClockIdle;
}

SPI Wait Function

Following function will wait till data reception to complete.

static void spiReceiveWait()
{
  while ( !SSPSTATbits.BF ); // Wait for Data Receipt complete
}

SPI Write

Following function will write data to SSPBUF register, for transmitting data.

void spiWrite(char dat) //Write data to SPI bus
{
  SSPBUF = dat;
}

SPI Data Ready

Following function can be used to check whether any data is available to read.

unsigned spiDataReady() //Check whether the data is ready to read
{
  if(SSPSTATbits.BF)
    return 1;
  else
    return 0;
}

SPI Read

Following function is used to read data.

char spiRead()    // Read the received data
{
  spiReceiveWait();      // Wait until all bits receive
  return(SSPBUF); // Read the received data from the buffer
}

Note : This function reads only already received / receiving data. If you want to read it from slave, you should read it after writing a dummy data to that slave.

PIC to PIC Communication using SPI

Here we will do PIC to PIC communication for demonstrating the working of PIC’s MSSP module in SPI mode. One microcontroller will be configured in Master mode and other will be in Slave mode. In this example we have bidirectional communication between two PIC microcontrollers. 8 bit switches and 8 LEDs are connected to each microcontroller. We will be controlling LEDs connected to a PIC with switches connected to other microcontroller.

Circuit Diagram

PIC to PIC Communication using SPI – Circuit Diagram

Hope you can easily understand above circuit diagram. You can make any of above 2 microcontroller as master, it depends only on the program written on it. 8MHz crystals are used for providing required clock for the operation of microcontrollers. An RCR (Resistor-Capacitor-Resistor) made using 10KΩ, 0.1µF, 4.7KΩ is connected to MCLR pin of PIC Microcontroller as recommended by Microchip in the device datasheet. It will work fine even if you tie MCLR pin directly to VDD but Microchip doesn’t recommends it. 680Ω resistors are used to limit current through LEDs which are connected to PORTD of PIC microcontroller. 8 bit dip switches are connected to the PORTB of PIC microcontroller. Here we are using Internal Pull Up of PORTB. Hence no external resistors are required for switches.

MPLAB XC8 Code

I recommend you to read “Getting Started with PIC Microcontroller using MPLAB XC8“, if you are new to this.

Don’t forget to include files spi.h and spi.c to your project.

Master

This is the program used in the Master microcontroller.

/*
* File : newmain.c
* Author : Ligo George
* Company : electroSome
* Project : SPI Master Example
* Microcontroller : PIC 16F877A
* Created on April 15, 2017, 5:59 PM
*/

// PIC16F877A Configuration Bit Settings
// 'C' source line config statements

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

// CONFIG
#pragma config FOSC = XT   // Oscillator Selection bits (XT oscillator)
#pragma config WDTE = OFF  // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF   // Low-Voltage In-Circuit Serial Programming Enable bit
#pragma config CPD = OFF   // Data EEPROM Memory Code Protection bit
#pragma config WRT = OFF   // Flash Program Memory Write Enable bits
#pragma config CP = OFF    // Flash Program Memory Code Protection bit

#include <xc.h>
#include <pic16f877a.h>
#include "spi.h"

#define _XTAL_FREQ 8000000

void main()
{
  nRBPU = 0;    //Enable PORTB internal pull up resistor
  TRISB = 0xFF; //PORTB as input
  TRISD = 0x00; //PORTD as output
  PORTD = 0x00; //All LEDs OFF
  TRISC7 = 0;
  RC7 = 1;

  spiInit(SPI_MASTER_OSC_DIV4, SPI_DATA_SAMPLE_MIDDLE, SPI_CLOCK_IDLE_LOW, SPI_IDLE_2_ACTIVE);

  while(1)
  {
    RC7 = 0; //Slave Select
    __delay_ms(1);

    spiWrite(PORTB);
    PORTD = spiRead();

    __delay_ms(1);
    RC7 = 1; //Slave Deselect

    __delay_ms(100);
  }
}

Slave

Slave program can be done with and without using interrupt. It is better to utilize the interrupt feature in the slave device as usually the slave microcontroller need to do other tasks also.

Without Interrupt

/*
* File : newmain.c
* Author : Ligo George
* Company : electroSome
* Project : SPI Slave Example
* Microcontroller : PIC 16F877A
* Created on April 15, 2017, 5:59 PM
*/

// PIC16F877A Configuration Bit Settings
// 'C' source line config statements

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

// CONFIG
#pragma config FOSC = XT   // Oscillator Selection bits (XT oscillator)
#pragma config WDTE = OFF  // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF   // Low-Voltage In-Circuit Serial Programming Enable bit
#pragma config CPD = OFF   // Data EEPROM Memory Code Protection bit
#pragma config WRT = OFF   // Flash Program Memory Write Enable bits
#pragma config CP = OFF    // Flash Program Memory Code Protection bit

#include <xc.h>
#include <pic16f877a.h>
#include "spi.h"

#define _XTAL_FREQ 8000000

void main()
{
  nRBPU = 0;    //Enable PORTB internal pull up resistor
  TRISB = 0xFF; //PORTB as input
  TRISD = 0x00; //PORTD as output
  PORTD = 0x00; //All LEDs OFF

  ADCON1 = 0x07;
  TRISA5 = 1;

  spiInit(SPI_SLAVE_SS_EN, SPI_DATA_SAMPLE_MIDDLE, SPI_CLOCK_IDLE_LOW, SPI_IDLE_2_ACTIVE);
 
  while(1)
  {
    if(spiDataReady())
    {
      PORTD = spiRead();
      spiWrite(PORTB);
    }
  }
}

With Interrupt

/*
* File : newmain.c
* Author : Ligo George
* Company : electroSome
* Project : SPI Slave Example
* Microcontroller : PIC 16F877A
* Created on April 15, 2017, 5:59 PM
*/

// PIC16F877A Configuration Bit Settings
// 'C' source line config statements

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

// CONFIG
#pragma config FOSC = XT   // Oscillator Selection bits (XT oscillator)
#pragma config WDTE = OFF  // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF   // Low-Voltage In-Circuit Serial Programming Enable bit
#pragma config CPD = OFF   // Data EEPROM Memory Code Protection bit
#pragma config WRT = OFF   // Flash Program Memory Write Enable bits
#pragma config CP = OFF    // Flash Program Memory Code Protection bit

#include <xc.h>
#include <pic16f877a.h>
#include "spi.h"

#define _XTAL_FREQ 8000000

void interrupt SPI_Slave_Read()
{
  if(SSPIF == 1)
  {
    PORTD = spiRead();
    spiWrite(PORTB);
    SSPIF = 0;
  }
}

void main()
{
  nRBPU = 0;    //Enable PORTB internal pull up resistor
  TRISB = 0xFF; //PORTB as input
  TRISD = 0x00; //PORTD as output
  PORTD = 0x00; //All LEDs OFF

  GIE = 1;
  PEIE = 1;
  SSPIF = 0;
  SSPIE = 1;
  ADCON1 = 0x07;
  TRISA5 = 1;

  spiInit(SPI_SLAVE_SS_EN, SPI_DATA_SAMPLE_MIDDLE, SPI_CLOCK_IDLE_LOW, SPI_IDLE_2_ACTIVE);

  while(1)
  {
    //Do something here
    __delay_ms(5);
  }
}

Download Here

You can download complete project files here including SPI library, Proteus simulation etc.