Using an ATtiny85 as SPI Slave

dhenry:
You don't have to use an interrupt. A test of the flag is sufficient.

True... but at least rearrange the example code like this:

...same as before

// Receive a byte from the master
byte spiSlaveTransfer()
{
  // Wait for complete byte to arrive
  while ((USISR & (1<<USIOIF))==0) {
  }
  // Clear counter overflow flag so next byte can begin arriving
  // While we're processing this one
  USISR = (1<<USIOIF);
  // Return received byte
  return USIDR;
}

It will be much more reliable that way around.

I would also return usibr, not usidr.

dhenry:
I would also return usibr, not usidr.

Good idea.

Hello there,

fungus and dhenry, thank you so much for the code and input you provided.

However I was trying for some time today and couldn´t get the ATtiny to transfer some Frequency readings via SPI. Here is a Picture of the setup I use.
At the bottom right you´ll see a DSO Quad. I used the DSO Quads Wave output to generate a 10 Hz Square Signal for the ATtiny85 at its Pin 3.

However This Setup and the code which I used Did´t return any results.

Here is the Master SPI Code I used on the Arduino Uno:

// Written by Nick Gammon
// April 2011


#include <SPI.h>
#include "pins_arduino.h"

void setup (void)
{
  Serial.begin (115200);
  Serial.println ();
  
  digitalWrite(SS, HIGH);  // ensure SS stays high for now

  // Put SCK, MOSI, SS pins into output mode
  // also put SCK, MOSI into LOW state, and SS into HIGH state.
  // Then put SPI hardware into Master mode and turn SPI on
  SPI.begin ();

  // Slow down the master a bit
  //SPI.setClockDivider(SPI_CLOCK_DIV8);
  
}  // end of setup

byte transferAndWait (const byte what)
{
  byte a = SPI.transfer (what);
  delayMicroseconds (20);
  return a;
} // end of transferAndWait

void loop (void)
{

  byte a ;
  
  // enable Slave Select
  digitalWrite(SS, LOW);    

  transferAndWait ('a');  // add command
  transferAndWait (0);
  a = transferAndWait (0);
  
  // disable Slave Select
  digitalWrite(SS, HIGH);

  Serial.println ("Frequency result:");
  Serial.println (a, DEC);
  
  delay (2000);  // 1 second delay 
}  // end of loop

And here is the SPI Slave Code which I used on the ATtiny85:

#include "pins_arduino.h"
#include <FreqPeriodCounter.h>

// what to do with incoming data
byte command = 0;

// Incoming data goes here
byte spiData;

int frq;

const byte counterPin = 3; 
const byte counterInterrupt = 1; // = pin 3
FreqPeriodCounter counter(counterPin, micros, 0);

void setup (void)
{
  attachInterrupt(counterInterrupt, counterISR, CHANGE);
}  // end of setup

// Tells us if we had an SPI interrupt
bool hadSpiInterrupt;

// The interrupt handler
ISR (USI_OVF_vect)
{
  byte b = USIBR;
  hadSpiInterrupt = true;
  
switch (command)
  {
  // no command? then this is the command
  case 0:
    command = b;
    USIBR = 0;
    break;
    
  // add to incoming byte, return result
  case 'a':
    USIBR = frq;  // spit out Frquency Reading
    break;

  } // end of switch
} // end of interrupt service routine ISR (USI_OVF_vect)

// Initialise as SPI slave
void spiSlaveInit()
{
  USICR = (1<<USIWM0)  // SPI mode
         |(1<<USIOIE)  // Enable interrupt
         |(1<<USICS1); // Clock is external
}

// Receive a byte from the master
byte spiSlaveTransfer()
{
  // Wait for complete byte to arrive
  while ((USISR & (1<<USIOIF))==0) {
  }
  // Clear counter overflow flag so next byte can begin arriving
  // While we're processing this one
  USISR = (1<<USIOIF);
  // Return received byte
  return USIBR;
}

void loop (void)
{
  if(counter.ready()) frq = (counter.hertz());
  // Serial.println(frq);
  // if SPI not active, clear current command
  // if (digitalRead (SS) == HIGH)
    // command = 0;
}  // end of loop

void counterISR()
{ counter.poll();
}

