Pages: [1] 2 3   Go Down
Author Topic: Using an ATtiny85 as SPI Slave  (Read 9165 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Jr. Member
**
Karma: 1
Posts: 61
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:

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
Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 207
Posts: 12925
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi apart,

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.

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

Quote
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

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

Offline Offline
Jr. Member
**
Karma: 1
Posts: 61
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
http://www.arduino.cc/playground/Code/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
Logged

Offline Offline
Edison Member
*
Karma: 116
Posts: 2205
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 146
Posts: 5511
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

Logged

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 207
Posts: 12925
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Code:
  // 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.
Logged

Offline Offline
Jr. Member
**
Karma: 1
Posts: 61
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@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:
Code:
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
« Last Edit: October 12, 2012, 03:55:47 pm by jabami » Logged

Offline Offline
Edison Member
*
Karma: 116
Posts: 2205
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Can you?

Don't worry. I am sure fungus can.

Otherwise, I am happy to write you one.
Logged

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 146
Posts: 5511
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@fungus:

I found some example Code for the ATtiny85 to set up as an SPI Slave in the Datasheet:
Code:
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:

Code:
// 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;
}
« Last Edit: October 13, 2012, 02:58:22 pm by fungus » Logged

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 146
Posts: 5511
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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)

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

Logged

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

Offline Offline
Edison Member
*
Karma: 116
Posts: 2205
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 146
Posts: 5511
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:

Code:
...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.

Logged

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

Offline Offline
Edison Member
*
Karma: 116
Posts: 2205
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I would also return usibr, not usidr.
Logged

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 146
Posts: 5511
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I would also return usibr, not usidr.

Good idea.
Logged

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

Offline Offline
Jr. Member
**
Karma: 1
Posts: 61
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:

Code:
// 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:

Code:
#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
Logged

Pages: [1] 2 3   Go Up
Jump to: