SPI Communication with PIC microcontroller

SPI stands for Serial Peripheral Interface. Like UART and I2C,  It is used to transfer data simply and quickly from one device to another. Most importantly, it has a faster data transfer rate than UART and I2C. We can use it to transmit and receive 8-bit data synchronous at the same time. Therefore, it supports full-duplex communication. SPI protocol consists of four lines such as SDO, SDI, SCK, and CS.

It is the popular embedded serial communication that is widely supported by many chip manufacture. It is considered as one of the fastest serial data transfer interfaces for the embedded system. Most of the time, it is used to communicate data to and from the micro-microcontroller.

SPI Communication Introduction

SPI is a synchronous protocol based on the master-slave communication type. The master device (usually a microcontroller ) controls slave devices. Because the slave device takes instructions from the master device.  It allows transmission of data from a master device to one or more slave devices and from slave devices to master devices over short distances at high speeds (MHz). It consists of 4 fours but three-wire communication is also possible.

  • MOSI/SDO: This line used to send data to a slave. It can be read as master-out salve-in
  • MOSI /SDI: MOSI wire used to receive data from a slave. It is an input line for the master and output line for a slave.
  • SCLK:  Clock signal wire
  • CS/SS:  In case of a single master and multiple slave configuration, this wire used to select to which slave, master device wants to send data.

SPI bus interface connection is incorporated into many devices like ADC, DAC, and EEPROM. If you want to explore further on SPI communication protocol, read this complete guide:

SPI Communication Module PIC Microcontrollers

SPI is implemented in the PIC microcontrollers by a hardware module called the Synchronous Serial Port or the Master Synchronous Serial Port. This module is built into many PIC microcontrollers.  This module also supports II2C communication that is a two-wire communication. It supports SPI serial communication between two or more devices at a high speed and is reasonably easy to implement.

Unlike I2C, we do not use slave address in serial peripheral interface communication. But we use a chip select pin to select a slave. In the case of multiple slaves, we need a chip select wire for every slave device.

PIC SPI communication module transmits/receives 8-bit data synchronously simultaneously. That means it supports full-duplex communication mode.

spi communication

SPI Communication Bus Pins

SPI was originally started by Motorola Corp, but now this standard is adopted by many semi-conductor chip companies. It consists of a total of 4 pins:

  • SDO: Serial data output from master and Input to slave [MOSI]
  • SDI: Serial data input to master and output from slave [MISO]
  • SCLK: Serial clock output from master [SCK] Input to slave [SCK]
  • CE: Chip Enable or SS (Slave Select)

Only two pins SDI (Data in) and SDO (Data out) are for data transfer. This reduction of data pins reduces the package size and power consumption. SPI bus has the SCLK (serial clock) pin to synchronize the data transfer between two chips. It is generated by the master device and controls when data is sent and when it is read. The last pin of SPI bus is CE (Chip Enable) which is used to initiate or terminate the data transfer. When the CE signal goes low at a slave device, only that slave is accessed by SPI and will listen for SPI clock and data signals. These four pins make the SPI a 4-wire interface. In the 3-wire interface, we have SCLK, CE and only one single pin for data transfer.

SPI Communication Data Transfer

In connecting a device with an SPI bus to a microcontroller, we use the MCU as a master device and the SPI device acts as a slave. The clock signal is provided by the master to provide synchronization. MCU generates the SCLK which is fed to the SCLK pin of the SPI device. The SPI protocol uses SCLK to synchronize the transfer of one bit at a time and the MSB (Most significant bit) goes in first.

During transfer:

  • CE must be high, CE=1.
  • Address and data are transferred between MCU and slave in a group of 8 bits.
  • The address byte is immediately followed by a Data byte.
  • For Write: A7 bit of address byte is always 1
  • For reading: A7 bit of address byte is always 0

SPI Data Transfer Modes

There are two modes of operation for SPI devices.

  1. Single byte write
  2. Multiple bytes write

 Single-Byte Write of SPI communication:

The following are the steps to send data in a single-byte mode for SPI devices.