Did I implement the provided SPI Code correctly ? I changed USIDR to USIBR everywhere. Was that correct?
And I was wondering how to implement the SS (Slave Select) PIN. I the Original Code I posted in my first Thread the Arduinos SPI library was used for that. I guess I can`nt use that on the ATtiny85.

Thanks and best regards,
Jan

jabami:
Did I implement the provided SPI Code correctly ? I changed USIDR to USIBR everywhere. Was that correct?

Like I said, it was just something I typed in. I never tested it or even compiled it.

USIBR should be the one to use for receiving data.

Ideas:
a) Maybe you need to set the clock/serial in pins as inputs.

b) Try putting in some code to see if the interrupt is happening, see if the program is doing what you expect. eg. You could set an output pin high in the interrupt and watch for it on your Quad. If it never goes high you're not getting the interrupt...etc.

jabami:
And I was wondering how to implement the SS (Slave Select) PIN. I the Original Code I posted in my first Thread the Arduinos SPI library was used for that. I guess I can`nt use that on the ATtiny85.

You only need that if you have more than one slave and you need to stop them both putting data on MISO at the same time.

Hi folks,

Im still stuck somehow. Could anyone of you help eventually?

Here is the code I was testing lately:

#include "pins_arduino.h"

// what to do with incoming data
byte command = 0;

// Tells us if we had an SPI interrupt
bool hadSpiInterrupt;

// Pin 3 has an LED connected on the ATtiny85.
// give it a name:
int led = 3;

void setup (void)
{
  // have to send on master in, *slave out*
   USICR = (1 << USIWM0) | (1 << USICS1) | (1 << USIOIE);
   // turn on SPI in slave mode
   // turn on interrupts
   // clock is external
   
   // initialize the digital pin as an output.
   pinMode(led, OUTPUT);   
   
}  // end of setup

// SPI interrupt routine
ISR(USI_OVF_vect)
{
  byte c = USIDR;
 
  hadSpiInterrupt = true; // We had an interrupt
  
  switch (command)
  {
  // no command? then this is the command
  case 0:
    command = c;
    USIDR = 0;
    break;
    
  // add to incoming byte, return result
  case 'a':
    USIDR = 10;  // spit out Frquency Reading
    break;

  } // end of switch
USISR = 1 << USIOIF;
}  // end of interrupt service routine (ISR) SPI_STC_vect

void loop (void)
{
  if (hadSpiInterrupt == true) {
    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(1000);               // wait for a second
  }
  else {
    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
    hadSpiInterrupt = false;
  // wait for a second
  }
}  // end of loop

I found another thread dealing with the same problem here:

http://arduino.cc/forum/index.php/topic,71975.0.html

What I'd like to know is how to set the clock/serial pins as output.

Any help would be greatly appreciated. Thanks in advance,

Jan

jabami:
What I'd like to know is how to set the clock/serial pins as output.

Any help would be greatly appreciated. Thanks in advance,

Jan

pinMode(...)

Hi there,

I now tried with the PIN 1 (MISO) and PIN 2 (SCK) set as output Pins on the ATtiny. I tried to mimic the code of this source:
BASCOM-AVR (see the "Example (SPI Slave with USI):" section).
Furthermore I now use the "spiSlaveTransfer()" function within the interrupt handler - I guess that should be correct. The "spiReceive()" function is unused an I guess I can delete this part of the code. However, I´m walking in the dark not completely knowing what I´m doing.
The SPI code for the master is still the same as in my first post in this topic thread. Here is my current, non-working SPI-Slave code:

#include "pins_arduino.h"

// Incoming data goes here
byte spiData;

//Initialize Output Pins
int MISO = 1;
int SCK = 2;


// Pin 3 has an LED connected.
// give it a name:
int led = 3;

// Tells us if we had an SPI interrupt
bool hadSpiInterrupt;

void setup()
{
  pinMode(MISO, OUTPUT);// initialize the MISO Port (PIN 1) as an output.
  pinMode(SCK, OUTPUT);// initialize the SCK Port (PIN 2) as an output.
  pinMode(led, OUTPUT); // initialize the digital pin as an output. 
  
  USICR = (1<<USIWM0)  // SPI mode; Uses DO, DI, and USCK pins.
          |(1<<USIOIE)  // Enable interrupt
          |(1<<USICS1); // Clock is external
}

// The interrupt handler
ISR(USI_OVF_vect)
{
  spiData = spiSlaveTransfer();
  hadSpiInterrupt = true;
  
  if(spiData == 'a')
  {
    USIDR = 5; //Generic reply of 5
  }
  else
  {  
    USIDR = 7; //Generic reply of 7
  }
}

// Get incoming SPI data, return -1 if none was received
int spiReceive()
{
  int result = -1;
  if (hadSpiInterrupt) {
    result = spiData;
    hadSpiInterrupt = false;
  }
  return result;
}

// Receive a byte from the master
byte spiSlaveTransfer()
{
  // Wait for complete byte to arrive
  while ((USISR & (1<<USIOIF))==0) {
  }
  // Clear counter overflow flag so next byte can begin arriving
  // While we're processing this one
  USISR = (1<<USIOIF);
  // Return received byte
  return USIDR;
}

