2020年5月10日日曜日

atmega328pbのI2Cポート2つを使ってみた


背景

atmega328pbとはArduino Unoなどで利用されるatmega328pというマイコンの後継機です。
I2Cとは通信規格の名前です。
前回は328pbに追加されたPORTEの端子を使ってみました。
今回は328pbに追加された2つ目のI2Cポートを使ってみます。

328pbでI2Cポート2つを使うプログラムを作成し、どのような使い方ができそうか試してみました。
備忘録を兼ねて試した内容を記事として共有します。

使ったもの

  • atmega328pbの信号線を引き出せる基板
  • PlatformIOをインストールしてUSBポートが使えるPC
  • ISPライター
    ProMicroにArduino as ISPを書き込んだものを利用しました
  • LCD + 3.3Vレギュレーター
    ew8pbをMasterとして動かす際の確認に利用しました
  • Masterとして動くマイコン2つ
    M5StackとM5Fireを利用しました
  • ブレッドボード、ジャンパワイヤ、ワイヤー、grove I2Cケーブルなど

書き込み環境

PlatformIOでATMega328PB向けのプロジェクトをつくり、それをArduino as ISPを書き込んだProMicroで書き込めるようにしました。

platformio.ini
[env:ATmega328PB]
platform = atmelavr
board = ATmega328PB
framework = arduino
board_build.f_cpu = 8000000L
upload_protocol = stk500v1
upload_port = /dev/ttyACM0
upload_speed = 19200
upload_flags =
-b$UPLOAD_SPEED
-P$UPLOAD_PORT
; use internal 8MHz and not divide it by 8
-Ulfuse:w:0xE2:m

Arduino as ISPを書き込んだProMicroをPCに接続し、atmega328pbのISPピンに接続して、下記のコマンドを実行するとプログラムを書けます。
pio run -t program


I2C通信に利用したピン

328pbのSDA0、SCL0はArduino Unoなどと同じA4, A5ピンです。
SDA1、SCL1は328pbで追加されたPORTEの0と1です。



2つのI2Cの呼び出し方

PlatformIOのプロジェクト設定に利用したATmega328PBという設定はMiniCoreというボード情報に含まれており、そのボード情報ではWireWire1を利用できます。
Wire1はWireとは別のヘッダファイルになっているので、それぞれのヘッダファイルを読み込むことで利用できます。

#include <Wire.h>
#include <Wire1.h>

void setup() {
Wire.begin();
Wire1.begin();
}

WireをMaster、Wire1をSlaveとして動かす

WireでLCDを動かし、Wire1をSlaveとして動かして他のMaster端末から情報を受け取り、受け取った値をLCDに表示してみました。


atmega328pbでWireで0.5秒間隔でLCDを更新しつつ、Wire2でSlaveとしてを値を受信するプログラムです。

#include <Arduino.h>
#include <Wire.h>
#include <Wire1.h>
#include <ST7032_asukiaaa.h>

ST7032_asukiaaa lcd;

uint8_t received;

void onReceive(int howMany) {
while(Wire1.available()) {
received = Wire1.read();
}
}

void onRequest() {
Wire1.write(received);
}

void setup() {
Wire.begin();
Wire1.begin(0x20);

lcd.setWire(&Wire);
lcd.begin(2, 8);
lcd.setContrast(30);

Wire1.onReceive(onReceive);
Wire1.onRequest(onRequest);
}

void loop() {
lcd.setCursor(0, 0);
String str = String(received);
while(str.length() < 4) {
str = ' ' + str;
}
lcd.print("val:" + str);
delay(500);
}

LCDを制御するためにST7032_asukiaaaというライブラリを利用しました。
platformio.iniに下記の記述を追加することで、利用できる状態になります。

lib_deps = ST7032_asukiaaa

M5Stackに書き込むMasterとして5と10を2秒間隔で交互に送信するプログラムです。

#include <Arduino.h>
#include <Wire.h>

void setup() {
Wire.begin();
}

void write(uint8_t v) {
Wire.beginTransmission(0x20);
Wire.write(v);
Wire.endTransmission();
}

void loop() {
delay(2000);
write(5);
delay(2000);
write(10);
}

Masterとして動くM5Stackからの情報を328pbで受信し、328pbが動かすLCDに情報を表示できました。


WireもWire1もSlaveとして動かす

M5StackとM5FireをMasterとして書き込みと読み込みを行い、片方が書き込んだ値を328pbを通して読み込むプログラムを動かしてみました。


