SPI Tutorial With PIC Microcontrollers | Serial Peripheral Interface

Previous Tutorial Previous Tutorial Tutorial 21 Next Tutorial Next Tutorial
SPI Tutorial | Serial Peripheral Interface
Intermediate Level ★★☆☆☆

 

In this tutorial, we’ll be discussing the SPI (serial peripheral interface) bus. How it works and how to establish serial communication between 2 PIC microcontrollers via SPI. This is going to be an extensively detailed guide for the ins and outs of the SPI communication in general, and programming PIC MCUs to be SPI master/slave in particular on MPLAB IDE and XC8 Compiler. So, it’s essentially a long-read and hopefully helpful. And let’s get started!

Tutorial Contents


   Required Components   

Qty. Component Name Buy On Amazon.com
2 PIC16F877A Add
2 BreadBoard Add
8 LED Add    Add
1 Resistors Kit Add    Add
1 Capacitors Kit Add    Add
1 Jumper Wires Pack Add    Add
1 LM7805 Voltage Regulator (5v) Add
1 Crystal Oscillator Add
1 PICkit2 or 3 Programmer Add
2 9v Battery or DC Power Supply Add    Add    Add

The Prototyping Board Setup

Prototyping Board - Embedded Systems Tutorials With PIC MCUs


   Serial Communication Introduction (Review)   

 

[ Disclaimer: This section has been discussed earlier in Tutorial-18 For UART. If you’ve already read it, just skip to the next one. ]

In Embedded Systems, Telecommunication, and Data Transmission applications, Serial Communication is known to be the process of sending data one bit at a time (bit-by-bit) sequentially, over the serial bus. It takes a complete clock cycle in order to transfer each bit from an end to the other. Conversely, parallel communication is known to be the process of sending several bits, even bytes, as a whole only in a single clock cycle. However, even if you transfer fewer data per cycle with a serial transmission, you can do it at much higher frequencies which results in higher net transfer rates than of the parallel communication.

 The Fundamental Concepts 

There do exist many serial communication protocols, each of which is working in a different way. However, it turns out to be some similarity that they do share in common. Frankly speaking, serial communication protocols have Shift Registers somehow/somewhere in their hardware implementation as the working-horse (core). Shift registers are used to shift out the data to be transmitted bit-by-bit each clock cycle. So, let’s dig deeper into a shift register in order to build up a complete understanding of what is actually happening at the Register(low)-Level.

Shift registers are basically some D-Flip-Flops serially connected while sharing the same clock line. Here is a graphical animation that demonstrates how does a shift register work internally.

Shift Register Flip-Flop Animation | UART Tutorial

 

As you might have noticed, the data input (0’s and 1’s) is being shifted from the input pin to the output end at bit-0. It takes only 1-clock to transfer a single bit, which means it takes 8-clocks for a single byte transfer. For the same of simplicity, we’ll represent the shift register as a single block instead of a series of D-Flip-Flops as shown above.

 

Here is an animation for an 8-Bit shift register with a serial input & serial output data lines.

Shift Register | UART Tutorial With PIC Microcontroller

 

Well, now you should know how shift registers are actually working. Frankly speaking, serial communication is as simple as connecting a couple of shift registers together! Connecting the data output of a shift register to be the data input of the other shift register enables us of sending digital data serially from an end to another!

 

In the following animation, I’m connecting a couple of 4-Bit shift registers. One at the transmitter device and the other at the receiver device. The serial bus consists of a couple of wires (data, and clock). Each clock, a bit is sent from the transmitter TX pin and received by the receiver’s RX pin.

Serial Communication | UART Tutorial With PIC Microcontroller

As you might have noticed, it takes 4-clocks to send the 4-Bit data from the transmitter to the receiver. This is simply the short answer to the “How Serial Communication Works?”. However, it’s not everything you should know about serial communication or at least to the level of being able to implement a simple serial data transfer process.

In fact, there are some other options and configurations for each serial communication protocols. Which includes, various data transmission rates, error detection, and correction mechanisms, and much more that adds to the overall complexity associated with each protocol. We’ll be working with the UART protocol in this tutorial, which has a decent steep learning curve.

 Serial VS Parallel Communication 

As we’ve stated earlier, serial communication is the process of sending data each bit at a time (bit-by-bit). And conversely, parallel communication is the process of sending multiple bits, even bytes, as a whole in a single clock cycle.

A very basic implementation, that you can create on your own, for the parallel data transfer is shown down below. As I’m connecting a couple of 4-Bit registers, via the 5-wire parallel bus (4-data pins + 1-clock). Here is an animation that shows you how parallel data transfer is done.

Parallel Communication | UART Tutorial

As you might have noticed, it takes only 1-clock to transfer the data from a transmitter device to the receiver! Frankly speaking, we can theoretically transfer any number of bits (or bytes) using parallel communication protocols in a single clock cycle at a time. 

Here is a brief comparison between serial communication and parallel communication protocols.

Feature Serial Communication Parallel Communication
Relative Speed Faster Slower
Distance Range Much Farther Shorter
Transfer Method One bit is transmitted at a time Bytes are transmitted in parallel, One byte or more at a time
Applications Computer small peripherals, modules interfacing, and sensors measuring & sending simple data frames Short distance High-Speed communication such as Computer printers, etc 
Wires Inside Few wires, all data bits pass only through the same data line Multiple, more wires, each bit has a dedicated wire. So as to be transmitted all at once
Cables Pictures Ethernet Serial Cable | UART TutorialUSB Serial Cable | UART Tutorial Serial Cable | UART Tutorial Parallel Port | UART Tutorial Parallel Cable | UART Tutorial Parallel Connectors | UART Tutorial

 Applications Of Serial Communication Protocols 

There are countless situations in which we do use one or more of the serial communication protocols. Here, I’ll try to categorize the main purposes for which we use serial communication instead of listing down all possible applications that may be serving the same purposes. Hence, any application will, hopefully, possibly fall under the umbrella of the following purposes.

  • External devices/modules communications
  • Downloading / Updating The Firmware
  • Console io
  • Data Transmission
  • Debugging interface

Serial communication protocols are of fundamental importance for each embedded systems application. And as embedded systems engineers, we must have a very good experience with almost all of the commonly used serial communication protocols. And in the next sub-section, we’ll see a brief collection of the commonly available serial communication protocols in the embedded market.

 Examples Of Serial Communication Protocols 

There are many serial communication protocols that are existent and being used in various domains and sub-domains of embedded systems. Some of which are more likely to be used in the automotive industry such as LIN & CAN, some of which is more likely to be used for external memory interfacing as I2C, high-speed computer interfacing as USB, and for audio applications such as I2S. Here is a list of the most common serial communication protocols in the industry.

