SPI ATMega (master) - ATTiny84 (slave) Problem reading data from slave

Hi,

I am working on a SPI implementation where ATMega1280 is the master communicating with multiple ATTiny84s as slaves.
My basic implementation is able to send data from the ATMega to a single ATTiny.
However, when I try to send back data from the ATTiny, that doesn’t work.

The workflow is: ATMega send a byte-command which tells the ATTiny to send back a byte. Then the ATMega initiates another ‘transfer’ to read in the byte from the ATTiny.

The problem: The byte received from the ATTiny is the same as the original command byte. I don’t know what I am doing wrong here…

Master code

/*
 Created by Abe Karnik 25 Jun 2013 based on:
 Digital Pot sketch created 10 Aug 2010 by Tom Igoe and originally based on
 Tutorial by Heather Dewey-Hagborg 2005
 */
// inslude the SPI library:
#include <SPI.h>

#define CMDSETW1 B10100000
#define CMDSETW2 B10110000
#define CMDGETW1 B11000000
#define CMDGETW2 B11010000

//serial part
char progVer[50]  =  "Prog ver: SPI Master test";
char startMsg[50] =  "Ready...";
const int SER_COMM_SPEED = 9600;

const int SPISS = 40;

void setup() {
  // set the slaveSelectPin as an output:
  pinMode (SPISS, OUTPUT);
  digitalWrite(SPISS,HIGH);
  // initialize SPI:

  SPI.begin(); 
  SPI.setDataMode(SPI_MODE0);
  SPI.setBitOrder(MSBFIRST);
  Serial.begin(SER_COMM_SPEED); //check this...          
  Serial.println(progVer);
  Serial.println(startMsg);  
}

void loop() {  
  if(Serial.available() > 0)
  {
    readMsg();
  }
}

//serial communication
void readMsg(void)
{
          int recvdVal=-1;
          char ch = Serial.read();
          switch(ch)
          {
               case 'a':
               case 'A':
                    sendDataWriteCmd(CMDSETW1, 196, SPISS);
                    break;
               //not working...     
               case 'p':
               case 'P':
                    recvdVal = sendDataReadCmd(CMDGETW1,  SPISS);
                    break;                          
              case 'v':
                   Serial.println(progVer);
                   break;              
              default:
                   Serial.print("Unknown cmd: ");
                   Serial.println(ch);
          }
}

int sendDataReadCmd(int spicmd, int csPin){
  Serial.print("Sending cmd: ");
  Serial.println(spicmd,BIN);
  
  digitalWrite(csPin, LOW);
  SPI.transfer(spicmd);
  digitalWrite(csPin,HIGH);

  delayMicroseconds(10); //1 microsecond delay?
  
  digitalWrite(csPin,LOW);
  int recv = SPI.transfer(0); //get the data sent by the slave
  digitalWrite(csPin,HIGH);  
  
  Serial.print("Received : ");
  Serial.println(recv,BIN);
  return recv;
}

void sendDataWriteCmd(int spicmd, int spidata, int csPin) {

  Serial.print("Sending cmd: ");
  Serial.println(spicmd,BIN);
  
  // take the SS pin low to select the chip; write the command and finally pull the csPin high
  digitalWrite(csPin,LOW);
  int getBack = SPI.transfer(spicmd);
  digitalWrite(csPin,HIGH);//toggle
  
  Serial.print("Response: ");
  Serial.println(getBack,BIN);
  delayMicroseconds(10); //1 microsecond delay?
  Serial.print("Sending data: ");
  Serial.println(spidata,BIN);
  
  digitalWrite(csPin,LOW);
  SPI.transfer(spidata);
  digitalWrite(csPin,HIGH);   
}

Slave code on ATTiny84

#include <PinChangeInterrupt.h>

/*
  SPI Slave Test Program on ATTiny84
  Abe Karnik 27-Jun-2013  
*/
 
