5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

YMF825をArduino Leonardoで鳴らす

Posted at

UNOとLeonardoは何が違う

Aruduino LeonardoはMPUにAtmega32U4(以下32U4)を使用したマイコンボードです
32u4はUSBインターフェイスを備え、HIDやUSB-MIDIなどのUSBデバイスを作る事が出来I/Oポート数も多いためPINの取り出しがいくらかUNOとは異なります
SPI回りがISP端子のみとなっておりSPIインターフェイスを使用した場合入出力ポートを3本多く使う事が出来、キー数が稼げるので以前本家Sample2を使ってUNOで作ったYMF825のキーボードをLeonardoで作って見ました。

SANY0197.JPG

UNOからの変更点

ソースはほぼそのままfmsd1_ino.cppのSS_PINの設定を変えただけ、YMF825のリセットは鍵盤数を稼ぐためパワーオンリセットに任せてISP端子からMOSI,SCKのみを取り出しSSはA5をデジタル出力として使用しています。
SANY0201.JPG
SANY0208.JPG

fsd1_ino.cpp
fsd1_ino.cpp

// fmsd1_ino.cpp
# include	<SPI.h>
# define SS_PIN A5
void writeSingleCPP( unsigned char adrs, unsigned char data )
{
	digitalWrite(SS_PIN, LOW);	//	select slave
	SPI.transfer(adrs);
	SPI.transfer(data);
	digitalWrite(SS_PIN, HIGH);
	delay(1);
}
extern "C" void writeSingle( unsigned char adrs, unsigned char data ){ writeSingleCPP(adrs,data);}
extern "C" void writeBurst( unsigned char adrs, unsigned char* data, int count )
{
	digitalWrite(SS_PIN, LOW);
	SPI.transfer(adrs);
	for (int i = 0; i<count; i++) {
		SPI.transfer(*(data + i));
	}
	digitalWrite(SS_PIN, HIGH);
	delay(1);
}
extern "C" void delayMs( int ms ){	delay(ms);}