USB CAN I2C
I2S LIN SPI
Ethernet 1-Wire UART/USART
And many others…

We’ll be focusing only on the SPI serial peripheral interface for the rest of this tutorial. Just to understand the fundamentals & mechanics of SPI communication. Only then, we’ll be able to develop the necessary firmware in order to transmit data from an embedded MCU to another as we’ll be doing the lab at the end of this tutorial. So, let’s get started with SPI.


   SPI | And How It Works?   

 

 What’s SPI? And How it Works? 

SPI is an acronym for (Serial Peripheral Interface) pronounced as “S-P-I” or “Spy”. Which is an interface bus typically used for serial communication between microcomputer systems and other devices, memories, and sensors. Usually used to interface Flash Memories, ADC, DAC, RTC, LCD, SDcards, and much more.

The SPI was originally developed by Motorola back in the 80s to provide full-duplex serial communication to be used in embedded systems applications.

In typical SPI communication, there should be at least 2 devices attached to the SPI bus. One of them should be the master and the other will essentially be a slave. The master initiates communication by generating a serial clock signal to shift a data frame out, at the same time serial data is being shifted-in to the master. This process is the same whether it’s a read or write operation.

Typical SPI Connection Diagram

Typical SPI Connection Diagram

 Pin Conventions & Connections 

The SPI bus includes 4 logic lines defined as follows:

MISO -> Master input slave output (Dout From Slave).

MOSI -> Master output slave input (Dout From Master).

SCLK -> Serial Clock, generated by the master and goes to the slave.

SS -> Slave Select. Generated by the master to control which slave to talk to. It’s usually an active-low signal.

Other naming conventions are followed by different manufacturers across the globe. But in the end, they are referring to the same things. Here are some of the commonly used terms to express the above 4 logic lines (SPI Bus).

MISO (master in slave out) => can also be SIMO, DOUT, DO, SDO, or SO (At The Master End).

MOSI (master out slave in) => can also be SOMI, DIN, DI, SDI, or SI (At The Master End).

SS (slave select) => can also be CE, CS, or SSEL.

SCLK (serial clock) => can also be SCK.

The convention we’re going to follow in this tutorial and upcoming topics related to SPI will be the following one

[ SDI, SDO, SCK, SS]

The typical SPI connection between a single master and a single slave is indicated in the diagram below.

SPI Master-Slave Connection

SPI Master-Slave Connection

 Modes of Operation 

Devices on the SPI bus can operate in either mode of the following: Master or Slave. There must be at least one master which initiates the serial communication process (For Reading/Writing). On the other hand, there can be single or multiple devices operating in slave mode.

The master device can select which slave to talk to by setting the SS (slave select) pin to logic low. If a single slave is being addressed, you can tie the SS pin of this slave device to logic low without the need to control this line by the master.

 Clock Rate (At The Master End) 

The Master device on the SPI bus has to configure and generate the clock signal at the beginning of any communication. During each SPI clock cycle, full-duplex data transmission occurs. The master sends a bit on the MOSI line and the slave reads it, while the slave sends a bit on the MISO line and the master reads it.

This sequence is maintained even when only one-directional data transfer is intended. Which means in order to receive any data you have to actually send something! in this case, we call it “Dummy Data” or “Junk”!

Theoretically, the clock rate can be whatever you want provided that it’s practically realizable. The SPI serial clock is derived from the CLKsys of your system, which means it can by Fosc/2 or 4, 8, 16 or whatever. The dynamic range starts from a few KHz up to several MHz. (Practical limitations are case-dependent for each system).

 Clock Polarity – CKP 

Besides configuring the serial clock rate (frequency), the SPI Master device should also configure the clock polarity. The clock polarity is usually written as CKP or CPOL depending on the hardware manufacturer but in the end, it’s the same. The clock polarity & phase together determine when will the data be latched on the data line.

CKP can be configured to be 1 or 0. Which means you can set the default state of the clock (the IDLE) to be high or low whichever you want. The polarity inversion can be achieved by a simple logic inverter. You have to refer to the device’s datasheet in order to set both CKP & CKE properly.

  • For (CKP = 0), the clock IDLEs at 0, and each cycle consists of a pulse of 1. That is, the leading edge is a rising edge, and the trailing edge is a falling edge.
  • For (CKP = 1), the clock IDLEs at 1, and each cycle consists of a pulse of 0. That is, the leading edge is a falling edge, and the trailing edge is a rising edge.

 Clock Phase (Edge) – CKE 

Besides configuring the serial clock rate and polarity, the SPI Master device should also configure the clock phase (or Edge). The clock phase is usually written as CKE or CPHA depending on the hardware manufacturer but in the end, it’s the same. The clock polarity & phase together determine when will the data be latched on the data line.

CKE can be configured to be 1 or 0. Which means you can set the edge on which the data will be latched whether it’s a rising or falling edge (leading or trailing). You have to refer to the device’s datasheet in order to set both CKP & CKE properly.

  • For (CKE = 0), the “out” side changes the data on the trailing edge of the preceding clock cycle, while the “in” side captures the data on (or shortly after) the leading edge of the clock cycle.
  • For (CKE = 1), the “out” side changes the data on the leading edge of the current clock cycle, while the “in” side captures the data on (or shortly after) the trailing edge of the clock cycle.

 SPI Clock Configurations Summary 

The clock phase and the polarity together control whether the data is latched on the rising or falling edge (CKE) as well as the IDLE state of the clock (CKP).  The diagram below summarizes all the clock configuration combinations and highlighting when the data is actually latched.

SPI Clock Phase And Polarity Table

SPI Clock Phase And Polarity Table

As you might have noticed, if the SPI Master doesn’t configure the CKP (clock polarity) & CKE (clock phase) to what the slave device expects, any sort of communication (write/read) will fail.

 Mode Numbers 

The combination of both clock polarity and phase is commonly referred to as “SPI Mode”. All the possible modes are numbered following the convention down below.

SPI Mode Clock Polarity

(CKP/CPOL)

Clock Phase

(CPHA)

Clock Edge

(CKE/NCPHA)

0    [00] 0 0 1
1    [01] 0 1 0
2    [10] 1 0 1
3    [11] 1 1 0

[Note that: NCPHA is the inversion of CPHA signal]. The above table shows the SPI mode numbers for Microchip PIC and ARM-Core Based Microcontrollers. Other microcontrollers follow the convention below

SPI Mode CPOL CPHA
0    [00] 0 0
1    [01] 0 1
2    [10] 1 0
3    [11] 1 1

You should also double-check the modes table included in the microcontroller’s datasheet to make sure everything is OK.

 SPI Bus Configurations 