void loop() 
{
  if (hadSpiInterrupt == true){
    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(1000);               // wait for a second
    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
    delay(1000);
  }  
}

Best regards,
Jan

jabami:
I now tried with the PIN 1 (MISO) and PIN 2 (SCK) set as output Pins on the ATtiny.

Shouldn't they be inputs...?

fungus:

jabami:
I now tried with the PIN 1 (MISO) and PIN 2 (SCK) set as output Pins on the ATtiny.

Shouldn't they be inputs...?

For a spi slave, SCK, MOSI and (N)SS are inputs, and only MISO (=Master Input/Slave Output) is an output.

If the size of the chip isn't an issue, I'd consider using an atmega8 instead of an attiny85. They are actually cheaper than the attiny85 in small qty (I'm buying them at about $1 ea at the moment), and are a more capable device. The atmega8 is pin compatible with the atmega168/328 chips. You can set the fuses to run off the 8MHz internal oscillator, same as the attiny85, simplifying the parts count on the circuit, if that matters.

The atmega8L variant can be powered at Vcc 3V3, which is also useful in some applications.

Just a thought. Pricing of chips isn't entirely rational -- at any given time, a more powerful part may actually cost than a less capable alternative. It's a good idea to keep an eye on the street prices.

pico:

fungus:

jabami:
I now tried with the PIN 1 (MISO) and PIN 2 (SCK) set as output Pins on the ATtiny.

Shouldn't they be inputs...?

For a spi slave, SCK, MOSI and (N)SS are inputs, and only MISO (=Master Input/Slave Output) is an output.

Yes, that's what I meant... I got MOSI and MISO mixed up. :slight_smile:

pico:
If the size of the chip isn't an issue, I'd consider using an atmega8 instead of an attiny85.

28 pins is a big difference in size...

The Tiny85 will work perfectly, it's just a case of getting the details right. I never used one as a slave before.

Hey guys,

thanks for the input. Actually, I'd thought of using a bigger microcontroller. But size is an issue.
However, looking at the SPI Slave code on this page:
http://avrhelp.mcselec.com/index.html?using_usi_universal_serial_int.htm
I thought setting D0 and USCK as outputs should be correct. At least they did it that way.
Did one of you take a look at the code I posted in my previous post? Is that correct or did I made some obvious mistakes?

Thanks,
Jan

Hi there,

a small success. I got some code working. But I obviously did a wiring mistake as well. I used different boards for flashing (an UNO) and for communication testing (a NANO). When I was switching between the boards i used to keep the SS wire. This is connected to PIN 10 on the UNO when flashing the ATtiny but has no use when testing communication. And as it turned out by chance, it is hindering communication. The code shown below seems to work one ways (from the slave to the Master) at least. But for some reason the other way around would not work.
I changed the inetrupt handler to use a case structure, furthermore you will notice that i changed the pins output/input settings to the post above. Actually that made a lot of sense since Im waiting for the Interupt on the SCK PIN 2. If that would be an output, I could never get a signal, right? I have no clou how the SPI Slave code of these BASCOM guys work - they seem to configure the Attinys85 PIN 2 as output. Anyways, here is the code so far. Any Input is highly appreciated.

#include "pins_arduino.h"

// Incoming data goes here
byte spiData = 0;

//Initialize Output Pins
int MOSI = PB0;
int MISO = PB1;
int SCK = PB2;

// Pin 3 has an LED connected.
// give it a name:
int led = PB3;

// Tells us if we had an SPI interrupt
bool hadSpiInterrupt;

void setup()
{
  pinMode(MOSI, INPUT);// initialize the MOSI Port (PIN 0) as an input. DI ---> MOSI of ATmega328 (Pin 11)
  pinMode(MISO, OUTPUT);// initialize the MISO Port (PIN 1) as an output. DO ---> MISO of ATmega328 (Pin 12)
  pinMode(SCK, INPUT);// initialize the SCK Port (PIN 2) as an input. USCK ---> SCK of ATmega328 (Pin 13)
  pinMode(led, OUTPUT); // initialize the digital pin as an output. 
  
  USICR = (1<<USIWM0)  // SPI mode; Uses DO, DI, and USCK pins.
          |(1<<USIOIE)  // Enable interrupt
          |(1<<USICS1); // Clock is external
}

// The interrupt handler
ISR(USI_OVF_vect)
{
  spiData = spiSlaveTransfer();
  hadSpiInterrupt = true;
  byte c = USIDR;
  
  switch (spiData)
  {
  // no command? then this is the command
  case 0:
    spiData = c;
    USIDR = 42; //Generic reply of 42
    break;
    
  // incoming byte, return result
  case 'a':
    USIDR = 5; //Generic reply of 5
    break;
  } // end of switch
}

// Receive a byte from the master
byte spiSlaveTransfer()
{
  // Wait for complete byte to arrive
  while ((USISR & (1<<USIOIF))==0) {
  }
  // Clear counter overflow flag so next byte can begin arriving
  // While we're processing this one
  USISR = (1<<USIOIF);
  // Return received byte
  return USIDR;
}

void loop() 
{
  if (hadSpiInterrupt == true){
    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(1000);    // wait for a second
    hadSpiInterrupt = false;
  }
  else {
    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
    spiData = 0;
    delay(1000);
  }  
}

Best regards,
Jan

Is your LED lighting up now?

Hi Fungus,

yes the LED blinks. thus Im having a connection with the master. Actually i get a answer which is 7. But as the master is sending an 'a' as command it should receive a 5. However I feel that Im somewhat close. Do you have suggestions for more tests?

Greetings,
Jan

jabami:
Hi Fungus,

yes the LED blinks. thus Im having a connection with the master. Actually i get a answer which is 7. But as the master is sending an 'a' as command it should receive a 5. However I feel that Im somewhat close. Do you have suggestions for more tests?

Writing a value to USDR won't send it to the master, you have to do another exchange.

(All SPI 'sends' are really an exchange of bytes between master/slave.

So... get the master to send another byte call spiSlaveTransfer(); again at the end of the interrupt. That's where the '5' will be sent to the master.

Just for the record: I was able to use this code with the AtTiny44 8Mhz (changing pin setup).
Had also to use SPI.setClockDivider(SPI_CLOCK_DIV8) as I was getting some garbage data.
Thanks guys!

Quite an old topic, but I think this can be a solution for those who seek it, so I'm writing it (I also needed it now :slight_smile: ):

I think your interrupt handler is broken. On SPI, the data is transferred half-duplex; which means "as you receive data, you send data". So in order to receive and send data back, you actually transfer 4 bytes, but two of them is garbage (or, simply not cared).

So, when the master sends you a command, it already receives whatever is in your USIDR register (which is most probably 0) - as in SPI, they "exchange" bytes (but master probably knows this data is garbage -- SPI library in Arduino knows it). Now, you must prepare your data at that instant and put it to USIDR, so that the master can receive it, when it sends garbage data (or any data which you won't care) and clock pulses. Note that, this "data preparation" can take some time, so master might need a delay there (between sending the command and receiving the answer). Basically,

ISR (USI_OVF_vect)
{
  byte b = USIBR;
  hadSpiInterrupt = true;
  
 if(command == 0)
    command = b;

  switch(command)
{   
  // add to incoming byte, return result
  case 'a':
    USIDR = frq;  // spit out Frquency Reading
    break;
   
   default: // this block will only work, when the master sent 'garbage' to retrieve answer
     USIDR = 0; // this is not important for one byte protocol - since this data is what is being sent at 'next clock pulse' -- which is the next command for one-byte-protocol
     command = 0; // we reset the command, so we can process the next one
     break;
  } // end of switch

} // end of interrupt service routine ISR (USI_OVF_vect)

Note that, whence the Master sent you command, you made some switch-case comparisons etc. before you put something back to USIDR (note that, not to the USIBR; because that register is just a 'buffer' (copy) register, you need to put data to USIDR, I think); so master must know 'how long to wait there'. Since clock is pulsed by the master; it's not a problem if it waits 'too long' as long as not too little. Say, it must wait 1 microseconds. After this, the master will start clock-pulsing again which will yield him whatever you put to the USIDR register - which is the response of the command. Note that we also reset command register to 0. This is true if all your commands return only one bytes. If they return more than one, you need a logic to determine this here (maybe check previous command and count the bytes sent to decide when it's over, etc. etc. etc.)

I'm sorry, I cannot test this right now but I think this is what was missing in your code. Can you try it? I'll also try it myself but I dunno when :slight_smile:

Tzimitsce:
master must know 'how long to wait there'. Since clock is pulsed by the master; it's not a problem if it waits 'too long' as long as not too little.

I2C is preferable for this, it automatically puts the master "on hold" until the slave responds.

I was thinking of adding an SPI protocol to the cyz_rgb firmware, so I might have go with this.

However, I would really try to avoid putting delays into SPI transactions. The slave should return data it has already calculated, or the master should disconnect and then poll a status register until the slave indicates it is ready. Pausing the SPI transfer is somewhat impossible with a hardware SPI.

At a pinch, you could insert a padding byte or two into the transaction to allow the slave some time, but you always have to draw the line somewhere.