Serial Peripheral Interface – SPI

Serial Peripheral Interface requires more wires than I2C, four instead of two, but is easier to implement. The CS line tells the slave that it has been selected, the clock line provides the timing and the MOSI (Master Out Slave In) and MISO (Master In Slave Out) lines each carry one way messages. Unlike I2C the data is usually read on the rising edge of the clock (depending on the configuration of the SPI).

Conceptually things are easier too. With SPI the two chips communicating share the contents of their 8 bit shift registers. The moment you send something out of the SPDR, the contents of this register are replaced with the received bit. You’ll also notice that the ISP pins that you use to flash programs on to your atmel are the same ones used for SPI communication between chips. This is because programs are flahsed onto the chip using SPI! This is handy because the six pin header can serve as both the header for programming and for plugging in slaves.

I found Elliot Williams' Make: AVR Programming book very helpful getting this to work.

Here is the SPI Master Code:


// *
// * Atmega168 SPI Master.c
// *
// * Created: 3/6/2019 10:38:11 AM
// * Author : FablabDigiscope
// */

#define F_CPU 8000000

#include <avr/io.h>


int main(void)
{

while (1)
{
	//SPI_init MASTER
	char dataout = 0b11111100; // 0xFC ...
	char datain = 0b00000000;

	DDRB = ((1<<DDB2)|(1<<DDB5)|(1<<DDB3)); //SPI pins on port B: SS, SCK, MOSI outputs
	//set MISO as input
	DDRD = 0b11111111; //green LED on port D
	DDRC = 0b11111111; //orange LED on port C

	PORTB |= (1<<DDB2); //start with SS high (slave not selected). DO THIS BEFORE BEGINING ISP
	PORTB |= (1<<DDB4); //MISO pull-up activated


	SPCR = ((1<<SPE)|(1<<MSTR)|(1<<SPR1));  // SPI enable, Master enable, f/64. DO THIS AFTER DDR!

	//SPI_Transmit
	PORTB &= ~(1<<DDB2); // pull slave select low
	SPDR = dataout; // byte to send


		while(!(SPSR & (1<<SPIF))) //wait for SPIF transmit flag to be set. After this, SPDR will contain the received byte!
		{
		PORTD = 0b11111111;//turn on LED
		datain = SPDR;

			if(datain == 0b11100001) // 0xE1 byte from slave
			{
				PORTC = 0b11111111;
			}
		}
		PORTB |= (1<<DDB2); //slave select high
		}
}

And here is the Slave side code:


/*
 * Atmega168 SPI Slave.c
 *
 * Created: 19/03/06 4:14:19 PM
 * Author : marrs
 */

#define F_CPU 8000000

#include <avr/io.h>


int main(void)
{

    while (1)
    {
			char datain =  0b11111100; //0xFC
			char dataout = 0b10101010; //0xAA


	       //SPI_init SLAVE
		   //MOSI, SS and SCK should be inputs, MISO should be output

			DDRB |= (1<<DDB4) ;//set MISO as output
			DDRD = 0b11111111; //green LED on port D
			DDRC = 0b11111111; //orange LED on port C

			//PORTB |= (1<<DDB2); //SS pull-up...does this make sense?
			PORTB |= (1<<DDB3); //MOSI pull-up...does this make sense?

			SPCR |= (1<<SPE);  // SPI enable. DO THIS AFTER DDR SETTING!
			SPDR = dataout; // in the byte trade this will be sent to the master

			while(!(SPSR & (1<<SPIF)))
			{
			PORTD = 0b11111111;//turn on LED
			}
				if (SPDR == datain)
					{
					PORTC = 0b11111111;//turn on orange LED
					}


		}

  }