All devices on the SPI bus including the master and all slaves can all be connected in a couple of different configurations. Which mostly depend on how they are manufactured and how data should be flowing through the network of devices in that particular application. I’ll briefly describe both of these configurations hereafter.

 Daisy Chain 
Daisy Chain

Daisy Chain

In the digital communication world, when devices are connected together in such a way that data has to circulate between devices until it reaches destination device, it’s then called a Daisy Chain!

Each device is connected between 2 other devices in a circular (Logically) way as shown in the diagram down below. [note that: a logical circle is a term that describes data circulation flow in the network which means that devices connection doesn’t necessarily need to be circular in shape].

Daisy Chain SPI Devices

The SPI port of each slave device is designed to send data out during the second group of clock pulses an exact copy of the data it received during the first group of clock pulses. The whole chain acts as a communication shift register. Each slave copies the input data to the output in the next clock cycle until the SS line goes high. Such a feature only requires a single SS line from the master, rather than a separate SS line for each slave.

That means a data frame will keep propagating through slave devices as long as the SS line is held low. The following diagram will show you how a single SPI master can address multiple slave devices using only one SS line.

SPI Daisy Chain Configuration

And that’s it for the daisy chain SPI bus configuration. Now, we’ll turn over to see the other configuration which we’ll be actually implementing before the end of this tutorial.

 Independent Slave 

In the independent slave configuration, there is an independent slave select line for each slave. This is the way SPI is normally used. The master asserts only one chip select at a time. Since the SDO/SDI pins of the slaves are connected together, they are required to be tri-state logic pins. Slave devices which don’t support tri-state may be used in independent slave configuration by adding a tri-state buffer chip controlled by the chip select signal.

A typical connection diagram for a system with multiple slaves connected in the independent configuration is shown below.

SPI Independent Slave Configuration

SPI Independent Slave Configuration

As you can see, this configuration can be impractical in case your system involves many SPI slave devices. Which will require too many IO pins to control all SS lines.

 Advantages of SPI Communication 

There are many reasons that make SPI stands out as a serial communication interface. Here is a short list for the pros of SPI.

  • Full-Duplex serial communication.
  • Potentially high-speed data transfer rates can be achieved with SPI.
  • Few wires are required for the SPI Bus (typically 4 lines).
  • Easy software configuration and setup.
  • Extremely flexible data transfer. It’s not limited to 8-Bits, it can be any arbitrarily-sized words.
  • Very simple hardware structure. Slaves don’t need unique addresses (unlike I2C). Slaves use the master’s clock and do not need precision oscillators (unlike UART). Transceivers are not needed (unlike CAN).

 Disadvantages of SPI 

  • No hardware slave acknowledgment (the master could be transmitting to nowhere without knowing).
  • Typically supports only one master device.
  • Requires more pins (unlike I2C).
  • No hardware-level error-checking protocol is defined.
  • Many existing variations and conventions make it ambiguous sometimes and you’ve to dig very deep in the datasheet and underline every single word.
  • Can only support very short distances (usually onboard communications) compared to RS-232 and CAN-Bus.

   SPI In PIC16F87X Microcontrollers   

 

 Description 

Microchip PIC microcontrollers do have a hardware module called “MSSP” which is an acronym for “Master Synchronous Serial Port”. The MSSP module can operate in one of two modes:
• Serial Peripheral Interface (SPI)
• Inter-Integrated Circuit (I2C)
    – Full Master mode
    – Slave mode (with general address call)

Which means that we need to configure this module in order to have it operating as an SPI or I2C interface. And that’s the goal of this tutorial, to see how can we set up the MSSP module to operate as a slave or a master.

The SPI mode allows 8 bits of data to be synchronously transmitted and received simultaneously. All four modes of SPI are supported. To accomplish communication, typically three pins are used:
• Serial Data Out (SDO) –> RC5/SDO
• Serial Data In (SDI) –> RC4/SDI/SDA
• Serial Clock (SCK) –> RC3/SCK/SCL
Additionally, a fourth pin may be used when in a Slave mode of operation:
• Slave Select (SS) –> RA5/AN4/SS/C2OUT

The figure below shows the functional logic diagram of the hardware MSSP module (in SPI Mode).

MSSP Module - SPI Mode Diagram

 SPI Operation 

When initializing the SPI, several options need to be specified. This is done by programming the appropriate control bits [ SSPCON<5:0> & SSPSTAT<7:6> ]. These control bits allow the following configurations to be specified:
• Master mode (SCK is the clock output)
• Slave mode (SCK is the clock input)
• Clock Polarity – CKP (Idle state of SCK)
• Data Input Sample Phase – SMB (middle or end of data output time)
• Clock Edge – CKE (output data on the rising/falling edge of SCK)
• Clock Rate (Master mode only)
• Slave Select mode (Slave mode only)

The MSSP consists of a transmit/receive shift register (SSPSR) and a buffer register (SSPBUF). The SSPSR shifts the data in and out of the device, MSB first. The SSPBUF holds the data that was written to the SSPSR until the received data is ready. Once the eight bits of data have been received, that byte is moved to the SSPBUF register. Then, the Buffer Full detect bit, BF (SSPSTAT<0>), and the interrupt flag bit, SSPIF, are set. This double-buffering of the received data (SSPBUF) allows the next byte to start reception before reading the data that was just received.

Any write to the SSPBUF register during transmission/reception of data will be ignored and the write collision detects bit, WCOL (SSPCON<7>), will be set. User software must clear the WCOL bit so that it can be determined if the following write(s) to the SSPBUF register completed successfully. When the application software is expecting to receive valid data, the SSPBUF should be read before the next byte of data to transfer is written to the SSPBUF. Buffer Full bit, BF (SSPSTAT<0>), indicates when SSPBUF has been loaded with the received data (transmission is complete). When the SSPBUF is read, the BF bit is cleared. This data may be irrelevant if the SPI is only a transmitter.

Generally, the MSSP interrupt is used to determine when the transmission/reception has completed. The SSPBUF must be read and/or written. If the interrupt method is not going to be used, then software polling can be done to ensure that a write collision does not occur.

The SSPSR is not directly readable or writable and can only be accessed by addressing the SSPBUF register. Additionally, the MSSP Status register (SSPSTAT) indicates the various status conditions.

 SPI IO Pin Configurations 

To enable the MSSP serial port, the SSP Enable bit, SSPEN (SSPCON<5>), must be set. To reset the MSSP or reconfigure SPI mode, clear the SSPEN bit, re-initialize the SSPCON registers and then set the SSPEN bit. This is done for SPI mode switching or in order to initiate I2C communication. This configures the SDI, SDO, SCK and SS pins as serial port pins. For the pins to behave as the serial port function, some must have their data direction bits (in the TRISx register) appropriately programmed. That is:
SDI   => is automatically controlled by the SPI module
SDO => must have TRISC<5> bit cleared
SCK => (Master mode) must have TRISC<3> bit cleared
SCK => (Slave mode) must have TRISC<3> bit set
SS    => must have TRISC<4> bit set
Any serial port function that is not desired may be overridden by programming the corresponding data direction (TRISx) register to the opposite value.

 SPI Typical Connection 

