October 25, 2012
by esoderberg
|
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); }
|