//chip pin to internal digital assignment
#define PPP7   4 //MOSI
#define PPP8   5 //MISO
#define PPP9   6 //USCK 
#define PPP12  9 //PCINT1 CS (Chip select interrupt)

//ADC Defines
#define ADC2 PA2
#define ADC3 PA3

//SPI Defines
#define MOSI PPP7
#define MISO PPP8
#define SCK  PPP9
#define CS   PPP12

//Input defines
#define W1 ADC2
#define W2 ADC3

#define CMDNONE 0
#define CMDRECV 1 //receive data from master
#define CMDSEND 2 //send data to master

//SPI Command and control structure... 
// B7: always on; B6-B5: 10 send data to master, 01 recv data; B4: 0 -> W1 value, 1 -> W2 value (set/get as per B7-B6);
#define CMDSETW1 B10100000
#define CMDSETW2 B10110000
#define CMDGETW1 B11000000
#define CMDGETW2 B11010000

#define CMDERROR B11110000 //this is returned by slave in case of any error.

bool csLow = false;
byte cmdtype = CMDNONE; //no command to start with.
byte spicmd = 0; //

//state variables for inputs and outputs
int w1setpt =0;
int w2setpt =0;
int w1curpt =0;
int w2curpt =0;

// the setup routine runs once when you press reset:
void setup() {                
  // initialize the digital pin as an output.
  setupSPI();  
}

// the loop routine runs over and over again forever:
void loop() {
  //read the potentiometers
  w1curpt = analogRead(W1);
  w2curpt = analogRead(W2);
  delay(10); //just random
}

void setupSPI(){
  //pin setup
  pinMode(MOSI,   INPUT);
  pinMode(MISO,  OUTPUT);
  pinMode(SCK,    INPUT);
  pinMode(CS,     INPUT);
  //chip select interrupt
  attachPcInterrupt(CS,chipSelect,FALLING);
  //actual USI setup
  USICR = 0; //set everything to zero.
  USICR = (1 << USIWM0) | (1 << USICS1) | (1 << USIOIE); //Three wire mode0, External clock positive edge both edges, Enable interrupt
}

//first Mega will select the chip
void chipSelect(){//this tells the Tiny that the data coming in is for it.
  csLow = true; //resetting is the job of the SPI overflow handler  
}

//then send the message which should trigger the ISR
ISR(USI_OVF_vect)
{//overflow
  byte c = USIDR; //read the USI Data Buffer
  USISR = 1 << USIOIF; //clear the interrupt flag, yeah I know, it needs to be set to 1 to clear and reset the counter
  if(csLow){  //this slave is selected, so command is for this slave
    switch(cmdtype){
      case CMDNONE://we haven't received a command yet
        spicmd = c; //save the command
        cmdtype = getCmdType(spicmd); //is it send or receive or none, none should not occur
        if(cmdtype == CMDSEND){ //immediate action required         
          USIDR = genSendData(spicmd);
          pinMode(MISO, OUTPUT); //only when sending out a byte
        }//if recv command, nothing to do till next byte arrives
        break;
      case CMDSEND://data clocked out
        cmdtype = CMDNONE; //reset command, we are done.
        break;
      case CMDRECV://we just got the data
        spiProcessData(spicmd,c);
        cmdtype = CMDNONE; //reset command, we are done.
        break;
    }//end switch
    csLow = false; //reset so that next byte can be processed
  }
  else{
    pinMode(MISO,INPUT); //reverse it so it doesn't interfere with other slaves
  } 
}  // end of interrupt service routine (ISR) SPI_STC_vect

byte getCmdType(byte cmd){  
  switch(cmd){
    case CMDGETW1:
    case CMDGETW2:  
      return CMDSEND; //these are the master telling to send back a byte at the next run
      break;
    case CMDSETW1:
    case CMDSETW2:
      return CMDRECV;
      break;
  }
  return  CMDNONE; //default condition
}