The diagram below shows a typical SPI Bus connection between two MCUs. The master controller (Processor 1) initiates the data transfer by sending the SCK signal. Data frame is shifted out of both shift registers on their programmed clock edge and latched on the opposite edge of the clock. Both processors should be programmed to the same Clock Polarity (CKP), then both controllers would send and receive data at the same time. Whether the data frame is meaningful (or dummy data) depends on the application software. which means there are three possible scenarios for SPI data transaction:
• Master sends a Data Frame – Slave sends Dummy Data
• Master sends a Data Frame – Slave sends another Data Frame
• Master sends Dummy Data – Slave sends a Data Frame

SPI Master-Slave Connection

SPI Master-Slave Connection

 SPI Bus Modes in PIC vs Standards 

The Table down below (From PIC16F87X Datasheet) shows the compatibility between the standard SPI modes and the states of the CKP and CKE control bits. Additionally, There is also an SMP bit which controls when the data is sampled, and it has no effect of the SPI mode number in fact.

SPI Bus Modes In PIC Microcontrollers

 SPI – Master Mode 

The SPI Master can initiate the data transfer at any time because it controls the SCK. The master determines when the slave is to broadcast data by the software protocol.

In Master mode, the data is transmitted/received as soon as the SSPBUF register is written to. If the SPI is only going to receive, the SDO output could be disabled (programmed as an input). The SSPSR register will continue to shift in the signal present on the SDI pin at the programmed clock rate.

As each byte is received, it will be loaded into the SSPBUF register as if a normally received byte (interrupts and status bits appropriately set). And it’s up to the programmers (you and me) to choose whether to poll the status bits or to use the interrupt signals.

The clock phase and polarity are selected by appropriately programming the control bits. This then would give waveforms for SPI communication as shown in the timings diagram down below.

SPI Master Mode Timing Diagram - PIC Tutorial

In Master mode, the SPI clock rate (bit rate) is user programmable to be one of the following:
• FOSC/4 (or TCY)
• FOSC/16 (or 4 • TCY)
• FOSC/64 (or 16 • TCY)
• Timer2 output/2
This allows a maximum data transfer rate (at Fosc = 40 MHz) of 10.00 Mbps.

 SPI – Slave Mode 

In Slave mode, the data is transmitted and received as the external clock pulses appear on SCK. When the last bit is latched, the SSPIF interrupt flag bit is set.
While in Slave mode, the external clock is supplied by the external clock source on the SCK pin. This external clock must meet the minimum high and low times as specified in the electrical specifications.
While in Sleep mode, the slave can transmit/receive data. When a byte is received, the device will wake-up from Sleep.

The SS pin allows a Synchronous Slave mode. The SPI must be in Slave mode with SS pin control enabled. The pin must not be driven low for the SS pin to function as an input. The data latch must be high. When the SS pin is low, transmission and reception are enabled and the SDO pin is driven.

When the SS pin goes high, the SDO pin is no longer driven even if in the middle of a transmitted byte and becomes a floating output. Any running/further SPI communication will be aborted. External pull-up/pull-down resistors may be desirable, depending on the application. The diagram down below shows the Slave Synchronization Waveforms & The Effect of pulling the SS pin to logic High (Note: that communication is suspended while SS is held high).

SPI Slave Synchronization Waveform - PIC Tutorial

The clock phase and polarity are selected by appropriately programming the control bits. This then would give waveforms for SPI communication (in Slave Mode) as shown in the timings diagrams down below.

SPI Slave Mode Timing Diagram1

SPI Slave Mode Timing Diagram2

 SPI SFR Registers 

The MSSP module has four registers for SPI mode operation. These are

  • MSSP Control Register (SSPCON)
  • MSSP Status Register (SSPSTAT)
  • Serial Receive/Transmit Buffer Register (SSPBUF)
  • MSSP Shift Register (SSPSR) – Not directly accessible

SSPCON & SSPSTAT

SSPCON and SSPSTAT are the control and status registers in SPI mode operation. The SSPCON register is readable and writable. The SPI hardware is configured by programming the appropriate bits of this couple of registers. The exact available options are indicated in the register table hereafter in the text (exactly as is in the datasheet).

SSPSR & SSPBUF

SSPSR is the shift register used for shifting data in or out. SSPBUF is the buffer register to which data bytes are written to or read from. In receive operations, SSPSR and SSPBUF together create a double-buffered receiver. When SSPSR receives a complete byte, it is transferred to SSPBUF and the SSPIF interrupt is set. During transmission, the SSPBUF is not double-buffered. A write to SSPBUF will write to both SSPBUF and SSPSR.

☑ SSPSTAT – MSSP Status Register (SPI Mode)

SPI Tutorial - SSPSTAT Register

☑ SSPCON1 – MSSP Control Register 1 (SPI Mode)

SPI Tutorial - SSPCON Register


   How To Configure An SPI Master?   

 

In this section, we’ll be focusing on the process of configuring a PIC microcontroller to work as an SPI Master. Enough of general information about SPI. Let’s get our microcontroller to work as a serial SPI Master (transmitter)!

 The Mechanics of SPI Transmission 

The SPI Master initiates communication when a data frame (Byte) is written to the buffer register SSPBUF. The SPI hardware will generate an SCK signal with the appropriate configurations and shifts out the data which was latched from the SSPBUF to the SSPSR shift register. The MSb is shifted out first followed by the rest of the 8-bit data frame. When the data (byte) is successfully transmitted, an interrupt signal is generated which may help you for starting or waiting before another data transaction process. You can now easily identify this functionality by carefully following the SPI hardware block diagram.

If it looks overwhelming, stay cool. It really does! Each of us has been at the same point and yes! it felt overwhelming. Take the time to understand each step of it. Down below are the exact brief steps that you should follow to configure your SPI Master, even without looking up any diagram in the datasheet. However, you should consider doing so sooner or later at some point.

MSSP Module - SPI Mode Diagram

 Steps For Configuring SPI Master 

1 Set The SPI Mode (to Master), and Set the desired Clock Rate for SCK by programming the (SSPM3:SSPM0) Bits [In Register SSPCON1<3:0>].
2 Enable The Synchronous Serial Port by setting the SSPEN bit.
3

 

 

 

 

Configure the Serial Clock Polarity & Phase by programming both CKP, CKE bits.

SPI Bus Modes In PIC Microcontrollers

