Using an ATtiny85 as SPI Slave

Hi together,

Im trying to use an Attiny85 as an SPI Slave to do some low Frequency measurements while the Master (an Arduino Mega) is connected to LabView running the Arduino Toolkit. When I say low Frequency I mean like 1kHz maximum. The Frequency to be measured is from an Flowmeter which delivers a nice Squaretype signal.
The Master cannot be used for the measurement as there are multiple sensors hooked up which are polled by the LabView Software directly.

Now what I did:
I first flashed an SPI Slave Code to an Arduino Nano (for testing the SPI connection and the Frquency measurement) which I had lying around.This worked flawlesly.
Here is the Code:

// Written by Nick Gammon
// April 2011

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

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

int frq;

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

void setup (void)
{
  // Serial.begin(9600); 
  
  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

  // turn on interrupts
  SPCR |= _BV(SPIE);

  attachInterrupt(counterInterrupt, counterISR, CHANGE);

}  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
{
  byte c = SPDR;
 
  switch (command)
  {
  // no command? then this is the command
  case 0:
    command = c;
    SPDR = 0;
    break;
    
  // add to incoming byte, return result
  case 'a':
    SPDR = frq;  // spit out Frquency Reading
    break;

  } // end of switch

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

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();
}

Now I´m trying to port this Code to an Attiny85. I installed the ATtiny Core libraries and can flash the ATiny85. But I get the following error when trying to compile the code presented above:

SPI_Slave_v1.cpp: In function 'void setup()':
SPI_Slave_v1:20: error: 'MISO' was not declared in this scope
SPI_Slave_v1:23: error: 'SPCR' was not declared in this scope
SPI_Slave_v1:23: error: 'SPE' was not declared in this scope
SPI_Slave_v1:26: error: 'SPIE' was not declared in this scope
SPI_Slave_v1.cpp: In function 'void SPI_STC_vect()':
SPI_Slave_v1:36: error: 'SPDR' was not declared in this scope
SPI_Slave_v1.cpp: In function 'void loop()':
SPI_Slave_v1:60: error: 'SS' was not declared in this scope

Now I understand that the Attiny uses USI as a basis for the SPI interface. But where and how do I declare the Pins (MISO, SPCR etc.) I need for the SPI to work properly? The "normal" SPI.h libraries for the 328 etc. do not need to be configured.
However I found an example for an SPI Slave with USI on an Attiny85 here:
http://avrhelp.mcselec.com/index.html?using_usi_universal_serial_int.htm
So in general I'm confident that it is going to work. I just don't know how.
Any help is appreciated.

Best regards,
Jan

Hi apart,

jabami:
Now I understand that the Attiny uses USI as a basis for the SPI interface.

Correct. The ATtiny family does not have a dedicated SPI subsystem.

But where and how do I declare the Pins (MISO, SPCR etc.) I need for the SPI to work properly?

Declaring the pins will not get you to your goal. USI and SPI are very different critters. You need SPI slave code written specifically for the USI.

However I found an example for an SPI Slave with USI on an Attiny85 here:
BASCOM-AVR

That example may work (after porting it to C). But a sanity check is necessary: What else will the ATtiny processor be doing? What SPI bitrate are you hoping to use?

Hi,

thank you for the reply.It seems like the task is more complex than I initially thought. I personally wont be able to port the above linked Code to C. I wonder why nobody else has written an SPI Slave Code for the Attiny using the Arduino IDE. I found the I²C Code for the ATtiny here:
Arduino Playground - USIi2c .
However, this I²C port does not use interrupts and the main loop of the code is stuffed with I²C Communication code. As Im exploiting frequency measuremnents here (which might be scallable in the near future) I find that rather unelegant. The above given SPI Slave example does use interrupts and the main loop is used heavily for the frequency measurement.

The ATtiny85 I will be using is just supposed to constantly measure the frequency of one flowmeter. When polled for it the ATtiny85 should transfer the measured frequency via SPI to the master. I guess a low bitrate will be sufficient for my case. Thus 9600 baud should do the job.

Best regards,
Jan

Writing a software slave is always difficult. Writing a spi slave, however, is easier than writing an i2c slave.

A minimum implementation would involve an interrupt to detect changes on the SCK pin. Once in that isr, process the CS line as well as the MOSI line.

