send-midi-messages-using-raspberry-pi-uart

Raspberry Pi の UART で MIDI 送信

MIDI 信号の送受信は基本的には UART 機能を使うだけなので簡単です。しかし、よくある MIDI の記事では大体 5V で MIDI 信号を生成している一方、Raspbery Pi の GPIO 入出力電圧は 3.3V ですので、それを解決するのがこの記事のキモです。結論から言えばアップデートされた MIDI 規格に 3.3V での送受信回路が規定されており、その通りの回路で無事に動きました。

3.3V 版 MIDI 送信回路

MIDI の受信回路にはオプトアイソレータ(フォトカプラ)を搭載し、送受信の回路を電気的に絶縁することになっています。具体的な回路図は他の記事を参照していただくとして1、基本的に送信回路の役目は、受信回路に搭載された 1 つの LED を点灯することです。MIDI 規格ではその LED に 5mA 以上を流すこととされています。

MIDI 1.0規格書 によると送受信回路はともに 5V の電源を前提にしていますが、そのアップデートである CA-033 MIDI 1.0 電気的仕様改訂(日本語版) には 3.3V 版の MIDI 送受信回路が規定されています。送信側機器の動作電圧が 3.3V なのか 5V なのかで、送信回路に挿入する抵抗値を変えるだけで良いようです。図 1 MIDI OUT 回路(CA-033 本文から引用)を見ると、電源電圧が 3.3V の場合は RA に 33 Ωを、RC に 10 Ωを使うことが分かります。

MIDI1.0規格における送信側回路の仕様
CA-033 図1 MIDI OUT 回路

受信回路には電源電圧に関係なく 220 Ωの抵抗が挿入されますから、合計で 263 Ωの抵抗がオプトアイソレータの LED に接続されることになります。今回用いた FOD817 というオプトアイソレータの場合、LED の順方向電圧は 1.2V が標準です。$\frac{3.3 - 1.2\, \mathrm{V}}{263\, \mathrm{\Omega}} \approx 7.98\, \mathrm{mA}$ ですので、仕様通りの電流で LED を駆動できそうです。

図 1 には RA として 0.5W、RC として 0.25W の抵抗器を用いるように書いてあります。RA がなぜ 0.5W もの耐消費電力が必要かというと、出力端子をグランドに短絡した場合に保護するためです。最悪ケースを想定して電源電圧が +5%、抵抗値が -5% とすると、4 番端子をグランドに短絡したときの抵抗 RA での消費電力は次の通りです。

$$ \frac{V_\mathrm{TX}^2}{R_\mathrm{A}} = \frac{(3.3\times 1.05)^2}{33\times 0.95}\approx 0.383\ \mathrm{W} $$

したがって 0.25W の抵抗器では十分に保護できず、0.5W が必要ということになります。

5 番端子がグランドに短絡された場合、UART 送信回路の構成によって RC に必要な耐消費電力が決まります。送信回路の出力段がオープンコレクタ(あるいはオープンドレイン)になっていれば、いくらグランドに短絡しようが電流は流れませんので、小さな耐消費電力の抵抗器で構いません。

送信回路がプッシュプル出力になっている場合、RC の耐消費電力よりもむしろ、送信回路がそれに耐えられるかが問題となるでしょう。5 番端子を短絡すると 3.3V / 10Ω = 330mA の電流が流れる計算になりますが、おそらく普通の送信回路は 330mA もの電流に耐えられません。したがって、短絡時の保護を考えれば送信回路の出力をオープンコレクタ回路にしたり、電流制限の仕組みを導入するのが望ましいです。今回はさぼって、Raspberry Pi の GPIO 端子をそのまま接続してしまっていますが。

Raspberry Pi を用いた送信回路

3.3V の信号で駆動できるということは、Raspberry Pi の GPIO に直接配線できるということです。お手軽ですね。Raspberry Pi Documentation - Raspberry Pi hardware から引用したピン配置図(図 2)を見ると、UART 送信(TXD)はピン 8 だと分かります。このピンを先の MIDI OUT 回路における VTX として回路を組めば OK です。

Raspberry Piピン配置図
図2 Raspberry Pi のピン配置

3.3V の電源はピン 1、グランドはピン 9、VTX はピン 8 ということで実装してみました。実装の様子を図 3 に示します。図 1 と合わせて見れば回路が分かりやすいかと思います。赤線がピン 1(3.3V)、青線がピン 8(TXD)、黒線がピン 9(Ground)に接続されています。

Raspberry Pi 4とブレッドボードで組んだMIDI送信回路
図3 ブレッドボードで組んだMIDI送信回路

抵抗が 3 本並んでいる部分が 33 Ωの抵抗を実現した部分です。33 Ωの抵抗そのものは標準的なもので手元にもあるのですが、規格書の 0.5W を満たすために 100 Ωの抵抗を 3 本並列にしました。これで 1/6W×3 = 0.5W の抵抗器になります。(じゃあ 10 Ωの方も 0.25W を満たすように 2 本並列で作れよ、と言われそうですが、まあいいかなって。)

