SPI Communication with PIC Microcontroller – MPLAB XC8

SPI Communication with PIC Microcontroller – MPLAB XC8

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 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
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
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
SSPSTAT Register MSSP Module PIC 16F877A
  • Bit 0 BF : This is the buffer full status bit. It is applicable only in receive mode and indicates that the data is ready to read. This bit is set once the data reception is complete and it is transferred from SSPSR to SSPBUF register.
  • Bit 1 UA : Update Address Bit. This bit is used only in I2C Mode. So please ignore it.
  • Bit 2 R/W : Read/Write information. This bit is used only in I2C Mode. So please ignore it.
  • Bit 3 S : Start Bit. This bit is used only in I2C Mode. So please ignore it.
  • Bit 4 P : Stop Bit. This bit is used only in I2C Mode. So please ignore it.
  • Bit 5 D/A : Data/Address bit. This bit is used only in I2C Mode. So please ignore it.
  • Bit 6 CKE : SPI Clock Edge Select. If this bit is 0, data transmission occurs on transition from idle to active clock state. And if it is 1, data transmission occurs on transition from active to idle clock state. Idle state means the status of the line when there is no data transfer, it can be LOW (0) or HIGH (1).
  • Bit 7 SMP : Sample bit.
    • Master Mode : If this bit is set 0, input data is sampled at the middle of the data output frame. And if it is set 1, input data is sampled at the end of data output frame.
    • Slave Mode : This bit must be set 0 in the slave mode.

SSPCON1 – MSSP Control Register 1

SSPCON1 Register MSSP Module PIC 16F877A
SSPCON1 Register MSSP Module PIC 16F877A
  • Bit 0 ~ 3 SSPM0 ~ SSPM3 : Synchronous Serial Port Mode Select
    SSP Mode Select Bits
    SSP Mode Select Bits
  • Bit 4 CKP : SPI Clock Polarity select bit. If this bit is 0, idle clock state will be LOW (0) and if it is 1, idle clock state will be HIGH (1).
  • Bit 5 SSPEN : Synchronous serial port enable. Setting this bit enables the MSSP module for using in SPI or I2C mode.
  • Bit 6 SSPOV : Receive overflow indicator bit.
    • Slave Mode : This bit is  set when a new byte is received while SSPBUF is holding the previous data. In this case data in the SSPSR is lost. Overflow will only occur in slave mode. You have to read any previous data in the SSPBUF before transmitting data to avoid overflow. This bit must be cleared in the software.
    • Master Mode : Overflow bit won’t set in master mode as each transmission/reception is initiated by writing to SSPBUF. Slave cannot push any data to Master. For reading data from slave device, master has to write some data (dummy) to the slave.
  • Bit 7 WCOL : Write collision detect bit. This flag is applicable only in transmit mode. It is set when the SSPBUF register is written while transmitting the previous word.

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_Type
    • SPI_MASTER_OSC_DIV4 : To configure MSSP module as SPI Master with a clock of (Oscillator Frequency / 4).
    • SPI_MASTER_OSC_DIV16 : To configure MSSP module as SPI Master with a clock of (Oscillator Frequency / 16).
    • SPI_MASTER_OSC_DIV64 : To configure MSSP module as SPI Master with a clock of (Oscillator Frequency / 64).
    • SPI_MASTER_TMR2 : To configure MSSP module as SPI Master with a clock generated by Timer 2 module.
    • SPI_SLAVE_SS_EN : To configure MSSP module as SPI Slave with Slave Select input.
    • SPI_SLAVE_SS_DIS : To configure MSSP module as SPI Slave without Slave Select input.
  • Spi_Data_Sample
    • SPI_DATA_SAMPLE_MIDDLE : Input data is sampled at the middle of data output time.
    • SPI_DATA_SAMPLE_END : Input data is sampled at the end of data output time
  • Spi_Clock_Idle
    • SPI_CLOCK_IDLE_HIGH : Idle clock level is HIGH (1).
    • SPI_CLOCK_IDLE_LOW : Idle clock level is LOW (0).
  • Spi_Transmit_Edge
    • SPI_IDLE_2_ACTIVE : Transmission occurs during transition from Idle to Active clock state.
    • SPI_ACTIVE_2_IDLE : Transmission occurs during transition from Active to Idle clock state.

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
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.

Share this post

  • Hi,
    I think Ifound little typo:
    you have:
    SPI_IDLE_2_ACTIVE = 0b00000000
    SPI_ACTIVE_2_IDLE = 0b01000000
    I think it should be like this (swapped):
    SPI_IDLE_2_ACTIVE = 0b01000000
    SPI_ACTIVE_2_IDLE = 0b00000000

  • Hello

    I can not open the Proteus file. I have proteus 8. How can I open that file? Could you send me the file compatible with proteus 8?

    Thanks


  • >