A/Dコンバータの通信最適化 [Raspberry Pi]
最近僕は、アナログ電圧の入出力をしたくてRaspberry Piを使うことが多くなってる。そのためにはA/D、D/Aコンバータを使う。大昔、僕が会社に入ったばかりの頃は、Analog Devicesなんかのパラレル入出力のチップを苦労して使っていたけど、最近Raspberry Piを使うようになってからは接続が簡単で速くて安いのでMicrochip Technology社のMCP3204/8とMCP4922ばっかりになった。どちらも負電位は扱えなくて、カタログ上のアナログ精度はそれなりなんだけど、実力は結構高いと僕は思う。
どちらもSPI経由でRaspberry Piと接続できて、外部回路はほとんど必要ない(Raspberry Piでは、最高速を狙わないでVCC=3.3Vで駆動すれば外付けは何も要らない)。ちょっと実験したいことができて、さくっと片付けようとすると非常に便利である。ありがたい。最近の若い人は恵まれているよな。こんな便利なものが簡単に手に入るんだから。
D/AであるMCP4922のほうは最高クロック周波数が20MHzまで動くけど、さすがにA/DのMCP3204/8は2MHzで、一桁違う。またD/Aはデータ入力はちょうど2バイトだけど、A/Dは最短で20ビット必要で、SPIの使い勝手のせいでバイト単位に丸めると3バイト食ってしまう。ちょっとでもD/Aのスピードに近づけようと思った.....
MCP3204/8のデータ入出力プロトコルはデータシートによると となっていて、上からチップセレクト(CS)、クロック(CLK)、制御入力(DIN)、データ出力(DOUT)である。CSがアクティブになった次のCLKの立ち上がりエッジから制御入力を読み込む。1ビット目はスタートビットでこれがHだとそれ以降が制御ビットだとみなす。4ビットぶんの制御入力の最後のビットで1.5クロックの間アナログのサンプリングして変換が始まる。データの最初はつねに0で高インピーダンスからの余裕をみている。その次から変換データの12ビット分が送られて、最短で500nsecの間CSがインアクティブになることで、次の制御入力待ち状態になる。したがってちょうど20クロックで1変換、クロック2MHzだと最高100kspsということになる。
ところがRaspberry Piを使うとSPIはバイト単位に丸められてしまうライブラリが多いので、3バイトとガードビットなどで最低でも27クロック、74kspsとなる(実際にはさらに遅い)。これを詰めようと思った。
僕がお気に入りのpigpioライブラリは2本目のSPIバスに限って32ビットまでの任意のビット数を1ワードに指定できる(最大64ビットワードと書いたけどこれは間違いだった。33以上はエラーになる)。これを使って20ビットSPI通信をやってみよう。
そしてこんな関数で1回分の変換をする。
本家の記述では8ビットを超えるワードでは最下位バイトから送出される、となっている。ARMはバイエンディアンだけどRaspbianはARMデフォルトのリトルエンディアンなので、当然そうだろうと思ってやってみるとどういうわけか、上位バイトから左詰め(8ビットで割り切れないときは上位ビットの0詰めなしで)送り出された。
手元にロジアナがないのでちゃんと見たわけではないけど、本家の記述の通りに上のコードにバイトスワップを入れていたら動かないで、スワップしなかったら動いたので、そうなってると考えないとつじつまがあわない。
まあとりあえず、これで実際に走らせて普通の3バイト使った場合と比べてみよう。ただ読み込むだけのループをまわして1秒間に何回読めたかを数える。3204への電源はRaspberry Piから横取りした5V、クロック周波数はちょうど2MHz、SPIモードは0で。
3バイトのときには32クロック使っているようである。20ビットワードにしても23クロック使ってるようである。これはおそらくCS信号がアクティブになってすぐクロックを入れたり、データが終わってすぐCSをインアクティブにせずに、ガードビットが入っているんだろう。CSがかならずインアクティブ状態を挟むと仮定して19ビットでやってみたら、それでも動いたのでつまりはそういうことだろう。
理論値の100kspsには到達できなかったけど、普通の3バイトワードのときに比べるとスループットは約4割り増しになるということで結構効果があるということがわかった。信号帯域がクリティカルな時はこうやってちょっとでもがんばることができる。
Raspberry Piのような安価なシングルボードコンピュータでさえA/Dコンバータの1クロックの間にコアあたり500命令近くを実行できるが、僕が知ってるRaspbianで動くpigpioを含む全てのライブラリでSPIアクセスの間、呼び出した関数はブロックする。SPIの1クロック減らすだけでもそれ以外のたくさんの仕事をこなすことができることになる。
ところでそういうのとは別に、Raspberry Piの5V電源を共有して僕がユニバーサル基板に手半田した場合、DCを入力しても平均的にRMS(Root Mean Square、2乗平均の平方根)の値で1〜2%ぐらいのエラーが乗っている(データのビットパターンによってもRMSの大きさはかなり違うので何やら癖がありそうである)ので、本当に帯域が厳しいときは電源品質や配線を見直した上で、もっと高速のA/Dを検討した方がよさそうである。
でもそれではRaspberry Piとのバランスという意味ではイマイチと言える。そういうのはもっと高級なデバイスに任せて、もっと低い帯域の信号に対して、こういうやりかたでサンプリング数を稼いで平均した値をデータとした方が、Raspberry Piにみあった部品や環境で最大のスループットを得るという意味で最適化されていると言えるだろう。
適材適所である。佐川君のことじゃないよ。
どちらもSPI経由でRaspberry Piと接続できて、外部回路はほとんど必要ない(Raspberry Piでは、最高速を狙わないでVCC=3.3Vで駆動すれば外付けは何も要らない)。ちょっと実験したいことができて、さくっと片付けようとすると非常に便利である。ありがたい。最近の若い人は恵まれているよな。こんな便利なものが簡単に手に入るんだから。
D/AであるMCP4922のほうは最高クロック周波数が20MHzまで動くけど、さすがにA/DのMCP3204/8は2MHzで、一桁違う。またD/Aはデータ入力はちょうど2バイトだけど、A/Dは最短で20ビット必要で、SPIの使い勝手のせいでバイト単位に丸めると3バイト食ってしまう。ちょっとでもD/Aのスピードに近づけようと思った.....
MCP3204/8のデータ入出力プロトコルはデータシートによると となっていて、上からチップセレクト(CS)、クロック(CLK)、制御入力(DIN)、データ出力(DOUT)である。CSがアクティブになった次のCLKの立ち上がりエッジから制御入力を読み込む。1ビット目はスタートビットでこれがHだとそれ以降が制御ビットだとみなす。4ビットぶんの制御入力の最後のビットで1.5クロックの間アナログのサンプリングして変換が始まる。データの最初はつねに0で高インピーダンスからの余裕をみている。その次から変換データの12ビット分が送られて、最短で500nsecの間CSがインアクティブになることで、次の制御入力待ち状態になる。したがってちょうど20クロックで1変換、クロック2MHzだと最高100kspsということになる。
ところがRaspberry Piを使うとSPIはバイト単位に丸められてしまうライブラリが多いので、3バイトとガードビットなどで最低でも27クロック、74kspsとなる(実際にはさらに遅い)。これを詰めようと思った。
僕がお気に入りのpigpioライブラリは2本目のSPIバスに限って32ビットまでの任意のビット数を1ワードに指定できる(最大64ビットワードと書いたけどこれは間違いだった。33以上はエラーになる)。これを使って20ビットSPI通信をやってみよう。
int spiOpen(unsigned spiChan, unsigned baud, unsigned spiFlags)のspiFlagsのビット16〜21の6ビットでワード幅を指定する。今回はこれを20ビットにする。そうするとバイト区切りのガードビットなしで20ビット連続のデータをやりとりできるようになる。もちろんAuxiliary SPIでないといけないのでspiFlagsの8ビット目を立てて、ハード的にもちゃんとそこに接続しておく。
そしてこんな関数で1回分の変換をする。
static uint32_t readFromAD2(int handle, int adChannel) { if (adChannel < 8) { uint32_t rbuf; uint32_t tbuf; tbuf = ((((uint32_t)3) << 18) | ((uint32_t)(adChannel << 15))); int err = spiXfer(handle, (char *)(&tbuf), (char *)(&rbuf), 4); if (err < 0) return 0xFFFFFFFF; return (rbuf >> 1) & 0x00000FFF; } return 0xFFFFFFFF; }先頭にスタートビットとシングルエンド/差動のビット(このコードだとシングルエンド決め打ち)があってその次にチャンネル指定が続く。受け取ったビット列の一番最後はCSをインアクティブにして待つビットなので捨てて残りをデータにする。
本家の記述では8ビットを超えるワードでは最下位バイトから送出される、となっている。ARMはバイエンディアンだけどRaspbianはARMデフォルトのリトルエンディアンなので、当然そうだろうと思ってやってみるとどういうわけか、上位バイトから左詰め(8ビットで割り切れないときは上位ビットの0詰めなしで)送り出された。
手元にロジアナがないのでちゃんと見たわけではないけど、本家の記述の通りに上のコードにバイトスワップを入れていたら動かないで、スワップしなかったら動いたので、そうなってると考えないとつじつまがあわない。
まあとりあえず、これで実際に走らせて普通の3バイト使った場合と比べてみよう。ただ読み込むだけのループをまわして1秒間に何回読めたかを数える。3204への電源はRaspberry Piから横取りした5V、クロック周波数はちょうど2MHz、SPIモードは0で。
ワード幅 | ksps |
---|---|
3バイト | 62 |
20ビット | 84 |
19ビット | 88 |
理論値の100kspsには到達できなかったけど、普通の3バイトワードのときに比べるとスループットは約4割り増しになるということで結構効果があるということがわかった。信号帯域がクリティカルな時はこうやってちょっとでもがんばることができる。
Raspberry Piのような安価なシングルボードコンピュータでさえA/Dコンバータの1クロックの間にコアあたり500命令近くを実行できるが、僕が知ってるRaspbianで動くpigpioを含む全てのライブラリでSPIアクセスの間、呼び出した関数はブロックする。SPIの1クロック減らすだけでもそれ以外のたくさんの仕事をこなすことができることになる。
ところでそういうのとは別に、Raspberry Piの5V電源を共有して僕がユニバーサル基板に手半田した場合、DCを入力しても平均的にRMS(Root Mean Square、2乗平均の平方根)の値で1〜2%ぐらいのエラーが乗っている(データのビットパターンによってもRMSの大きさはかなり違うので何やら癖がありそうである)ので、本当に帯域が厳しいときは電源品質や配線を見直した上で、もっと高速のA/Dを検討した方がよさそうである。
でもそれではRaspberry Piとのバランスという意味ではイマイチと言える。そういうのはもっと高級なデバイスに任せて、もっと低い帯域の信号に対して、こういうやりかたでサンプリング数を稼いで平均した値をデータとした方が、Raspberry Piにみあった部品や環境で最大のスループットを得るという意味で最適化されていると言えるだろう。
適材適所である。佐川君のことじゃないよ。
2018-08-05 21:34
nice!(1)
コメント(0)
コメント 0