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 » Reading Registers with SPI Master and Slave

October 25, 2012
by esoderberg
esoderberg's Avatar

The posted code provides for SPI communications between a Master and Slaves in order to read or write to Slave registers. Supports burst reads.

The read function in the Master code is passed three elements: which slave to read, which slave register to start reading from,and how many sequential bytes should be read.

The write function is passed three elements: which slave to write to, which slave register to write to,and what data to write.

The Slave code responds to the above protocol.

It seems to be robust and won't hang your Master code. It's also uploaded to the library as previously there was no entry under SPI.

        ////////////////////////////////SPI MASTER
        //E.Soderberg

        #define F_CPU 16000000
        #include <inttypes.h>
        #include <stdio.h>
        #include <math.h>
        #include "../utility/io_328p.h"
        #include <avr/io.h>
        #include <avr/interrupt.h>
        #include <avr/pgmspace.h>

        #include "../utility/delay.h"
        #include "../utility/lcd.h"

        #define SPI_SLAVE_DEVICE0 PB2
        #define SPI_SLAVE_DEVICE1 PC4

        #define SPI_BUFFER_SIZE 10

        //variables
        uint8_t i, heart_beat;

        volatile uint8_t spi_data_in[SPI_BUFFER_SIZE], spi_timeout;

        //setup as SPI master
            void master_init(){

            //set MOSI,SCK,SS as output
            DDRB |= (1<<PB3) | (1<<PB5) | (1<<SPI_SLAVE_DEVICE0);//MOSI, SCK, SS (0)
            DDRB &= ~(1<<PB4);//MISO
            DDRC |= (1<<SPI_SLAVE_DEVICE1);//SS(1)

            //keep spi slave inactive
            PORTB |= (1<<SPI_SLAVE_DEVICE0);//gyro CS
            PORTC |= (1<<SPI_SLAVE_DEVICE1);//slave mcu CS

            //initiate the SPI module in master mode, data rate clk/4 or clk/16 with SPR0
            SPCR |= (1<<SPE)  | (1<<MSTR);}// | (1<<SPR0);}

        //************************************************************************************************//
        void spi_read(uint8_t slave_select,uint8_t address_read, uint8_t read_bytes){// slave_select, address to read from, number of bytes to read

            switch (slave_select){

            case 0:  PORTB &= ~(1<<SPI_SLAVE_DEVICE0);//pull CS line low to activate gyro slave spi
            break;
            case 1:  PORTC &= ~(1<<SPI_SLAVE_DEVICE1);//pull slave CS line low to activate slave MCU
            break;
            default:
            break;}

            delay_us(10);
            spi_timeout=0;

            address_read=(address_read | 0x80);//sets MSB in byte to tell slave this is a read request

            //send data to begin transmit/receive
            SPDR = address_read;

            // Wait for transmission complete
            while((!(SPSR & (1<<SPIF)))&&(spi_timeout<100)){spi_timeout++;}
            spi_timeout=0;

            delay_us(50);//allow slave to set outgoing data

            for(i=0; i<read_bytes; i++){
            //Send consecutive  transmissions for burst read starting at previously selected register value in slave
            SPDR = address_read;
            // Wait for transmission complete
            while((!(SPSR & (1<<SPIF)))&&(spi_timeout<100)){spi_timeout++;}
            spi_timeout=0;

            spi_data_in[i]= SPDR;
            delay_us(10);
            }

            switch (slave_select){

            case 0:  PORTB |= (1<<SPI_SLAVE_DEVICE0);//release gyro slave spi
            break;
            case 1:  PORTC |= (1<<SPI_SLAVE_DEVICE1);//release slave MCU
            break;
            default:
            break;}
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////
        void spi_write(uint8_t slave_select,uint8_t address_write, uint8_t write_byte){//address to write to and data to write

                switch (slave_select){

            case 0:  PORTB &= ~(1<<SPI_SLAVE_DEVICE1);//pull CS line low to activate gyro slave spi
            break;
            case 1:  PORTC &= ~(1<<SPI_SLAVE_DEVICE1);//pull slave CS line low to activate slave MCU
            break;
            default:
            break;}

            //reset spi time out
            spi_timeout=0;
            //send data to begin transmit/receive
            SPDR = address_write;//send adddress to write to
            // Wait for transmission complete
            while((!(SPSR & (1<<SPIF)))&&(spi_timeout<100)){spi_timeout++;}
            spi_timeout=0;

            address_write=SPDR;

            SPDR = write_byte;//send byte to write
            // Wait for transmission complete
            while((!(SPSR & (1<<SPIF)))&&(spi_timeout<100)){spi_timeout++;}

                switch (slave_select){

            case 0:  PORTB |= (1<<SPI_SLAVE_DEVICE0);//release gyro slave spi
            break;
            case 1:  PORTC |= (1<<SPI_SLAVE_DEVICE1);//release slave MCU
            break;
            default:
            break;} 
        }
        //************************************************************************************************//
        int main() {

        // init SPI
        master_init();

            // fire up the LCD
          lcd_init();
          FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
          lcd_home();

        //**************************************************************************************//

            while(1){//Main loop//

        heart_beat++;//only here to show code doesn't hang with SPI comms disrupted

        spi_read(1,3,5);//comms with SPI_SLAVE_DEVICE1, reads 5 consecutive bytes starting from slave address 3
        spi_write(1,5,72);//comms with SPI_SLAVE_DEVICE1, writes 72 to address 0x05 on slave

    lcd_goto_position(1,0);
    fprintf_P(&lcd_stream, PSTR("%3d %3d %3d %3d"),spi_data_in[0], spi_data_in[1], spi_data_in[2], spi_data_in[3]);
    lcd_goto_position(2,0);
    fprintf_P(&lcd_stream, PSTR("%3d %3d %3d %3d"),spi_data_in[4], spi_data_in[5], heart_beat, spi_timeout);

        }

      return 0; 
    }

   ////////////SPI SLAVE///////////
//E.Soderberg

#define F_CPU 16000000
#include <inttypes.h>
#include <stdio.h>
#include <math.h>
#include "../utility/io_328p.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>

#include "../utility/delay.h"
#include "../utility/lcd.h"

#define SPI_BUFFER_SIZE 10

//variables

volatile uint8_t spi_data_in[SPI_BUFFER_SIZE], spi_data_out[SPI_BUFFER_SIZE], data_in, data_out;
volatile uint8_t auto_register_index, address;
volatile uint16_t master_write;
uint8_t i;

/////////////////////////////////////////////
void init_slave_release_indication(){

  //Enable PIN Change Interrupt 1 - This enables interrupts on pins
  //PCINT14...8 see p70 of datasheet
  PCICR |= (1<<PCIE0);

  //Set the mask on Pin change interrupt 1 so that only PCINT2 (PB2) triggers
  //the interrupt. see p71 of datasheet
  PCMSK0 |= (1<<PCINT2);}
  ///////////////////////////////////////////

  ISR(PCINT0_vect){
  if (PINB&(1<<PB2)){
  auto_register_index=0;//reset auto index once burst read is over
  }}
 //////////////////////////////////////////// 
void slave_init(){
    DDRB |= (1<<PB4);//MISO-Output

    //SPI on and interupt enabled
    SPCR |= (1<<SPE) | (1<<SPIE);}

///////////////////////////////////////////////////////

ISR(SPI_STC_vect){//return data depending on which address is called
data_in=SPDR;

    if ((data_in&0x80)&&(!(master_write&0x100))){    //master wants to read data on next transmission

        if (auto_register_index==0) auto_register_index = (data_in&0x7F);//record starting register for burst read
        else auto_register_index++;

        if (auto_register_index<SPI_BUFFER_SIZE)
        SPDR=spi_data_out[auto_register_index];
        else SPDR=0xFF;}

    else {if (!(master_write&0x100)) master_write = (0x100 | data_in);//if not already set to receive incoming byte by looking
    //at ninth data bit on master_write, set the ninth bit so next hit will induce writing of data to slave register
    //even if the byte to write has MSB set (and otherwise would be taken as indication of looking to read)

    else {//master_write bit was already set so write new data to address recorded from last hit
            spi_data_in[master_write&0xFF]=data_in;

            master_write=0;//now that write to assigned register is done reset master_write so read or write cycle
         //can start over
         }
         SPDR=0xFF;//return junk to master on Master Write to Slave cycle
        } 
    }

///////////////////////////////////////////////////////
int main(void){

// activate interrupts
sei();

//init some output data for demo
for(i=0; i<SPI_BUFFER_SIZE; i++){
spi_data_out[i]=i*3;}

//init SPI slave mode
slave_init();

//init the slave release interrupt
init_slave_release_indication();

// fire up the LCD
lcd_init();
FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
lcd_home();

    while(1){

lcd_goto_position(1,0);
fprintf_P(&lcd_stream, PSTR("%3d %3d %3d %3d"),spi_data_in[0], spi_data_in[1], spi_data_in[2], spi_data_in[3]);
lcd_goto_position(2,0);
fprintf_P(&lcd_stream, PSTR("%3d %3d %3d %3d"),spi_data_in[4], spi_data_in[5], spi_data_in[6], spi_data_in[7]);

spi_data_out[6]++;//provide some real time data updates for demo

    }
                        return (0);   }

Post a Reply

Please log in to post a reply.

Did you know that the printf format string "%.3f" will show three digits after the decimal point? Learn more...