「えだまめ」しているラズパイ

30年ぶりに半田ごて握ってラズパイ勉強中。

ATTiny85 を利用した I2Cスレーブ 12キーボードの作成

◯やりたい事

ATTiny85をI2Cのスレーブ機器として利用し、12KEYの入力装置を作成する

◯やった事

ハードウエア作成

AT85の6ピンあるI/O端子を利用し、2本をI2C用(SDA,SCL=PB0,PB2)、1本をアナログ入力用(PB3)、3本をデジタル出力用(PB1,PB4,PB5)として、ブレッドボード上に4×3のキーマトリクスを組んでみました。

f:id:greensoybean:20220216102317j:plain

AT85の入力(PB3/A3)が4つのSWによる抵抗分圧式のアナログ入力で、出力側がSWの一端をグランドレベルにするためのデジタル出力(PB1/D1,PB4/D4,PB5/D5)となっています。普段はこの出力をHレベルに保ち、センスしたいラインだけをLレベルに落としてアナログ値を読み込むという形式です。回路図を載せたいところですが残念ながら手書き以外に回路図を書くノウハウを持ち合わせておらず…。まあそんな難しい回路ではないのでブレッドボードの配線からでも読み取れるかもしれませんので上面からのアップを載せておきます。

f:id:greensoybean:20220216102255j:plain

(回路図、頑張って書いてみました)
f:id:greensoybean:20220218114911p:plain

分圧抵抗の値は各SW間は4.7KΩ、最終段のAT85入力ピンは33KΩでブルアップしています。SW入力を割込みで検知することも考えてどのSWが押された時でもLレベルの維持を意識しました。ただそうすると各SW間のアナログ値に余裕がなくなってしまい誤入力の頻度が増えてしまったので(特に3.3V動作時)必要がない限りHレベルいっぱいのアナログ値を使うようにするか5V動作にした方がいいかもしれません。
ちなみに写真はPB5(リセット)ピンを利用できるようHFuseを書き換えたAT85を使ったものです。スケッチの開発などでISPを利用している場合はとりあえずPB1,PB4の2ラインで動作確認をし、確認後PB5ラインを追加したスケッチを書込んでヒューズを書き換えればよいかと思います。

ソフトウエア作成

上記を前提にスケッチを作成してみました。

/**
 *      CPU     : ATtiny85
 *       D0/PWM : SDA(AT85固有)
 *       D1/PWM : SW_COM
 *       D2/A1  : SCL(AT85固有)
 *       D3/A3  : SW in
 *       D4/A2  : SW_COM
 *       D5/A0  : SW_COM(Reset)
 *       
 *      Board   : ATTinyCore ATtiny25/45/85 (no bootloader)
 *
**/

// Library
/* Standerd */
#include <Wire.h>

// Unique Value
/* I2C */
#define SDA           0         // D0(PWM)
#define SCL           2         // D2
#define I2C_ADR       0x40      // I2C アドレス

/* I2C Buffer */
volatile uint8_t      TX_BUF  = 0;

/* SW */
#define SW            3         // D3(A3) アナログ入力#
uint8_t SW_COM[] =    {1,4,5};  // SW common(D1,D4,D5)
uint8_t SW_LAST =     0;

void setup() {
  // 8MHzモードに設定
  CLKPR = 0x80;
  CLKPR = 0x00;

  // ピンモード設定
  pinMode(SW, INPUT);
  for (uint8_t i = 0; i < sizeof(SW_COM); i++) {
    digitalWrite(SW_COM[i], HIGH);
    pinMode(SW_COM[i], OUTPUT);
  }

  // I2C設定
  Wire.begin(I2C_ADR);
  Wire.onRequest(requestEvent);
}

void loop() {
  // SW 押下検出 -> TX_BUF
  sw_push();
}

// I2C Control
/* I2C SWデータを送信 */
void requestEvent() {
  Wire.write(TX_BUF);
  TX_BUF = 0;
}

// SW control
/* sw decord */
uint8_t sw_dec() {
  int d = analogRead(SW);
  if (d < 64)       return 1;
  else if (d < 163) return 2;
  else if (d < 242) return 3;
  else if (d < 400) return 4;
  else return 0;
}

/* SW Scan (チャタリングキャンセル) */
uint8_t sw_scan()  {
  uint8_t sw = 0;
  for (uint8_t i = 0; i < sizeof(SW_COM); i++) {
    // sw common -> Low
    digitalWrite(SW_COM[i], LOW);
    delay(10);

    // sw 入力
    sw = sw_dec();
    delay(80);
    uint8_t c = sw_dec();
    digitalWrite(SW_COM[i], HIGH);
    delay(10);

    // 入力ありは リターン
    if (sw==c && sw!=0) {
      sw=(i*4+sw);
      break;
    }
  }
  return sw;
}

/* SW 押下検出 */
void sw_push() {
  uint8_t c = sw_scan();
  if (c==0) {
    SW_LAST=0;
    return;
  }
  else {
    if(SW_LAST==0) {
      SW_LAST = c;
      TX_BUF = c;
    }
  }
}

SWの入力がなければ 0、押下されていれば対応するSWの値(1~12)をI2C用の送信バッファに格納し、I2Cマスターからの要求があった時点で値を返します。マスターからの読み出しがない限り最後に押されたSW値を記憶しています。

PB5をリセット端子として利用する場合

SW_COM[ ] = {1,4,5}; の部分を SW_COM[ ] = {1,4}; として2ラインのスキャンでテストし、SW値に0~8の値が返ってくることを確認してからPB5を追加すればよいかと思います。ヒューズを書き換えないままPB5ラインをLレベルにするとリセットがかかるかもしれません。やってみてはいませんけど…。

〇やってみて

えだまめにコマンドを送信する端末としてなかなかいい感じです。
照明のON/OFFや

f:id:greensoybean:20220216102614j:plain

日時、温湿度などの表示用に活躍できそうです。

f:id:greensoybean:20220216102554j:plain

実験用にちょっとしたWiFiサーバーボードも作ってみてATP3012(i2c)による発声も実験してました。キーを押すと時間や温度を教えてくれます。電池動作が可能なのでラズパイとの機能上の住み分けが進みそうです。

f:id:greensoybean:20220216102638j:plain

〇応用例

これを応用した I2Cスレーブ機器としてRGB-LEDを搭載したものも活躍しています。4key、4RGB-LED、1Tone動作です。

f:id:greensoybean:20220216102230j:plain