uchan が 2022年11月15日08時40分08秒 に編集
初版
タイトルの変更
Raspberry Pi の UART で MIDI 送信
タグの変更
RaspberryPi
MIDI
UART
メイン画像の変更
記事種類の変更
製作品
ライセンスの変更
(MIT) The MIT License
本文の変更
# Raspberry Pi の UART で MIDI 送信 MIDI 信号の送受信は基本的には UART 機能を使うだけなので簡単です。しかし、よくある MIDI の記事では大体 5V で MIDI 信号を生成している一方、Raspbery Pi の GPIO 入出力電圧は 3.3V ですので、それを解決するのがこの記事のキモです。結論から言えばアップデートされた MIDI 規格に 3.3V での送受信回路が規定されており、その通りの回路で無事に動きました。 ## 3.3V 版 MIDI 送信回路 MIDI の受信回路にはオプトアイソレータ(フォトカプラ)を搭載し、送受信の回路を電気的に絶縁することになっています。具体的な回路図は他の記事を参照していただくとして[^midi-circuit]、基本的に送信回路の役目は、受信回路に搭載された 1 つの LED を点灯することです。MIDI 規格ではその LED に 5mA 以上を流すこととされています。 [^midi-circuit]: 受信回路について、例えばこの記事: [MIDI調判定機 by uchan | elchika](https://elchika.com/article/7a8ff868-5cb7-4fec-9976-a2fc69677d73/) [MIDI 1.0規格書](https://amei.or.jp/midistandardcommittee/MIDIspcj.html) によると送受信回路はともに 5V の電源を前提にしていますが、そのアップデートである [CA-033 MIDI 1.0 電気的仕様改訂(日本語版)](https://amei.or.jp/midistandardcommittee/RP&CAj.html) には 3.3V 版の MIDI 送受信回路が規定されています。送信側機器の動作電圧が 3.3V なのか 5V なのかで、送信回路に挿入する抵抗値を変えるだけで良いようです。図 1 MIDI OUT 回路(CA-033 本文から引用)を見ると、電源電圧が 3.3V の場合は RA に 33 Ωを、RC に 10 Ωを使うことが分かります。 ![MIDI1.0規格における送信側回路の仕様](https://camo.elchika.com/8d7dc5ecb30221fc74c86a2adb13251d1699bc23/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63333936313234302d643365342d346361652d396632662d3965396366383634616562342f38643966396336382d643131382d343365632d396434322d663461396639306437613330/ "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 もの耐消費電力が必要なのかは分かりません。仮に規定の 10 倍の電流(50 mA)が流れるとしても、RA で消費される電力は 0.0825 W にしかなりません。1/4W あるいは 1/6W の小型の抵抗器で十分な気がします。 ## Raspberry Pi を用いた送信回路 3.3V の信号で駆動できるということは、Raspberry Pi の GPIO に直接配線できるということです。お手軽ですね。[Raspberry Pi Documentation - Raspberry Pi hardware](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html) から引用したピン配置図(図 2)を見ると、UART 送信(TXD)はピン 8 だと分かります。このピンを先の MIDI OUT 回路における VTX として回路を組めば OK です。 ![Raspberry Piピン配置図](https://camo.elchika.com/e2e52bc3a62fe5cf4d7f70c74e5c0a09f864771c/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63333936313234302d643365342d346361652d396632662d3965396366383634616562342f38323831663361352d396432342d343839622d616139302d306236343463323662636531/ "図2 Raspberry Pi のピン配置") 3.3V の電源はピン 1、グランドはピン 9、VTX はピン 8 ということで実装してみました。実装の様子を図 3 に示します。図 1 と合わせて見れば回路が分かりやすいかと思います。赤線がピン 1(3.3V)、青線がピン 8(TXD)、黒線がピン 9(Ground)に接続されています。 ![Raspberry Pi 4とブレッドボードで組んだMIDI送信回路](https://camo.elchika.com/fe1baf6c79021ef9ad9596ce780f27c2f336e79f/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63333936313234302d643365342d346361652d396632662d3965396366383634616562342f32396130396163632d316464632d343632372d396637312d336539336234396164653532/ "図3 ブレッドボードで組んだMIDI送信回路") 抵抗が 3 本並んでいる部分が 33 Ωの抵抗を実現した部分です。33 Ωの抵抗そのものは標準的なもので手元にもあるのですが、規格書の 0.5W を満たすために 100 Ωの抵抗を 3 本並列にしました。これで 1/6W×3 = 0.5W の抵抗器になります。(じゃあ 10 Ωの方も 0.25W を満たすように 2 本並列で作れよ、と言われそうですが、まあいいかなって。) ![ブレッドボードで組んだMIDI送信回路の抵抗周辺拡大](https://camo.elchika.com/36529f721ece7bd756f78588d155264145589b8b/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63333936313234302d643365342d346361652d396632662d3965396366383634616562342f39333539313161362d336632362d343336642d616662392d616639393662626264623431/ "図4 MIDIコネクタと抵抗器") 図 4 に抵抗器付近を拡大した写真を示します。MIDI コネクタの足は太くてブレッドボードに刺さりませんので、変換基板を介しています。左側の 3 本の抵抗器(茶黒茶金)が 100 Ω×3 で、右側の 1 本(茶黒黒金)が 10 Ωです。 ## 実演 作った回路をアナログシンセサイザーに接続して演奏した様子を Twitter で公開しています。興味あればご覧ください。[Raspberry PiとアナログシンセでMIDI演奏してみた](https://twitter.com/uchan_nos/status/1591380491424649221) ## 送信プログラム Raspberry Pi の UART 機能を用いて MIDI 送信するには、UART を MIDI 用の速度 31.25Kbps に変更したり、UART を操作できるライブラリを使ってプログラミングする必要があります。速度の設定変更やライブラリの使い方は [MIDI調判定機 by uchan | elchika](https://elchika.com/article/7a8ff868-5cb7-4fec-9976-a2fc69677d73/) が参考になるでしょう。送信プログラムのコードを以下に示します。ライセンスは MIT とします。 ```c /* 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](https://abyz.me.uk/rpi/pigpio/) を使っています。UART は `/dev/ttyAMA0` を 38.4Kbps で開くのがポイントです。config.txt の設定がきちんとできていれば、これで 31.25Kbps の通信速度になるはずです。 上記のプログラムは 4 つの楽器パートを個別に演奏できる構成にしてありますが、お好みのパート数に変えて使ってください。演奏タイミングがズレないために、`gpioDelay` で待つ時間の計算に工夫を加えています。具体的には `WaitUntil` 関数を使っている部分です。例えば 4 分音符を演奏する際、単に 4 分音符の時間を `gpioDelay` で待つのではなく、4 分音符の MIDI メッセージを送信するのにかかる時間を考慮し、少なめに待つようにしているのです。この工夫によって、複数パートの演奏がきっちり同じタイミングで演奏できます。