328pbのプログラムです。
WireとWire1をSlaveとして動かし、Wireで受け取った値をWire1の送信に、Wire1で受け取った値をWireに送信に利用しています。

#include <Arduino.h>
#include <Wire.h>
#include <Wire1.h>

uint8_t received0, received1;

void onReceive0(int howMany) {
while(Wire.available()) {
received0 = Wire.read();
}
}

void onRequest0() {
Wire.write(received1);
}

void onReceive1(int howMany) {
while(Wire1.available()) {
received1 = Wire1.read();
}
}

void onRequest1() {
Wire1.write(received0);
}

void setup() {
Wire.begin(0x20);
Wire.onReceive(onReceive0);
Wire.onRequest(onRequest0);
Wire1.begin(0x20);
Wire1.onReceive(onReceive1);
Wire1.onRequest(onRequest1);
}

void loop() {
delay(1000);
}

Masterとして動かすM5StackとM5Fireのプログラムです。
analogピン0番の値をrandomSeedで初期化したrandomの値を書き込む値として利用します。
randomを利用しているので、M5StackにもM5Fireにも同じプログラムを書き込んでも異なる値を送信してくれます。

#include <Arduino.h>
#include <Wire.h>
#include <M5Stack.h>
#include <utils_asukiaaa/string.h>

#define DEVICE_ADDRESS 0x20

String createLineStr(String label, uint8_t value) {
return utils_asukiaaa::string::padEnd(label + ':', 6, ' ') + utils_asukiaaa::string::padStart(String(value), 4, ' ');
}

void updateLcd(uint8_t w, uint8_t r) {
M5.Lcd.setCursor(0, 0);
M5.Lcd.println(createLineStr("wrote", w));
M5.Lcd.println(createLineStr("read", r));
}

void setup() {
M5.begin();
M5.Lcd.begin();
M5.Lcd.setTextSize(4);
M5.Lcd.print("start");
randomSeed(analogRead(0));
Wire.begin();
}

void write(uint8_t v) {
Wire.beginTransmission(DEVICE_ADDRESS);
Wire.write(v);
Wire.endTransmission();
}

void read(uint8_t* v) {
Wire.requestFrom(DEVICE_ADDRESS, 1);

if (Wire.available() > 0) {
*v = Wire.read();
}
}

void loop() {
static unsigned long wroteAt = 0;
static uint8_t wroteV, readV;
static uint8_t showedWroteV, shoedReadV;
if (wroteAt == 0 || millis() - wroteAt > 2000UL) {
wroteAt = millis();
wroteV = random(0xff);
write(wroteV);
}
read(&readV);
if (wroteV != showedWroteV || readV != shoedReadV) {
showedWroteV = wroteV;
shoedReadV = readV;
updateLcd(wroteV, readV);
}
delay(100);
}

ライブラリとしてM5Stackとutils_asukiaaaを利用したので、下記の記述をM5Stackにプログラムを書き込むPlatformIOのplatformio.iniに追加してください。

lib_deps =
M5Stack
utils_asukiaaa

期待通りに片方が書き込んだ値を328pbを通してもう片方のMasterで受け取れました。


WireもWire1もMasterとして動かす(Wire1は難あり)

Wire1をSlaveとして動かした時にWireはLCDの表示に利用できました。
利用したLCDのライブラリはI2Cポートを切り替えられるように実装しているのですが、Wier1はTwoWire1というクラスで定義されているため、ESP32のようにWireを切り替えようとしても、型エラーが発生してビルドできません。

ビルドできない記述
#include <Arduino.h>
#include <Wire.h>
#include <Wire1.h>
#include <ST7032_asukiaaa.h>

ST7032_asukiaaa lcd0, lcd1;

void setup() {
Wire.begin();
lcd0.setWire(&Wire);
Wire1.begin();
lcd1.setWire(&Wire1); // ここで型の不一致が発生
}

Wire1を利用するのは使いたいライブラリでTwoWire1も使えるようにする変更が必要になるため、Wireのようにライブラリを利用して外部装置を利用するのは、記事を書いている時点では難しそうでした。
(今後Wire1もTwoWireとして定義する変更がMiniCoreにあれば、Wire1もI2Cポート切り替え機能の付いているライブラリで利用できるようになるかもしれません。)

まとめ

PlatformIOでMiniCoreのATMega328PBを利用することで、「片方Master片方Slave」や「両方Slave」として動かせました。
両方Masterは動きはしますが、Wire1の型が特殊なため、それをライブラリを通して利用するのは難しそうでした。

自分がやりたかった片方Master片方Slaveができたので満足です。

0 件のコメント :