SPI - Daniel-Git-Hub/ELEC3042 GitHub Wiki

SPI, Serial Peripheral Interface

SPI is a de facto standard* that allows full duplex** synchronous*** comminucation between two (or more) devices.

*de facto standard Meaning that while it is commonly accepted and used it is not offically established. This means that while most devices will behave sensibly, becuse there is no regulatory body it is not guaranteed

**full duplex Full duplex means that theoretically the standard supports both transmistting a message AND reading a message at the same time, as it will use a different wire for each, I2C on the other hand is only half-duplex as it can both send and recieve but not at the same time

***Synchronous This means that the the rate of commincation between the two devices is syncronised by a single clock, this is done by using a clock wire This means that the two devices could have wildly different native frequencies but still be able to commicate assuming they can listen to the clock wire A counter example is USART (Serial over Tx and Rx) which realises on the two devices independently comminicating at the correct buad rate

Overview

Setup

The following will use this This code is almost identical to the code from SPI Briefing

Connected using this schematic.

For the graphs I am using a 8 channel logic anaylser connected using a program called Pulse View This allows me to see the state of a pin at extreemly small intervals (importantly the logic anaylser is running faster then the Arduino, 24MHz vs 16MHz, meaning it can capture if a wire is pulled high or low with perfect precision. The program I'm using can also decode the signal

I don't necessary recommend either over any other alternative, but it is what I own and what I know how to use

*Also note that a logic anaylzer is not required and in fact I only used it to write this document to show how the comminication works. Typically becuase SPI (and USART and I2C) are well behaved you don't need one (I use mine for debugging hardware)

Terminolgy

Rising Edge This is when a digital signal changes from a LOW signal to a HIGH signal

Falling Edge This is when the digital signal changes from a HIGH signal to a LOW signal

Rising/Falling Image

SPI Bus The collection of the 4 wires

Host Any device connected to the SPI Bus. These could be sensors, the Arduino, a port expander.

Master A singluar host which everything communicates to (by default slave to slave commincation isn't possible). This host also generates the clock signal. For most of our applications we will use the Arduino as the master host. Some use cases for using the Arduino (ATMEGA328p) as a slave is if we had mulitple programmable chips (for example a Raspberry Pi, another Arduino, etc) we would only declare one host as a master

Slave All other hosts on the SPI Bus. These communicate at a rate set by the Master.

Sampling In SPI Sampling is when a host reads it data wire, that is Master reads MISO and the slave reads MOSI

Setup In SPI Setup is when a hosts sets or clears its data wire, the Master changes MOSI and a slave changes MISO

Adressing The SPI standard does not support addressing natively, addressing comes from the CS wire. Some devices support addressing (thus allowing multiple devices on a single CS wire) like the MCP23s17 port expander but it should never be assumed.

Wires

MOSI (sometimes called SDI) Arduino pin 11 (3rd bit in PORTB) Master Out, Slave In. For devices which are purely Slaves (think sensors) this pin can be called SDI, Slave Date In This is the wire that the Master changes and the Slaves read from

MISO (sometimes called SDO) Arduino pin 12 (4th bit in PORTB) Master In, Slave Out. For devices which are purely Slaves (think sensors) this pin can be called SDO, Slave Date Out This is the wire that the Slaves changes and the Master read from

SCK Arduino pin 13 (5th bit in PORTB) Master Clock Output The Master sends a square wave out on this wire, and all hosts commicate using the timing set by this wire. By default, most SPI hosts will communication at CPOL (Clock Polairty) = 0, that is for each bit of commincation it will lead with a rising edge on SCK and a trailing edge will be a falling edge on SCK. This practically means that it will do a bit of communication on when SCK is high this is reversed on CPOL = 1, where it will send/recieve a bit on a low single of SCK

Further more the way SPI works it has another Mode called CPHA, Clock Phase For CPHA = 0 (The default) It will the Master will setup MOSI and the Slave will setup MISO on the falling edge And Master will sample MISO and the Slave will sample MOSI on the rising edge For CPHA = 1 this changes so MISO and MOSI get Changed on the rising edge and get read from on a falling edge

Some devices do not allow configuration so to understand which modes to set the ATMEGA328p chip to read that devices datasheet, often this information can be found in the "timing" chapter of those datasheet. AS ALWAYS READ THE DATASHEET

CS (or sometimes called SS) ANY pin on the Arduino Chip Select (or Slave Select) Each slave normally (unless it supports addressing) needs its own CS wire, that is a unique wire from the Master to each of the slaves. This is how slaves tell if the message on MOSI is for them and if they can send messages along MISO. typically if this wire is pulled high (the bit in PORT is set to 1) a slave will NOT do any SPI commincation. And if this wire is pulled low then it will send/recieve messages.

What a Message looks like

This graph was generated by sending the byte "01011010" to an arbitary SPI host

SPI Send Example

PulseView shows the Sampling at the start of each bit in its decoder

To note the reason why CS does not look as neatly aligned is because it is MANUALLY cleared then set by modifing the PORTB register

It can also be seen that while the chip is not sending messages the CLK pin on the Arduino reads as low and it does not send a pulse.

What a message then a response looks like

For this a simple read message is sent to the MCP23s17 to see the current state of PORTA

Implementing SPI

Setup

Preface we are going to set up the ATMEGA328 as a Master SPI device comminicating with a MCP23S17 (SPI Port Expander)

Pinmodes From page 171 of the datasheet we find a useful table that tells us for SPI in Master Mode MOSI - User Defined MISO - Input SCK - User Defined SS - User Defined

the SS pin is also flexible and can be any pin on the ATMEGA BUT all the others HAVE to be there disgnated pins

We are going to setup MOSI as on Output (Master Out, Slave In). We will want to generate the clock from the ATMEGA so we will set it as an output. and we will also need to set the chip select (SS) as an output

From page 8 of the MCP datasheet we can see that a conversation starts when SS is pulled low, so when not commicating we will pull it high

    DDRB =   0b00101111;
	PORTB |= 0b00000100;		// set SS signal high

Control Registers These are the setting registers for SPI

SPCR -> SPI Control Register Here we set the 6th bit, this enables SPI We also set the 4th bit, this sets the ATMEGA to be in master mode

Note We also will clear any bit currently set in SPSR (SPI Status Register) this ensures that it is clean

    SPCR = 0b01010000;   // set master SPI, SPI mode 0 operation
	SPSR = 0;            // clear interrupt flags and oscillator mode.

MCP Setup Here we will use the SPI send function (talked about later) to set the data direction for the MCP chip

When reading the MCP chip look at registers for Bank = 0 as this is the mode it is in by default

    SPI_Send_Command(0x00, 0x00);   // register IODIRA (port A data direction)
    SPI_Send_Command(0x01, 0xf0);   // register IODIRB (port B data direction)
    SPI_Send_Command(0x0d, 0xff);   // register GPPUB (port B GPIO Pullups)

In Total

void setup_SPI() {
    DDRB =   0b00101111;
	PORTB |= 0b00000100;		// set SS signal high
/*
 * Setup SPI operations
 * See pp176-177 of the data sheet
 */
	SPCR = 0b01010000;   // set master SPI, SPI mode 0 operation
	SPSR = 0;            // clear interrupt flags and oscillator mode.

//
// Now that the SPI interface is configured we need to send SPI commands to configure the MCP23S17
// port expander IC
//
// We will configure port A as all outputs, port B as all inputs.
// use the SPI_send_command to send the SPI commands.
// This will require the register address in r20 and the register data in r21
//
    SPI_Send_Command(0x00, 0x00);   // register IODIRA (port A data direction)
    SPI_Send_Command(0x01, 0xf0);   // register IODIRB (port B data direction)
    SPI_Send_Command(0x0d, 0xff);   // register GPPUB (port B GPIO Pullups)
}

Transfer

From the ATMEGA Datasheet we can see that SPDR (SPI Data Register) is for both sending and recieving data. so at the start of a transfer we will set this register to be are data at the start of a transfer then we can read this register to see the response

We can see see that the 7th bit of the SPSR (SPI Status Register) is set a data transfer is complete so we can wait until that bit is 1

uint8_t SPI_transfer(uint8_t data) {
    SPDR = data;
    while ((SPSR & 0b10000000) == 0) {
        ;   // wait until transfer completed
    }
    return SPDR;
}

Read/Send Command

Important This is only relevevant to the MCP other SPI slaves can and will be different, as always READ THE DATASHEET for your chip/device/module

According to the MCP datasheet to send a data we need to do five things

  1. Pull the SS pin to low This starts aa conversation

  2. transfer the device address and the read/write
    From figure 3-7 in the MCP datasheet we send it 0b0100000x (this can change depending on the address pins) where to write to a register x = 0 and to read from a register we set x = 1

  3. transfer the register we want to read/write to These can be seen in figure 3-3 and 3-5 (BANK = 0) where the trailing A or B represents whether you are talking about PORTA or PORTB and the register descriptions can be found in the following pages of the data sheet

  4. IF writing then transfer the data otherwise 0 The 0 is from the timings in the datasheet

  5. Pull the SS pin high this ends the conversation

void SPI_Send_Command(uint8_t reg, uint8_t data) {
    // Send a command + byte to SPI interface
    PORTB &= ~_BV(2);    // SS enabled (low))
    SPI_transfer(0x40);  //Send the address with a write bit
    SPI_transfer(reg);   //Send which Register we want to write to
    SPI_transfer(data);  //Send the data we want to write to this register
    PORTB |= _BV(2);     // SS disabled (high)
}
uint8_t SPI_Read_Command(uint8_t reg) {
    uint8_t data;
    
    // Read a command output from SPI interface
    PORTB &= ~_BV(2);    // SS enabled (low))
    SPI_transfer(0x41);
    SPI_transfer(reg);
    data = SPI_transfer(0);
    PORTB |= _BV(2);    // SS disabled (high)
    return data;
}