やっと動きました!!音が鳴りました!!
まずは、カーネルを改造せず、ラズベリーパイから提供されるBCLK、LRCK、DATAに加え、de0-socのpllで作成したマスタークロックのxckで音を鳴らしてみよう、と考えた。確かに、snd_soc_rpi_dacというi2sドライバーを登録することができたが、そもそも、ラズベリーパイ2で、本当にsnd_soc_pcm1794a,snd_soc_rpi_dac,snd_soc_bcm2835_i2sのドライバーのセットで動作するのかは内心、自信がなく、それにもかかわらず、できるんじゃないの?という仮説だけでここまで誘導してきたので、冷や冷やしていた(汗汗;)。音を鳴らすことができることを確認できたので、報告。
snd_soc_pcm1794a,snd_soc_rpi_dac,snd_soc_bcm2708_i2sが24ピン+αのラズベリーパイver1 Bで動くことは知っていたものの、後継の40ピンのラズベリーパイver1 B+以降でsnd_soc_bcm2835_i2sが動作し、その改造すべき対象がsnd_soc_bcm2835_i2sであることは、いまいち自信がなかった。
まず、そもそも、音声データをどのようにDACに搬送するのかを、ご存じでないという方のために、簡単に説明しておく。下は、wm8731のpdfに含まれているi2sの図を引用した。
bck(図のBCLK)は、音声データのビット、例えば32ビットのデータの各ビットを、時間を区切って、読み取るタイミングを決める。音声データ(図の1~nまでの箱)は、一定時間(箱の横幅)間隔で同じ値を維持しており、その中央の時間帯で、bckが0から1に切り替わったタイミングで読み取る。以下32ビットを例にとり説明する。ステレオ32ビット、192kHzなら、192kHzの各離散時間で、特定の音量を32ビットで定義しており、1/192kHzの時間間隔の間に、左右で64ビット搬送しなければならないから、bckは、少なくとも、192kHzの64倍以上細かく刻んでいるパルス波形でなければならず、周波数は、192kHz×64倍=12.288MHz以上である必要がある。32ビットのデータは各ビットについて、一定時間間隔ごとにデータを0ないし1で維持し、そのデータ間隔と半波長ずれたBCKがその立ち上がりのタイミングで(箱の時間間隔のほぼ中央のタイミングで)、ラッチ回路により取り込まれる。その32ビットのデータの搬送開始時をどのように決めるかであるが、これは、ビット読み取り用のBCKとは別に設けたLRCK(左右クロック?)の立下り、立ち上がりを合図として開始する。LRCKは、たまたま、サンプリング周波数(96k、44.1kなど。上の図の1/fsの時間間隔がサンプリング周波数と対応。)と一致することもあるが、必ずしも一致しない。
では、どのように、時系列で32ビットのデータ列を並べるかについては、最小桁ビットから並べる方法や、最大桁ビットから並べる方法がある。また、32bitに満たない、24ビットのデータを伝送する場合、残りの8ビットをどのように0で埋めるかについての種々の方法がある。それらは規格で決まっている。i2sでは、最上位ビットから順に並べる。つまり、最上位ビットを最初にして、最下位ビットに至るまで、一定の時間間隔で、0ないし1の信号を維持する。i2sの場合には、上の図にあるように、LRclockが立上がった後(立下がった後)の次のBCKの立上がり(立下り)で、1サンプリングの32ビットのデータの転送を開始する。今考えると、LRclockの立上がり、立下りでは、波形が異なることがあるから(立上がりのほうが信号の変化が速いことが多い)、常にbckの立ち上がりで、データ転送を開始したほうが、タイミング的に正確になりやすいのかもしれない。
マスタークロックxckは何に使うかというと、私もよくは原理を知らないがラムダシグマ変換で、音量の大きさを、パルス密度という形で時間領域で表し、それを積分により音量に変換するのに使うものと考えられる。そのパルスの最小単位の時間間隔を決めるがのxck、という理解(かなり怪しい)。
では次に、これらのbck、LRCK、xckを、何によって生成するかで、伝送の精度が変わってくるし、ジッターの面でも影響があると考えられる。
xckは、ラズパイ内部では生成されないので、外部で作るしかない。
また、基本的には、xckは、bckと位相が変わっていてもよいが、bckは、xckを分周したものと同じ周期である必要があると考えられる。具体的には、DACのpcm5102のように、xckをbckを入力とするpllで周波数の同期をとる必要があるように思うが、それとは関係なく50MHzから勝手に作ったxckでも、音的にはノイズも出なかった。
以下では、bckその他各信号の生成を、どの機器が受け持つかについて、2つの方法を挙げて説明する。
<ラズベリーパイがクロックマスターの場合>
この場合、この図の通り、ラズベリーパイが、bck,LRCKを、内部のクロックに基づいてPLLなどを用いて生成する。LRCKは、ラズパイ内部で、bckを分周して、周波数を1/64にするか、16bitの場合、1/32にする。マスタークロックのxckは、巷で問題視されているようにラズパイ内部では生成されなくて、外部で作るか、DACのpcm5102のように、bckを入力とするpllにより作成する。いずれにせよ、外部で用意しなければならない。bckとxckは(位相を合わせる必要はないが)厳密に整数倍の周波数でなければならないはずだが、bckとは全く別にde0-socで作ったものでも、目立ったノイズは生じなかった。
de0-socは、DACのwm8731を内部に備えているので、ラズパイのgpioからbck,data,LRCKの信号を受け取り、pin plannerによりプログラム的に構成したde0-socの内部配線でgpioとDACと直結し、これらの信号をDACへ送る。つまり、bck,data,LRCKについては、de0-socは受け取った信号を横流ししているだけである。
de0-socでは、また、マスタークロックであるxckをde0-soc内部にある50MHzに基づき、pllで作成する。
ここで、bckが曲者で、周波数を織り交ぜて、厳密な周波数に合わせているという報告があり、ジッターをわざと生じさせる原因になっている。BCKを分周するLRCKも同様である。それゆえ、きちんとしたbckを外部から入力する必要があり、それが、次の方法による。
<ラズベリーパイがクロックスレーブの場合>
この場合、この図の通り、de0-socが、マスタークロックxckを生成する。de0-socでは、また、マスタークロックであるxckをde0-soc内部にある50MHzに基づき、pllで生成する。または、外部の正確なクロックから、マスタークロックを生成してもよい。また、マスタークロックを分周した信号に該当するbckを、gpioを通じて、ラズベリーパイに入力する。ラズベリーパイは、この入力されたbckを分周して、LRCKを作成し、gpioを通じて、de0-socへ伝送する。de0-socは、DACのwm8731を内部に備えているので、ラズパイのgpioからdata,LRCKの信号を受け取り、pin plannerによりプログラム的に構成したde0-socの内部配線でgpioとDACとを直結し、これらの信号をDACへ送る。つまり、bck,LRCKについては、de0-socは受け取った信号を横流ししているだけである。
この場合、マスタークロックも、bckも、LRCKもいずれも位相ノイズの少ない信号を生成でき、これらはいずれも同一クロックから分周により生成した関係にあり、厳密な同期をとることができる。したがって、ジッターの少ない音楽再生ができる可能性がある。
ただし、これまでの過去記事で方法を述べてきたように、カーネルに属する上記3つのドライバーを改造する必要が生じる。
以下が、ラズベリーパイがクロックマスターとした場合に、de0-socで作った、サンプリング周波数をDACへ入力する回路図の概要である。
(クリックすると拡大します。)
プロジェクトは、以下の通り。
clock_select _ras_master.zip
まずは、前回作ったプロジェクトを別名で保存した。すなわち、プロジェクトをコピーして、新たなプロジェクト名称を決め、フォルダ名称をプロジェクト名称に変え、最上位の回路図の名称を、プロジェクト名称にした。また、最上位の回路図をtop-level-entityに設定した。次に、pin plannerを用いて、xckをDACへ入れるよう設定し、bck、LRCKは、もう1つあるgpioの端子であるgpio1の、gpio1-8,gpio1-12へ出力するようにした。したがって、de0-socが生成し、DACに送っている信号は、マスタークロックのxckのみである。
また、gpio0に入力したdata、LRCK、bckは、pin plannerを用いてDACと直結し、そのまま、DACへ流すようにした。
その他グラウンドに接続するgpioピンについては、グラウンドに接続した。
なお、タイミングが合いませんなる警告が出るが、問題ないと思ったので修正していない。この辺が気になる方は、修正するなどしてほしい。「.sdc」ファイルは、周波数の品位が合致しているかを規定しチェックするものであるが、今回はde0-socで生成し、実際にDACへ送っているのは、pll信号を1回だけ1/2の周波数に分周したマスタークロックだけなので、問題ないと判断した。
de0-socにつき、qualtusのprogrammerを立ち上げ、ファイルをoutputにある、同名のファイルを選択し、スタートを押すと、
プログラムがde0-socにインストールされる。すでにコンパイル済みである。詳しくは、インストールCDに同封されているmy-first-fpgaのpdfを参考にしてほしい。
ほかのボードへ移植するには、alteraのボードの場合、pin plannerの設定を変えるだけで、可能であるはずである。もっとも、DACを内蔵しているとは限らないので、信号経路の変更が必要になる場合もありうるだろうと思われる。
<ラズパイでの再生>
次に、ラズパイを起動し、tera termから、ラズパイを遠隔制御する。
cd_nas_all
dir
xxxx.wav xxxx.wav
aplay -D plughw:0,0 xxxx.vav
により再生開始。この0,0は、該当するドライバを ”aplay -l”(エル)でしらべる。
de0-socの薄黄緑のステレオイヤホンジャックの部分につき、片側:ステレオイヤホンジャック、片側:2本のピンケーブルとなるケーブルを用いて、テレビのビデオ端子につなぎ、ビデオ入力へ入力切替。
通常の再生装置へ接続してもよいが、その場合には、ノイズなどの影響で、どかんと大音量が出ないよう音量を小さくしておく。
また、緑のジャックにヘッドホンを直接つなげると、バカでかい音で割れたような音声となるので、簡単な動作確認以外の用途にはお勧めできない。
なお、現在この連載で議論しているi2sドライバである、snd_soc_pcm1794a,snd_soc_rpi_dac,snd_soc_bcm2835_i2sでは、音量の変更などは受け付けない。もっとも、デジタルでの音量変更は、ビットのけた落ちを用いて、音量を変更するものであると考えられ、あまりお勧めできない。
<以後の展望>
以後は、上記のi2sドライバーを改造して、
上記のラズベリーパイ側がスレーブモード(bckをラズベリーパイの外部から入力するモード)で動作させることを、検討していくことにする。
LRCKは、外部入力したbckに基づいて作成する。
また、bckを外部に設ける場合、サンプリング周波数に応じて、信号の切り替えを行う必要があり、gpioのうち、3つの端子のビット信号から、自動的に変更するようにする。
最終的には、pllではなくて、高精度な外部クロックを接続していきたい。
<2018.4.21修正>
de0-cvではなくて、de0-socでした。修正いたします。