You can code it in a way that it is almost register-name compatible with the hardware spi.

dhenry:
A minimum implementation would involve an interrupt to detect changes on the SCK pin. Once in that isr, process the CS line as well as the MOSI line.

No need for that. The hardware does most of the work for you. You just select an external pin as clock input and wait for the complete byte to arrive (which you can do via polling or generate an interrupt when it's complete).

The Tiny85 datasheet has example code.

jabami:
When I say low Frequency I mean like 1kHz maximum.

That does not match the code you posted.

This (incorrectly) limits the value to 255...

  // add to incoming byte, return result
  case 'a':
    SPDR = frq;  // spit out Frquency Reading
    break;

Is the maximum expected frequency 1 KHz? If it is, two bytes must be returned and you will need to take a snapshot of frq.

@coding Badly:

This is true, but for my initial test it was sufficient. The Flowmeter I´m using delivers ~2000 pulses per liter. I'm expecting a throughflow of 500ml/min max. Thus I'm expecting an input frequency of 16,666667777 Hz to start with. So the limitation to a value of 255 is more than sufficient for now.
However, I'm going to implement an rpm meter in the same fasion. And for this rpm meter I will need the higher frequency measurement capabilities and consequently a interface with higher values (than 255). Note that the rpm meter will be on a separate ATtiny85.

@fungus:

I found some example Code for the ATtiny85 to set up as an SPI Slave in the Datasheet:

SPI Slave Operation Example
The following code demonstrates how to use the USI as an SPI slave:

init:
     ldi    r16,(1<<USIWM0)|(1<<USICS1)
     out    USICR,r16
SlaveSPITransfer:
     out    USIDR,r16
     ldi    r16,(1<<USIOIF)
     out    USISR,r16
SlaveSPITransfer_loop:
     in     r16, USISR
     sbrs   r16, USIOIF
     rjmp   SlaveSPITransfer_loop
     in     r16,USIDR
     ret

The code is size optimized using only eight instructions (plus return). The code example
assumes that the DO and USCK pins have been enabled as outputs in DDRB. The value stored
in register r16 prior to the function is called is transferred to the master device, and when the
transfer is completed the data received from the master is stored back into the register r16.
Note that the first two instructions is for initialization, only, and need only be executed once.
These instructions set three-wire mode and positive edge clock. The loop is repeated until the
USI Counter Overflow Flag is set.

But Im no good at programming and can not port this to the Arduino IDE. Can you? I guess this would be helpful for future reference as well.

Best regards,
Jan

Can you?

Don't worry. I am sure fungus can.

Otherwise, I am happy to write you one.

jabami:
@fungus:

I found some example Code for the ATtiny85 to set up as an SPI Slave in the Datasheet:

SPI Slave Operation Example

The following code demonstrates how to use the USI as an SPI slave:

init:
    ldi    r16,(1<<USIWM0)|(1<<USICS1)
    out    USICR,r16
SlaveSPITransfer:
    out    USIDR,r16
    ldi    r16,(1<<USIOIF)
    out    USISR,r16
SlaveSPITransfer_loop:
    in     r16, USISR
    sbrs   r16, USIOIF
    rjmp   SlaveSPITransfer_loop
    in     r16,USIDR
    ret




But Im no good at programming and can not port this to the Arduino IDE. Can you? I guess this would be helpful for future reference as well.

This is the literal port:

// Set USI to slave mode - do this at startup
void spiSlaveInit()
{
   USICR = (1<<USIWM0)|(1<<USICS1);
}

// Receive a byte from the master
byte spiSlaveTransfer()
{
  // Clear counter overflow flag
  USISR = (1<<USIOIF);
  // Wait for complete byte to arrive
  while ((USISR & (1<<USIOIF))==0) {
  }
  // Return received byte
  return USIDR;
}

I'm not sure I'd do it that way though, it blocks program flow and probably won't work very well. This is one place where an interrupt is called for, I'd do it something like this: (warning, untested code)

// Incoming data goes here
byte spiData;

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

// The interrupt handler
ISR(USI_OVF_vect)
{
  spiData b = USIDR;
  hadSpiInterrupt = true;
}

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

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

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

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...?