Raspberry Piには、GPIOという入出力端子があるので、音源ICを接続してみました。
ざっくりとした回路図はこちらです。
AY-3-8910(以下、AY8910)を扱うにはデータピン8本、制御ピン2本の合計10本程度です。
Raspberry Pi(以下、RasPi)のGPIO端子は26本あるのですが、そのうちの17個くらいまでが自由に使えるようです。ただ、RasPiのGPIOはシリアル通信やI2Cに使える端子が決まっているので、I2Cに液晶やメモリをつなげたりする事を考えると、用途が限定されていない端子はそれほど多くありません。また、RasPiは3.3Vで動いているため、5Vで動作するAY-3-8910や74xx系のICをつなぐには電圧の変換が必要になります。
3.3Vから5Vへの変換はFXMA108というICがあるのですが、これ1つで8本までの変換なので、10本の信号を電圧変換するには2個のICが必要になってしまいます。
その辺の事情を踏まえて、データピン8bitはシリアルパラレル変換ICの74595を使うようにして、3本の線(DATA/CLOCK/LATCH)でシリアル信号をパラレル化8bitにする事にしました。これでFXMA108では、合計5本の信号を電圧変換すればよくなります。ただ、AY8910の出力をRasPiで受け取ることができない(AY8910のレジスタやI/Oを読み取れない)のですが、今回はRasPiからAY8910へ一方的にデータを送るだけなので問題ありません。
AY8910のBC2は常に1(+5V)につないでいます。RESETも+5Vにつなぐか、RasPiから制御した方がいいのかもしれませんが、オープンのままでも動作しました。A8とA9は使わない場合はオープンのままでいいとマニュアルに書いてあります。
3つのアナログ出力は適当に束ねて適当に抵抗とコンデンサを挟んでアンプに繋いでいます。
AY8910のクロックは、3.58MHzのオシレータの出力を4040で2分周して入力しています。MSXと同じだと思います、たぶん。
ちなみにRaspPiはレゴで作ったケースに入っています。
RasPiにはraspbian(linux)を入れて、GPIOの制御にはwiringPiというライブラリを使っています。
AY8910にデータを送るサンプルプログラムはGitHubにUpってあります。
makeして実行するだけですが、一発目に音が出なかったり、何回か実行してると音が出ない時があります、このへんの挙動が謎です。
GPIOの端子の状態を変化させるには、wiringPiのdigitalWrite()という命令を使うと端子1本毎に状態を変えられるのですが、AY8910の制御ピンはBDIRとBC1を同時に変化させなければなりません。同時といっても、若干のズレがあっても大丈夫ですが、ズレの許容時間はかなり短いです。
一方、digitalWrite()はC言語用の関数で、関数の呼び出しと関数内で信号線の状態変化などの処理を実行しているので、digitalWrite(BDIR変更);とdigitalWrite(BC1変更);を連続して実行しても、そこには処理時間の分だけタイムラグが発生してしまいます。その時間をロジアナでザックリと見たところ、AY8910の制御をするには危うい時間差でした。なので、digitalWriteByte()という、ピンの状態をまとめて変更する関数を使っています。
digitalWrite()がどの程度の処理時間を使うのかは環境依存だからかドキュメント化されてないっぽいんですよね。
そんな感じでAY8910にそれっぽくデータを送れば音がでます、単純です。AY8910に10個のスイッチを繋いで手でパチパチとスイッチを切り換えても音が出るんじゃないでしょうか。さすがに手作業だと単純な音しか出せませんけど。
続いて、YOUTUBEにUpした動画について説明します。
曲データとサウンドドライバはMSX版ドラゴンクエストから拝借しました。そういう理由からソースファイルを公開する事ができません。
ドラゴンクエスト内のサウンドドライバ(Z80のコード)と曲データをバイナリファイルとして抜き出し、RaspPi上でZ80エミュレータ(シミュレータ)を動作させ、サウンドドライバをエミュレータで逐次実行しています。
サウンドドライバの呼び出し方法や終了アドレスを確認するためにプログラムの解析とパッチ当てをしました。また、AY8910の制御にはMSXのBIOSルーチンを使っていたので、CBIOSのソースを参考にしました。
サウンドドライバはシンプルな作りだったので、そのままC言語化できそうだったのですが変換作業のバグ探しに労力をさきたくなかったのでやめました。RaspPiは700MHzで動作しているのでZ80 CPUだけのエミュレーションなら余裕ですし。
たまに音痴になったり音がでなくなるのはZ80エミュレータの実行実時間とRaspPiの処理速度が違うからだと思います(ちゃんと調べてません)。
MSXは1/60秒で割り込みが発生して、ドラゴンクエストのサウンドドライバも、この割り込みを使っています。なので、RasPi側では1/60秒のタイマールーチンを作ってサウンドルーチンを呼び出しているのですが、サウンドルーチン内でAY8910のレジスタを何度か書き換えていると、実際にMSXの処理速度よりもRaspPiの処理速度の方が速いのでレジスタ書き換えでおかしなことになってしまっている・・・のだと思います。
そうそう、gccの最適化でO3だと音楽が今以上にヘロヘロになってしまいました。分かる範囲では変数にvolatile指定をしているのですが、うまくいかなかったので最適化オプションは(-O0)で。
ちゃんと音を鳴らしたかったら、マジメにサウンドドライバを作ろうねという話ですネ。もっとも、素のLinux上で動かすユーザプロセスはリアルタイム処理に向いていないなーと思ったので、OSをいじるかドライバを書くとかというレベルで考えないといけなさそうな気がします。
あと、GPIOの書き換え時に他のプロセス(デーモン含む)にタスク切り替えが発生するとよろしくないと思うので、GPIO書き換え時は割り込み禁止にしたかったのですが、その方法がわかりませんでした。local_irq_disable()とlocal_irq_enable()で出来るみたいな資料はあるのですけど、それがどこで定義されてて、どこのライブラリに入っているのかわからなくて。
どうやらカーネルのソースに入ってるっぽかったので、カーネルの再コンパイル環境を作ってソースから引っ張り出してみたのですけど、ちゃんと割り込み禁止になってるのか怪しいです(irqflags.hにインラインアセンブラの形で定義されてるみたいですが効いているのか不明です)。
これ以上ちゃんとやるとなるとアナログ回路部分のノイズ対策をしなきゃですし、それはブレッドボードでやることではないですし、そもそも私はLinuxとMSX両方共に詳しくないので、音が出ただけで満足したのでオシマイ。