今回はM5ATOM Liteに使い慣れたI2C接続のLCDディスプレイAQM1602XAを接続しようとして足をすくわれました。まさか。結論から言えばI2Cバスに接続してあった他のデバイスのプルアップ抵抗が強力すぎ。無駄な努力でMicroPythonでACK無視するI2Cもどきを作成、表示OK。端子変えれば済む話だ。
※「トホホな疑問」投稿順Indexはこちら
秋月電子通商殿の以下のI2CキャラクタLCDモジュールには、相当前からお世話になっております。
ぶっちゃけ、ディスプレイなど無いマイコンに(多くは問題発生したときに)ちょっと表示が欲しいなどというときに取り付けて参りました。「歴戦の(使い回しとも言う)」デバイスであります。今回は、M5ATOM Lite に取り付けたい要求あり、取り付けて「ハマって」しまいました。使用しているソフトウエアは、MicroPython (ESP32用のgeneric port版)です。
動いて当然、な筈、だった
M5ATOM LiteにAQM1602を取り付けたことは無かったのですが、同じくMicroPythonをのせたRaspberry Pi Picoには取り付けたことがあったのです。
MicroPython的午睡(22) ラズパイPico、AQM1602 LCDパネル接続
勿論、表示OK、動いておりました。このときMicroPythonスクリプトで制御できていたので、これをコピペして、使用するI2Cインタフェースの番号(と端子番号)を変更すれば動作するであろうと考えました(通常ならこれで問題なく動いた筈。)
M5ATOMLiteからはI2Cバスを既に引き出しており、Uno様用に「5V化されてしまって」いるBMP280モジュールに接続していました。M5ATOMLiteの3.3VのI2Cバスを5V化するために以下のモジュール(やはり秋月製)を噛ませてありました
AQM1602は5Vでも動作します。また、先にとりつけてあるBMP280とI2Cアドレスも衝突しないです。よって、バスにつなげば(当然5V電源で)動くでしょう、と。しかし動きませぬ。
見れば分かる、波形を
動く筈のスクリプトを動作させるとエラーが返ってきます。こんな感じ。
ここでの OSError: [Errno 19] ENODEV の意味が良くわらず大分無駄な時間を費やしました。トホホ。結論からいうと
I2Cバスでの通信時に相手デバイスからACKが返ってこない場合にも発生
するようです。波形見ればしみじみ分かります。まずは、正常に動作している5V化されたBMP280モジュールの波形。黄色C1がSCL、青色C2がSDAです。注目は黄色のパルスの9発目、ちょっと隙間があいた直後のところ。SCLの立ち上がり辺でロウに落ちているのが分かるかと思います。
一方、エラーとなるAQM相手の通信の波形が以下です。赤で囲ったあたりに注目。
ここは、AQM1602がACKを返すべく、ロウにバスを引いているところ、しかしながら、バスに接続しているプルアップが強すぎて中間電位になっているようでした。AE-PCA9306のモジュール上のプルアップ抵抗 1kΩ ありました。電位からするとAQM1602のオープンドレインとほぼほぼ同程度の力で引っ張り合いになっているように想像されます。この電位ではACKが返ったようには見えないと。
ACKが返らないとMicroPythonのI2C関係の関数は以降の転送をキャンセルしてOSErrorを返すみたいです。これはハードウエアI2Cだけでなく、ソフトウエアI2Cというインタフェースでも同様な動作みたいです。
まあI2Cバスの速度によるんだと思います。100kHzでの動作なら、4.7kΩとか10kΩとかでプルアップしても十分じゃないかと思うので、1kΩは強すぎる気もしないでもないです。
とはいえこのACKを無視したらAQM1602動くのか。もしかして壊れてたりしない?
無理矢理の接続スクリプト
ACKを無視して無理やりAQM1602を動作させるスクリプトを書いてみました。本来、AQM1602がACKを返してくるタイミングではSDAピンを入力に切り替えて信号を読まねばなりません。しかし、AQM1602はオープンドレインで電流引いている筈なので、SDAを出力のままこちらからもロウだしてやればいいじゃん、という乱暴な割り切りです。作成した「フルソフトのOUTPUTオンリ」なMicroPythonスクリプトは以下です。
import time from machine import Pin scl=Pin(21, Pin.OUT) sda=Pin(25, Pin.OUT) def startCond(): scl.value(1) sda.value(1) time.sleep_us(10) sda.value(0) time.sleep_us(10) scl.value(0) def stopCond(): scl.value(0) sda.value(0) time.sleep_us(10) scl.value(1) sda.value(0) time.sleep_us(10) sda.value(1) def sendBit(bdat): scl.value(0) sda.value(bdat) time.sleep_us(10) scl.value(1) time.sleep_us(10) scl.value(0) def sendByte(Bdat): Bdat &= 0xFF for i in range(8): bitData = (Bdat >> (7-i)) & 0x1 sendBit(bitData) sendBit(0) #dummy ACK def softAQM1602(sel, dat): startCond() sendByte(0x7C) if sel == 0: sendByte(0x00) else: sendByte(0x40) sendByte(dat) stopCond() def writeDatAQM1602(dat): softAQM1602(1, dat) time.sleep_ms(1) def writeComAQM1602(com): softAQM1602(0, com) time.sleep_ms(1) def initAQM1602(): time.sleep_ms(100) writeComAQM1602(0x38) time.sleep_ms(20) writeComAQM1602(0x39) time.sleep_ms(20) writeComAQM1602(0x14) time.sleep_ms(20) writeComAQM1602(0x73) time.sleep_ms(20) writeComAQM1602(0x56) time.sleep_ms(20) writeComAQM1602(0x6C) time.sleep_ms(20) writeComAQM1602(0x38) time.sleep_ms(20) writeComAQM1602(0x01) time.sleep_ms(20) writeComAQM1602(0x0C) time.sleep_ms(20) def writeLineAQM1602(nL, lin): buf = bytearray(lin) if len(buf) <= 0: return False if len(buf) > 16: buf = buf[0, 16] if nL == 0: writeComAQM1602(0x80) else: writeComAQM1602(0xC0) for idx in range(0, len(buf)): writeDatAQM1602(buf[idx]) initAQM1602() while(1): writeLineAQM1602(0, "Hello, World!") writeLineAQM1602(1, "1234567890ABCDEF")
これを走らせたところが以下です。
液晶に文字が表示されているのが見えますか?部品が壊れていないことは分かったけれど、無駄な努力です。
別端子にAQM1602を接続した方が良いでしょうね。そしてちゃんとMicroPythonのI2Cインタフェースを呼び出す、と。