MSX0でI2C通信を試してみた #MSX #MSX0 #Arduino

※本記事に掲載のソースは、GitHubにも公開しております

MSX0といえばIoT BASIC(IoT関連機能の拡張命令を追加した拡張BASIC)ということで、IoTっぽいことをやってみようと思ったのですが、温湿度センサーとか持っていないので、まずは疎通確認的なのできないかなあということで。

I2C通信については、添付のディスクイメージ(SAMPLE.DSKファイル)にI2C.BASというサンプルコードが入っているので、それをベースにサンプルコードよりもより単純な通信を試してみました。

通信というからには通信相手も必要です。

I2Cはマスター・スレーブでの主従関係を持つ通信になるのですが、MSX0側をマスターとみなすと考えますと、スレーブになる通信相手が必要です。

こういう時は、何と言ってもお手軽さが群を抜いているのがArduinoですね。

文献がネットにたくさんあるので、すぐにサンプルを持ってきて試すことができます。

ということで、まずは、マスター(MSX0)側からスレーブ(Arduino)側へ一方的に値を送信するパターンです。


スレーブ(受信側)の準備

スレーブ(Arduino)側のコードについては、ネットを調べるといろいろとサンプルコードが出てきます。中の人は、こちらの記事を参考に作ってみました。というか、お手本ほとんどそのままです。

Arduino初心者編:I2C通信によるArduino間のデータ送受信 | STEMSHIP https://stemship.com/arduino-beginner-i2c/


このページを参考に作ったスレーブ側の待ち受けプログラムです。

動作としては、マスターからのデータ送信を待ち受け、データを受け取ったらシリアルコンソールに表示するというだけの単純なもの・・・などと偉そうに言っておりますが、上記の記事のおかげで短時間で完成することができました。ありがとうございます。


#include "Wire.h"

volatile byte receiveValue = 0;
volatile boolean received = false;

void setup() {
  Wire.begin(8);
  Wire.onReceive(dataReceive);

  Serial.begin(9600);
  Serial.println("I2C slave.");
}

void loop() {
  if (received) {
    Serial.println(receiveValue);
    received = false;
  }
}

void dataReceive(int number) {
  if(Wire.available()) {
    receiveValue = Wire.read();
    received = true;
  }
}


ちなみに、Wire.begin(8);で指定している引数8は、スレーブのデバイスIDの指定と思われます。特に決まりみたいなものはないと思うのですが、今回はこのスレーブのデバイスIDを8としました。

[追記] ちなみにスレーブのデバイスIDは何でも良いわけではなく、予約されている番号などもあるので、注意が必要です。

また、Wire.onReceive()で指定しているリスナー関数 dataReceiveには引数としてint型のnumberを受け取るようになっていますが、このnumberはマスターから送信されるデータのバイト数になります。なので、本来は関数内のWire.read()はnumberの数分だけ繰り返し受け取る必要がありますが、今回は1バイト送信決め打ちにしているので無視しています。


続いていよいよMSX0側です。


マスター側(MSX0側)の準備

MSX0側では、IoT BASICでI2C通信をプログラミングします。サンプルプログラムとして添付されていたI2C.BASをベースに、処理を単純化したものです。


※今よく見たら、120行目のDEFINT Dは変数自体を使っていないので不要な気がしますね。失礼しました。

130行目の
_IOTFIND("device/i2c_a",C)
で、おそらくI2Cスレーブデバイスの個数が変数Cに格納されて返されるのではないかと思います。マニュアルでは「対象のノードが持つアイテムの個数を取得します」とあるので、I2Cの場合はスレーブの数なのではないかと思われます。引数に指定している"device/i2c_a"は、デバイスのノードパスで、アクセスしたいデバイスに応じて変化します。ちなみに、"i2c_a"と「A」がついているのは、PORT Aを示しているものと思われます。



ちなみに上記のプログラムでは、ここは常に1が返ってきます。MSX0から1個のスレーブにしか繋いでいないためと思われます。Cが0でない場合はスレーブが存在するとして次へ進みます。

150行目を実行するのは、I2Cスレーブが存在することが確認できた場合なので、今度はデバイスIDを含めて取得するために、
_IOTFIND("device/i2c_a",A$(0),C)
のように配列変数を指定すると、アイテム名(I2Cの場合はスレーブのデバイスID)が配列としてA$に格納されます。変数Cにはおそらく先ほどと同じくスレーブデバイスの数が返されると思われます。

これを調べて、期待されるデバイスID(ここでは、スレーブのデバイスIDである"08"のこと)に等しいものが配列A$に含まれていれば、次の処理に進みます。
 ※ 返されるアイテム名はゼロサプレスされていないので、比較照合の際は注意が必要

[追記] 正直、I2Cハブなどを使っていなくて、1つのスレーブしか繋いでいなければ、1個めのIDをそのまま無条件に採用しても良いと思われますが、間違って違うデバイスを繋いだときのことを考慮するとチェックするに越したことはないのでしょうね

デバイスIDが確定したら、ノードパスにデバイスIDを追加します。180行目の
N$="device/i2c_a/"+A$(I)
の箇所です。


マスターからスレーブに送信したい情報がある場合は、
_IOTPUT(ノードパス,送信したい値)
と指定します。8bitの情報を送りたい場合は、CHR$(送りたい値)のように文字列として指定するようです。複数バイト一挙に送りたい場合は、CHR$(nn)を連結して複数文字の文字列とすればよいようです。

MSX0とArduinoを接続