spi single byte writing
  • Make CE=1 to begin writing.
  • The 8-bit address is shifted one bit at a time with each edge of the clock.
  • When all the 8 bits of the address are sent, the SPI device will expect to receive the data for the particular address location.
  • The 8bit data is shifted with one-bit time with each edge of the clock.
  • Make CE=0, which indicates the end of the write cycle.

SPI Multiple Burst Write

This mode loads consecutive locations. We just provide the address for the first location, followed by the data for that location. From then on consecutive bytes are written to that consecutive memory location. SPI device internally increments the address location as long as CE is high.

spi multi byte writing

PIC Microcontroller SPI Module Registers

In this section, we will PIC18F452 microcontroller SPI module registers and their configuration settings. MSSP (Master synchronous serial port) module inside the Pic MCU supports the SPI protocol. Four registers are associated with SPI of the MSSP module of PIC18F452, which are:

  • SSPBUF     (synchronous serial port buffer Register)
  • SSPCON1   (synchronous serial port control Register)
  • SSPSTAT     (synchronous serial port status Register)
  • SSPSR  ( Shift register but use cannot access it)

To transfer a byte of data, it is placed in SSPBUF. This register holds the byte received. It will then transfer the contents to the SSPSR. SSPSR is the synchronous shift register for the SPI module. It shifts data in and out of the device.

SPI SSPCON1 Register

The SSPCON1 register controls the SPI mode operation.

bit7bit6bit5bit4bit3bit2bit1bit0
WCOLSSPOVSSPENCKPSSPM3SSPM2SSPM1SSPM0
  • WCOL is a status flag that indicates if write collision error occurs.
SSPCON1bits.WCOL= 1; //  1 = The SSPBUF register is written while it is still transmitting the previous word
SSPCON1bits.WCOL= 0; // No error
  • SSPOV bit indicates an overflow error and this is useful in slave mode only.
  • SSPEN bit (Synchronous Serial Port Enable) in the SSPCON1 register must be set “High” to allow the use of Pins of pic MCU for SPI protocol. To enable SPI port in MPLAB XC8, we configure SSPCON1 register like this:
SSPCON1bits.SSPEN=1 ;  // Enables serial port and configures SCK, SDO, SDI, and SS as serial port pins 
SSPCON1bits.SSPEN=0 ; // Disables serial port and configures these pins as I/O port pins
  • SSPM3: SSPM0 bits are used to select SPI port mode. We can select either master or slave mode with different clock frequency options.
spi frequency selection bits in pic microcontroller

SPI SSPSTAT register

bit7bit6bit5bit4bit3bit2bit1bit0
SMPCKED/APSR/WUABF
  • SMP bit7: This bit is used to sample data in master mode and this bit must be set to zero when SPI is used in slave mode.
SSPSTATbits.SMP=1; //input data sampled at the end of data output time
SSPSTATbits.SMP=0; // input data sampled at the middle of data output time
  • CKE: It selects clock edge for SPI ( positive edge or negative edge)

When CKP = 0:

SSPSTATbits.CKE = 1; // Data transmitted on rising edge of SCK
SSPSTATbits.CKE = 0; // Data transmitted on falling edge of SCK

When CKP = 1:

SSPSTATbits.CKE = 1; //Data transmitted on the falling edge of SCK
SSPSTATbits.CKE = 0; //Data transmitted on the rising edge of SCK

Buffer Full status bit:

  • BF=1, receive complete, SSPBUF is full
  • BF=0, receive not complete, SSPBUF is empty

Note: Bits from 1-5 of the SSPSTAT register are reserved for I2C communication only.

SPI communication with PIC microcontroller Example

In this section, we will see example codes to use SPI communication with pic microcontroller in master and slave mode. In the end, PIC to PIC SPI communication example will be discussed.

PIC SPI Module Master Mode Example

In this example, we will learn to use the SPI module as a master. For demonstration, we use an external EEPROM (25LC256) that communicates over SPI protocol. We will write data to EEPROM with the PIC18F452 microcontroller. After that, we will read from EEPROM and display its value on seven segment display as shown in this circuit diagram.

