Loading...
Pages: [1] 2   Go Down
Author Topic: Faking SPI with Attiny85  (Read 828 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Sr. Member
****
Karma: 6
Posts: 414
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I'm working on a little project with an attiny85 where it will need to talk to a couple of shift registers.  With a 328 I'll usually just use SPI mode 0 and pretend that the shift register is an SPI device and it always works great. 

I know the USI on the attiny can do SPI mode 0 but before I start coding it up I thought I would check and see if there isn't already a library for that.  Anyone got one?  If not I'll definitely share what I come up with.
Logged

Global Moderator
Melbourne, Australia
Offline Offline
Shannon Member
*****
Karma: 226
Posts: 14110
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


Bit of Googling gave:

http://playground.arduino.cc/Code/USIi2c

http://quinndunki.com/blondihacks/?p=840
Logged


Offline Offline
Sr. Member
****
Karma: 6
Posts: 414
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Yeah I saw those two.  The first one is i2c which seems to be a little more straightforward on the tiny.

The second one I had a hard time following.  It seemed to deal more with making it work on that propeller board.  But no real library I could use.

I'm going to try to write something that looks like the arduino SPI library.  If I don't just say screw it and bang it out in my code.  I reckon writing it now will save me in the future.  If I get something that works I'll post it back here.
Logged

Global Moderator
Melbourne, Australia
Offline Offline
Shannon Member
*****
Karma: 226
Posts: 14110
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Yeah I saw those two.  The first one is i2c which seems to be a little more straightforward on the tiny.

Oops, sorry. I followed a Google search of SPI though. smiley

The SPI library is pretty small anyway, so adapting it for the Tiny shouldn't be too bad.
Logged


0
Offline Offline
Full Member
***
Karma: 2
Posts: 115
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Try this
http://www.forkrobotics.com/2012/05/relay-control-over-i2c/
Logged

Global Moderator
Melbourne, Australia
Offline Offline
Shannon Member
*****
Karma: 226
Posts: 14110
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

This might help with the registers:

https://github.com/kehribar/Little-Wire/blob/master/arduino/LittleWire/LittleWire.cpp
Logged


Leeds, UK
Offline Offline
Edison Member
*
Karma: 38
Posts: 1029
Once the magic blue smoke is released, it won't go back in!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I have written a drop in replacement library for SPI. I have both a USI Master version and a bitbanged software Master version. If you would like I can upload them.
Logged

~Tom~

Global Moderator
Melbourne, Australia
Offline Offline
Shannon Member
*****
Karma: 226
Posts: 14110
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I'd like one, because I've just been working on one myself. smiley
Logged


Global Moderator
Melbourne, Australia
Offline Offline
Shannon Member
*****
Karma: 226
Posts: 14110
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

This is what I just developed:

Code:
// Written by Nick Gammon
// March 2013

// ATMEL ATTINY45 / ARDUINO pin mappings
//
//                         +-\/-+
// RESET  Ain0 (D 5) PB5  1|    |8  Vcc
// CLK1   Ain3 (D 3) PB3  2|    |7  PB2 (D 2) Ain1  SCK  / USCK / SCL
// CLK0   Ain2 (D 4) PB4  3|    |6  PB1 (D 1) pwm1  MISO / DO
//                   GND  4|    |5  PB0 (D 0) pwm0  MOSI / DI / SDA
//                         +----+

namespace tinySPI
  {

  const byte DI   = 0;  // D0, pin 5  Data In
  const byte DO   = 1;  // D1, pin 6  Data Out (this is *not* MOSI)
  const byte USCK = 2;  // D2, pin 7  Universal Serial Interface clock
  const byte SS   = 3;  // D3, pin 2  Slave Select
  
  void begin ()
    {
    digitalWrite (SS, HIGH);  // ensure SS stays high until needed
    pinMode (USCK, OUTPUT);
    pinMode (DO,   OUTPUT);
    pinMode (SS,   OUTPUT);
    USICR = _BV (USIWM0);  // 3-wire mode
    }  // end of tinySPI_begin
    
  // What is happening here is that the loop executes 16 times.
  // This is because the 4-bit counter in USISR is initially zero, and then
  // toggles 16 times until it overflows, thus counting out 8 bits (16 toggles).
  // The data is valid on the clock leading edge (equivalent to CPHA == 0).
  
  byte transfer (const byte b)
    {
    USIDR = b;  // byte to output
    USISR = _BV (USIOIF);  // clear Counter Overflow Interrupt Flag, set count to zero
    do
      {
      USICR = _BV (USIWM0)   // 3-wire mode
            | _BV (USICS1) | _BV (USICLK)  // Software clock strobe
            | _BV (USITC);   // Toggle Clock Port Pin
      } while ((USISR & _BV (USIOIF)) == 0);  // until Counter Overflow Interrupt Flag set
      
    return USIDR;  // return read data
    }    // end of tinySPI_transfer

  };  // end of namespace tinySPI

What this does is provide the functionality of SPI.begin and SPI.transfer for the Attiny45/85.

Example code using the above:

Code:
void setup (void)
  {
  tinySPI::begin ();
  }  // end of setup

void loop (void)
  {
  char c;
  
  // enable Slave Select
  digitalWrite (tinySPI::SS, LOW);
  
  // send test string
  for (const char * p = "Hello, world!" ; c = *p; p++)
    tinySPI::transfer (c);

   // disable Slave Select
   digitalWrite (tinySPI::SS, HIGH);

   delay (100);
  }  // end of loop

Logic analyzer output:



As you can see, it outputs a byte in about 8.9 uS, with a SPI clock speed of around 1 MHz. This doesn't handle incoming interrupts or slave mode, however it should be a good start for talking to shift registers. smiley
« Last Edit: March 05, 2013, 08:37:32 pm by Nick Gammon » Logged


Offline Offline
Sr. Member
****
Karma: 6
Posts: 414
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks Nick.  That is exactly what I was about to do.

On a side note, is there a particular reason you did it as a namespace and not as a class?  Namespace is something I've never really understood how to use and your reasons for doing that may clue me in. 
Logged

Global Moderator
Melbourne, Australia
Offline Offline
Shannon Member
*****
Karma: 226
Posts: 14110
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

If you make a class, then you have to have an instance of the class. Whilst this is reasonable for (say) Serial where you might have multiple serial ports, for SPI on an 8-pin chip that is overkill.

The namespace is just a way of having the internal names (eg. begin, transfer, DI, DO) "protected" by being in their own namespace, and avoids clumsy naming like:

Code:
tinySPI_begin, tinySPI_transfer
Logged


Global Moderator
Melbourne, Australia
Offline Offline
Shannon Member
*****
Karma: 226
Posts: 14110
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

You can "use" the namespace and thus avoid qualifying everything in it, like this:

Code:
using namespace tinySPI;

void setup (void)
  {
  begin ();
  }  // end of setup

void loop (void)
  {
  char c;
  
  // enable Slave Select
  digitalWrite(SS, LOW);
  
  // send test string
  for (const char * p = "Hello, world!" ; c = *p; p++)
    transfer (c);

   // disable Slave Select
   digitalWrite(SS, HIGH);

   delay (100);
  }  // end of loop
Logged


Offline Offline
Sr. Member
****
Karma: 6
Posts: 414
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset



Code:

    do
      {
      USICR = _BV (USIWM0)   // 3-wire mode
            | _BV (USICS1) | _BV (USICLK)  // Software clock strobe
            | _BV (USITC);   // Toggle Clock Port Pin
      } while ((USISR & _BV (USIOIF)) == 0);  // until Counter Overflow Interrupt Flag set
     

This is where I would have messed up.  I would have fixed it when I saw it on the scope, but I would have done this wrong the first time.

From the second example on the datasheet, the one that just toggles r16 and r17, I had the impression that for every write to USICLK I would need a second write to USITC to toggle the pin back and complete one cycle.  

From the Datasheet:
Code:
SPITransfer_Fast:
     out USIDR,r16
     ldi r16,(1<<USIWM0)|(0<<USICS0)|(1<<USITC)
     ldi r17,(1<<USIWM0)|(0<<USICS0)|(1<<USITC)|(1<<USICLK)
     out USICR,r16 ; MSB
     out USICR,r17
     out USICR,r16
     out USICR,r17
     out USICR,r16
     out USICR,r17
     out USICR,r16
     out USICR,r17
     out USICR,r16
     out USICR,r17
     out USICR,r16
     out USICR,r17
     out USICR,r16
     out USICR,r17
     out USICR,r16 ; LSB
     out USICR,r17
     in r16,USIDR
ret
Logged

Global Moderator
Melbourne, Australia
Offline Offline
Shannon Member
*****
Karma: 226
Posts: 14110
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I followed their first example, and am not sure why they did that except for timing reasons.
Logged


Global Moderator
Melbourne, Australia
Offline Offline
Shannon Member
*****
Karma: 226
Posts: 14110
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Put it like this, the second writes aren't doing much except buying time, as far as I can see. Whereas the looping example buys time by the time taken to test for the end condition.
Logged


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