お疲れさまです。
見た目の問題とmidi鍵盤を接続すればいいことなのですが、analog2.0をkorgのMS-20 miniのように、
鍵盤と一体型にしたいと思いました。midi鍵盤を内部でつないであとは木で枠でもつければいいと思いつつ、
まず最近はmidi鍵盤がほとんどusb-midiになってしまいサイズの問題からもちょうどいいものがみつかりませんでした。
仕方がないのでusb-midi鍵盤を用意して、midiは使わずcv発生器のスイッチのところを鍵盤につないでいきました。
1オクターブ分のところまでは上手くいったのですが、2オクターブを同時にだすのはどうやらこれはatttiny2313の
プログラムを変えないといけないと気が付きました。
この掲示板では改造の話題もでていますが、プログラムは改造していいのでしょうか?
公開されているということは自由に変えていいのでしょうか?
まず、cv発生器では4つのピンからの信号で1オクターブを操作しているようなので、これを5つのピンで操作するように
しないといけないと思います。attiny2313の3ピンは使ってないようですのでこれを使えるでしょうか?
あとオクターブスイッチで変わるのは1オクターブですのでこれも2オクターブに変更しないといけません。
プログラムに関しても初心者ですが、みてみたところmidiのところは関係ないとしてとばして関係がありそうなのが、
* iosetup()
* Initializes I/O ports.
*/
void iosetup(void)
{
/* setup output data port */
DDRB = _BV(PB0) | // tuning tone
_BV(PB1) | // gate out
_BV(PB2) | // note CV
_BV(PB3); // pitch bend
PORTB = 0xf0; // key reader
PORTD = _BV(PD2) | _BV(PD3) | _BV(PD4) | _BV(PD5) | _BV(PD6);
のところと、
// check the keyboard
//
octave = ((PIND >> 3) ^ 0x0f) * 12;
key = ((PINB >> 4) ^ 0x0f);
のところかなと思いいろいろやってみましたが上手くいきません。
どうかよろしくお願いします。
hyokure さん
一体型良いですよね。作るの大変ですけれどもつまみいっぱいのキーボードはやっぱりわくわくします
プログラムの改変は自由に行ってください。Analog2.0 のリソースは、MiniBoard のソースコードも含めて以下のサイトでGPLv3 というライセンスで公開しています。
https://github.com/naokiiwakami/analog2.0
GPL の概要は以下のリンクでの解説がわかりやすいと思います。
http://easylabo.com/2015/04/rapid-prototyping/9045/
改変自由、再公開自由、商用利用自由のライセンスですが、改変したソフトを配布する時には必ずソースコードを公開して同じく GPLv3 のライセンスで行ってください。
2オクターブ分出したいということは、キーボードが2オクターブ分 25キーあるということですよね?それですと、今の回路の延長ではダイオードの数が増えすぎて少し大変かもしれません。(今の MiniBoard の回路の設計は実は少し考えが足りなかったと後悔している部分です)鍵盤数が多い時にはキーボードマトリクスという回路を使うとマイコンの少ないピン数で読み取りができて良いです。以下のリンクの解説などがわかりやすいです
http://miqn.net/periph/48.html
https://www.ei.fukui-nct.ac.jp/~t-saitoh/exp/h8/key-matrix.html
書籍では MiniBoard のプログラムや回路の解説をまるまる省いてしまったのでちょっとわかりにくいですね。少し整理してからまた投稿します。少しお待ちください
返信ありがとうございます。
ライセンスの件は了承しました。
あれからまたいろいろやってみて、現在高い「ソ」まで同時に出せるようになりました。
この調子で2オクターブ同時に出して、一体型にできそうです。
ピッチベンドの機能は個人的になくてもよいと思ったのでそのピンを使って5本のピンで試しにやっています。
そのほうがやりやすかったので、
ダイオードは安価ですので数は増えますが、なんとかなります。
プログラムもはじめてだったのですが、右シフトなどとても勉強になります。
一体型にしている他の製作者は少ないようですし、完成したら写真をあげようと思います。
hyokure さん改造は進んでいるとのことで、すばらしいです。
いつも対応が遅めになって申し訳ないのですが、プログラムをしっかり読む時間がやっとできたので、ざっとプログラムの概要をまとめたいと思います。全部を説明するのは非常に長くなるので関係の深そうな部分に絞っています。もっと詳しい説明が必要なところがあったらお知らせください。
AVR のプログラムは、main() 関数から始まるので最初にここを読むと良いのですが、MiniBoard の main 関数は以下のようになっています。
int main(void) {
iosetup(); // IO ポートを初期化
init_uart(); // シリアルを初期化
init_timer(); // タイマーを初期化
init_midi_decoder(); // MIDI デコーダモジュールを初期化
init_midi_controllers(); // MIDI コントローラモジュールを初期化
key_prev = 0;
tone_factor = 0;
sei();
// このループでは、MIDI 入力を監視し続けて入力があればデコードし CV と Gate として出力する
while (true) {
uint16_t rxByte = uart_getchar();
if (rxByte != -1) {
digest(rxByte);
}
}
}
MIDI を使わない場合、初期化が終わってしまったら、 main プログラムはループを空回りさせるだけで実質何もしません。
AVR マイクロプロセッサには、ペリフェラル(周辺装置?)といって、特定の仕事をするモジュールが何種類か内蔵されていて、MiniBoard はこれらのうち以下を使っています。これらが main プログラムの初期化部分で設定されています。init_uart() という関数と init_timer() という関数です。
- UART (シリアル入力) -- MIDI 信号の読み取りに使う
- Timer0 -- ノート(音階) CV の出力に使う (PWM という技術を使います)
- Timer1 -- ベンド(音階) CV の出力に使う (PWM という技術を使います)
このうちキー読み取りに関連の深いのは Timer1 ですが詳細は後ろに書きます。
マイクロプロセッサのプログラミングでこの他に重要なのは、入出力ピンに適切な役割を設定することで、これは main 関数から呼ばれている iosetup() という関数で設定が行われています。ピンの割り当てについては後で書きます。
初期化の終わった main 関数が実質何もしないのにプログラムがどうやって働くかというと、割り込みという機能を使っています。割り込みは、何か「割り込みイベント」というコトが起こるたびにプロセッサの処理に割り込んで実行する関数です。割り込み関数は名前が決まっていて ISR() という名前の関数があれば、それが割り込みです(厳密には関数でなくてマクロなんですが細かいことは省略)。引数はイベントの種類でプロセッサが対応しているものがあらかじめ決められています。キーの読み取りで重要なのが Timer1 の周期が終わるたびに呼ばれる ISR(TIMER1_OVF_vect) です。この関数の中でキーの読み取りをしている、つまり Timer1 の周期が終わるたびにキーの読み取りが行われるのですが、その詳細に入る前に、キー読み取りに使われる入出力ピンの割り当てと押されたキーのデコード方法について説明します。
回路図を見てもわかるかと思いますが、キーの読み取りには 4本の入出力ピンが使われています。それらは、PB4 (16), PB5 (17), PB6 (18), PB7 (19) です。かっこの中はピン番号ですがプログラムの中には出て来ず、PBx という記述で表されます。ATTiny2313 には A, B, D という三種類の入出力ポートがあって、プログラム内では、PORTA, PORTB, PORTD (出力), PINA, PINB, PIND (入力) という特殊な 8ビット整数の形で読み書きができます。PB4, PB5, PB6, PB7 というのは、8ビットのうち、0 から始まって 4, 5, 6, 7 番目のビットに割り当てられているという意味です(要は上位4ビットです)。
次に MiniBoard のハードウェア部分ですが、ダイオードを使って、押したキーが二進数に変換されるようになっています。割り当ては以下の通りです。プログラム内部でビットを反転して読み取るので内部での値は表の右端のようになります。
状態 | PB7 | PB6 | PB5 | PB4 | 値 | 値(反転) |
何も押していない | 1 | 1 | 1 | 1 | 0xf | 0x0 |
lowC | 1 | 1 | 1 | 0 | 0xe | 0x1 |
C# | 1 | 1 | 0 | 1 | 0xd | 0x2 |
D | 1 | 1 | 0 | 0 | 0xc | 0x3 |
D# | 1 | 0 | 1 | 1 | 0xb | 0x4 |
E | 1 | 0 | 1 | 0 | 0xa | 0x5 |
F | 1 | 0 | 0 | 1 | 0x9 | 0x6 |
F# | 1 | 0 | 0 | 0 | 0x8 | 0x7 |
G | 0 | 1 | 1 | 1 | 0x7 | 0x8 |
G# | 0 | 1 | 1 | 0 | 0x6 | 0x9 |
A | 0 | 1 | 0 | 1 | 0x5 | 0xa |
A# | 0 | 1 | 0 | 0 | 0x4 | 0xb |
B | 0 | 0 | 1 | 1 | 0x3 | 0xc |
highC | 0 | 0 | 1 | 0 | 0x2 | 0xd |
ここで、Timer1 の割り込み関数を見てみると、以下のようになっています。この割り込み部分でキーの読み取りを行なっていて、改造するならこの部分のコード変更が必要です。
ISR(TIMER1_OVF_vect) {
static uint16_t button_count = 0;
uint8_t key;
uint8_t octave;
// ピッチベンド更新部は省略
// check the keyboard
octave = ((PIND >> 3) ^ 0x0f) * 12; // オクターブスイッチを読み取り
key = ((PINB >> 4) ^ 0x0f); // PB4, PB5, PB6, PB7 を読み取り <-- キー数を拡張する場合ここを変更する必要あり
if (key) {
key += octave;
}
if (key != key_prev) {
if (key_prev) {
note_off(key_prev + C0);
}
if (key) {
note_on(key + C0, 127);
}
}
key_prev = key;
if (! (PIND & _BV(PD2))) {
++button_count;
}
else {
button_count = 0;
}
if (button_count >= 19531) {
tone_factor ^= _BV(PORTB0);
button_count = 0;
}
}
読み取りの処理
key = ((PINB >> 4) ^ 0x0f);
は、少しわかりにくいですが、もっとわかりやすく分解すると以下のような処理をしています。
uint8_t pin_values = PINB; // PINB レジスタからピン情報を読む。4, 5, 6, 7 ビット目にキー情報が入っている。
// 例えば lowC キーが押された場合、この時点で pin_values は 0xe0 (16進数) あるいは 11100000 (2進数)
pin_values = (pin_values >> 4); // 4ビット右にシフトして 4, 5, 6, 7 ビット目を 0, 1, 2, 3 ビット目に移動する。 4, 5, 6, 7 ビット目には 0 が入る
// 例えば lowC の場合この時点で pin_values は 0x0e (16進数) あるいは 00001110 (2進数)
key = pin_values ^ 0x0f; // 0x0f は二進数であらわすと 00001111。 ^ は xor 演算子で 1111 を掛け合わせると入力ビットが反転する。
// 例えば、lowC の入力値はこの段階で 00001110 で、00001111 と掛け合わせると 00000001 になる。(0xe -> 0x1)
この辺りがキー入力のコードを読んだり改造したりするために重要な部分です。長くなってしまったので、改造についてのご質問への答えは別の投稿に書きます。
引用まず、cv発生器では4つのピンからの信号で1オクターブを操作しているようなので、これを5つのピンで操作するように
しないといけないと思います。attiny2313の3ピンは使ってないようですのでこれを使えるでしょうか?
おそらく大丈夫だと思います。このピンはシリアルの出力を使う場合にはふさがってしまうので、何か MIDI 出力をするような改造はできなくなりますが、今回の改造では大丈夫でしょう。ピン数をケチる方法は他にいくつかありますが ピン 3 を使うのが一番簡単だと思います。
前の投稿で書きましたように、MiniBoard ではダイオードの回路を使ってキーごとに別の二進数の値を割り当てているのですが、1オクターブで使われるのは 0x01 から 0x0d までです。5つのピンで操作するようにすると、値は 0 -31、16進数ですと 0x00 から 0x1f まで使えるので、二オクターブ目の C# から上の C までは、0x0e から 0x19 までの値が割り当てられるようにダイオード回路を組んでみてください。
ソフトウェアの変更も必要です。まずは入出力の設定のところで、新しく加える入力ピンをプルアップする必要があります。前回の投稿では煩雑になるので省略してしまったのですが、キー読み取りに使われる PB4, PB5, PB6, PB7 は、プルアップといって、何も繋いでいない状態では 1 になるようにプロセッサ内部で高抵抗を介してピンに high 状態が入力されています。iosetup 関数の中でそれが設定されています。
void iosetup(void) {
/* setup output data port */
DDRB = _BV(PB0) | // tuning tone
_BV(PB1) | // gate out
_BV(PB2) | // note CV
_BV(PB3); // pitch bend
PORTB = 0xf0; // ここで PB4, PB5, PB6, PB7 に 1 が設定される。このことによりこれらのピンがプルアップされる
PORTD = _BV(PD2) | _BV(PD3) | _BV(PD4) | _BV(PD5) | _BV(PD6);
}
この部分に、ピン 3 のプルアップを加える必要があります。ピン 3 は PD1 ですので、以下のように変更すれば良いはずです。
void iosetup(void) {
/* setup output data port */
DDRB = _BV(PB0) | // tuning tone
_BV(PB1) | // gate out
_BV(PB2) | // note CV
_BV(PB3); // pitch bend
PORTB = 0xf0; // ここで PB4, PB5, PB6, PB7 に 1 が設定される。このことによりこれらのピンがプルアップされる
PORTD = _BV(PD1) | _BV(PD2) | _BV(PD3) | _BV(PD4) | _BV(PD5) | _BV(PD6); // PD1 が加わった
}
あとは、ISR(TIMER1_OVF_vect) の中での、キー読み取りの行
key = ((PINB >> 4) ^ 0x0f);
を、以下のように変えれば良いと思います。
key = (((PIND & _BV(PD1)) << 3) ^ 0x10) + ((PINB >> 4) ^ 0x0f);
ところで、この回路でひとつちょっと問題なのが、キーの同時押しをしてしまうと変な値が入ります。出音が変な風になるかもしれません。
引用
あとオクターブスイッチで変わるのは1オクターブですのでこれも2オクターブに変更しないといけません。
オクターブスイッチのシフトは 1 オクターブと 2 オクターブでどちらが弾きやすいかわかりませんが、ISR((TIMER1_OVF_vect) の中のオクターブスイッチ読み取りの行
octave = ((PIND >> 3) ^ 0x0f) * 12;
これを、以下のように変更すれば 2 オクターブ飛びになります。現状の回路では 16 段階のスイッチだったと思いますが、最大 32 オクターブのトランスポーズをしてしまうので、上の方は CV の出せる範囲を突き抜けてしまうかもしれません。
octave = ((PIND >> 3) ^ 0x0f) * 24;
それから、マイクロプロセッサのプログラムを理解するにはプログラムそのものだけでなくプロセッサ特有のレジスタやその使い方も知っていた方が良いです。それにはデータシートが一番充実した資料です。ATTiny2313 のデータシートは以下から見られます。
http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-2543-AVR-ATtiny2313_Datasheet.pdf
Ganさん 詳しい解説誠にありがとうございます。
ピッチベンドの機能は削ったのですが、やっぱりあったほうがいいかな?と思っていました。
オクターブのところはここかな?と勘でやっていたところがぴったり当たっていました。
attiny2313に何度も書き込みしていましたので、ヒューズリセッターをつくったりもしていました。
プログラムは全く初めてでしたが、とても勉強になり面白いです。
大変参考になります。ありがとうございます。