SPI communication with external EEPROM using pic microcontroller

SPI Master Mode Code PIC18F452 Microcontroller

This code uses an SPI master mode of PIC18F452 microcontroller. It will write data to external EEPROM which acts as an SPI slave device. Firstly, it will write data to EEPROM. Secondly, it will read data from the same locations and display its value on the seven segment display. Make sure to add a configuration file in this code.

#include <xc.h>
#define SCK RC3 //Serial Clock
#define SDI RC4 //Serial Data In
#define SD0 RC5 //Serial Data Out
#define SS RA5 //Slave Select: Not used in this application
#define SCK_dir TRISC3
#define SDI_dir TRISC4
#define SDO_dir TRISC5
#define SS_dir TRISA5
#define WREN 0x06 //Write enable
#define WRDI 0x04 //Write Disable
#define WRITE 0x02
#define READ 0x03
#define RDSR 0x05 //Read Status Register
//----------------------Common Anode 7-Segment Codes----------------------------
char data[16] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x98,0x88,0x83,0xC6,0xA1,0x86,0x8E};
/* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F */
int address[16] = {0x0103,0x0B31,0x1032,0x0C23,0x6004,0xC0BF,0x10C6,0x0317,0x0AF8,0x0A19,0x102A,0x0CCB,0x1388,0x612D,0x501E,0x42AF};
/* Addresses are 16 bit(hence int data type) wide <ie 4 nibbles>(According to Datasheet info). NB: Chosen at Random */
//------------------------------------------------------------------------------
void EEPROM_write(int address,char data);
char EEPROM_read(int address);
char EEPROM_status();
char EEPROM_busy();
char EEPROM_wrEnable();
void delay(unsigned int nMilliseconds)
{
#define CYCLES_PER_MS 100 /* Number of decrement-and-test cycles. */
unsigned long nCycles = nMilliseconds * CYCLES_PER_MS;
while (nCycles--);
}
void SPI_init()
{
SS_dir = 1; //Diselect chip initially
SCK_dir = 0;
SDI_dir = 1;
SDO_dir = 0;
SS_dir = 0;
//SS_dir = 0; //Disable: Because operating in master mode

SSPSTAT = 0x80; //SMP = 1: Sample bit. Input data sampled at end of data output time. CKE = 0: SPI Clock Edge Select bit CLEAR
SSPCON1 = 0x30; //SSPEN = 1: Enable Serial Port. CKP = 1: Clock Polarity Select bit: Idle state for clock is a high level
}
//------------------------------------------------------------------------------
void SPI_write(char data)
{
SSPBUF = data;
while(BF == 0);
}
//-----------------------------------------------------------------------------
char SPI_read(char dummy)
{
SSPBUF = dummy; //Assign to clear
while(BF == 0);

return SSPBUF;
}


#include <xc.h>

void main(void) 
{
SPI_init();
TRISD = 0;
PORTD = data[0];

for( int i = 0 ; i < 16 ; i++ )
EEPROM_write(address[i],data[i]);

while(1)
{
for( int i = 0 ; i < 16 ; i++ )
{
PORTD = EEPROM_read(address[i]); //Pass Address and Return value at that very address tp PORTD
delay(1000);
}
}
return;
}
void EEPROM_write(int address,char data)
{
while( EEPROM_busy() == 1 );

//Set write enable latch
SS = 0;
SPI_write(WREN);
SS = 1; //Latch-Enabled. use isWriteEn() if you want to verify

//Start Write sequence(only occurs when/if Write latch is Enanled)
SS = 0;
SPI_write(WRITE);
SPI_write(address>>8);
SPI_write(address);
SPI_write(data);
SS = 1;
}
//------------------------------------------------------------------------------
char EEPROM_read(int address)
{
while( EEPROM_busy() == 1 ); //Wait for previous Write/Read operations to finish

SS = 0;
SPI_write(READ);
SPI_write(address>>8); //Upper Byte
SPI_write(address); //Lower Byte
char data = SPI_read(0);
SS = 1;

return data;
}
//------------------------------Used as a flag----------------------------------
char EEPROM_status()
{
SS = 0;
SPI_write(RDSR); //Intsruction: Read Status register of the EEPROM
char status = SPI_read(0) ; //Send a Dummy parameter to read. Thus ANY int number is valid
SS = 1;

return status;
}
//------------------------------------------------------------------------------
char EEPROM_busy()
{
char busy = EEPROM_status();

return (busy & 0x01); //LSB is Write-In-Process (WIP) bit. When Set, a write is in progress, when Clear, no write is in progress
}
//------------------------------------------------------------------------------
char EEPROM_wrEnable()
{
char isWrite = EEPROM_status();

return (isWrite & 0x02); //Bit 2 is the Write Enable Latch (WEL) bit and indicates the status of the write enable latch
}