4 Configure when the input data is sampled (at the end or middle of data output time) by programming the SMP bit [Register SSPSTAT<7>].
5

 

 

Configure the IO pins which will be used by the SPI hardware for master mode by writing to the corresponding bits in TRISx register.

  • SDO -> Output
  • SDI -> Input
  • SCK -> Output
6 Enable SPI Reception Interrupts, if needed and it’s highly recommended, by setting (SSPIE, PEIE, and GIE).
7 Read The Data From SSPBUF Register. This step clears the BF bit. And don’t also forget to clear the interrupt flag bit SSPIF after reading the received data from the buffer.

 Implementing SPI Master Driver Firmware 

 Step1  Set The SPI Mode (to Master), and Set the desired Clock Rate

// Set Spi Mode To Master + Set SCK Rate To Fosc/64 
SSPM0 = 0;
SSPM1 = 0;
SSPM2 = 0;
SSPM3 = 0;

 Step2  Enable The Synchronous Serial Port
SSPEN = 1;

 Step3  Configure the Serial Clock Polarity & Phase
// Configure The Clock Polarity & Phase (SPI Mode Num. 1)
CKP = 0;
CKE = 0;

 Step4  Configure when the input data is sampled
SMP = 0;  // (At The Middle)

 Step5  Configure the IO pins For SPI Master Mode
TRISC5 = 0; // SDO -> Output
TRISC4 = 1; // SDI -> Input
TRISC3 = 0; // SCK -> Output

 Step6  Enable SPI Transmission Interrupts (if needed)
SSPIE = 1;
PEIE  = 1;
GIE   = 1;

 SPI Master Full Code Listing 

You can easily use those 2-functions within your code. Calling the SPI_Master_Init() procedure will configure the SPI as a Master for you. Then, calling the SPI_Write(data) will shift-out the data frame from your transmitter to the receiving end.

void SPI_Master_Init()
{
  // Set SPI Mode To Master + Set SCK Rate To Fosc/64
  SSPM0 = 0;
  SSPM1 = 0;
  SSPM2 = 0;
  SSPM3 = 0;
  // Enable The Synchronous Serial Port
  SSPEN = 1;
  // Configure The Clock Polarity & Phase (SPI Mode Num. 1)
  CKP = 0;
  CKE = 0;
  // Configure The Sampling Time (Let's make it at middle)
  SMP = 0;
  // Configure The IO Pins For SPI Master Mode
  TRISC5 = 0; // SDO -> Output
  TRISC4 = 1; // SDI -> Input
  TRISC3 = 0; // SCK -> Output
  // If Interrupts Are Needed, Un-comment The Line Below
  // SSPIE = 1; PEIE = 1; GIE = 1;
}

void SPI_Write(uint8_t Data)
{
  SSPBUF = Data; // Transfer The Data To The Buffer Register
  // while(!BF); // Un-comment it if you're Not Using Interrupts!
  // The Above While Loop Protects Against Writing To The Buffer Before 
  // The Previous Transmission Ends
}

 


   How To Configure An SPI Slave?   

 

In this section, we’ll be discussing the process of configuring a PIC microcontroller to work as an SPI Slave. And develop the necessary firmware code step-by-step.

 The Mechanics of SPI Reception 

The SPI Master shifts the data out of its SSPSR register, MSb first. The SSPSF at the slave is simultaneously receiving the exact same bits being shifted out of the master device. Once the eight bits of data have been received, the byte is then moved to the SSPBUF register. Then, the Buffer Full detect bit, BF (SSPSTAT<0>), and the interrupt flag bit, SSPIF, are set. This double-buffering of the received data (SSPBUF) allows the next byte to start reception before reading the data that was just received.

Down below are the exact brief steps that you should follow to configure your SPI Master, even without looking up any diagram in the datasheet. However, you should consider doing so sooner or later at some point.

 

 

MSSP Module - SPI Mode Diagram

 Steps For Configuring SPI Slave 

1 Set The SPI Mode (to Slave + SS Enabled) by programming the (SSPM3:SSPM0) Bits [In Register SSPCON1<3:0>].
2 Enable The Synchronous Serial Port by setting the SSPEN bit.
3

 

 

 

 

Configure the Serial Clock Polarity & Phase by programming both CKP, CKE bits. [Same As Master]

SPI Bus Modes In PIC Microcontrollers

4 Clear the SMP bit [Register SSPSTAT<7>].
5

 

 

 

Configure the IO pins which will be used by the SPI hardware for slave mode by writing to the corresponding bits in TRISx register.

  • SDO -> Output
  • SDI -> Input
  • SCK -> Intput
  • SS -> Input
6 Enable SPI Transmission Interrupts, it’s highly recommended, by setting (SSPIE, PEIE, and GIE).
7 Read The Received Data From the SSPBUF Register. This step clears the BF bit. Don’t also forget to clear the interrupt flag bit SSPIF after reading the SSPBUF register.

 Implementing SPI Slave Driver Firmware 

 Step1  Set The SPI Mode (to Slave + SS Enabled)

// Set SPI Mode To Slave + SS Enabled 
SSPM0 = 0;
SSPM1 = 0;
SSPM2 = 1;
SSPM3 = 0;

 Step2  Enable The Synchronous Serial Port
SSPEN = 1;

 Step3  Configure the Serial Clock Polarity & Phase
// Configure The Clock Polarity & Phase (SPI Mode Num. 1)
CKP = 0;
CKE = 0;

 Step4  Clear The SMP Bit
SMP = 0;  // (as stated in the datasheet)

 Step5  Configure the IO pins For SPI Master Mode
TRISC5 = 0; // SDO -> Output
TRISC4 = 1; // SDI -> Input
TRISC3 = 1; // SCK -> Intput
PCFG3 = 0;  // Set SS (RA5/AN4) To Be Digital IO
PCFG2 = 1;
PCFG1 = 0;
PCFG0 = 0;
TRISA5 = 1; // SS -> Input

 Step6  Enable SPI Reception Interrupts (Highly Recommended)
SSPIE = 1;
PEIE  = 1;
GIE   = 1;

 SPI Slave Full Code Listing 

You can easily use those functions within your code. Calling the SPI_Slave_Init() procedure will configure the SPI Slave for you. Then, calling the SPI_Read() will return the data frame from your receiver buffer register. It’s not recommended to poll the SPI BF buffer filled status bit. The best way is to use interrupt signals.

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

uint8_t SPI_Read()
{
  uint8_t Data;
  if(BF) // Check If Any New Data Is Received
  {
    Data = SSPBUF; // Read The Buffer
    BF = 0; // Clear The Buffer-Filled Indicator Bit
    SSPIF = 0; // Clear The Interrupt Flag Bit
    SSPOV = 0; // Clear The Overflow Indicator Bit
    return Data;
  }
}

