2020年10月18日日曜日

ArduinoでI2Cのペリフェラル(スレーブ)を作る時に便利なクラスを作ってみた


背景

ArduinoでI2Cのペリフェラル(スレーブ)を作る際、毎回同じような関数をコピーしていたので、PeripheralHandlerというクラスにまとめて再利用できるようにしてみました。

同じ様にペリフェラルを作る人の役に立ったら良いと思うので、使い方を共有します。

使ったもの

  • Arduinoのプログラムをビルドできる環境
    Arduino IDEとPlatformIOで利用できることを確認しました。
  • wire_asukiaaa
    今回の便利クラスを含んでいるライブラリです。
    準備でインストール方法を、使い方で具体的な記述内容を説明します。

準備

Arduino IDEの場合

ライブラリマネージャでwire_asukiaaaをインストールしてください。


PlatformIOの場合

platformio.iniのlib_depsとしてwire_asukiaaaを追加してください。
platformio.ini
lib_deps = wire_asukiaaa

使い方

ライブラリのペリフェラル(スレーブ)に関するサンプルコードと大体同じです。
説明する処理対応するセントラルのコードを知りたい場合は、セントラル(マスター)のサンプルコードを見てください。
#include <wire_asukiaaa.h>

#define DEVICE_ADDRESS 0x61
#define BUFF_LEN 10

wire_asukiaaa::PeripheralHandler wirePeri(&Wire, BUFF_LEN);

unsigned long handledReceivedAt = 0;

void setup() {
Wire.onReceive([](int v) { wirePeri.onReceive(v); });
Wire.onRequest([]() { wirePeri.onRequest(); });
Wire.begin(DEVICE_ADDRESS);
Serial.begin(9600);
Serial.println("Start");
}

void loop() {
if (wirePeri.receivedAt != handledReceivedAt) {
handledReceivedAt = wirePeri.receivedAt;
for (int i = 0; i < wirePeri.buffLen; ++i) {
Serial.print(wirePeri.buffs[i]);
Serial.print(" ");
}
Serial.println();
Serial.println("receivedAt: " + String(wirePeri.receivedAt));
}
wirePeri.buffs[wirePeri.buffLen - 1] = millis() / 1000 % 0xff;
delay(1);
}

要所を説明します。

紹介する便利関数はwire_asukiaaaを読み込むことで呼び出せます。
#include <wire_asukiaaa.h>

やりとりするI2Cのバスとバイトの長さを渡して、PeripheralHandlerのインスタンスをwirePeri定義します。
#define BUFF_LEN 10

wire_asukiaaa::PeripheralHandler wirePeri(&Wire, BUFF_LEN);

wirePeriの関数をonReceiveとonRequestとして登録し、Wireにペリフェラルモードのアドレスを渡して動かします。
#define DEVICE_ADDRESS 0x61

void setup() {
Wire.onReceive([](int v) { wirePeri.onReceive(v); });
Wire.onRequest([]() { wirePeri.onRequest(); });
Wire.begin(DEVICE_ADDRESS);
}

wirePeriはbuffLen、buffs、receivedAtを参照できるので、redeivedAtが更新されたらbuffの内容が書き換わったと判断して処理を行います。
unsigned long handledReceivedAt = 0;

void loop() {
if (wirePeri.receivedAt != handledReceivedAt) {
handledReceivedAt = wirePeri.receivedAt;
// do something
}
delay(1);
}

I2Cのセントラルからの書き込み禁止もできるので、ペリフェラルから情報を配信したいレジスタは書き込み禁止にし、そのレジスタに値を書き込んでおくと、セントラルから要求があったらその値を返せます。
下記のコードではレジスタの最後のバイトを書き込み禁止にしています。
bool prohibitWriting(int index) {
return index == BUFF_LEN - 1;
}
wire_asukiaaa::PeripheralHandler wirePeri(&Wire, BUFF_LEN, prohibitWriting);

void loop() {
wirePeri.buffs[wirePeri.buffLen - 1] = millis() / 1000 % 0xff;
}

今回のクラスを使わなかった場合

先ほど紹介したコードと同じ動作をするコードを記述します。
onReceiveとonRequestの関すると関連する変数が増えます。
(このコードは書き込み禁止処理は記述していないので、それをしたい場合はonReceiveにif分の追加が必要になります。)
#include <Wire.h>

#define DEVICE_ADDRESS 0x61

const uint16_t buffLen = 10;
uint16_t buffIndex;
uint8_t buffs[buffLen];
unsigned long receivedAt;

void onReceive(int) {
int receivedLen = 0;
while (0 < Wire.available()) {
uint8_t v = Wire.read();
if (receivedLen == 0) {
buffIndex = v;
} else {
if (buffIndex < buffLen) {
buffs[buffIndex] = v;
}
++buffIndex;
}
++receivedLen;
}
receivedAt = millis();
}

void onRequest() {
if (buffIndex < buffLen) {
Wire.write(&buffs[buffIndex], buffLen - buffIndex);
} else {
Wire.write(0);
}
}

unsigned long handledReceivedAt = 0;

void setup() {
Wire.onReceive(onReceive);
Wire.onRequest(onRequest);
Wire.begin(DEVICE_ADDRESS);
Serial.begin(9600);
Serial.println("Start");
}

void loop() {
if (receivedAt != handledReceivedAt) {
handledReceivedAt = receivedAt;
for (uint16_t i = 0; i < buffLen; ++i) {
Serial.print(buffs[i]);
Serial.print(" ");
}
Serial.println();
Serial.println("receivedAt: " + String(receivedAt));
}
delay(1);
}

今までは上記のonReceiveとonRequestと関連する変数をプロジェクトごとにコピーしていましたが、それが面倒に感じたので今回ライブラリにまとめた次第です。

まとめ

I2Cのペリフェラルの動作に必要な関数や変数をライブラリにまとめたことで、ペリフェラルの処理を少ない記述量で書けるようになりました。

ペリフェラルを作ることがあれば、良かったら使ってみてください。
不具合や課題があれば、プルリクエストを作成したり、issueやこの記事のコメントとして提起してもらえると、対応するかもしれません。

0 件のコメント :