Simulation Result

Master mode SPI communication with pic microcontroller simulation

PIC to PIC SPI Communication MPLAB XC8 Compiler

In this example, we use one pic microcontroller (PIC18F452) as a master and one as a slave. The master microcontroller sends an instruction to the slave PIC18F452 to control an LED. Make connections according to this circuit diagram:

PIC to PIC SPI communication with LED Control Example
  • Connect SCK pin of master with SCK of slave pic microcontroller
  • Connect SDO pin (master) to SDI (slave)
  • Master input is a logic probe. When the user will press this probe, the master SPi device will send an instruction to the slave to control an LDE.
  • Connect an LED with RB0 pin of slave pic microcontroller

Simulation Result

PIC to PIC SPI communication MPLAB XC8 Compiler example

PIC SPI Master Code MPLAB XC8

Upload this code to the master-microcontroller and make sure to add configuration file in this code. You can generate configuration bits with mplab xc8 compiler.

#include <xc.h>
#include "newxc8_header.h"  // generated configuration bits header file
void delay(unsigned int nMilliseconds)
{
#define CYCLES_PER_MS 100 /* Number of decrement-and-test cycles. */
unsigned long nCycles = nMilliseconds * CYCLES_PER_MS;
while (nCycles--);
}
void SPI_init()
{
// this setting selects master mode with frequency fosc/4
SSPCON1bits.SSPM0 = 0;
SSPCON1bits.SSPM1 = 0;
SSPCON1bits.SSPM2 = 0;
SSPCON1bits.SSPM3 = 0;
// Enable SPI Port
SSPCON1bits.SSPEN = 1;
// Configure The Clock Polarity & Phase
SSPCON1bits.CKP = 0;
SSPSTATbits.CKE = 0;
//  Slew rate control enabled for High Speed mode
SSPSTATbits.SMP = 0;
// Set SPI pins as digital I/O
TRISC5 = 0; // SDO -> Output
TRISC4 = 1; // SDI -> Input
TRISC3 = 0; // SCK -> Output
//Uncommnent this line, if you want to send send over interrupt
// SSPIE = 1; PEIE = 1; GIE = 1;
}
//------------------------------------------------------------------------------
void SPI_write(char data)
{
SSPBUF = data;
while(BF == 0);
}
//-----------------------------------------------------------------------------

void main(void) 
{
SPI_init();
TRISB=0x00;
while(1)
{ 
if(RB0) 
SPI_write(0x01);    // if switch pressed, send 0x01
else 
SPI_write(0x02);    // if switch is pressed pressed, send 0x02
}
return;
}

PIC SPI Slave Code MPLAB XC8

#include <xc.h>
#include "newxc8_header.h"