void __interrupt ISR(void)
{
  if(SSPIF)
  {
    Data = SSPBUF; // Read The Buffer
    SSPIF = 0; // Clear The Interrupt Flag
  }
}

 


   µC-To-µC SPI Communication – LAB   

 

 

Lab Name SPI Basic Communication
Lab Number 20
Lab Level Intermediate
Lab Objectives Learn how to create a serial communication interface between 2 microcontrollers using SPI Protocol. We’ll send specific data frames from a transmitter MCU and receive it back with another one then double-check the data correctness and validate the communication process.

 

       1. Coding       

 

Open the MPLAB IDE and create a couple of new projects name them “SPI_Master” and “SPI_Slave” for master and slave MCUs respectively. If you have some issues doing so, you can always refer to the previous tutorial using the link below.

Create New Project With MPLAB IDE

Set the configuration bits to match the generic setting which we’ve stated earlier. And if you also find troubles creating this file, you can always refer to the previous tutorial using the link below.

Now, open the main.c file of each project and let’s start developing the firmware for our project.

1. SPI Transmitter (Master)

[ Transmitter Firmware ]

Our task is to take 3 inputs from the user using 3 push buttons to do the following tasks:

Button1: increment the data value

Button2: decrement the data value

Button3: send the current data frame via SPI

We’ll use PORTD to display the current data value for the user. And 3 input pins of PORTB. Obviously, we’ll also configure our microcontroller to operate as a transmitter as we’ve seen earlier in this tutorial. Here is the full code listing for the transmitter MCU chip.

/*
* LAB Number: 20
* LAB Name: SPI_Basic_Communication (Master Code)
* Author: Khaled Magdy
* For More Information Visit My Website @ DeepBlueMbedded.com
*
*/

#include <xc.h>
#include <stdint.h>
#include "config.h"
#define _XTAL_FREQ 4000000
//--------------------------------
// IO Pins Defines (Mappings)
#define UP RB0
#define Down RB1
#define Send RB2
//--------------------------------
// Functions Declarations
void SPI_Master_Init();
void SPI_Write(uint8_t);
//--------------------------------
// Main Routine
void main(void)
{
  //--[ Peripherals & IO Configurations ]--
  SPI_Master_Init(); // Initialize The SPI in Master Mode @ Fosc/64 SCK
  uint8_t Data = 0; // The Data Byte
  TRISB = 0x07; // RB0, RB1 & RB2 Are Input Pins (Push Buttons)
  TRISD = 0x00; // Output Port (4-Pins)
  PORTD = 0x00; // Initially OFF
  //---------------------------
  while(1)
  {
    if (UP) // Increment The Data Value
    {
      Data++;
      __delay_ms(250);
    }
    if (Down) // Decrement The Data Value
    {
      Data--;
      __delay_ms(250);
    }
    if (Send) // Send The Current Data Value Via SPI
    {
      SPI_Write(Data);
      __delay_ms(250);
    }
    PORTD = Data; // Display The Current Data Value @ PORTD
  }
  return;
}
//-------------------------------
// Functions Definitions

void SPI_Master_Init()
{
  // Set Spi Mode To Master + Set SCK Rate To Fosc/64
  SSPM0 = 0;
  SSPM1 = 0;
  SSPM2 = 0;
  SSPM3 = 0;
  // Enable The Synchronous Serial Port
  SSPEN = 1;
  // Configure The Clock Polarity & Phase (SPI Mode Num. 1)
  CKP = 0;
  CKE = 0;
  // Configure The Sampling Time (Let's make it at middle)
  SMP = 0;
  // Configure The IO Pins For SPI Master Mode
  TRISC5 = 0; // SDO -> Output
  TRISC4 = 1; // SDI -> Input
  TRISC3 = 0; // SCK -> Output
  // If Interrupts Are Needed, Un-comment The Line Below
  // SSPIE = 1; PEIE = 1; GIE = 1;
}

void SPI_Write(uint8_t Data)
{
  SSPBUF = Data; // Transfer The Data To The Buffer Register
  // while(!SSPSTATbits.BF); // Un-comment it if you're Not Using Interrupts!
  // The Above While Loop Protects Against Writing To The Buffer 
  // Before The Previous Transmission Ends
}

2. SPI Receiver (Slave)

[ Receiver Firmware ]

Our task is to set up the SPI module to operate in slave mode and enable the SPI data reception interrupt. Within the ISR handler, we should read the received byte of data and latch it out to PORTB with some LEDs for data inspection. And here is the full code listing for the receiver MCU chip.

/*
* LAB Number: 20
* LAB Name: SPI_Basic_Communication (Slave Code)
* Author: Khaled Magdy
* For More Information Visit My Website @ DeepBlueMbedded.com
*
*/

#include <xc.h>
#include <stdint.h>
#include "config.h"

void SPI_Slave_Init();
uint8_t SPI_Read();

uint8_t Data; // Global Received Data Variable (Not Recommended)
              // Just For Demonstration Purposes Only!

void main(void) 
{
  SPI_Slave_Init(); 
  TRISB = 0x00;
  while(1)
  {
    // Dumping Received Data To PORTB To Validate Communication Success!
    PORTB = Data; 
  }
  return;
}

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

uint8_t SPI_Read() // Not Recommended Method To Read SPI Data
{
  uint8_t Data;
  if(BF) // Check If Any New Data Is Received
  {
    Data = SSPBUF; // Read The Buffer
    BF = 0; // Clear The Buffer-Filled Indicator Bit
    SSPIF = 0; // Clear The Interrupt Flag Bit
    SSPOV = 0; // Clear The Overflow Indicator Bit
    return Data;
  }
}

void __interrupt ISR(void)
{
  if(SSPIF) 
  {
    Data = SSPBUF; // Read The Buffer
    SSPIF = 0; // Clear The Interrupt Flag
  }
}

And We’re Done With The Firmware! Cross Your Fingers! And Hit The Compile Button!

 

       2. Simulation       

 

Create the simulation project on your simulator and connect your circuitry as shown in the schematic diagram down below. Add the hex code to the respective microcontroller chip. The receiver code to the receiver chip and so. Click the run button and test out everything!

SPI Simulation - PIC Tutorials

 

 The Running Simulation Test! 

SPI_Simulation

 

       3. Prototyping       

 

If you’ve correctly followed the schematics shown above in the simulation section, you should get your code up and running on a real breadboard flawlessly! However, you should be careful with grounding and connections and make sure that you’ve (flashed) uploaded the right code to each chip (Master & Slave).

And here is the final running prototype for this project in case you’re curious.

Play Video On YouTube

 

       4. Firmware Template       

 