byte genSendData(byte cmd){
  switch(cmd){
    case CMDGETW1:
      return (byte)(w1curpt>>2);
      break;  
    case CMDGETW2:
      return (byte)(w2curpt>>2);
      break;  
  }
  return CMDERROR; //error flag
}

void spiProcessData(byte cmd, byte inData){
  //cmd = cmd&(B11110000); //mask last 4 
  switch(cmd){
    case CMDSETW1:
      w1setpt = (inData<<2);
      break;  
    case CMDSETW2:
      w2setpt = (inData<<2);
      break;      
  }
}

All I see in the data sheet is that if a byte needs to be clocked out, it needs to be loaded into the USIDR which I am doing.
But something doesn’t seem to be working right.

Thanks.

Regards,
Abe

  delayMicroseconds(10); //1 microsecond delay?

10 uS might be too short.

Try reading: Gammon Forum : Electronics : Microprocessors : SPI - Serial Peripheral Interface - for Arduino

Sweet! That works.

On a side note, since the ATTiny84 lacks a proper SS pin, is my approach of inverting the pin direction of DO (MISO) a sensible idea?
I will eventually have 50 slaves hooked up to a single bus and only one of them will be transmitting at a time based on its own CS port pin being pulled low.

Thanks.

Regards,
Abe

Abek42:
On a side note, since the ATTiny84 lacks a proper SS pin,

That's because the SS pin is really just an ordinary I/O pin.

The decision to force a particular pin on the Mega328 is a very strange one.

fungus:

Abek42:
On a side note, since the ATTiny84 lacks a proper SS pin,

That's because the SS pin is really just an ordinary I/O pin.

Yes, true. What I meant was that if the slave is not selected, it would be expected that the device's MISO pin tri-stated? Otherwise it would interfere with the slave that is selected?

fungus:
The decision to force a particular pin on the Mega328 is a very strange one.

I am not forcing a pin on Mega328. Only the slave's MISO (or DO) pin is being set to input instead of output.

Does that make sense?

Thanks.

Regards,
Abe

Abek42:

fungus:

Abek42:
On a side note, since the ATTiny84 lacks a proper SS pin,

That's because the SS pin is really just an ordinary I/O pin.

Yes, true. What I meant was that if the slave is not selected, it would be expected that the device's MISO pin tri-stated? Otherwise it would interfere with the slave that is selected?

Yes, you definitely need to do that.

(It might do more than interfere, if you connect two output pins together with one HIGH and the OTHER low then you can kill those pins).

Abek42:

fungus:
The decision to force a particular pin on the Mega328 is a very strange one.

I am not forcing a pin on Mega328. Only the slave's MISO (or DO) pin is being set to input instead of output.

I meant that Atmel forces you to use pin 10 as SS. Try setting pin 10 as INPUT and see if your SPI bus keeps on working...

Thanks fungus.

I get what you are saying about the SS on the 328 now. Since I have to worry about 50 slaves, I will be using a very different scheme for selecting each. But I will remember to exclude pin 10 (51 on the Mega128) from use.

Thanks.

Regards,
Abe

I get an error... it seems the whole site is down...

It Works Now... and one of Nick's better tut's too

Abek42:
I get what you are saying about the SS on the 328 now. Since I have to worry about 50 slaves, I will be using a very different scheme for selecting each. But I will remember to exclude pin 10 (51 on the Mega128) from use.

Why do you have to exclude pin 10? 50 slaves sounds like a lot, have you tried using I2C?

Well, it is a bit convoluted as an implementation. The Mega also has to deal with 100 RGB LEDs and 100 touch sensors apart from driving 100 motors. The touch sensors run off I2C and LEDs (12mm Diffused Thin Digital RGB LED Pixels (Strand of 25) [WS2801] : ID 322 : $39.95 : Adafruit Industries, Unique & fun DIY electronics and kits) will be using software SPI (I am planning to keep 4 strands separate so the update overhead on the tail pixel is reduced) The motor driving (servo control) is offloaded to the ATTiny84 (one 'drives' two motors). I have a shield (Mux Shield II - DEV-11723 - SparkFun Electronics) which I plan to use as my slave select pin mux.
Exclusion of pin 10 meant that I would not use it for anything else.

