GreenPAK(SLG46826)をI2C経由で触ってみる その3 実際の回路を書き込んで実験

概要

前回は回路データの読み書きの方法を検証してみました。今回は実際の回路を書き込んで動作を確認してみたいと思います。

回路設計

今回私が設計したのが上記回路です。素晴らしい!

下にあるのが専用出力です。PIN2(IO0)、PIN7(IO5)、PIN10(IO6)、PIN15(IO9)はI2Cで直接制御が可能なPINとなります。I2Cマクロセルからは8本足が伸びており、適当なPINに接続しています。このため、I2Cからは固定4ピン+可変8ピンの12ピンの制御が可能となっています。

回路データのエクスポート

メニューより「Export」→「Export NVM」を選択します。

選択できるファイル形式は3種類あります。

txt

index		value		comment
0		0		//
1		0		//
2		0		//
3		0		//
4		0		//
5		0		//
(省略)
2044		0		//
2045		1		//
2046		0		//
2047		1		//

なんとビット単位で数値が出力されています。さすがに2048行取り込むのは面倒ですね。

hex

:1000000000000000000000000000000000000000F0
:1000100000000000000000000000000000000000E0
:1000200000000000000000000000000000000000D0
:10003000000000F76FD7F40FFCC02CFFC01FFFF0CB
:100040000F000000000000000000000000000000A1
:1000500000000000000000000000000000000000A0
:100060000080000080800000000080800000000010
:1000700000303030000000000000000000000000F0
:1000800000000000001422300C00000000000000FE
:100090000000000000000000000000000000000060
:1000A0000000002000010000000201000002000129
:1000B0000000020100000200010000020100000235
:1000C00000010000020001AA000001010000000080
:1000D0000000000000000000000000000000000020
:1000E0000000000000000000000000000000000010
:1000F000000000000000000000000000000000A55B
:00000001FF

ちょっと実際のデータより大きい気がしますが、使いやすそうなフォーマットです。

csv

00
00
00
00
00
00
00
(省略)
00
00
00
00
A5

どこがcsvなのか謎ですが、1バイト単位で256バイトのデータが素直に出力されています。とはいえ、さすがに256行あると目視で確認が面倒なのでhexフォーマットが良さそうですね。

HEXフォーマットの確認

別途解析してしまったのですが、Intel HEXフォーマットで一般的なものだったんですね。

:1000000000000000000000000000000000000000F0
:1000100000000000000000000000000000000000E0
:1000200000000000000000000000000000000000D0
:10003000000000F76FD7F40FFCC02CFFC01FFFF0CB
:100040000F000000000000000000000000000000A1
:1000500000000000000000000000000000000000A0
:100060000080000080800000000080800000000010
:1000700000303030000000000000000000000000F0
:1000800000000000001422300C00000000000000FE
:100090000000000000000000000000000000000060
:1000A0000000002000010000000201000002000129
:1000B0000000020100000200010000020100000235
:1000C00000010000020001AA000001010000000080
:1000D0000000000000000000000000000000000020
:1000E0000000000000000000000000000000000010
:1000F000000000000000000000000000000000A55B
:00000001FF

ざっくりと、色がついているところが必要なデータになります。最初にアドレス情報などが入っており、最後の2桁がチェックサムみたいですね。

データの登録方法検討

公式形式

上記のスケッチで採用されている方法です。

