ATtinyのUSI-SPI通信


ATtinyでのSPI通信を調べていたら、ATtinyで USI-SPI を行うライブラリを見つけましたので紹介します。
USI-SPI についてはこちらこちらで紹介されています が、DO DI USCK の3つのピンのUSI機能を使ってSPI通信を行う方法です。

最初にこちらから tinySPI のライブラリをダウンロードし、ライブラリーに追加します。
調べてみたら、ATtiny44/84 ATtiny45/85 しかサポートしていないようなので、
ATtiny461/861 ATtiny2313/4313でも使えるようにしてみました。

tinySPIフォルダーに有る tinySPI.h を以下のように変更します。
修正前(抜粋)
//USI ports and pins
#if defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
#define SPI_DDR_PORT DDRA
#define USCK_DD_PIN DDA4
#define DO_DD_PIN DDA5
#define DI_DD_PIN DDA6
#elif defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
#define SPI_DDR_PORT DDRB
#define USCK_DD_PIN DDB2
#define DO_DD_PIN DDB1
#define DI_DD_PIN DDB0
#endif

修正後(抜粋)
//USI ports and pins
#if defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
  #define SPI_DDR_PORT DDRA
  #define USCK_DD_PIN DDA4
  #define DO_DD_PIN DDA5
  #define DI_DD_PIN DDA6
#elif defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
  #define SPI_DDR_PORT DDRB
  #define USCK_DD_PIN DDB2
  #define DO_DD_PIN DDB1
  #define DI_DD_PIN DDB0
#elif defined(__AVR_ATtiny461__) || defined(__AVR_ATtiny861__)
  #define SPI_DDR_PORT DDRB
  #define USCK_DD_PIN DDB2
  #define DO_DD_PIN DDB1
  #define DI_DD_PIN DDB0
#elif defined(__AVR_ATtiny2313__) || defined(__AVR_ATtiny4313__)
  #define SPI_DDR_PORT DDRB
  #define USCK_DD_PIN DDB7
  #define DO_DD_PIN DDB6
  #define DI_DD_PIN DDB5

#endif

//SPI data modes
#define SPI_MODE0 0x00
#define SPI_MODE1 0x04

examplesフォルダーにATtiny84/85で74HC595を使うサンプルがあります。
そこで、サンプルを参考にして、1つのソースでATtiny44/84 ATtiny45/85 ATtiny461/861 ATtiny2313/4313で
動くスケッチを作りました。
0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80のデータを1000回SPIで74HC595に書き込みます。
74HC595の出力(QAからQH)にはそれぞれLEDを接続します。
//
// 74HC595 Sample with tinySPI Library
//
// USI-SPIは以下のピンを使う
// USCK:USIクロック入出力
// DO:USIデータ出力
// DI:USIデータ入力
//
// Connections:
//  ATtiny84 pin 5 (DIP pin 8, PA5) to 74HC595 pin 14 (data in)
//  ATtiny84 pin 6 (DIP pin 9, PA4) to 74HC595 pin 11 (shift register clock)
//  ATtiny84 pin 3 (DIP pin 6, PA7) to 74HC595 pin 12 (storage register clock)
//
//  ATtiny85 pin 1 (DIP pin 6, PB1) to 74HC595 pin 14 (data in)
//  ATtiny85 pin 2 (DIP pin 7, PB2) to 74HC595 pin 11 (shift register clock)
//  ATtiny85 pin 3 (DIP pin 2, PB3) to 74HC595 pin 12 (storage register clock)
//
//  ATtiny861 pin 8 (DIP pin 2, PB1) to 74HC595 pin 14 (data in)
//  ATtiny861 pin 7 (DIP pin 3, PB2) to 74HC595 pin 11 (shift register clock)
//  ATtiny861 pin 3 (DIP pin 9, PB6) to 74HC595 pin 12 (storage register clock)
//
//  ATtiny4313 pin 15 (DIP pin 18, PB6) to 74HC595 pin 14 (data in)
//  ATtiny4313 pin 16 (DIP pin 19, PB7) to 74HC595 pin 11 (shift register clock)
//  ATtiny4313 pin 3  (DIP pin 5,  PA0) to 74HC595 pin 12 (storage register clock)
//

