[MSX0][Arduino] YMF825BoardをMSX0で制御してドレミファソを鳴らす #MSX #MSX0 #Arduino #YMF825


過去にポケコンを使ってYMF825Boardでドレミファソを鳴らすというのをやっておりましたが、今回はMSX0でやってみたいと思います。

ポケコンの場合は、IOピンを直接制御してSPIのなんちゃって送信を行いましたが、MSX0は現状SPI通信はIoT BASICではサポートされていないため、例によってArduinoに仲介させて、MSX0からI2Cで演奏データを書き出し、ArduinoがSPI通信に変換してYMF825Boardに送るという方式をとります。

演奏データやアルゴリズムについては、

https://github.com/hasebems/YMF825_sample

にあるサンプルコードを参考にさせていただきました。


仲介役Arduinoの実装

おなじみMSX0で出来ない件を縁の下で支えるArduinoの実装です。

I2Cでマスタからデータを受信すると、バッファにため込み、予定のバイト数を受信したらSPIスレーブにデータを送信します。

YMF825では一回の命令を複数バイト一度に送るケースがあるため、1バイト受け取るごとにSPIに送信、とはしていません。

また、ここで注意したいのは、ArduinoのI2Cデータの受信バッファは32バイトであるということです。32バイト以上のデータを受け取りそのまま継続しようとするとスレーブデバイスとしてはハングアップのような状態になります。(エラー処理をちゃんと書けば回避できるかもしれないのですが試していないです)

そのため、I2Cマスター(MSX0)からは、いったん命令のバイト数を送信し、続けて命令をそのバイト数分送るようにしています。I2Cスレーブでも、まず最初の1バイトは命令のバイト数として受信し、次から命令のバイト数分をバッファ配列に溜めるようにしています。


#include "Wire.h"
int i2c_slave_addr = 0x08;

#include <SPI.h>

unsigned char buf[256];
unsigned int data_length = 0;
unsigned int bufpos = 0;
boolean command = true;

void setup(){
  
  Serial.begin(115200);
  Serial.println("I2C to SPI(YMF825)");

  for(int i=0;i<256;i++) {
    buf[i] = 0xff;
  }

  delay(500);

  Wire.begin(i2c_slave_addr);
  Wire.onReceive(dataReceive);
  Wire.onRequest(requestEvent);
  
  SPI.begin();
  digitalWrite(SS,HIGH);

  // YMF825 Reset
  pinMode(9,OUTPUT);
  digitalWrite(9,LOW);
  delay(100);
  digitalWrite(9,HIGH);
  delay(100);  
}

void loop(){
}

// I2C data receive from master
void dataReceive(int number) {
  if (Wire.available()) {
    if (command) {
      data_length = Wire.read();
      command=false;
      Serial.println("length:"+String(data_length));
      Serial.print("(");
    } else {
      buf[bufpos] = Wire.read();
      Serial.print(String(buf[bufpos])+" ");
      bufpos++;
      if (bufpos==data_length) {    
        sendToSPI(buf,data_length);
        command=true;
        bufpos=0;
        Serial.println(")");
      }
    }
  }
}

// Send data to SPI slave
int sendToSPI(unsigned char* val,int number) {
  Serial.print(">"+String(number));
  Serial.print("[");
  for(int i=0;i<number;i++) {
    Serial.print(String(val[i],HEX)+" ");
  }    
  Serial.print("]");
  digitalWrite(SS,LOW);
  SPI.transfer(val,number);
  digitalWrite(SS,HIGH);
  Serial.println();
  return 0;
}

// I2C data send to master
void requestEvent() {
}


MSX0の実装

MSX0は通常のI2C通信で、あまり特別なことをしていません。

MSX0のIoT BASICでのI2C通信の基本的なことについては、別記事にまとめていますのでご覧ください。

mobileFF's blog: MSX0でI2C通信を試してみた #MSX #MSX0 #Arduino
https://mobileff.blogspot.com/2023/09/msx0i2c-msx-msx0-arduino.html


演奏データはDATA文で書かれています。これをREAD文で読み、_IOTPUT()でひたすら書き出すという感じなのですが、今回のプログラム(YMF825へのデータ送信)では

  • 2バイトずつ送信するケース(行20000~)
  • 36バイト送信するケース(行21000~)
  • 2バイト送信で2バイト目だけDATA文を使わないで任意の値を指定するケース(行22000~)

の3パターンがあるため、それぞれにサブルーチンを作ってそこに飛ばすようにしています。そのため少しプログラムが複雑に見えると思いますが、飛び先などの行番号にコメントを入れているので、参考になれば幸いです。

1 'SAVE"YMF825-2.BAS"
1000 'Init
1010   D$="08"
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 "---- YMF825 ----"

3000 'INIT
3010 PRINT "---- INIT ----"
3100 RESTORE 40000:'INITDB
3120 FOR I=1 TO 18
3130 GOSUB 20000:'WRITE
3140 NEXT I

4000 'TONE
4010 PRINT "---- TONE ----"
4100 RESTORE 41000:'TONEDB
4110 GOSUB 20000:'Write Word
4120 GOSUB 20000:'Write Word
4130 GOSUB 21000:'Write Multi Bytes

5000 'CHANNEL
5010 PRINT "---- CHANNEL ----"
5100 RESTORE 42000:'CHANNELDB
5110 FOR I=1 TO 5
5120   GOSUB 20000:'Write Word
5130 NEXT I

