NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » Master/Slave SPI

May 03, 2011
by Noter
Noter's Avatar

Here is an example of master/slave communication between the nerdkit and another ATmega using the SPI interface. It's quite simple, the master sends a number to the slave, the slave increments the number and sends the result back to the master. Although SPI is full duplex, the catch is that the slave can't give the result back to the master until the next time the master sends a number. Other than that one byte delay, SPI is fairly easy to grasp.

//
// master_SPI.c - load on nerdkit with LCD
//
#include <avr/io.h> 
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <stdbool.h>
#include <stdlib.h>
#include <ctype.h>

#include "../libnerdkits/lcd.h"

// preprocessor macros for port/pin manulipulation
//
#define INPUT2(port,pin) DDR ## port &= ~_BV(pin) 
#define OUTPUT2(port,pin) DDR ## port |= _BV(pin) 
#define CLEAR2(port,pin) PORT ## port &= ~_BV(pin) 
#define SET2(port,pin) PORT ## port |= _BV(pin) 
#define TOGGLE2(port,pin) PORT ## port ^= _BV(pin) 
#define READ2(port,pin) ((PIN ## port & _BV(pin))?1:0)
//
#define INPUT(x) INPUT2(x) 
#define OUTPUT(x) OUTPUT2(x)
#define CLEAR(x) CLEAR2(x)
#define SET(x) SET2(x)
#define TOGGLE(x) TOGGLE2(x)
#define READ(x) READ2(x)
#define PULLUP_ON(x) INPUT2(x); SET2(x)
#define PULLUP_OFF(x) INPUT2(x); CLEAR2(x)

// local functions
void spi_master_init(void);
uint8_t spi_xmit_char(uint8_t send);

// define ports, pins
//
// wire master MISO to slave MISO
#define DD_MISO         B,4

// wire master MOSI to slave MOSI
#define DD_MOSI         B,3

// wire master SCK to slave SCK
#define DD_SCK          B,5

// wire master SS to slave SS
#define DD_SS           B,2

// wire PC1 to slave reset (PC6)
#define SLAVE_RESET     C,1

// setup SPI prescalers
# define SPI_bitrate_div_4      0
# define SPI_bitrate_div_16     1
# define SPI_bitrate_div_64     2
# define SPI_bitrate_div_128    3
# define SPI_bitrate_div_2      4
# define SPI_bitrate_div_8      5
# define SPI_bitrate_div_32     6
# define SPI_bitrate_div__64    7

// LCD stream file - enable printf in functions outside of main()
FILE lcd_stream;

// --------------------------------------------------------------------------------------------------------
int main() {
    // initialize LCD display
    lcd_init();
    fdev_setup_stream(&lcd_stream, lcd_putchar, 0, _FDEV_SETUP_WRITE); 
    lcd_clear_and_home();
    fprintf_P(&lcd_stream, PSTR("master_SPI"));

    // initialize SPI
    spi_master_init();

    // reset the slave so it will be in sync
    // with me (the master) thus responding with 1 from the start.
    OUTPUT(SLAVE_RESET);
    CLEAR(SLAVE_RESET);
    _delay_ms(500);
    SET(SLAVE_RESET);
    _delay_ms(500);

    // send 100 test bytes
    int i;
    uint8_t byte_received;
    for(i=1;i<100;i++){
        // select slave
        CLEAR(DD_SS);
        // transmit/receive
        // the slave will increment i and send 
        // it back next time. That's the catch with
        // SPI, the slave response is always a byte behind.
        byte_received=spi_xmit_char(i);
        // deselect slave
        SET(DD_SS);
        // check result
        lcd_goto_position(1,0);
        if(byte_received==i)
            fprintf_P(&lcd_stream, PSTR("%d OK"),i); 
        else {
            fprintf_P(&lcd_stream, PSTR("Error at byte %d"), i); 
            lcd_goto_position(2,0);
            fprintf_P(&lcd_stream, PSTR("expected %d, got %d"), i, byte_received); 
            break;
        }   
    }

    // done
    while(true);
}
// --------------------------------------------------------------------------------------------------------
//
void spi_master_init(void){
    // setup master SPI pins
    INPUT(DD_MISO);
    OUTPUT(DD_MOSI);
    OUTPUT(DD_SCK);
    OUTPUT(DD_SS);
    // enable master SPI
    SPCR = _BV(SPE)|_BV(MSTR)|SPI_bitrate_div_128;
}

uint8_t spi_xmit_char(uint8_t send){
    // send character
    SPDR = send;
    // wait until done
    while(!(SPSR & _BV(SPIF)));
    // return recieved character
    return(SPDR);
}

================================================================================================

//
// slave_SPI.c
//
#include <avr/io.h> 
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <stdbool.h>
#include <stdlib.h>
#include <ctype.h>

// preprocessor macros for port/pin manulipulation
//
#define INPUT2(port,pin) DDR ## port &= ~_BV(pin) 
#define OUTPUT2(port,pin) DDR ## port |= _BV(pin) 
#define CLEAR2(port,pin) PORT ## port &= ~_BV(pin) 
#define SET2(port,pin) PORT ## port |= _BV(pin) 
#define TOGGLE2(port,pin) PORT ## port ^= _BV(pin) 
#define READ2(port,pin) ((PIN ## port & _BV(pin))?1:0)
//
#define INPUT(x) INPUT2(x) 
#define OUTPUT(x) OUTPUT2(x)
#define CLEAR(x) CLEAR2(x)
#define SET(x) SET2(x)
#define TOGGLE(x) TOGGLE2(x)
#define READ(x) READ2(x)
#define PULLUP_ON(x) INPUT2(x); SET2(x)
#define PULLUP_OFF(x) INPUT2(x); CLEAR2(x)