#include <tinySPI.h>         //http://github.com/JChristensen/tinySPI

#define SS_Pin  3

void setup(void)
{
  SPI.begin();
  pinMode(SS_Pin, OUTPUT);
  digitalWrite(SS_Pin, HIGH);
}

void loop(void) {
  for (int b=0; b<8; b++) {
    uint8_t myByte = 1 << b;
    // enable Slave Select
    digitalWrite(SS_Pin, LOW);
       
    // write the same byte 1000 times
    for(int n=0; n<1000; n++) {
        SPI.transfer(myByte);
    }
    // disable Slave Select
    digitalWrite(SS_Pin, HIGH);
  }
}

【ATtiny84@1MHz】
以下の結線でLEDが順番に点滅します。
FemtoCowのAddOnを使ってコンパイルしています。



【ATtiny85@1MHz】
以下の結線でLEDが順番に点滅します。



【ATtiny861@1Mhz】
以下の結線でLEDが順番に点滅します。



【ATtiny4313】
以下の結線でLEDが順番に点滅します。



このライブラリの素晴らしいところは、非常に高速に動作することです。
1000回同じデータをSPI転送していますが、このループ回数を少なくすると、点滅しているのが分からないぐらいです。
ちなみに、前回紹介したbitBangedSPIのライブラリで同じ動作を行 う場合、以下のスケッチとなります。

//
// 74HC595 Sample with bitBangedSPI Library
//
// Connections:
//  ATtiny84 pin 5 (DIP pin 8, PA5) to 74HC595 pin 14 (data in)
//  ATtiny84 pin 6 (DIP pin 9, PA4) to 74HC595 pin 11 (shift register clock)
//  ATtiny84 pin 3 (DIP pin 6, PA7) to 74HC595 pin 12 (storage register clock)
//
//  ATtiny85 pin 1 (DIP pin 6, PB1) to 74HC595 pin 14 (data in)
//  ATtiny85 pin 2 (DIP pin 7, PB2) to 74HC595 pin 11 (shift register clock)
//  ATtiny85 pin 3 (DIP pin 2, PB3) to 74HC595 pin 12 (storage register clock)
//
//  ATtiny861 pin 8 (DIP pin 2, PB1) to 74HC595 pin 14 (data in)
//  ATtiny861 pin 7 (DIP pin 3, PB2) to 74HC595 pin 11 (shift register clock)
//  ATtiny861 pin 3 (DIP pin 9, PB6) to 74HC595 pin 12 (storage register clock)
//
//  ATtiny4313 pin 15 (DIP pin 18, PB6) to 74HC595 pin 14 (data in)
//  ATtiny4313 pin 16 (DIP pin 19, PB7) to 74HC595 pin 11 (shift register clock)
//  ATtiny4313 pin 3  (DIP pin 5,  PA0) to 74HC595 pin 12 (storage register clock)
//

#include <bitBangedSPI.h> // https://github.com/nickgammon/bitBangedSPI

#if defined(__AVR_ATtiny84__)
  #define DO_Pin   5    // PA5
  #define USCK_Pin 6    // PA4
  #define SS_Pin   3    // PA7
  #define DI_Pin   -1   // NC
#elif defined(__AVR_ATtiny85__)
  #define DO_Pin   1    // PB1
  #define USCK_Pin 2    // PB2
  #define SS_Pin   3    // PB3
  #define DI_Pin   -1   // NC
#elif defined(__AVR_ATtiny861__)
  #define DO_Pin   8    // PB1
  #define USCK_Pin 7    // PB2
  #define SS_Pin   3    // PB3
  #define DI_Pin   -1   // NC
#elif defined(__AVR_ATtiny4313__)
  #define DO_Pin   15   // PB6
  #define USCK_Pin 16   // PB7
  #define SS_Pin   3    // PA0
  #define DI_Pin   -1   // NC
#endif

bitBangedSPI bbSPI (DO_Pin, DI_Pin, USCK_Pin, 0);  // MOSI, MISO, SCK, Delay