// Store nvmData in PROGMEM to save on RAM
const char nvmString0[]  PROGMEM = "010E000000008E3F0300000000000000";
const char nvmString1[]  PROGMEM = "000000000000000000704B1200000000";
const char nvmString2[]  PROGMEM = "0000000000000000000000000000E003";
const char nvmString3[]  PROGMEM = "00000000000000000028000000000000";
const char nvmString4[]  PROGMEM = "00000000000000000000000000000000";
const char nvmString5[]  PROGMEM = "00000000000000000000000000000000";
const char nvmString6[]  PROGMEM = "00303000303030300000E83030003030";
const char nvmString7[]  PROGMEM = "30303033000000000000880000000000";
const char nvmString8[]  PROGMEM = "00000200001422300C3C000000000000";
const char nvmString9[]  PROGMEM = "08000000000000000000100000000000";
const char nvmString10[] PROGMEM = "00000020000100045D26CC0000020001";
const char nvmString11[] PROGMEM = "00000201000002000100000201000002";
const char nvmString12[] PROGMEM = "00010000020001000000010100000000";
const char nvmString13[] PROGMEM = "00000000000000000000000000000000";
//                               ↓↓ 0 1 2 3 4 5 6 7 8 9 a b c d e f
const char nvmString14[] PROGMEM = "00000000000000000000000000000000";
//                               ↑↑ 0 1 2 3 4 5 6 7 8 9 a b c d e f
const char nvmString15[] PROGMEM = "000000000000000000000000000000A5";

んー、わかりやすいけれどここまでするんだったら16進数で入れてもいいんじゃないかな?

あおいさや師匠形式

日本GreenPAKエバンジェリストのあおいさや師匠の形式です。

const char nvmData[] PROGMEM ="\
:1000000000000000000000000000000000000000F0\
:1000100000000000000000000000000000000000E0\
:1000200000000000000000000000000000000000D0\
:10003000000000F76FD7F40FFCC02CFFC01FFFF0CB\
:100040000F000000000000000000000000000000A1\
:1000500000000000000000000000000000000000A0\
:100060000080000080800000000080800000000010\
:1000700000303030000000000000000000000000F0\
:1000800000000000001422300C00000000000000FE\
:100090000000000000000000000000000000000060\
:1000A0000000002000010000000201000002000129\
:1000B0000000020100000200010000020100000235\
:1000C00000010000020001AA000001010000000080\
:1000D0000000000000000000000000000000000020\
:1000E0000000000000000000000000000000000010\
:1000F000000000000000000000000000000000A55B\
:00000001FF\
";

オーソドックスなC言語形式ですね。最後に\を追記する必要がありますが公式に比べると作業量が減っています。

ヒアドキュメント形式

最低限の作業で入れ替えができるようにしてみたいと思います。

const char hexStr[] = R"(
:1000000000000000000000000000000000000000F0
:1000100000000000000000000000000000000000E0
:1000200000000000000000000000000000000000D0
:10003000000000F76FD7F40FFCC02CFFC01FFFF0CB
:100040000F000000000000000000000000000000A1
:1000500000000000000000000000000000000000A0
:100060000080000080800000000080800000000010
:1000700000303030000000000000000000000000F0
:1000800000000000001422300C00000000000000FE
:100090000000000000000000000000000000000060
:1000A0000000002000010000000201000002000129
:1000B0000000020100000200010000020100000235
:1000C00000010000020001AA000001010000000080
:1000D0000000000000000000000000000000000020
:1000E0000000000000000000000000000000000010
:1000F000000000000000000000000000000000A55B
:00000001FF
)";

R”はC言語のヒアドキュメント形式です。R”rawliteral(text)rawliteral”みたいに使われていることが多いですね。今回はこれを使ってみたいと思います。

データシート確認

I2C経由でのIO操作の方法を確認します。

I2C直結(4ピン)

データシートで確認したところ、C7ですね。dataが出力値で、selectは1にするとdata値を出力するようになるはずですが、ここはデザイナのアプリケーションから変更する場所ですね。注意事項として、一般的には上位ビットから説明してあるデータシートが多いですが、これは先頭からのビット順なので下位ビットから記載されています。

I2C可変接続ピン(8ピン)

自分で結線して、任意のピンに接続できる8ピンについては上記の7Aになります。ここは上位ビットから機能を割り当てているので、数字の順番が逆になっていますので注意してください。

書き込みスケッチ

#include <M5StickC.h>

extern const char hexStr[];

const int i2cAddress = 0x00;