The previous code listings for this lab despite being fully-functional. It may need a little bit of work to get it working at a different data rate or in another SPI mode number. That’s why I’ll give you a template for more portable and easier to use firmware for both your transmitter & receiver devices.

Transmitter ( Master Code )

/*
* File Name: SPI Firmware Template For (Master / Transmitter)
* Author: Khaled Magdy
* For More Information Visit My Website @ DeepBlueMbedded.com
*
*/

#include <xc.h>
#include <stdint.h>
#include "config.h"
#define _XTAL_FREQ 4000000
//--------------------------------
// Functions Declarations
void SPI_Master_Init(void);
void SPI_Write(uint8_t);
void SPI_Write_String(char *);
//--------------------------------
// Main Routine
void main(void) 
{
  //--[ Peripherals & IO Configurations ]--
  SPI_Master_Init(); // Initialize The SPI in Master Mode @ Fosc/64

  //---------------------------
  while(1) 
  {
    // Do Whatever You Want !
    // ...
  } 
  return;
}
//--------------------------------
// Functions Definitions

void SPI_Master_Init()
{
  // Set Spi Mode To Master + Set SCK Rate To Fosc/64 
  SSPM0 = 0;
  SSPM1 = 0;
  SSPM2 = 0;
  SSPM3 = 0;
  // Enable The Synchronous Serial Port
  SSPEN = 1;
  // Configure The Clock Polarity & Phase (SPI Mode Num. 1)
  CKP = 0;
  CKE = 0;
  // Configure The Sampling Time (Let's make it at middle) 
  SMP = 0;
  // Configure The IO Pins For SPI Master Mode
  TRISC5 = 0; // SDO -> Output
  TRISC4 = 1; // SDI -> Input
  TRISC3 = 0; // SCK -> Output
  // If Interrupts Are Needed, Un-comment The Line Below
  // SSPIE = 1; PEIE = 1; GIE = 1;
}

void SPI_Write(uint8_t Data)
{
  SSPBUF = Data; // Transfer The Data To The Buffer Register
  // while(!SSPSTATbits.BF); // Un-comment it if you're Not Using Interrupts!
  // The Above While Loop Protects Against Writing To The Buffer 
  // Before The Previous Transmission Ends
}

void SPI_Write_String(char *text)
{
  uint16_t i;
  for(i=0;text[i]!='\0';i++)
    SPI_Write(text[i]);
}

Receiver ( Slave Code )
/*
* File Name: SPI Firmware Template For (Slave / Receiver)
* Author: Khaled Magdy
* For More Information Visit My Website @ DeepBlueMbedded.com
*
*/

#include <xc.h>
#include <stdint.h>
#include "config.h"
#define _XTAL_FREQ 4000000
//--------------------------------
// Functions Declarations
void SPI_Slave_Init();
uint8_t SPI_Read();
void SPI_Read_String(uint8_t *, uint16_t);
//--------------------------------
// Main Routine
void main(void)
{
  //--[ Peripherals & IO Configurations ]--
  SPI_Slave_Init(); // Initialize The SPI in Master Mode @ Fosc/64

  //---------------------------
  while(1) 
  {
    // Do Whatever You Want !
    // ... 
  }
  return;
}
//--------------------------------
// Functions Definitions

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

uint8_t SPI_Read() // Not Recommended Method To Read SPI Data
{
  uint8_t Data;
  if(BF) // Check If Any New Data Is Received
  {
    Data = SSPBUF; // Read The Buffer
    BF = 0; // Clear The Buffer-Filled Indicator Bit
    SSPIF = 0; // Clear The Interrupt Flag Bit
    SSPOV = 0; // Clear The Overflow Indicator Bit
    return Data;
  }
}

void SPI_Read_String(uint8_t *Output, uint16_t length)
{
  uint16_t i;
  for(int i=0;i<length;i++)
    Output[i] = SPI_Read();
}

void __interrupt ISR(void)
{
  if(SSPIF)
  {
    Data = SSPBUF; // Read The Buffer
    SSPIF = 0; // Clear The Interrupt Flag
  }
}

 


   Tips + Concluding Remarks   

 

  1  

Never Ever Trust SPI !

The serial peripheral interface (SPI) is not guaranteed to follow any sort of standards. So, knowing how SPI works will not help you that much. You have to carefully read the datasheet and dig very deep to underline & get familiar with their conventions in order to establish a successful SPI communication with their device.

  2  

SPI Transfer Rate (Clock Selection)

SPI clock frequency selection determines the effective (Data transmission rate) of the communication. The master can, theoretically, set the clock rate to whatever value it wants. However, practical considerations must be taken. The slave can not handle such high-speed data reception rates due to interrupt latency and its own Fsys. The maximum data transfer rate should be 1/10 of the slave system clock frequency for most devices.

  3  

SPI Data Rate Variants

Double Data Rate – Some SPI devices are developed to support double-data-rate transmission.

Dual SPI – Because of the Full-Duplex nature of SPI, an extension uses both data pins in a half-duplex configuration to send two bits per clock cycle. Typically a command byte is sent requesting a response in dual mode, after which the MOSI line becomes SIO0 (serial I/O 0) and carries even bits, while the MISO line becomes SIO1 and carries odd bits.

Quad SPI – While Dual-SPI re-uses the existing serial I/O lines, Quad-SPI needs two more I/O lines (SIO2 and SIO3) and sends 4 data bits per clock cycle. it’s also requested by special commands, which enable the Quad-SPI mode after the command itself is sent in single mode.

  4  

SPI Clock Phase And Polarity

Both Serial Clock Phase & Polarity identifies the SPI mode. The 4 possible combinations for them determines the “SPI Mode Number” which can somehow help us follow some sort of standards for configuring SPI devices. The table down below is used to get this done properly.

  5  

SPI Standard Modes Table

SPI Mode Number Clock Polarity

(CKP/CPOL)

Clock Phase

(CPHA)

Clock Edge

(CKE/NCPHA)

0    [0 0] 0 0 1
1    [0 1] 0 1 0
2    [1 0] 1 0 1
3    [1 1] 1 1 0

  6  

Debugging With Virtual Terminal

Communication channels can go crazy in almost any system. Whether it’s a wired or wireless communication. Let’s focus our investigation on the serial SPI communication. Imagine a situation in which you’ve got a device A (master) communicating with device B (slave).

Device A: is programmed to send a data frame of (0x05)

Device B: is programmed to latch the received data within the ISR and write it out to PORTB

Yet, device B is printing out some random garbage!

The question is, who is the criminal to suspect? is it A? or B? or even the hardware bus on-board?

That’s why it’s kind of a tricky business to debug serial bus communications!

You just can’t decide which firmware has to be fixed. Arbitrarily suspicion & random modifications to either of firmware at A or B can make it more difficult and time-consuming. What I prefer, is to use one of the following methods

  • An oscilloscope or Logic Analyzer (inspection)
  • Standard virtual serial protocols debugger