void setup (void) {
  bbSPI.begin ();
  pinMode (SS_Pin, OUTPUT);
}  // end of setup

void loop (void) {
  for (int b=0; b<8; b++) {
    uint8_t myByte = 1 << b;
    // enable Slave Select
    digitalWrite(SS_Pin, LOW);

    // write the same byte 1000 times
    for(int n=0; n<1000; n++) {
      bbSPI.transfer (myByte);
    }
    // disable Slave Select
    digitalWrite(SS_Pin, HIGH);
  }
}  // end of loop

また、標準で準備されているshiftOut()を使って同じ動作を行う場合、以下のスケッチとなります。

//
// 74HC595 Sample with shiftOut
//
// Connections:
//  ATtiny84 Pin 5 (DIP Pin 8, PA5) to 74HC595 Pin 14 (data in)
//  ATtiny84 Pin 6 (DIP Pin 9, PA4) to 74HC595 Pin 11 (shift register clock)
//  ATtiny84 Pin 3 (DIP Pin 6, PA7) to 74HC595 Pin 12 (storage register clock)
//
//  ATtiny85 Pin 1 (DIP Pin 6, PB1) to 74HC595 Pin 14 (data in)
//  ATtiny85 Pin 2 (DIP Pin 7, PB2) to 74HC595 Pin 11 (shift register clock)
//  ATtiny85 Pin 3 (DIP Pin 2, PB3) to 74HC595 Pin 12 (storage register clock)
//
//  ATtiny861 Pin 8 (DIP Pin 2, PB1) to 74HC595 Pin 14 (data in)
//  ATtiny861 Pin 7 (DIP Pin 3, PB2) to 74HC595 Pin 11 (shift register clock)
//  ATtiny861 Pin 3 (DIP Pin 9, PB6) to 74HC595 Pin 12 (storage register clock)
//
//  ATtiny4313 Pin 15 (DIP Pin 18, PB6) to 74HC595 Pin 14 (data in)
//  ATtiny4313 Pin 16 (DIP Pin 19, PB7) to 74HC595 Pin 11 (shift register clock)
//  ATtiny4313 Pin 3  (DIP Pin 5,  PA0) to 74HC595 Pin 12 (storage register clock)
//

int i; //これを宣言しないとエラーになる
// 理由は http://memo.tank.jp/archives/1437

//Pin definitions
#if defined(__AVR_ATtiny84__)
  const int DO_Pin = 5;
  const int USCK_Pin = 6;
  const int SS_Pin = 3;
#elif defined(__AVR_ATtiny85__)
  const int DO_Pin = 1;
  const int USCK_Pin = 2;
  const int SS_Pin = 3;
#elif defined(__AVR_ATtiny861__)
  const int DO_Pin = 8;
  const int USCK_Pin = 7;
  const int SS_Pin = 3;
#elif defined(__AVR_ATtiny4313__)
  const int DO_Pin = 15;
  const int USCK_Pin = 16;
  const int SS_Pin = 3;
#endif

void setup(void) {
  pinMode(DO_Pin, OUTPUT);
  pinMode(USCK_Pin, OUTPUT);
  pinMode(SS_Pin, OUTPUT);
  digitalWrite(SS_Pin, HIGH);
}

void loop(void) {
  for (int b = 0; b < 8; b++) {
    uint8_t myByte = 1 << b;
    digitalWrite(SS_Pin, LOW);

    // write the same byte 1000 times
    for (int n = 0; n < 1000; n++) {
      shiftOut(DO_Pin, USCK_Pin, MSBFIRST, myByte);
    }
    digitalWrite(SS_Pin, HIGH);
  }
}

結線を変えずに、各スケッチを書き込むと、動作スピードの違いはすぐに分かります。

多少遅くてもいいから自由なピンを使ってSPIで通信したい
 入力もしたい→bitBangedSPIライブラリ
 出力だけでいい→shiftOut()
とにかく全速力でSPIで通信したい→tinySPIライブラリ
メモリを1バイトでも節約したい→tinySPIライブラリ

なお、tinySPIは以下の機能はサポートしていません。
sending least-significant bit first
SPI data modes 2 and 3
various SPI clock frequencies.

続く....