MSX0のPORT Aは、向かって左から「SCL(クロック信号)」「SDA(データ)」「VCC」「GND」となっています。電源も含めてArduino側に供給することもできると思われるのですが、中の人はGroveコネクタ(M5StackのPORTに繋ぐ用の4ピンケーブル)を持っていないくて手持ちのジャンプワイヤーで繋いでいるのですが、これだとワイヤの幅が足りず3本までしか繋げないっぽいので、とりあえずArduino側の電源は別で取ることにします。(Groveケーブルはポチったので、届いたらまた試してみます)

それと、MSX0のマニュアルに「MSX0貸出機ではPORTAのI2CとFaccsを同時に利用することはできないため、Facesを外してください。BattryBotomを本体に装着してPORTAのI2Cを利用することは可能です。」という記載があったので、ここでも、FacesからはMSX0本体は取り外してBatteryBottomを付けています。




Arduino側なんですが、ひとつ気になるポイントとして、中の人が良く使っているArduino Unoは通常5V駆動ですが、M5Stack(のマイコンであるESP32)は3.3V駆動のようで、電圧レベルの異なる機器をそんなにひょいひょい繋いじゃっていいの?問題があるようです。

この件について考察されている下記の記事を読む限り、繋いだ途端に破損・・・ということはないようですが(中の人も最初はArduino Unoで繋いで動かしましたが無事でした)、なんとなく心配は心配なので、可能であれば3.3V駆動のArduinoに繋ぐのが無難なのかもしれません。


M5StackのI2C信号の電圧|くりばた https://note.com/kuribata/n/n0c46035cc5e0


中の人はたまたま偶然にもArduino Pro Mini互換機で3.3V駆動の物を所有していたので、以後はそちらで動かしてみることにします。

Arduino(UnoもProMiniも共通)では、I2C用のポートは、以下のようになっています。

[ピン] Arduino ←→ MSX0
[SDA] A4 ←→ PORT Aの左から2番目のピン
[SCL] A5 ←→ PORT Aの一番左のピン

GNDはArduino側に専用のポートがあるのでそこにそのままMSX0のPORT AのGNDを繋ぎます。

繋いだ様子はこんな感じです。



左下にArduino Uno互換機がいるのは、Arduino Pro Miniへの3.3V電源共有のためと、PC上でシリアルモニタの様子を確認するために、Arduino UnoのUSB←→シリアル変換機能を間借りするためで、直接MSX0と繋がっているのは、右下のArduino Pro Mini互換機です。

Arduino Pro MiniはUSB接続の手段を単独で持っていないので、こんな風に少々工夫が必要なのですが、以下のページが大変参考になりました。

Arduino UnoやNanoを使いArduino Pro Miniにスケッチを書き込む方法! | ぶらり@web走り書き

[追記] 秋月電子で販売されている AE-UM232R とかがあればそちらを使うほうが簡単便利でおすすめです。

スレーブから値を受け取れるようにする

MSX0のI2C利用例では温湿度センサーの値を受け取ったりしていますが、当然ながらマスターから一方的に値を送るだけでなく、スレーブからも値を受け取りたいことはあるでしょう。

ということでスレーブからも値を送れるようにしてみます。

I2Cではスレーブ側から何の前触れもなく値を送り付けるということはないらしく(あったとしてもマスター側で受け取れない?)、あくまでマスター側からリクエストされたら値を送り返す、という仕組みになっているようです。

スレーブ側(Arduino)では、Wire.onRequest(リスナー関数名);という関数で、マスター側から値を送るようリクエストを受けたときに実行したい関数を設定できます。

リスナー関数内では、直前にマスターから受け取った値を9から減算したものをマスターにWire.write(9-receiveValue);で送信しています。

これらを組み入れたスケッチは以下のようになります。



#include "Wire.h"

volatile byte receiveValue = 0;
volatile boolean received = false;

void setup() {
  Wire.begin(8);
  Wire.onReceive(dataReceive);
  Wire.onRequest(requestEvent);

  Serial.begin(9600);
  Serial.println("I2C slave.");
}

void loop() {
  if (received) {
    Serial.println(receiveValue);
    received = false;
  }
}

// numberは送信されてくるバイト数
void dataReceive(int number) {
  if(Wire.available()) {
    receiveValue = Wire.read();
    received = true;
  }
}

void requestEvent() {
  Wire.write(9-receiveValue);
}

次に、MSX0側ですが、_IOTPUT()を実行した直後に、_IOTGET()を使ってスレーブからの送信値を受け取ります。_IOTGET(ノードパス,受け取りたい値)という感じに指定ができます。

リストはこんな感じです。


実行結果のスクリーンショットです。

Arduino側ではマスター(MSX0)から送信されてきた値(0,1,2,3・・・9)が表示されているのに対して、MSX0側ではスレーブから送信されてきた「9-マスターからの送信値」→ 9,8,7・・・0が表示されていることが分かり、正しくスレーブからの値を受け取れているらしいことがわかります。

[追記] ちなみに、_IOTGET()で得られる値なのですが、上記リストではASC(S$)として1バイト分のデータだけを取り出していますが、どうも実際には_IOTGET()で返却される文字列は16バイト固定のようです。ASC()関数は文字列の先頭1文字目のアスキーコードを返す仕様なので、上記のコードではあたかもスレーブから1バイトだけ受け取っているように見えていますが、実際には後ろの15文字は捨てていることになります。実際、スレーブからは1バイトしか返信していないので、実際上の問題はないのですが。



なにぶんにも、中の人は電子工作、電子回路については全くの素人なので、いろいろと間違いもあるかと思います。

Arduinoは互換機含め様々入手経路があり、価格も安価なものがたくさんありますが、MSX0については、2023年9月時点で破損すると、基本的には一般販売開始を待たなければいけなくなりますので、この記事の内容を試される場合はくれぐれも自己責任でお願いいたします。

また何か間違いを発見された場合は、ブログのコメントかTwitter等でご指摘いただけましたら幸いです。




コメント