// local functions
void spi_slave_init(void);
uint8_t spi_recv_char(uint8_t send);

// define ports, pins
//
#define DD_MISO         B,4
#define DD_MOSI         B,3
#define DD_SCK          B,5
#define DD_SS           B,2

// --------------------------------------------------------------------------------------------------------
int main() {
    // initialize SPI
    spi_slave_init();

    uint8_t byte_received;
    uint8_t byte_to_send;
    byte_to_send=1;

    // get a byte, increment it, and send back
    while(true){
        byte_received = spi_recv_char(byte_to_send);
        byte_to_send = byte_received+1;
    }
}
// --------------------------------------------------------------------------------------------------------
//
void spi_slave_init(void){
    // setup slave SPI pins
    OUTPUT(DD_MISO);
    INPUT(DD_MOSI);
    INPUT(DD_SCK);
    INPUT(DD_SS);
    // enable SPI
    SPCR = _BV(SPE);
}

uint8_t spi_recv_char(uint8_t send){
    // character to send
    SPDR = send;
    // wait for received character
    while(!(SPSR & _BV(SPIF)));
    // return recieved character
    return(SPDR);
}
May 04, 2011
by Ralphxyz
Ralphxyz's Avatar

Man Paul, that is great, I've just gone through it quickly.

One question what about multiple Slaves, I am sure it is in there but ...

Ralph

May 04, 2011
by Noter
Noter's Avatar

Hi Ralph,

This example has only one slave and uses the master SS pin to select it. For more slaves you need to use other master pins connected to the slave(s) SS to select them. Don't be confused by my use of the master SS in this example, it's not absolutely required. Hexorg has shown on another thread the master SS needs to be configured as output else it is used to enable/disable the SPI interface. So it's probably a good rule of thumb to use the master SS for your first slave just to be sure it is always configured as output.

Paul

September 08, 2011
by Rachid
Rachid's Avatar

Hi,

Can anyone explain the following line of code:

define OUTPUT2(port,pin) DDR ## port |= _BV(pin)

I understand this what #define OUTPUT2(port,pin) port |= _BV(pin) means but I dont understand DDR##.

Thank you

September 09, 2011
by 6ofhalfdozen
6ofhalfdozen's Avatar

Rachid,

Noter would be the expert since its his code, but I believe the DDR ## stands for the Data Direction Register for Port ##, which you pick/assign. As I recall this is part of one method of assigning pins as inputs or outputs. Hopefully that helps enough until Noter or someone more knowledgable can answer.

September 09, 2011
by bretm
bretm's Avatar

The ## is the concatenation operator. "port" is a string such as "B" but you can't just say DDRport because that's just a different identifier. DDR ## port concatenates the string "DDR" and the value of port, so you get "DDRB"

September 09, 2011
by Ralphxyz
Ralphxyz's Avatar

Is that concatenation operator (##) only a C pre-processor operator?

Ralph

September 10, 2011
by bretm
bretm's Avatar

Yes, that's right.

September 14, 2011
by Rachid
Rachid's Avatar

Thanks alot guys.

May 28, 2012
by sask55
sask55's Avatar

This is a very good thread that can be used by anyone as an example of SPI. Some time ago I noticed one small inaccuracy that would be inconsequential for most people that may be basing a SPI communication project from this thread. I thought that I might point out changes that could be made in order to make the SPI operate as expected at all possible speeds.

The set of defined prescalers will not work as expected for values above 3

// setup SPI prescalers 
# define SPI_bitrate_div_4      0 
# define SPI_bitrate_div_16     1 
# define SPI_bitrate_div_64     2 
# define SPI_bitrate_div_128    3 
# define SPI_bitrate_div_2      4 
# define SPI_bitrate_div_8      5 
# define SPI_bitrate_div_32     6 
# define SPI_bitrate_div__64    7

That is to say the statement used to set the bits in the SPCR register

 // enable master SPI 
 SPCR = _BV(SPE)|_BV(MSTR)|SPI_bitrate_div_128;

Will fail for the last four SPI clock ratios listed.

The statement above will set the 3 LSB (lease significant bits) in the SPRC register in accordance with the values defined in the list. From the data sheet the 3 LSB of SPRC register are CPHA, SPR1 and SPR0. CPHA is the Clock phase bit not a clock rate select bit. From table 18-5 in the data sheet Bit 0 (SPR0) and bit 1 (SPR1) are the correct bits to set the SPI clock rate ratio. However the MSB from this table is SPI2X which is bit 0 on the SPSR register. So in order to select a SPI clock ratio from the lower half of the table, bits should be set as required in both registers.

example of fosc/32

using Noter method

SPCR = _BV(SPE)|_BV(MSTR)|SPI_bitrate_div_64;
SPSR = _BV(SPI2X)

or

SPCR |= (1<<SPE)| (1<<MSTR) | (1<<SPR1)
SPSR |= (1<<SPI2X)

Excellent thread: I just thought this issue if not noticed may cause a problem in some applications

Post a Reply

Please log in to post a reply.

Did you know that you can make a spooky Halloween Jack-O-Lantern with a microcontroller? Learn more...