Thanks.

Regards,
Abe

I tried to use your code with Arduio 1.6.5. It seems I have to install. It seems I have to install GitHub - rambo/arduino-tiny: Fork (using git-svn) so I can handily test my "improvements". But this library does not support V1.6. Is there anyway to do it?

Use a different attiny core that does
gestures towards signature

I tried to use the original code using Arduino 1.0.6 and Uno board. I did the following changes in the master code:

const int SPISS = 10;

I tryed different delays in Master code as Nick suggested. This is the result with delayMicroseconds(10);

This is the terminal output when I send "a" a few times. I reset UNO too.

Prog ver: SPI Master test
Ready...
Sending cmd: 10100000
Response: 11111
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11100000
Sending data: 11000100
Prog ver: SPI Master test
Ready...
Sending cmd: 10100000
Response: 10011111
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000110
Sending data: 11000100
Sending cmd: 10100000
Response: 11100000
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Prog ver: SPI Master test
Ready...
Sending cmd: 10100000
Response: 10011111
Sending data: 11000100
Sending cmd: 10100000
Response: 11000010
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000010
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100

This is the result with delayMicroseconds(20);

Prog ver: SPI Master test
Ready...
Sending cmd: 10100000
Response: 11111111
Sending data: 11000100
Sending cmd: 10100000
Response: 11000010
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000110
Sending data: 11000100
Prog ver: SPI Master test
Ready...
Sending cmd: 10100000
Response: 11011111
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000010
Sending data: 11000100

This is the result with delayMicroseconds(30);

Prog ver: SPI Master test
Ready...
Sending cmd: 10100000
Response: 11111111
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000010
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Prog ver: SPI Master test
Ready...
Sending cmd: 10100000
Response: 10011111
Sending data: 11000100
Sending cmd: 10100000
Response: 11000110
Sending data: 11000100
Sending cmd: 10100000
Response: 11100000
Sending data: 11000100

This is the result with delayMicroseconds(40);

Prog ver: SPI Master test
Ready...
Sending cmd: 10100000
Response: 0
Sending data: 11000100
Sending cmd: 10100000
Response: 0
Sending data: 11000100
Sending cmd: 10100000
Response: 0
Sending data: 11000100
Sending cmd: 10100000
Response: 0
Sending data: 11000100
Sending cmd: 10100000
Response: 0
Sending data: 11000100
Prog ver: SPI Master test
Ready...
Sending cmd: 10100000
Response: 0
Sending data: 11000100
Sending cmd: 10100000
Response: 0
Sending data: 11000100

Again I checked the result with delayMicroseconds(10);

Prog ver: SPI Master test
Ready...
Sending cmd: 10100000
Response: 0
Sending data: 11000100
Sending cmd: 10100000
Response: 0
Sending data: 11000100
Sending cmd: 10100000
Response: 0
Sending data: 11000100

It seems that the connection is not reliable and every time i just receive a random number. How can I improve it?

I lowered the spi speed by adding the below line after SPI.begine() to the master code:
SPI.setClockDivider(SPI_CLOCK_DIV64);
Also used 30us delay.

This is the result when I send 'A' command. Very consistent:

Prog ver: SPI Master test
Ready...
Sending cmd: 10100000
Response: 11111111
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100
Sending cmd: 10100000
Response: 11000100
Sending data: 11000100

but when I send 'P' command results are eaten half way as you see.

Prog ver: SPI Master test
Ready...
Sending cmd: 11000000
Received : 11000000
Sending cmd: 11000000
Received : 10011
Sending cmd: 11000000
Received : 10100
Sending cmd: 11000000
Received : 10011
Sending cmd: 11000000
Received : 10011
Sending cmd: 11000000
Received : 10011
Sending cmd: 11000000
Received : 10100

Any suggestion to improve the code further?