ブレッドボードで組んだMIDI送信回路の抵抗周辺拡大
図4 MIDIコネクタと抵抗器

図 4 に抵抗器付近を拡大した写真を示します。MIDI コネクタの足は太くてブレッドボードに刺さりませんので、変換基板を介しています。左側の 3 本の抵抗器(茶黒茶金)が 100 Ω×3 で、右側の 1 本(茶黒黒金)が 10 Ωです。

実演

作った回路をアナログシンセサイザーに接続して演奏した様子を Twitter で公開しています。興味あればご覧ください。Raspberry PiとアナログシンセでMIDI演奏してみた

送信プログラム

Raspberry Pi の UART 機能を用いて MIDI 送信するには、UART を MIDI 用の速度 31.25Kbps に変更したり、UART を操作できるライブラリを使ってプログラミングする必要があります。速度の設定変更やライブラリの使い方は MIDI調判定機 by uchan | elchika が参考になるでしょう。送信プログラムのコードを以下に示します。ライセンスは MIT とします。

/*
Copyright 2022 Kota UCHIDA

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include <pigpio.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PIN_OUT 21
#define BPM 175
#define QNOTE_US (60 * 1000 * 1000 / BPM)
#define MIDI_CH 0

int ser;

void NoteOn(int note, int velocity) {
  serWriteByte(ser, 0x90 + MIDI_CH); // Note On
  serWriteByte(ser, note);           // Note Number
  serWriteByte(ser, velocity);       // Velocity
}

void NoteOff(int note) {
  serWriteByte(ser, 0x80 + MIDI_CH); // Note Off
  serWriteByte(ser, note);           // Note Number
  serWriteByte(ser, 0);
}

void WaitUntil(uint32_t tick) {
  uint32_t current = gpioTick();
  gpioDelay(tick - current);
}

void NoteOne(int note, int velocity, uint32_t wait_until) {
  NoteOn(note, velocity);
  WaitUntil(wait_until);
  NoteOff(note);
}

int main(int argc, char** argv) {
  if (gpioInitialise() < 0) {
    return -1;
  }

  int part = 0;

  char serDevice[] = "/dev/ttyAMA0";
  ser = serOpen(serDevice, 38400, 0);
  if (ser < 0) {
    fprintf(stderr, "failed to open %s\n", serDevice);
    return -1;
  }

  gpioSetMode(PIN_SW, PI_INPUT);
  gpioSetPullUpDown(PIN_SW, PI_PUD_UP);

  while (1) {
    gpioDelay(1000 * 1000);
    printf("part = %d\n", part);
    uint32_t begin = gpioTick();

    if (part == 0) {
      NoteOne(66, 127, begin + QNOTE_US * 2); // F+
      NoteOne(61, 127, begin + QNOTE_US * 3); // C+
      NoteOne(64, 127, begin + QNOTE_US * 5); // E
      NoteOne(59, 127, begin + QNOTE_US * 6); // B
      NoteOne(62, 127, begin + QNOTE_US * 8); // D
      NoteOne(55, 127, begin + QNOTE_US * 9); // G
      WaitUntil(begin + QNOTE_US * 12);

      begin += QNOTE_US * 12;

      NoteOne(67, 127, begin + QNOTE_US * 2); // G
      NoteOne(62, 127, begin + QNOTE_US * 3); // D
      NoteOne(66, 127, begin + QNOTE_US * 5); // F+
      NoteOne(61, 127, begin + QNOTE_US * 6); // C+
      NoteOne(64, 127, begin + QNOTE_US * 8); // E
      NoteOne(57, 127, begin + QNOTE_US * 9); // A
      WaitUntil(begin + QNOTE_US * 12);

      begin += QNOTE_US * 12;
      
      《中略》
    } else if (part == 1) {
      《中略》
    } else if (part == 2) {
      《中略》
    } else if (part == 3) {
      《中略》
    }

    part = (part + 1) % 4;
  }
}

Raspberry Pi の GPIO を操作するために pigpio library を使っています。UART は /dev/ttyAMA0 を 38.4Kbps で開くのがポイントです。config.txt の設定がきちんとできていれば、これで 31.25Kbps の通信速度になるはずです。

上記のプログラムは 4 つの楽器パートを個別に演奏できる構成にしてありますが、お好みのパート数に変えて使ってください。演奏タイミングがズレないために、gpioDelay で待つ時間の計算に工夫を加えています。具体的には WaitUntil 関数を使っている部分です。例えば 4 分音符を演奏する際、単に 4 分音符の時間を gpioDelay で待つのではなく、4 分音符の MIDI メッセージを送信するのにかかる時間を考慮し、少なめに待つようにしているのです。この工夫によって、複数パートの演奏がきっちり同じタイミングで演奏できます。


作成:2022-11-15 06:42:02

最終更新:2022-12-01 16:25:35