ATtinyのUSI-i2c通信(その1)


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

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

TinyWireMフォルダーに有る USI_TWI_Master.h を以下のように変更します。
修正前(抜粋)
#if defined(__AVR_ATtiny25__) | defined(__AVR_ATtiny45__) | defined(__AVR_ATtiny85__) | \
    defined(__AVR_AT90Tiny26__) | defined(__AVR_ATtiny26__)
    #define DDR_USI             DDRB
    #define PORT_USI            PORTB
    #define PIN_USI             PINB
    #define PORT_USI_SDA        PORTB0
    #define PORT_USI_SCL        PORTB2
    #define PIN_USI_SDA         PINB0
    #define PIN_USI_SCL         PINB2
#endif

#if defined(__AVR_ATtiny84__) | defined(__AVR_ATtiny44__)
    #  define DDR_USI           DDRA
    #  define PORT_USI          PORTA
    #  define PIN_USI           PINA
    #  define PORT_USI_SDA      PORTA6
    #  define PORT_USI_SCL      PORTA4
    #  define PIN_USI_SDA       PINA6
    #  define PIN_USI_SCL       PINA4
#endif

#if defined(__AVR_AT90Tiny2313__) | defined(__AVR_ATtiny2313__)
    #define DDR_USI             DDRB
    #define PORT_USI            PORTB
    #define PIN_USI             PINB
    #define PORT_USI_SDA        PORTB5
    #define PORT_USI_SCL        PORTB7
    #define PIN_USI_SDA         PINB5
    #define PIN_USI_SCL         PINB7
#endif

修正後(抜粋)
#if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || \
    defined(__AVR_AT90Tiny26__) || defined(__AVR_ATtiny26__)
    #define DDR_USI             DDRB
    #define PORT_USI            PORTB
    #define PIN_USI             PINB
    #define PORT_USI_SDA        PORTB0
    #define PORT_USI_SCL        PORTB2
    #define PIN_USI_SDA         PINB0
    #define PIN_USI_SCL         PINB2
#endif

#if defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
    #define DDR_USI           DDRA
    #define PORT_USI          PORTA
    #define PIN_USI           PINA
    #define PORT_USI_SDA      PORTA6
    #define PORT_USI_SCL      PORTA4
    #define PIN_USI_SDA       PINA6
    #define PIN_USI_SCL       PINA4
#endif

#if defined(__AVR_ATtiny461__) || defined(__AVR_ATtiny861__)
    #define DDR_USI             DDRB
    #define PORT_USI            PORTB
    #define PIN_USI             PINB
    #define PORT_USI_SDA        PORTB0
    #define PORT_USI_SCL        PORTB2
    #define PIN_USI_SDA         PINB0
    #define PIN_USI_SCL         PINB2
#endif

#if defined(__AVR_AT90Tiny2313__) || defined(__AVR_ATtiny2313__) || defined(__AVR_ATtiny4313__)
    #define DDR_USI             DDRB
    #define PORT_USI            PORTB
    #define PIN_USI             PINB
    #define PORT_USI_SDA        PORTB5
    #define PORT_USI_SCL        PORTB7
    #define PIN_USI_SDA         PINB5
    #define PIN_USI_SCL         PINB7
#endif

私の知識では、IFの演算子に 「|」(ビットOR)を使ったときに、どのように動くのか分からなかったので、
「||」(論理OR)に変更しました。

i2c通信のスレーブ側にはUNOを使い、以下のスケッチを書き込みます。
Arduinoのi2c通信についてはこ ちらに紹介されています。
大いに参考にさせていただきました。

//
// arduino I2Cスレーブ
//

#include <Wire.h>

char buf[100];
volatile boolean process_it;

// i2c受信関数
void receiveEvent(int howMany)
{
  int n = 0;
 
  // 送信された全てのデータを受信
  while(Wire.available()) {
    buf[n++] = (char)Wire.read();   
  }
  buf[n] = '\0';
  process_it = true;
}

void setup() {
  Serial.begin (9600);
  process_it = false;
  Wire.begin(8) ;                    // i2cの初期化、自アドレスを8とする
  Wire.onReceive(receiveEvent) ;     // 受信関数の登録
}

void loop() {
  if (process_it) {
    Serial.print (buf);
    process_it = false;
  }  // end of flag set
}

ATtinyはi2cのマスター側になります。
マスター側のスケッチは以下の通りです。
ライブラリのソースを確認したところ、「USI_BUF_SIZE    18」と定義されていたので、
10文字ごとに分割して送信しています。
//
// attiny I2Cマスタ
//

#include <TinyWireM.h> //https://github.com/adafruit/TinyWireM

#if defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
  #if defined(__AVR_ATtiny44__)
    #define Model "ATtiny44"
  #else
    #define Model "ATtiny84"
  #endif 
#elif defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
  #if defined(__AVR_ATtiny45__)
    #define Model "ATtiny45"
  #else
    #define Model "ATtiny85"
  #endif 
#elif defined(__AVR_ATtiny461__) || defined(__AVR_ATtiny861__)
  #if defined(__AVR_ATtiny461__)
    #define Model "ATtiny461"
  #else
    #define Model "ATtiny861"
  #endif 
#elif defined(__AVR_ATtiny4313__)
  #define Model "ATtiny4313"
#endif


// 最大10文字にデータを分割してデータの送信
void i2c_print(char *str) {
  int pnum;
  int jmax;
  pnum = (strlen(str)+9)/10;  //パケット数
  for (int i=0;i<pnum;i++) {
    jmax = i*10+10;
    if (strlen(str) < jmax) jmax = strlen(str);
    TinyWireM.beginTransmission(8) ;  // 通信の開始処理、スレーブのアドレスは8とする
    for (int j=i*10;j<jmax;j++) {  // 1パケットの最大文字数は10文字
      TinyWireM.send(str[j]) ;  // 通信データ送信
    }
    TinyWireM.send(0) ;  // 通信データ送信(終端文字)
    TinyWireM.endTransmission();
  }
}

void setup()
{
  TinyWireM.begin() ;  // i2cの初期化、マスターとする
  delay(5000) ;        // 5秒後に開始
}

// 繰り返し実行されるメインの処理関数
void loop()
{
  static int num = 0;
  char sbuf[40];

  sprintf(sbuf,"TinyWireM num=%d model=%s\n",num,Model);
  num++;
  i2c_print(sbuf);
  delay(1000);
}

【ATtiny84@1MHz】
以下の結線で正常に動作します。




【ATtiny85@1MHz】
以下の結線で正常に動作します。




【ATtiny861@1Mhz】
以下の結線で正常に動作します。





【ATtiny4313】
以下の結線で正常に動作します。





ATtinyでも SPI(Master) i2c(Master) の通信ができることが分かりました。

ちなみに、シリアルモニターのスピードを十分早くすれば、Master 側の待ち時間を0にしても、
取りこぼし無く表示されることを確認しています。

{前略}

// 繰り返し実行されるメインの処理関数
void loop()
{
  static int num = 0;
  char sbuf[40];

  sprintf(sbuf,"TinyWireM num=%d model=%s\n",num,Model);
  num++;
  i2c_print(sbuf);
//  delay(1000);
}




また、ソフトウェアシリアルと違って、マスターとスレーブはGNDを共有する必要はありません。
左側のUNOはISP専用、右側のUNOはデバッグモニター専用にすることができます。




右側のUNOはCoolTermでCOMポートをモニターしていれば、左側のUNOを使ってコードに専念できます。
左側のIDEを使ってスケッチを書き込めば、その結果はすぐに右側に反映されます。



充分デバッグに使えます。

続く...