[MSX0] MSX0からUART接続のデバイスに通信する #MSX0 1月 07, 2024 リンクを取得 Facebook Twitter Pinterest メール 他のアプリ ## DFPlayer MiniをMSX0から制御したい 最近、DFPlayer MiniというMP3再生用のモジュールがあるのを知り、年末年始でいじってました。 このモジュール、何が良いかというと、操作コマンドをUARTで行えるので、制御のハードルが比較的低いことがあります。 これを、ポケコンとかMSX0とかのデバイスから制御できないか・・・?ということで、本記事はMSX0での接続についてです。 ## 接続の仕組み 御存知の通り?MSX0は2024年1月現在、IoT BASIC/BIOSではUARTをサポートしていません。 外部との通信はWiFiかI2Cになります。 シリアルコンソールもあるといえばあるのですが、こちらはMSX0をリモートで操作するための仕組みなので、MSX0へのインプットとして使う手はあるものの、MSX0から外部へのデータ送信に使うには中の人にはちょっとハードルが高めではあります。 もちろん、WiFiという手もあるのですが、今回はとりあえずMSX0らしく?ということで、I2Cで送ったデータをUARTに流してもらうところを実装してみました。 変換エンジンには、Raspberry Pi Picoを利用しています。 仕掛けはとても単純で、 - まず、MSX0から「データ長(送信データのバイト数)」と「返却を受け取るかどうか(1=受け取る、それ以外=受け取らない)」を`CALL IOTPUT`で2バイト送信。 - その後、「データ本体」をまとめて`CALL IOTPUT`で送信。 - ラズパイpico側はそれらの指示に従い、所定のバイト数を受け取ったら、「データ本体」をUARTに送信。 - 「返却を受け取るかどうか」に1を指定したときは、続けてUARTからレスポンスを受け取り、I2C側に返却する。 - MSX0では`CALL IOTGET`でレスポンスを受信。 ざっくりいうと上記のような感じです。 下記ファイルはGitHubでも公開しています。 [https://github.com/MobileFF/msx0_sample/tree/main/I2C_to_UART](https://github.com/MobileFF/msx0_sample/tree/main/I2C_to_UART) ## MSX0側の実装 ```BASIC 1 'SAVE"I2C_UART.BAS" 1000 'Init 1010 D$="3C" 1020 _IOTFIND("device/i2c_a",C) 1030 PRINT "IOTFIND:";C 1040 IF C=0 THEN 1200:'Not Found 1050 _IOTFIND("device/i2c_a",A$(0),C) 1060 PRINT "Slave Address : "; 1070 FOR I=0 TO C-1 1080 PRINT A$(I);" "; 1090 IF A$(I)=D$ THEN 1300:'Create Device Path 1100 NEXT I 1200 'Not Found 1210 PRINT "Device not found." 1220 END 1300 'Create Device Path 1310 N$="device/i2c_a/"+A$(I) 2000 'Main 2010 PRINT 2020 PRINT "---- DFPlayer Mini ----" 3000 'INIT 3010 PRINT "---- RESET ----" 3100 RESTORE 40000:'RESET 3120 GOSUB 20000:'RESET 3125 FOR I=0 TO 1000:NEXT I 3130 GOSUB 20000:'SET VOLUME 3140 GOSUB 20000:'REPEAT TRACK 1 3150 RESTORE 40300:'QUERY STATUS 3160 GOSUB 20000:'QUERY STATUS 3170 FOR I=0 TO 100:NEXT I 3180 GOTO 3150 9999 END 20000 'Write Multi Bytes 20100 READ LN 20110 READ NR 20120 PRINT "[";LN;",";NR;"]" 20200 T1$=CHR$(LN)+CHR$(NR) 20210 _IOTPUT(N$,T1$) 20300 T2$="" 20310 PRINT "["; 20320 FOR I=1 TO LN 20330 READ V$ 20340 PRINT V$;","; 20350 T2$=T2$+CHR$(VAL("&H"+V$)) 20360 NEXT I 20370 PRINT "]" 20380 _IOTPUT(N$,T2$) 20400 IF NR=0 THEN RETURN 20500 _IOTGET(N$,RP$) 20600 PRINT ">> "; 20610 IF LEN(RP$) = 0 THEN GOTO 20500 20620 FOR I=1 TO LEN(RP$) 20630 PRINT HEX$(ASC(MID$(RP$,I,1)));","; 20640 NEXT I 20650 PRINT 20999 RETURN 40000 'RESET 40010 DATA 8,0 40020 DATA 7E,FF,06,0C,00,00,00,EF 40100 'SET VOLUME 40110 DATA 8,0 40120 DATA 7E,FF,06,06,00,00,10,EF 40200 'REPEAT TRACK 1 40210 DATA 8,0 40220 DATA 7E,FF,06,08,00,00,01,EF 40300 'QUERY STATUS 40310 DATA 8,1 40320 DATA 7E,FF,06,42,00,00,00,EF ``` 上記の仕掛けに基づき、データ送信サブルーチン(20000〜20999行)では、以下のように`DATA`文から2つのデータを読み込み、`_IOTPUT`で送信しています。 ここでは、`LN=8`および`NR=0`なので、\[8,0\]という2バイトを送信しています。 ```BASIC 20100 READ LN 20110 READ NR 20200 T1$=CHR$(LN)+CHR$(NR) 20210 _IOTPUT(N$,T1$) ``` その後、さらに`DATA`文から以下のように指定バイト数分(変数`LN`で決定。ここでは8バイト)データを読み込んで変数`T2$`に追加していき、ループ終了後に`_IOTPUT`で`T2$`の内容(8バイト)を送信しています。 ```BASIC 20300 T2$="" 20320 FOR I=1 TO LN 20330 READ V$ 20350 T2$=T2$+CHR$(VAL("&H"+V$)) 20360 NEXT I 20380 _IOTPUT(N$,T2$) ``` ただ一方的にデータを送り続けるだけで良ければ、先頭2バイト(\[8,0\])は不要で、データそのものをひたすら送るでも大丈夫なのですが、応答を取得できるようにするため、いったん送信がどこで終わるかを知る必要があるため、最初にバイト数を指定しています。 DATA文に記載の8バイトのデータはDFPLayer Miniを想定したものですが、制御コマンドの仕様等については、[こちら](https://github.com/DFRobot/DFRobotDFPlayerMini/tree/master/doc)にあるデータシートPDFを参考にしています。 ## Raspberry Pi pico側の実装 Raspberry Pi picoの環境はMicroPythonを想定しています。ファイルは`main.py`と`i2c_slave.py`になります。 I2Cは0番、UARTは1番を使いますので、 - 0番ピン=I2C(0) SDA - 1番ピン=I2C(0) SCL - 6番ピン=UART(1) TX - 7番ピン=UART(1) RX につなぎます。 ラズパイpicoとDFPlayerMiniの間は、 |ラズパイpico| ←→ | DFPlayer Mini| | ---------- | ---- | ----------- | |6番ピン(TX)| ←→ | 2番ピン(RX)| |7番ピン(RX)| ←→ | 3番ピン(TX)| となります。 \[main.py\] ```Python from i2c_slave import I2C_slave import time from machine import UART,Pin # I2C設定 (I2C識別ID 0or1, SDA, SCL) i2c = I2C_slave(0,sda=0,scl=1,slaveAddress=0x3C) # UART初期設定(UART番号,クロックレート,TXピン:GP4/6Pin,RXピン:GP5/7Pin) uart = UART(1,baudrate=9600,tx = Pin(4),rx = Pin(5)) while True: length = i2c.get() need_resp = i2c.get() print("length {0}/ need_resp {1}".format(length,need_resp)) payload = bytearray(length) for i in range(length): payload[i] = i2c.get() print("{0}:{1}".format(i,hex(payload[i]))) print(payload) uart.write(payload) uart.flush() while (uart.txdone()==False): pass time.sleep(0.01) if need_resp == 1: response = bytearray() waitCount = 0 while True: resp = uart.read(1) if resp == None: waitCount += 1 if waitCount>10000: break else: response.append(resp[0]) print(response) while i2c.anyRead()==False: pass for d in response: i2c.put(d) ``` 以下のI2Cスレーブ動作用のクラスは、 [Use Pico as an I2C slave? - Raspberry Pi Forums](https://forums.raspberrypi.com/viewtopic.php?t=302978) に掲載されていたコードを拝借しています。作者の方に深く感謝いたします。 \[i2c_slave.py\] ```Python from machine import mem32,Pin class I2C_slave: I2C0_BASE = 0x40044000 I2C1_BASE = 0x40048000 IO_BANK0_BASE = 0x40014000 mem_rw = 0x0000 mem_xor = 0x1000 mem_set = 0x2000 mem_clr = 0x3000 IC_CON = 0 IC_TAR = 4 IC_SAR = 8 IC_DATA_CMD = 0x10 IC_RAW_INTR_STAT = 0x34 IC_RX_TL = 0x38 IC_TX_TL = 0x3C IC_CLR_INTR = 0x40 IC_CLR_RD_REQ = 0x50 IC_CLR_TX_ABRT = 0x54 IC_ENABLE = 0x6c IC_STATUS = 0x70 def write_reg(self, reg, data, method=0): mem32[ self.i2c_base | method | reg] = data def set_reg(self, reg, data): self.write_reg(reg, data, method=self.mem_set) def clr_reg(self, reg, data): self.write_reg(reg, data, method=self.mem_clr) def __init__(self, i2cID = 0, sda=0, scl=1, slaveAddress=0x41): self.scl = scl self.sda = sda self.slaveAddress = slaveAddress self.i2c_ID = i2cID if self.i2c_ID == 0: self.i2c_base = self.I2C0_BASE else: self.i2c_base = self.I2C1_BASE # 1 Disable DW_apb_i2c self.clr_reg(self.IC_ENABLE, 1) # 2 set slave address # clr bit 0 to 9 # set slave address self.clr_reg(self.IC_SAR, 0x1ff) self.set_reg(self.IC_SAR, self.slaveAddress &0x1ff) # 3 write IC_CON 7 bit, enable in slave-only self.clr_reg(self.IC_CON, 0b01001001) # set SDA PIN mem32[ self.IO_BANK0_BASE | self.mem_clr | ( 4 + 8 * self.sda) ] = 0x1f mem32[ self.IO_BANK0_BASE | self.mem_set | ( 4 + 8 * self.sda) ] = 3 # set SLA PIN mem32[ self.IO_BANK0_BASE | self.mem_clr | ( 4 + 8 * self.scl) ] = 0x1f mem32[ self.IO_BANK0_BASE | self.mem_set | ( 4 + 8 * self.scl) ] = 3 # 4 enable i2c self.set_reg(self.IC_ENABLE, 1) def anyRead(self): status = mem32[ self.i2c_base | self.IC_RAW_INTR_STAT] & 0x20 if status : return True return False def put(self, data): # reset flag self.clr_reg(self.IC_CLR_TX_ABRT,1) status = mem32[ self.i2c_base | self.IC_CLR_RD_REQ] mem32[ self.i2c_base | self.IC_DATA_CMD] = data & 0xff def any(self): # get IC_STATUS status = mem32[ self.i2c_base | self.IC_STATUS] # check RFNE receive fifio not empty if (status & 8) : return True return False def get(self): while not self.any(): pass return mem32[ self.i2c_base | self.IC_DATA_CMD] & 0xff if __name__ == "__main__": import utime from machine import mem32 from i2cSlave import i2c_slave s_i2c = i2c_slave(0,sda=0,scl=1,slaveAddress=0x41) counter =1 try: while True: if s_i2c.any(): print(s_i2c.get()) if s_i2c.anyRead(): counter = counter + 1 s_i2c.put(counter & 0xff) except KeyboardInterrupt: pass ``` ## おことわり 本記事で使用しているDFPlayer Miniは正規品ではなく互換品のため、プログラムで想定している挙動が、正規品とは異なっている可能性があります。あしからずご了承ください。 ちなみに中の人が入手した互換品は[Amazonで5個セット1,399円のもの](https://amzn.to/47r4oxP)です。 コメント
コメント
コメントを投稿