void initSPI(void)
{
	//	init SPI
	/*
	* Conditions only for Arduino UNO
	*  SPI master
	*  RST_N- 
	*  SS   - A5
	*  MOSI - MOSI
	*  MISO - 
	*  SCK  - SCK
	*/
	pinMode(SS_PIN, OUTPUT);
	digitalWrite(SS_PIN, HIGH);
	SPI.setBitOrder(MSBFIRST);
	SPI.setClockDivider(SPI_CLOCK_DIV4);	//	4MHz
	SPI.setDataMode(SPI_MODE0);
	SPI.begin();
}
void initSD1( void )
{
	//	1.	powerOnReset( void );
	pinMode(9, OUTPUT);
	digitalWrite(9, LOW);

	//	2. wait 100usec
	delay(1);

	//	3.	RST_N : high
	digitalWrite(9, HIGH);

	//	4.	DRV_SEL	: low
	writeSingleCPP(29, 0);  //  5v

	//	5.	AP0 : 0
	writeSingleCPP(2, 0x0e);

	//	6.	wait for Quarz stability
	delay(1);

	//	7.	CLKE : "1"
	writeSingleCPP(0, 0x01);

	//	8.	ALRST : low
	writeSingleCPP(1, 0x00);

	//	9.	SFTRST	:	0xa3
	writeSingleCPP(26, 0xa3);
	delay(1);

	//	10.	SFTRST	:	0x00
	writeSingleCPP(26, 0x00);

	//	11.	wait 30msec
	delay(30);

	//	12.	AP1 AP3: "0"
	writeSingleCPP(2, 0x04);

	//	13.	wait 10usec
	delayMicroseconds(10);

	//	14.	AP2: "0"
	writeSingleCPP(2, 0x00);

	writeSingleCPP(25, 0x60);	//	0[dB]
	writeSingleCPP(27, 0x3f);	//	set itp max
	writeSingleCPP(20, 0x00);	//	set itp on
	writeSingleCPP(3, 0x01);	//	amp gain(6.5[dB])
	writeSingleCPP(9, 0xb8);	//	Sequencer Volume

ブレッドボード上でキーボードにする

SANY0207.JPG

手持ちのタクトスイッチがブレッドボードに上手く刺さったので、使用できる入力ポートを全てINPUT_PULLUPとしてキーが押されたらGNDへ落とすように配線しました
チャタリング対策は一度LOWへ落ちたらカウンタをセットし一定時間は入力を無視するようにソフトウェアで対策し全キーをスキャンして押されたキーに対応するMIDIコマンドをsample2のMIDIライブラリに送って音を鳴らしています
A4ポートに繋がるキーをCOMMANDキーに設定し、このキーが押されたら次に押されたキーにより音色の変更やオクターブシフトを行えるようにし音程用にA3~A0,0~13PINの順にキーを接続します。

追加機能

せっかくUSBデバイスに出来るので
MIDI-USBライブラリをインクルードしてUSB-MIDIキーボード兼用にしてみました。
sample2のソースに、ここのソース2つ貼り付けてコンパイルすれば鳴るはず。

tinyYmf825keyboarlleonardoMIDI.ino
tinyYmf825KeyboardleonardoMIDI.ino
# include <frequencyToNote.h>
# include <MIDIUSB.h>
# include <MIDIUSB_Defs.h>
# include <pitchToFrequency.h>
# include <pitchToNote.h>

extern "C" {
# include "fmif.h"
}
# include "fmsd1.h"

# define COMMAND_KEY  A4
# define SS_PIN  A5


midiEventPacket_t event;

//                      C  #C   D  #D   E   F  #F   G  #G   A  #A   B   C  #C   D    #D    E    F
uint8_t pin_no[18] = { A3, A2, A1, A0,  0,  1,  2,  3  , 4,  5  , 6,  7,  8,  9,  10,  11,  12,  13};
uint8_t state[18];
uint8_t note_no[18];

int pg_no = 0;
uint8_t pg_state = 0;
int tone_offset = 60;
unsigned char  master_vol = 0x60;

void setup() {
  initSPI();
  initSD1();
  Fmdriver_init();

  TIMSK0 = 0;

  for (int i = 0; i < 18; i++) {
    pinMode(pin_no[i], INPUT_PULLUP);
    state[i] = 0;
  }
  pinMode(COMMAND_KEY, INPUT_PULLUP);


}

void loop() {
  int command;
  for (int i = 0; i < 18; i++) {
    if (HIGH == digitalRead(pin_no[i])) {

      if (state[i] == 1) {
        state[i] = 0;
        Fmdriver_sendMidi(0x90);
        Fmdriver_sendMidi(note_no[i]);
        Fmdriver_sendMidi(0);

        event = {9, 0x90, note_no[i], 0};
        MidiUSB.sendMIDI(event);
      } else {
        if (state[i] > 1)
          state[i] = state[i] - 1;
      }
    } else {
      if (state[i] == 0) {
        state[i] = 255;

        uint8_t note = i + tone_offset ;
        note_no[i] = note;
        Fmdriver_sendMidi(0x90);
        Fmdriver_sendMidi(note);
        Fmdriver_sendMidi(120);

        event = {9, 0x90, note, 120};
        MidiUSB.sendMIDI(event);
      }
    }
    if (digitalRead(COMMAND_KEY) == LOW) {
      if (pg_state == 0) {
        pg_state = 1;
        while (1) {
          for ( command = 0; command < 15; command++) {
            if (digitalRead(pin_no[command]) == LOW)
              break;
          }
          if (command < 14)
            break;
        }

        switch (command) {
          case 0:   //プログラムチェンジ +
            pg_no--;
            if (pg_no < 0)
              pg_no = 7;
            Fmdriver_sendMidi(0xc0);
            Fmdriver_sendMidi(pg_no);
            break;

          case 1:   //プログラムチェンジ -
            pg_no++;
            if (pg_no > 7)
              pg_no = 0;
            Fmdriver_sendMidi(0xc0);
            Fmdriver_sendMidi(pg_no);
            break;


          case 2:   //オクターブシフトー
            tone_offset -= 12;
            if (tone_offset < 0)
              tone_offset += 12;
            break;

          case 3:  //オクターブシフト+
            tone_offset += 12;
            if (tone_offset > 108)
              tone_offset -= 12;
            break;

          case 4:
            tone_offset = 60;
            break;

          case 5:
            tone_offset--;
            if (tone_offset < 0)
              tone_offset = 0;
            break;
          case 6:
            tone_offset++;
            if (tone_offset > 108)
              tone_offset = 108;
            break;

          case 7:  //volume-
            master_vol -= 8;
            if (master_vol < 0x20)
              master_vol = 0x10;
            Fmdriver_sendMidi(0xB0);
            Fmdriver_sendMidi(0x07);
            Fmdriver_sendMidi(master_vol);
            break;

          case 8:  //volume+
            master_vol += 8;
            if (master_vol > 0x78)
              master_vol = 0x78;
            Fmdriver_sendMidi(0xb0);
            Fmdriver_sendMidi(0x07);
            Fmdriver_sendMidi(master_vol);
            break;

          default:
            break;
        }
        delay(500); //チャタリング対策
      }

    } else {
      if ( pg_state == 1) {
        pg_state = 0;
      }
    }
  }
  MidiUSB.flush();
}
 

おまけ

変な音色含んだfmtone.c
fmtone.c
//	fmtone.c
# include	"fmtype.h"
# include	"fmtone.h"
# include	"fmsd1.h"

# define		IMMUTABLE_TONE_MAX		8
# define		MUTABLE_TONE_MAX		IMMUTABLE_TONE_MAX
# define		AVAILABLE_TONE_NUMBER	(IMMUTABLE_TONE_MAX+MUTABLE_TONE_MAX)
# define		MAX_EXCLUSIVE_HEADER_SIZE	5

# define 	MAX_ELEMENT_PRM			2
# define		OPERATOR_PRM_REG_SZ		7
# define 	MAX_TONE_PRM_SZ 		(MAX_FM_OPERATOR*OPERATOR_PRM_REG_SZ + MAX_ELEMENT_PRM)

typedef enum {
	WAIT_DATA,
	DURING_SETTING,
	SET_STATE_MAX
} SET_STATE;

//	Variable
static SET_STATE _toneSetState;
static int _tprmIndex;
static ToneData _userTone[MUTABLE_TONE_MAX];
static const ToneData TPRM[IMMUTABLE_TONE_MAX] = {

	{	//	GrandPiano
0x2b,  // VoiceCommon
{//  KC | AR | DR | SR | RR | SL | TL | VB | DT | WS
   {0x01,0x0f,0x07,0x00,0x06,0x0f,0x27,0x00,0x01,0x08}, 
   {0x07,0x0a,0x03,0x02,0x03,0x03,0x1c,0x00,0x06,0x00}, 
   {0x00,0x0b,0x03,0x02,0x04,0x01,0x18,0x01,0x01,0x00}, 
   {0x06,0x0d,0x03,0x02,0x06,0x04,0x00,0x01,0x01,0x00}, 
}

    /*
		0x0b,	//	VoiceCommon
		{ //  KC | AR | DR | SR | RR | SL | TL | VB | PT | WS
			{0x01,0x0f,0x07,0x00,0x06,0x0f,0x27,0x00,0x01,0x08},	// op1
			{0x07,0x0e,0x03,0x02,0x03,0x02,0x28,0x00,0x05,0x00},	// op2
			{0x00,0x0d,0x01,0x01,0x04,0x03,0x22,0x01,0x01,0x00},	// op3
			{0x06,0x0d,0x02,0x02,0x06,0x04,0x00,0x01,0x01,0x00}		// op4
		}
   */
	},
	{	// E.Piano
		0x0d,	//	VoiceCommon
		{ //  KC | AR | DR | SR | RR | SL | TL | VB | PT | WS
			{0x54,0x0f,0x04,0x05,0x0c,0x0b,0x23,0x44,0x07,0x12},	// op1
			{0x02,0x0f,0x02,0x01,0x08,0x0f,0x04,0x45,0x01,0x00},	// op2
			{0x25,0x0f,0x00,0x01,0x0b,0x01,0x12,0x44,0x01,0x00},	// op3
			{0x04,0x0f,0x02,0x01,0x07,0x0f,0x04,0x41,0x01,0x00}		// op4
		}
	},
	{	//	TenorSax
0x35,  // VoiceCommon
{//  KC | AR | DR | SR | RR | SL | TL | VB | DT | WS
   {0x70,0x07,0x03,0x01,0x05,0x01,0x18,0x00,0x22,0x00}, 
   {0x00,0x07,0x02,0x03,0x06,0x03,0x00,0x00,0x42,0x00}, 
   {0x70,0x08,0x03,0x01,0x05,0x01,0x1a,0x00,0x21,0x00}, 
   {0x02,0x08,0x02,0x03,0x06,0x03,0x08,0x03,0x12,0x00}, 
}

    /*
		0x0d,	//	VoiceCommon
		{ //  KC | AR | DR | SR | RR | SL | TL | VB | PT | WS
			{0x36,0x07,0x03,0x00,0x00,0x00,0x05,0x44,0x01,0x01},	// op1
			{0x00,0x07,0x02,0x00,0x09,0x00,0x0f,0x43,0x01,0x08},	// op2
			{0x36,0x07,0x03,0x00,0x00,0x00,0x08,0x44,0x01,0x09},	// op3
			{0x02,0x07,0x02,0x00,0x09,0x00,0x0d,0x43,0x01,0x00}		// op4
		}*/
	},
	{	//	PickBass
//SynsTrumpet
0x27,  // VoiceCommon
{//  KC | AR | DR | SR | RR | SL | TL | VB | DT | WS
   {0x50,0x07,0x00,0x00,0x04,0x00,0x08,0x17,0x41,0x02}, 
   {0x00,0x07,0x02,0x06,0x05,0x06,0x0c,0x10,0x61,0x06}, 
   {0x00,0x09,0x03,0x05,0x04,0x05,0x08,0x00,0x31,0x00}, 
   {0x00,0x0b,0x04,0x00,0x04,0x00,0x0c,0x30,0x31,0x04}, 
}
    
    /*
		0x0b,	//	VoiceCommon
		{ //  KC | AR | DR | SR | RR | SL | TL | VB | PT | WS
			{0x56,0x0f,0x07,0x02,0x03,0x01,0x13,0x44,0x01,0x00},	// op1
			{0x04,0x0c,0x0b,0x04,0x06,0x07,0x15,0x44,0x07,0x00},	// op2
			{0x06,0x0f,0x09,0x02,0x06,0x02,0x17,0x44,0x02,0x00},	// op3
			{0x04,0x0b,0x02,0x06,0x08,0x06,0x00,0x44,0x01,0x00}		// op4
		}*/
	},
	{	//	TnklBell
0x37,  // VoiceCommon
{//  KC | AR | DR | SR | RR | SL | TL | VB | DT | WS
   {0x30,0x08,0x02,0x00,0x05,0x08,0x00,0x10,0x41,0x09}, 
   {0x00,0x07,0x04,0x03,0x03,0x02,0x08,0x30,0x31,0x00}, 
   {0x02,0x06,0x06,0x00,0x04,0x00,0x00,0x05,0x41,0x09}, 
   {0x00,0x07,0x05,0x00,0x04,0x02,0x06,0x03,0x31,0x1b}, 
}

 /*   
		0x0d,	//	VoiceCommon
		{ //  KC | AR | DR | SR | RR | SL | TL | VB | PT | WS
			{0x31,0x0f,0x06,0x03,0x04,0x05,0x10,0x44,0x0e,0x00},	// op1
			{0x02,0x0c,0x06,0x07,0x06,0x0e,0x0b,0x44,0x02,0x00},	// op2
			{0x00,0x0c,0x06,0x02,0x02,0x05,0x1e,0x44,0x77,0x01},	// op3
			{0x00,0x0f,0x05,0x04,0x05,0x0d,0x01,0x54,0x06,0x00}		// op4
		}
   */
	},
	{	//	NewAgePd
		0x0d,	//	VoiceCommon
		{ //  KC | AR | DR | SR | RR | SL | TL | VB | PT | WS
			{0x54,0x0f,0x0f,0x03,0x03,0x00,0x26,0x44,0x07,0x01},	// op1
			{0x02,0x0f,0x07,0x04,0x04,0x00,0x0b,0x44,0x05,0x00},	// op2
			{0x62,0x06,0x01,0x00,0x01,0x00,0x18,0x03,0x71,0x01},	// op3
			{0x02,0x08,0x01,0x00,0x05,0x01,0x00,0x03,0x01,0x00}		// op4
		}
	},
	{	//	Rim Shot
//Scratch
0x48,  // VoiceCommon
{//  KC | AR | DR | SR | RR | SL | TL | VB | DT | WS
   {0x00,0x0f,0x07,0x05,0x0f,0x0b,0x00,0x00,0x01,0x18}, 
   {0x00,0x0f,0x0f,0x05,0x0f,0x00,0x00,0x00,0x01,0x06}, 
   {0x13,0x0f,0x0f,0x00,0x02,0x03,0x26,0x00,0x02,0x08}, 
   {0x02,0x0a,0x0f,0x00,0x07,0x00,0x02,0x01,0x01,0x08}, 
}
    /*
		0x0d,	//	VoiceCommon
		{ //  KC | AR | DR | SR | RR | SL | TL | VB | PT | WS
			{0x7c,0x0f,0x00,0x05,0x05,0x00,0x05,0x44,0x0c,0x02},	// op1
			{0x0c,0x0f,0x07,0x07,0x07,0x07,0x00,0x44,0x0b,0x00},	// op2
			{0x08,0x0f,0x0a,0x06,0x06,0x08,0x00,0x44,0x0c,0x00},	// op3
			{0x08,0x0f,0x07,0x07,0x07,0x07,0x00,0x44,0x07,0x02}		// op4
		}
   */
	},
	{	//	Castanet

0x5c,  // VoiceCommon
{//  KC | AR | DR | SR | RR | SL | TL | VB | DT | WS
   {0x24,0x0c,0x07,0x05,0x03,0x06,0x0d,0x44,0x0b,0x00}, 
   {0x04,0x09,0x00,0x00,0x03,0x04,0x31,0x04,0x02,0x00}, 
   {0x02,0x0d,0x04,0x01,0x06,0x02,0x24,0x00,0x00,0x00}, 
   {0x00,0x09,0x09,0x02,0x08,0x01,0x00,0x00,0x01,0x00}, 
}

/*
		0x0d,	//	VoiceCommon
		{ //  KC | AR | DR | SR | RR | SL | TL | VB | PT | WS
			{0x68,0x0f,0x07,0x05,0x09,0x0f,0x02,0x44,0x07,0x01},	// op1
			{0x0c,0x0a,0x08,0x05,0x0f,0x0f,0x00,0x44,0x05,0x06},	// op2
			{0x08,0x0f,0x05,0x06,0x05,0x00,0x27,0x44,0x02,0x05},	// op3
			{0x08,0x0c,0x0a,0x09,0x09,0x0a,0x14,0x44,0x05,0x00}		// op4
		} */
	}

};
const unsigned char tExcCheck[MAX_EXCLUSIVE_HEADER_SIZE] = {
	0x43,	//	Exclusive:1, Yamaha ID
	0x7f,	//	Exclusive:2, Make/DIY ID1
	0x02,	//	Exclusive:3, Make/DIY ID2
	0x00,	//	Exclusive:4, YMF825 ID
	0x00	//	Exclusive:5, reserved
};
void Tone_init( void )
{
	int i;
	for ( i=0; i<MUTABLE_TONE_MAX; i++ ){
		_userTone[i] = TPRM[i];
	}
	_toneSetState = WAIT_DATA;
	_tprmIndex = 0;

	Tone_sendTone();
}
void Tone_setToneExc( unsigned char data, int excNum )
{
	if ( _toneSetState == WAIT_DATA ){
		if (( excNum == 1 ) && ( data == tExcCheck[0] )){
			_toneSetState = DURING_SETTING;
		}
	}
	else if ( _toneSetState == DURING_SETTING ){
		if ( excNum-1 < MAX_EXCLUSIVE_HEADER_SIZE ){
		 	if ( data != tExcCheck[excNum-1] ){ _toneSetState = WAIT_DATA; }
		}
		else if ( excNum == 6 ){
			if ( data < MUTABLE_TONE_MAX ){ _tprmIndex = data; }
			else { _toneSetState = WAIT_DATA; }
		}
		else if ( excNum == 7 ){
			_userTone[_tprmIndex].voiceCommon = data;
		}
		else if ( excNum < 48 ){
				_userTone[_tprmIndex].opPrm[(excNum-8)/MAX_OPERATOR_PRM][(excNum-8)%MAX_OPERATOR_PRM] = data;
			}
		else { _toneSetState = WAIT_DATA; }
	}
}
void Tone_sendTone( void )
{
	int	i,j;
	unsigned char regImage[MAX_TONE_PRM_SZ*AVAILABLE_TONE_NUMBER + 1 + 4]; // 485

	//	top
	regImage[0] = 0x80 + AVAILABLE_TONE_NUMBER;

	for ( i=0; i<AVAILABLE_TONE_NUMBER; i++ ){
		unsigned char* riPtr = &regImage[MAX_TONE_PRM_SZ*i + 1];

		ToneData* td;
		if ( i < IMMUTABLE_TONE_MAX ){
			td = (ToneData*)&(TPRM[i]);
		}
		else {
			td = (ToneData*)&(_userTone[i-IMMUTABLE_TONE_MAX]);
		}

		riPtr[0] = (td->voiceCommon & 0x60)>>5;
		riPtr[1] = ((td->voiceCommon & 0x18)<<3) | (td->voiceCommon & 0x07);

		for ( j=0; j<MAX_FM_OPERATOR; j++ ){
			riPtr[MAX_ELEMENT_PRM+OPERATOR_PRM_REG_SZ*j+0] = (td->opPrm[j][3] << 4) | (td->opPrm[j][0] & 0x08) | ((td->opPrm[j][0] & 0x04)>>2);
			riPtr[MAX_ELEMENT_PRM+OPERATOR_PRM_REG_SZ*j+1] = (td->opPrm[j][4] << 4) | td->opPrm[j][2];
			riPtr[MAX_ELEMENT_PRM+OPERATOR_PRM_REG_SZ*j+2] = (td->opPrm[j][1] << 4) | td->opPrm[j][5];
			riPtr[MAX_ELEMENT_PRM+OPERATOR_PRM_REG_SZ*j+3] = (td->opPrm[j][6] << 2) | (td->opPrm[j][0] & 0x03);
			riPtr[MAX_ELEMENT_PRM+OPERATOR_PRM_REG_SZ*j+4] = td->opPrm[j][7];
			riPtr[MAX_ELEMENT_PRM+OPERATOR_PRM_REG_SZ*j+5] = ((td->opPrm[j][8] & 0x0f) << 4) | ((td->opPrm[j][8] & 0xf0) >> 4);
			riPtr[MAX_ELEMENT_PRM+OPERATOR_PRM_REG_SZ*j+6] = (td->opPrm[j][9] << 3) | ((td->opPrm[j][0] & 0x70) >> 4);
		}
	}

	//	end 
	regImage[MAX_TONE_PRM_SZ*AVAILABLE_TONE_NUMBER + 1] = 0x80;
	regImage[MAX_TONE_PRM_SZ*AVAILABLE_TONE_NUMBER + 2] = 0x03;
	regImage[MAX_TONE_PRM_SZ*AVAILABLE_TONE_NUMBER + 3] = 0x81;
	regImage[MAX_TONE_PRM_SZ*AVAILABLE_TONE_NUMBER + 4] = 0x80;

	//	 Soft Reset
//    writeSingle(26,0xa3);
//    writeSingle(26,0x00);
//	writeSingle(8,0xf6);
  writeSingle(8,0x16);
	delayMs(1);
	writeSingle(8,0x00);

	writeBurst( 7, regImage, MAX_TONE_PRM_SZ*AVAILABLE_TONE_NUMBER + 5 );
}
5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?