I assume that everybody should be familiar with capturing signals on a generic CRO or a Logic analyzer. What I’d really like to point out to are the Virtual Serial Debugger Tools. Just a standard SPI receiver/transmitter which you can graphically configure using a GUI. Using it will make your life much easier, but why?

Well, if you’ve got a standard receiver that is guaranteed to correctly receive each and every data frame, Then you can tell if your SPI transmitter (device A) is malfunctioning or not!

Conversely, if you’ve got a standard transmitter that’s is guaranteed to generate and sens specific data frames at a specific baud rate flawlessly, Then you can tell if your SPI receiver (Device B) is malfunctioning or not!

SPI Debugging - PIC Tutorials

It’s available in proteus ISIS simulator and can be easily configured/connected as you’ve seen in the figure above. Use your own tools and what you personally prefer for debugging. Always be sure that you’re consistently stepping forward toward solving the problem or finding out who is the hiding criminal. And determine where and which changes have to be made!

  7  

Software SPI (Emulation)

The software can emulate an SPI transmitter/receiver module using only some code and consuming a considerable amount of resources specifically CPU’s time and/or a hardware timer. It can extend your available on-chip SPIs or compensate for the lack of a hardware SPI at all and to some extents. There is always fundamental limitations and drawbacks for what software can emulate.

All in all, if you’re in a situation where you need an additional SPI interface, you can emulate it using the software. Just in case your demand for an SPI is moderately low. But if your application needs extensive SPI communication with crazy high-speed data rates, then you should look for a native hardware solution. As overloading your CPU with unnecessarily expensive (in time) software will be a totally inefficient way to go.

  8  

SPI Related Definitions (Quick Review)

☑  SCLK  

The Serial Clock. It’s generated by the master which controls it’s frequency as well. The rate of SCK determines the data transfer rate. As each cycle, a single bit is transmitted/received.

☑  MISO  

The Master-In->Slave-Out. 

☑  MOSI  

The Master-Out->Slave-In.

☑  SS – SE  

The Slave Select Line (Slave Enable). As long as it’s held High (Logic 1), No data can be received by the slave. The SPI Master controls the SS line/s in order to select which slave should receive the data. This line is usually an active low which required a low signal (Logic 0) to enable SPI data reception at the slave end.

☑  CPOL – CKP  

Clock Polarity. This term defines the IDLE state of the serial clock (SCK) whether it’s High or Low.

☑  CPHA  

Clock Phase. This term defines on which edge of the serial clock (SCK) the data will be transmitted whether it’s the leading edge or the trailing one.

☑  Bit Sampling  

When the input data is sampled (latched). It can be configured to sample the input at the end of the output data time or in the middle of it.

☑  Write Collision  

Any write to the SSPBUF register during transmission/reception of data will be ignored and the write collision detect bit (WCOL) will be set. User software must clear the WCOL bit so that it can be determined if the following write(s) to the SSPBUF register completed successfully.

☑  Dummy Data  

During each SPI clock cycle, full-duplex data transmission occurs. The master sends a bit on the MOSI line and the slave reads it, while the slave sends a bit on the MISO line and the master reads it. This sequence is maintained even when only one-directional data transfer is intended. Which means in order to receive any data you have to actually send something! in this case, we call it “Dummy Data” or “Junk”!

 


if you find this a useful resouece. Then, why not share it with your network ?!

You can also Support DeepBlue on patreon, paypal. Every single contribution helps alot. Cheers ^^

 

Previous Tutorial Previous Tutorial Tutorial 21 Next Tutorial Next Tutorial

 

Share This Page With Your Network!

Khaled Magdy

I'm an embedded systems engineer doing both Software & Hardware. I'm an EE guy who studied Computer Engineering, But I'm also passionate about Computer Science. I love reading, writing, creating projects and Technical training. A reader by day a writer by night, it's my lifestyle. You can view my profile or follow me via contacts.

You may also like...

23 Responses

  1. adrian says:

    Great Job!
    When you will put the next tutorial?

  2. Max says:

    This is the most comprehensive explanation that can be found. Great great work!!!

  3. Stig Allan Jensen says:

    Hi.

    I am an electronic student, at the moment we are learning about PIC Microcontroller and the SPI protocol.

    So I trying to make this project in Proteus 8.

    In the Master file I get this message:

    Error at file ../main.c line 20 column 19: (141) can’t open include file “config.h”: No such file or directory

    So It look like the header file config.h is missing.

    Do you have any idea to solve this problem.

    • Khaled Magdy says:

      Hmmm. Try creating a new project with a new header configurations file. Then move your system to it, that may solve your issue

  4. Pramod says:

    Hi am trying to write a code Pic16f72 with I2C(ds1307) using proteus can help me to write code. This is my wts app no 7204268734.

  5. yasar kosger says:

    That was very informative tutorial. I want to ask what while(!BF); means.I could not find anything on internet about this.

  6. Suangpi says:

    Very very good and the most comprehensive Tutorials I have ever seen. All tutorials are great and crystal clear. Thanks a lot Sir Khaled Magdy.

  7. Andrew Eliasz says:

    Very good tutorial … what tools did you use to develop the simulations please.

  8. Brianv says:

    I’m looking forward to reviewing your material. I noticed in the table under “Here is a brief comparison between serial communication and parallel communication protocols.” you have the ‘relative speed’ reversed.
    Parallel is much faster than serial. That’s a key trade off. Speed vs. distance vs. #pins needed.

  9. Burgert says:

    That uint8_t thing can you help me understand what that is please

  10. Jaikrishnan Gopinathan says:

    May i know the name of the compiler, you written in this spi master slave code..

  11. Juan Diaz says:

    Great tutorials , I feel I am learning a lot !! Just a quick question .. in the SPI _Read() function I can see BF=0; which I don’t fully understand ..I reckon that is bit 0 from the SSPSTAT register and that it is Read only and is set to 0 automatically by hardware as soon as SSPBUF is read . Am I missing something ? it is something I have also seen in the I2c tutorial. Thanks again for your great tutorials ..!!

    • Khaled Magdy says:

      Hi Juan!
      Thanks for your note. As far as I can remember it was required in an MCU part that I used in the past which made me extra stressful over clearing that bit.
      But as you’ve said in the datasheet it’s read only and automatically handled by hardware. This line of code is redundant ofcourse!

  12. Lomesh Pudipeddi says:

    The 5th line of the SPI Master Full Code Listing should be SSPM1=1; for setting SCK Rate to Fosc/64.
    The current code SSPM1=0; sets the SCK to Fosc/4.

Leave a Reply

%d bloggers like this: