【I2C on EV3 & arduino】センサーを増やせるように改造【リスト型構造】


EV3とarduinoをI2C通信するプログラムを作る機会があり、以下の記事を参考にした。

参考記事：

https://qiita.com/Rintaro1234/items/6ad43c2a5ed9bc3f0095


本記事ではセンサーを複数使用するための前段階として、
データをリスト型構造で管理するように改造したので、簡易メモとしてまとめておく。

# 前提知識

太字の知識は特に重要

- アルゴリズムとデータ構造(**リスト型構造**)
- **I2C通信**
- Arduino
- ROBOTC

# I2CDATA

設定したアドレスとセンサーのデータを
同時に管理しておきたいので、構造体を作る。

```c
typedef struct {
  byte addr;
  byte *data;
} I2C_DATA;
```


# リスト型構造

Vectorとかあればラクなんだろうが、
Arduinoにそのような高級ライブラリがあるかわからないので適当に自作する。

前項の構造体を拡張して色々作る。
(エラーが沢山でたので生成AIになおしてもらったのは内緒)

```c
typedef struct _TAG_DAT_LIST {
  byte addr;
  byte *data;
  struct _TAG_DAT_LIST *next;
} LIST_I2C_DATA;

LIST_I2C_DATA *lst;

// 新しい項目を作成
LIST_I2C_DATA* newItem(byte addr) {
  LIST_I2C_DATA *item = (LIST_I2C_DATA*)malloc(sizeof(LIST_I2C_DATA));
  if (item == NULL) {
    return NULL;
  }
  item->addr = addr;
  item->data = (byte*)malloc(sizeof(byte) * DATASIZE);
  if (item->data == NULL) {
    free(item);
    return NULL;
  }
  memset(item->data, 0, sizeof(byte) * DATASIZE);
  item->next = NULL;
  return item;
}

// リストを初期化
LIST_I2C_DATA* listInit(byte addr) {
  lst = newItem(addr);
  return lst;
}

// リストに項目を追加
LIST_I2C_DATA* appendList(byte addr) {
  LIST_I2C_DATA *cur = lst;
  while (cur->next != NULL) {
    cur = cur->next;
  }
  LIST_I2C_DATA *last = newItem(addr);
  cur->next = last;
  return last;
}

```

# 構造体をI2Cの送信モジュールに組み込み

初期化時に配列を登録する方式から、リスト型構造に依存する形へと変更。

```c:EV3.h
#include "Wire.h" 

// ArduinoのI2Cアドレス
#define I2C_ADDRESS 0x31
// 送るデータのサイズ
#define DATASIZE 16

uint8_t getAdres;

// 構造体の定義
typedef struct {
  byte addr;
  byte *data;
} I2C_DATA;

typedef struct _TAG_DAT_LIST {
  byte addr;
  byte *data;
  struct _TAG_DAT_LIST *next;
} LIST_I2C_DATA;

LIST_I2C_DATA *lst;

LIST_I2C_DATA* newItem(byte addr);
LIST_I2C_DATA* listInit(byte addr);
LIST_I2C_DATA* appendList(byte addr);

void initEV3Connect();
void receiveEvent(int DataNum);
void requestEvent();

// 新しい項目を作成
LIST_I2C_DATA* newItem(byte addr) {
  LIST_I2C_DATA *item = (LIST_I2C_DATA*)malloc(sizeof(LIST_I2C_DATA));
  if (item == NULL) {
    return NULL;
  }
  item->addr = addr;
  item->data = (byte*)malloc(sizeof(byte) * DATASIZE);
  if (item->data == NULL) {
    free(item);
    return NULL;
  }
  memset(item->data, 0, sizeof(byte) * DATASIZE);
  item->next = NULL;
  return item;
}

// リストを初期化
LIST_I2C_DATA* listInit(byte addr) {
  lst = newItem(addr);
  return lst;
}

// リストに項目を追加
LIST_I2C_DATA* appendList(byte addr) {
  LIST_I2C_DATA *cur = lst;
  while (cur->next != NULL) {
    cur = cur->next;
  }
  LIST_I2C_DATA *last = newItem(addr);
  cur->next = last;
  return last;
}

void initEV3Connect() {
  Wire.begin(I2C_ADDRESS);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);
}

void receiveEvent(int DataNum) {
  getAdres = Wire.read();
}

// データ送信要求時の処理
void requestEvent() { 
  LIST_I2C_DATA *cur = lst;
  while (cur != NULL) {
    if (getAdres == cur->addr) {
      Wire.write(cur->data, DATASIZE);
    }
    cur = cur->next;
  }
}
```

# センサープログラム

参考記事から変更がないので省略

# テストスケッチ

今回はアドレスを複数使うためのテストなので、
アドレスによって違う固定値を出すだけの簡易的なプログラムを作る。

注意してほしいのは、
1つ目のセンサーは`listInit(0x10)`とリスト自体を初期化する必要があるが、
2つ目以降のセンサーに行うのはリストの追加なので`appendList(0x11)`になっていることである。
以降3つ目、4つ目、…と追加するならば、同様に、`appendList(0x12)`、`appendList(0x13)`、…という形になる。

```c:I2C_test.ino
#include "sensor.h" // センサーの値を取得するコードが入ったヘッダー
#include "EV3.h" // EV3との通信を行うコードが入ったヘッダー

// センサから取得した値を保持しておく配列のポインタ
#define SENSER_NUM 2
LIST_I2C_DATA *S1;
LIST_I2C_DATA *S2;

void setup()
{
  // デバッグ用シリアル通信の初期化
  Serial.begin(9600);

  // センサーとArduinoの通信(SDA,SCL)
  initI2CPort(13, 12);

  // センサー1およびセンサー2のリストを初期化
  S1 = listInit(0x10);
  S2 = appendList(0x11);

  initEV3Connect();
}

void loop() 
{

  S1->data[0]=1;
  S1->data[2]=2;
  S1->data[4]=3;

  S2->data[0]=4;
  S2->data[2]=5;
  S2->data[4]=6;

  delay(200);
}

```

# ROBOTCのプログラム

参考記事のプログラムから変数・処理を複製して値を変えただけ。

```c:arduino_connect.c
#define Arduino_ADDRESS 0x31

// ポートに応
void checkI2C(tSensors port)
{
  while(nI2CStatus[S1]!=0)
  {}
  return;
}

task main()
{
  // Arduinoに送信するデータ
  char Wbuf[3] = {2, Arduino_ADDRESS << 1, 0x10};
  char Wbuf2[3] = {2, Arduino_ADDRESS << 1, 0x11};
  // Arduinoからの値を入れるバッファ
  char Rbuf[16];
  char Rbuf2[16];

  while(true)
  {
  // ArduinoにWbufの中身を送信
  sendI2CMsg(S1, Wbuf, 16);
  // Arduinoからの応答があるまで待つ
  checkI2C(S1);
  // Arduinoからの値をRBufに入れる
  readI2CReply(S1, Rbuf,16);
  
  sendI2CMsg(S1, Wbuf2, 16);
  checkI2C(S1);
  readI2CReply(S1, Rbuf2,16);

  unsigned short *SensorVal = (unsigned short *)&Rbuf[0];

  eraseDisplay();
  for(int i=0;i<4;i++)
  {
  	displayBigTextLine(i*2,"  %5d : %5d",i,SensorVal[i]);
  }


  SensorVal = (unsigned short *)&Rbuf2[0];

  for(int i=0;i<4;i++)
  {
  	displayBigTextLine((i+4)*2,"  %5d : %5d",i,SensorVal[i]);
  }

  wait1Msec(500);
  }
}

```

# まとめ

参考元のプログラムを改造してセンサーの値を管理する方法をリスト型に依存する形へ変更した。

これにより、センサーを追加したいときは、元のコードを書き換えずに`appendList()`を使えば簡単にできる…ハズ