6000 'LOOP
6010 PRINT "---- LOOP ----"
6100 KA=&H14
6110 KD=&H65
6120 GOSUB 30000:'Loop SubRoutine
6200 KA=&H1C
6210 KD=&H11
6220 GOSUB 30000:'Loop SubRoutine
6300 KA=&H1C
6310 KD=&H42
6320 GOSUB 30000:'Loop SubRoutine
6400 KA=&H1C
6410 KD=&H5D
6420 GOSUB 30000:'Loop SubRoutine
6500 KA=&H24
6510 KD=&H17
6520 GOSUB 30000:'Loop SubRoutine
6600 PRINT "----- LOOP END ----"
6610 GOTO 6000:'LOOP

20000 'Write Word
20100 _IOTPUT(N$,CHR$(2))
20110 READ V1$
20120 _IOTPUT(N$,CHR$(VAL("&H"+V1$)))
20130 READ V2$
20140 _IOTPUT(N$,CHR$(VAL("&H"+V2$)))
20150 PRINT "["+V1$+V2$+"]"
20200 RETURN

21000 'Write Multi Bytes
21100 READ CN
21110 _IOTPUT(N$,CHR$(CN))
21120 PRINT "[";
21230 FOR IM=1 TO CN
21240   READ V$
21250   PRINT V$;
21260   _IOTPUT(N$,CHR$(VAL("&H"+V$)))
21270 NEXT IM
21280 PRINT "]"
21300 RETURN

22000 'Write Custom Word
22100 _IOTPUT(N$,CHR$(2))
22110 _IOTPUT(N$,CHR$(VAL("&H"+V$ )))
22120 _IOTPUT(N$,CHR$(VAL("&H"+V2$)))
22200 RETURN

30000 'Loop SubRoutine
30100 GOSUB 31000:'KEYON
30110 FOR I=0 TO 320:NEXT I:'WAIT
30120 GOSUB 32000:'KEYOFF
30130 FOR I=0 TO 130:NEXT I:'WAIT
30140 RETURN

31000 'KEYON
31010 PRINT "---- KEYON ----"
31100 RESTORE 50000:'KEYONDB
31110 GOSUB 20000:'Write Word
31120 GOSUB 20000:'Write Word
31130 READ V$
31140 V2$=HEX$(KA)
31150 GOSUB 22000:'Write Custom Word
31160 READ V$
31170 V2$=HEX$(KD)
31180 GOSUB 22000:'Write Custom Word
31190 GOSUB 20000:'Write Word
31200 RETURN

32000 'KEYOFF
32010 PRINT "---- KEYOFF ----"
32100 RESTORE 51000:'KEYOFFDB
32110 GOSUB 20000:'Write Word
32120 RETURN

40000 'INITDB
40010 DATA 1D,00,02,0E
40020 DATA 00,01,01,00,1A,A3
40030 DATA 1A,00
40040 DATA 02,04
40050 DATA 02,00,19,F0,1B,3F,14,00,03,01,08,F6
40060 DATA 08,00,09,F8,0A,00,17,40,18,00
41000 'TONEDB
41010 DATA 08,F6
41020 DATA 08,00
41030 DATA 36,07,81,01,85,00,7F,F4,BB,00,10,40,00,AF,A0,0E,03,10,40,00,2F,F3,9B,00,20,41,00,AF,A0,0E,01,10,40,80,03,81,80
42000 'CHANNELDB
42010 DATA 0F,30
42020 DATA 10,71
42030 DATA 11,00
42040 DATA 12,08
42050 DATA 13,00
50000 'KEYDB
50010 DATA 0B,00
50020 DATA 0C,54
50030 DATA 0D
50040 DATA 0E
50050 DATA 0F,40
51000 'KEYOFFDB
51010 DATA 0F,00


MSX0とArduino、YMF825Boardの接続

今回は、Arduino(3.3V)とYMF825Board(5V)の電源をMSX0から共有してみようと思います。

とりあえず、仲介役のArduino Mini Proとは別に、Arduino UNOを用意します。

MSX0のPORT Aの[5V]と[GND]をArduino UNOの[VIN]と[GND]に繋ぎます。

MSX0(PORTA) ←→ Arduino UNO
[5V] ←→ [VIN]
[GND] ←→ [GND](どれでもOK)

すると、Arduino UNOの電源が入りますので、そこから[5V]をYMF825Boardに、[3.3V]をArduino Mini Proに繋ぎます。

Arduino UNO ←→ YMF825 Board
[5V] ←→ [5V]
[GND](どれでもOK) ←→ [GND]

Arduino UNO ←→ Arduino Pro Mini
[3.3V] ←→ [VCC]
[GND](どれでもOK) ←→ [GND](どれでもOK)


次にMSX0とArduino Pro Mini、Arduino Pro MiniとYMF825Boardを接続します。

MSX0(PORTA) ←→ Arduino Pro Mini
[SDA] ←→ [A4]
[SCK] ←→ [A5]

Arduino Pro Mini ←→ YMF825Board
[D10] ←→ [SS]
[D11] ←→ [MOSI]
[D12] ←→ [MISO]
[D13] ←→ [SCK]
[D9] ←→ [RST_N]


中の人はYMF825の仕様のことが全然わからないので、オリジナルの音色やメロディとかが作れず、参考にしたサンプルコードの通りに、ドレミファソと鳴らすだけになっておりますが、ひとまず動きました、という感じです。

現場からは以上です。


コメント