Using an ATtiny2313/4313 as SPI master

If you're building smaller projects, the ATtiny2313 chip is a nice one, as it still comes with a real UART - something the other tiny chips don't have.

Now what if you want to talk to SPI slaves, such as LED drivers or ordinary shift registers? Of course you can use simple bit-banging, but this won't give you the maximum possible speed.

As you may know the ATtiny chips come with an USI module, which can be made to work as an SPI (master/slave) device, or even a I²C device as well. The latter is a bit tricky though for beginners. There's an article + library on the playground as well.

Recommended include files:

  1. #include <avr/io.h>
  2. #include <inttypes.h>
  3. #include <avr/interrupt.h>
  4. #include <util/delay.h>

Setup for the DO,DI,USICLK pins:

  1. /*
  2.  * DON'T use the MOSI/MISO pins. They're for ISP programming only!
  3.  * Read the datasheet.
  4.  */
  5. DDRB |= _BV(PB4); // as output (latch)
  6. DDRB |= _BV(PB6); // as output (DO) - data out
  7. DDRB |= _BV(PB7); // as output (USISCK) - clock
  8. DDRB &= ~_BV(PB5); // as input (DI) - data in
  9. PORTB |= _BV(PB5); // pullup on (DI)

SPI send function:

  1. uint8_t spi_transfer(uint8_t data) {
  2.   USIDR = data;
  3.   USISR = _BV(USIOIF); // clear flag
  4.  
  5.   while ( (USISR & _BV(USIOIF)) == 0 ) {
  6.    USICR = (1<<USIWM0)|(1<<USICS1)|(1<<USICLK)|(1<<USITC);
  7.   }
  8.   return USIDR;
  9. }

Example - sending bytes to a LED driver / shift register:

  1. #define F_CPU 8000000UL
  2. #include <avr/io.h>
  3. #include <inttypes.h>
  4. #include <avr/interrupt.h>
  5. #include <util/delay.h>
  6.  
  7. #define __LATCH_LOW PORTB &= ~(1 << PB4)
  8. #define __LATCH_HIGH PORTB |= (1 << PB4)
  9.  
  10. void setup(void);
  11. void loop(void);
  12. uint8_t spi_transfer(uint8_t data);
  13. int main(void);
  14.  
  15. void setup(void) {
  16.   DDRB |= _BV(PB0); // set LED pin as output
  17.   PORTB |= _BV(PB0); // turn the LED on
  18.  
  19.   // USI stuff
  20.  
  21.   DDRB |= _BV(PB4); // as output (latch)
  22.   DDRB |= _BV(PB6); // as output (DO)
  23.   DDRB |= _BV(PB7); // as output (USISCK)
  24.   DDRB &= ~_BV(PB5); // as input (DI)
  25.   PORTB |= _BV(PB5); // pullup on (DI)
  26. }
  27.  
  28. void loop(void) {
  29.   __LATCH_LOW;
  30.     spi_transfer(0x01); // channel 1 active (red)
  31.   __LATCH_HIGH;
  32.   _delay_ms(500);
  33.  
  34.   PORTB ^= _BV(PB0); // toggle LED
  35.  
  36.   __LATCH_LOW;
  37.     spi_transfer(0x02); // channel 2 active (green)
  38.   __LATCH_HIGH;
  39.   _delay_ms(500);
  40.  
  41.   PORTB ^= _BV(PB0); // toggle LED
  42.  
  43.   __LATCH_LOW;
  44.     spi_transfer(0x04); // channel 3 active (blue)
  45.   __LATCH_HIGH;
  46.   _delay_ms(500);
  47.  
  48.   PORTB ^= _BV(PB0); // toggle LED
  49.  
  50.   __LATCH_LOW;
  51.     spi_transfer(0x07); // channels 1,2,3 active (white)
  52.   __LATCH_HIGH;
  53.   _delay_ms(500);
  54.  
  55.   PORTB ^= _BV(PB0); // toggle LED
  56.  
  57.   __LATCH_LOW;
  58.     spi_transfer(0x00); // all outputs off
  59.   __LATCH_HIGH;
  60.   _delay_ms(500);
  61.  
  62.   PORTB ^= _BV(PB0); // toggle LED
  63. }
  64.  
  65. int main(void) {
  66.         setup();
  67.         for(;;) {
  68.                 loop();
  69.         }
  70. };
  71.  
  72. /*
  73. Functions dealing with hardware specific jobs / settings
  74. */
  75.  
  76. uint8_t spi_transfer(uint8_t data) {
  77.   USIDR = data;
  78.   USISR = _BV(USIOIF); // clear flag
  79.  
  80.   while ( (USISR & _BV(USIOIF)) == 0 ) {
  81.    USICR = (1<<USIWM0)|(1<<USICS1)|(1<<USICLK)|(1<<USITC);
  82.   }
  83.   return USIDR;
  84. }
  85.  

In this example there's an LED + current limiting resistor connected to PB0 and GND. PB0 is physical pin #10 on the chip.

Fuse settings:

Chips fresh from the factory have the 'CLKDIV8' fuse enabled. If you plan to use the chip running at 8MHz (internal RC oscillator or external resonator), this fuse must be adapted.

I recommend this fuse calculator + reading the datasheet.

http://www.engbedded.com/fusecalc

For an external 8MHz ceramic resonator and brownout reset at 2.7V, the fuse settings can be set with:

  1. avrdude -c usbtiny -p attiny2313 -U lfuse:w:0xDC:m -U hfuse:w:0xDB:m

Make sure these settings match your project by verifying them using the fuse calculator!

Compilation & upload:

  1. avr-gcc -Os -g -fno-exceptions -ffunction-sections -fdata-sections -mmcu=attiny2313 ./code.c -o code.elf
  2. avr-objcopy -O ihex code.elf code.hex
  3. avr-objdump -D -S -s code.elf
  4. avr-size code.elf
  5. avrdude -c usbtiny -p attiny2313 -e -U flash:w:code.elf:i

Share