void setup() {
  M5.begin();
  Wire.begin(32, 33);
  Wire.setClock(100000);

  uint8_t hexData[16][16];
  memset(hexData, 0, sizeof(hexData));
  Serial.printf("sizeof(hexData) = %d\n", sizeof(hexData));

  uint16_t hexStrLen = strlen(hexStr);
  Serial.printf("strlen(hexStr) = %d\n", strlen(hexStr));

  uint16_t pos = 0;
  for (int i = 0; i < 16; i++) {
    // :を探す
    while (pos < hexStrLen && hexStr[pos] != ':') {
      pos++;
    }

    // データが十分にあるか確認
    if (hexStrLen < (pos + 43)) {
      Serial.println("Error");
      while (1)
        ;
    }

    // データ領域にすすめる
    pos += 9;

    // データに変換
    for (int j = 0; j < 16; j++) {
      uint8_t byteData = 0;
      if ('A' <= hexStr[pos]) {
        byteData += (hexStr[pos] - 'A' + 10) * 16;
      } else {
        byteData += (hexStr[pos] - '0') * 16;
      }
      pos++;
      if ('A' <= hexStr[pos]) {
        byteData += hexStr[pos] - 'A' + 10;
      } else {
        byteData += hexStr[pos] - '0';
      }
      pos++;
      hexData[i][j] = byteData;
    }
  }

  // 出力してみる
  for (int i = 0; i < 16; i++) {
    for (int j = 0; j < 16; j++) {
      Serial.printf("%02X", hexData[i][j]);
    }
    Serial.println();
  }

  // Erase
  uint8_t address = (i2cAddress << 3) + 1;
  for (int i = 0; i < 16; i ++) {
    Wire.beginTransmission(address);
    Wire.write(0xE3);
    Wire.write(0x80 + i);
    Wire.endTransmission();
    Serial.println();
    delay(100);
  }

  // Write
  address = (i2cAddress << 3) + 2;
  for (int i = 0; i < 256; i += 16) {
    Wire.beginTransmission(address);
    Wire.write(i);
    for (int j = 0; j < 16; j++) {
      Wire.write(hexData[i / 16][j]);
    }
    Wire.endTransmission();
    Serial.println();
    delay(100);
  }

  // Read
  address = (i2cAddress << 3) + 2;
  for (int i = 0; i < 256; i += 16) {
    Wire.beginTransmission(address);
    Wire.write(i);
    Wire.endTransmission();
    Wire.requestFrom(address, 16);
    for (int j = 0; j < 16; j++) {
      uint8_t val = Wire.read();
      Serial.printf("%02X", val);
    }
    Serial.println();
  }

  Serial.println();
}

void loop() {
}

const char hexStr[] = R"(
:1000000000000000000000000000000000000000F0
:1000100000000000000000000000000000000000E0
:1000200000000000000000000000000000000000D0
:10003000000000F76FD7F40FFCC02CFFC01FFFF0CB
:100040000F000000000000000000000000000000A1
:1000500000000000000000000000000000000000A0
:100060000080000080800000000080800000000010
:1000700000303030000000000000000000000000F0
:1000800000000000001422300C00000000000000FE
:100090000000000000000000000000000000000060
:1000A0000000002000010000000201000002000129
:1000B0000000020100000200010000020100000235
:1000C00000010000020001AA000001010000000080
:1000D0000000000000000000000000000000000020
:1000E0000000000000000000000000000000000010
:1000F000000000000000000000000000000000A55B
:00000001FF
)";

さくっと作ってみました。最終的にはライブラリに書き換える予定です。Intel HEXフォーマットは:を探して、そこから実データまでスキップしてからパースしています。あとは削除してから書き込みと前回と同じ流れです。

ところで、ヒアドキュメントのhexStrが一番下に置いてあります。これはArduino IDEの自動整形がヒアドキュメントに対応しておらず、それ以降の自動整形ができなくなるためです。

IOを制御してみる

#include <M5StickC.h>