char data;
void __interrupt() ISR(void)
{
if(SSPIF)  // check if interrupt occurs due to SPI module
{
data = SSPBUF; // copy data from receive buffer into a global variable data
SSPIF = 0; // reset flag interrupt for SPI module
}
}
void delay(unsigned int nMilliseconds)
{
#define CYCLES_PER_MS 100 /* Number of decrement-and-test cycles. */
unsigned long nCycles = nMilliseconds * CYCLES_PER_MS;
while (nCycles--);
}
void SPI_init()
{

// Set Spi Mode To Slave + SS Enabled 
SSPCON1bits.SSPM0 = 0;
SSPCON1bits.SSPM1 = 0;
SSPCON1bits.SSPM2 = 1;
SSPCON1bits.SSPM3 = 0;
// Enable The Synchronous Serial Port
SSPCON1bits.SSPEN = 1;
// Configure The Clock Polarity & Phase (SPI Mode Num. 1)
SSPCON1bits.CKP = 0;
SSPSTATbits.CKE = 0;
// Clear The SMP Bit
SSPSTATbits.SMP = 0;
// Configure The IO Pins For SPI Master Mode
TRISC5 = 0; // SDO -> Output
TRISC4 = 1; // SDI -> Input
TRISC3 = 1; // SCK -> Intput
TRISA5 = 1; // SS -> Input
// Enable Interrupts. Comment Them To Disable interrupts(NOT Recommended)
SSPIE = 1;
PEIE = 1;
GIE = 1;
}

void main(void) 
{
SPI_init();
TRISB = 0x00;
while(1)
{
if (data==0x01)     
{
RB0=1;       //if received data = 0x01, turn on LED
}
else if (data==0x02)
{
RB0=0;  //if received data = 0x02, turn off LED
}
}
return;
}

PIC to PIC SPI Communication Example

spi communication with pic microcontroller circuit diagram

The main devices used in the circuit are the two 18F452 (one acts as a master and one as a slave) and  LEDs. The 4 SPI control lines connect the two PICs together.

  • SCLK: RC3 (master)to RC3 (slave)
  • MOSI: RC5 (master) to RC4 (slave)
  • MISO: RC4 (master) to RC5 (slave)
  • SS:RA0 (master) to RA5 (slave)

7 LEDs will be used to display the data transferred to the SPI slave Pic MCU from the SPI master Pic MCU. The LEDs are connected to PORTB (RB0-RB6) of the slave Pic.

Code SPI communication with pic microcontroller

There will be two codes for this example. One for the SPI master and one for SPI slave.MPLAB code is given below for both devices.The master is sending commands and slave is receiving and displaying it on the LED output port. LEDs act like a binary counter, counting down to zero.

SPI communication Master Code:

void main(void){

unsigned x = 0xFF;

unsigned mask = 0x80;

TRISA = 0x00;

TRISC = 0x00;

PORTA = 0x03;

OpenSPI(SPI_FOSC_16, MODE_10, SMPMID);

while(1){

        PORTAbits.RA0 = 0;                 //Slave Select

putcSPI(x);

        PORTAbits.RA0 = 1;                 //Slave Select

Delay10KTCYx(75);

if(x == 0)

            x = 0xFF;

else

x--;

    }

}

SPI communication Slave Code:

void main(void){

unsignedchar x;

ADCON1 =0b00000110;  //PORTA All Digital

TRISA =0xFF;

TRISB =0x00;

PORTA =0x00;

PORTB =0x00;

TRISCbits.TRISC3 =1;                       //SCLK

TRISCbits.TRISC4 =1;                       //MOSI

OpenSPI(SLV_SSON, MODE_10, SMPMID);

while(1){

while(!DataRdySPI());

        x =getcSPI();//ReadSPI();

        PORTB =(x>>1);

Delay10KTCYx(5);

}

}

Other SPI communication tutorials:

11 thoughts on “SPI Communication with PIC microcontroller”

  1. Really helpful to understand for the beginners . Good work bro keep it up .
    Can you help me? I need to make a protocol for micro controller communication over power lines.

    Reply
  2. Good explanation. I am confused about which PIC pin should I connect to SDI, SCLK and CS pins, if I want to use SPI available internally.

    Reply
  3. I am interfacing 2 gb SD Card with PIC16F887, so is it possible to code for it without using FAT ….??? Kindly tell..

    Reply
  4. I wanted to know how SPI signals are doing and here on this site everything is clear for me clear. Thank You and good luck to everyone with programming and also in life <3

    Reply

Leave a Comment