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で作って見ました。
UNOからの変更点
ソースはほぼそのままfmsd1_ino.cppのSS_PINの設定を変えただけ、YMF825のリセットは鍵盤数を稼ぐためパワーオンリセットに任せてISP端子からMOSI,SCKのみを取り出しSSはA5をデジタル出力として使用しています。
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
ブレッドボード上でキーボードにする
手持ちのタクトスイッチがブレッドボードに上手く刺さったので、使用できる入力ポートを全てINPUT_PULLUPとしてキーが押されたらGNDへ落とすように配線しました
チャタリング対策は一度LOWへ落ちたらカウンタをセットし一定時間は入力を無視するようにソフトウェアで対策し全キーをスキャンして押されたキーに対応するMIDIコマンドをsample2のMIDIライブラリに送って音を鳴らしています
A4ポートに繋がるキーをCOMMANDキーに設定し、このキーが押されたら次に押されたキーにより音色の変更やオクターブシフトを行えるようにし音程用にA3~A0,0~13PINの順にキーを接続します。
追加機能
せっかくUSBデバイスに出来るので
MIDI-USBライブラリをインクルードしてUSB-MIDIキーボード兼用にしてみました。
sample2のソースに、ここのソース2つ貼り付けてコンパイルすれば鳴るはず。
tinyYmf825keyboarlleonardoMIDI.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
# 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 = ®Image[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 );
}