const int i2cAddress = 0x00;

uint8_t readC7(void) {
  // Read Reg(0xC7)
  uint8_t address = (i2cAddress << 3) + 1;
  Wire.beginTransmission(address);
  Wire.write(0xC7);
  Wire.endTransmission();
  Wire.requestFrom(address, 1);
  uint8_t val = Wire.read();
  Serial.printf("%02X\n", val);
  Serial.printf("%d : IO0 I2C output expander data\n",   (val & (1 << 0)) != 0);
  Serial.printf("%d : IO0 I2C output expander select\n", (val & (1 << 1)) != 0);
  Serial.printf("%d : IO5 I2C output expander data\n",   (val & (1 << 2)) != 0);
  Serial.printf("%d : IO5 I2C output expander select\n", (val & (1 << 3)) != 0);
  Serial.printf("%d : IO6 I2C output expander data\n",   (val & (1 << 4)) != 0);
  Serial.printf("%d : IO6 I2C output expander select\n", (val & (1 << 5)) != 0);
  Serial.printf("%d : IO9 I2C output expander data\n",   (val & (1 << 6)) != 0);
  Serial.printf("%d : IO9 I2C output expander select\n", (val & (1 << 7)) != 0);

  return val;
}

void writeC7(uint8_t val) {
  // Write Reg(0xC7)
  uint8_t address = (i2cAddress << 3) + 1;
  Wire.beginTransmission(address);
  Wire.write(0xC7);
  Wire.write(val);
  Wire.endTransmission();
}

void write7A(uint8_t val) {
  // Write Reg(0x7A)
  uint8_t address = (i2cAddress << 3) + 1;
  Wire.beginTransmission(address);
  Wire.write(0x7A);
  Wire.write(val);
  Wire.endTransmission();
}

void setup() {
  M5.begin();
  Wire.begin(32, 33);
  Wire.setClock(100000);

}

void loop() {
  uint8_t val = readC7();
  writeC7(val | (1 << 0));
  readC7();
  delay(500);

  writeC7(val | (1 << 2));
  readC7();
  delay(500);

  writeC7(val | (1 << 4));
  readC7();
  delay(500);

  writeC7(val | (1 << 6));
  readC7();
  delay(500);

  writeC7(val);
  readC7();
  delay(500);

  for(int i=0;i<8;i++){
    write7A(1<<i);
    delay(200);
  }

  write7A(0);
  delay(500);
}

とりあえずベタベタに書いてみました。各ピンに出力がでていれば成功です。手元には全ピンにLEDを接続したGreenPAKがあるので動作確認がしやすいですが、DIPタイプとかだとちょっと確認が面倒ですね。。。

まとめ

ざっくりとI2C経由で回路の書き込みと、制御ができるようになりました。次回以降にもう少し使いやすい形に取りまとめていきたいと思います。

続編

コメント

  1. あおいさや より:

    新品だと、Control codeは0001だったはずなので、
    const int i2cAddress = 0x00;
    は、0x01の方がいいんじゃないかなぁと思います。

    また、I2C通信エラーのチェックもあったほうが嬉しいです。

    ちなみに「書き込みスケッチ」を走らせてから「IOを制御してみる」を走らせても動作しませんでした。GreenPAKの電源を抜き差しして、電源を入れなおすと動作しました。

    どこかに0xC8に0x02を書き込む(reloading NVM into Data register (soft reset))と、いちいちGreenPAKの電源を入れなおさなくてもよいので、使いやすいと思います。
    (どこに入れるかは趣味ですが、自分は書き込み後にリセットを入れています)

    ご参考まで。

    • たなかまさゆき より:

      アドレスとかはそうですね
      前回からの続きで、前回アドレス0に初期化しているのでそのままです

      リセットは入れたつもりですが、入っていない、、、
      他の実験コードではいれていて、ブログ用のコードでは入れ忘れたみたいです、、、

      このへんはのちほど整理するのでいまは適当に作っています