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.
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.
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.
// 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:
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
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.
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.
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:
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:
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
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.
That would be useful to have. It seems to me that the author of the standard library didn't consider that there would ever be a need to use a different SPI implementation - he/she seems to have gone out of their way to ensure that the global SPI object couldn't ever be augmented or replaced, and of course all libraries using SPI have a hard-coded dependency on the global SPI variable. It's a right old mess.
The lines of code for USI aren't buying time so much as they are performing a function. To do SPI with the USI module you have to generate the clock cycles manually by writing to the config register - not only that but you have to manually do every edge, so for an 8bit transfer you have to perform 16 writes to the config register.
If you just put 16 instructions in a line, you can get a clock rate of Fcpu/2, if you use a loop, that drops to Fcpu/6.
You will have to edit USI.h to provide the correct Arduino pin numbers corresponding to the USI pin locations. There are three #defines at the top of the .h file which set this.
[USI library attached]
For the software library, you can use any of the 4 SPI modes, and have a choice over data order. Due to the way it is generated, the fastest speed I could get was 1/16th of the clock frequency.
With the software library you have to specify which pins to use for SPI in the begin call:
begin(byte SCK_, byte MOSI_, byte MISO_, byte SS_)
There is also a function which allows you to set the state of the SS pin:
void writeSS(boolean state);
Delta_G:
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:
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 ; LSB
out USICR,r17
in r16,USIDR
ret
You are right, I've looked at it again. You need 16 instructions because you need 16 clock strobes (8 on and 8 off). Thus every second one writes the USICLK bit. That shifts the data register along one, which you only need to do 8 times. So you need 16 clock pulses and 8 shifts.
out USICR,r16 ; <------ clock toggle
out USICR,r17 ; <------ clock toggle and shift left a bit
Wanted to let you know I used your code and it worked perfectly just as written.
Thanks a bunch!
Here's a simple sketch using it on attiny85. All it does is fade a ShiftBright LED into random colors. I tossed it together to make a funky lamp. But it shows that the easy solution works.
// tinySPI
// 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
const byte LATCH_PIN = 4; //D4, pin 3
const byte ENABLE_PIN = 3; //D3, pin 2 ALSO Slave Select
int current_red = 0;
int current_green = 0;
int current_blue = 0;
int step_time = 20;
int command_mode = 1;
void sendPacket(int, int, int);
void setup() {
pinMode(LATCH_PIN, OUTPUT);
tinySPI::begin ();
digitalWrite(LATCH_PIN, LOW);
digitalWrite(ENABLE_PIN, HIGH); // Turn off shiftbright and clear any garbage in the registers
delay(1000);
digitalWrite(ENABLE_PIN, LOW);
sendPacket(120,100,100);
command_mode = 0;
}
void sendPacket(int red_val, int green_val, int blue_val)
{
tinySPI::transfer(command_mode << 6 | blue_val >> 4);
tinySPI::transfer(blue_val << 4 | red_val >> 6);
tinySPI::transfer(red_val << 2 | green_val >> 8);
tinySPI::transfer(green_val);
delayMicroseconds(15);
digitalWrite(LATCH_PIN, HIGH);
delayMicroseconds(15);
digitalWrite(LATCH_PIN, LOW);
}
void loop() {
int red_target = random(0, 1023);
int green_target = random(0, 1023);
int blue_target = random(0, 1023);
while ((current_red != red_target) || (current_green != green_target) || (current_blue != blue_target))
{
if (current_red != red_target)
{
current_red = current_red + ((current_red < red_target)? 1 : -1);
}
if (current_green != green_target)
{
current_green = current_green + ((current_green < green_target)? 1 : -1);
}
if (current_blue != blue_target)
{
current_blue = current_blue + ((current_blue < blue_target)? 1 : -1);
}
sendPacket(current_red, current_green, current_blue);
delay(step_time);
}
}