Raspberry Pi用pigpio Library - その7 [Raspberry Pi]
pigpioライブラリの概観(その1、その2、その3、その4、その5、その6)の続き。今日は僕がpigpioを知るきっかけになったSPIについて。
おそらくRaspbery PiではSPIはGPIO(1ビットデジタルI/O)についで頻繁に使われるインターフェイスだろう。I2Cはどちらかというともっと小規模で低速なデバイス用で、Raspberry Piのパフォーマンスから考えるとちょっと遅くて、それに見合った高速なA/DやD/AをつなぐときはSPIになることが多い。
Raspberry PiのSPIは速いクロックでも安定に動作して、あまり引っ張りまわさない限り安心して使える。昔、苦労した2MHzクロックでもRaspberry Piではただ半田付けするだけで問題なく動いたので驚いた。
ところがSPIにはI2Cと違ってアドレス空間の概念がない。そのおかげで通信プロトコルはゆるくて、他のシリアル規格のデバイスを無理やり使い回すとかいうこともできるんだけど、複数のデバイスを同じバスに接続するためにはチップセレクト信号というハードウェアが必要になる。Raspbianのカーネルドライバ(spidevモジュール)がサポートしているSPIバスではチップセレクト信号はCE0とCE1の2本しかない。つまりそのままでは1本のバスに最大二つのデバイスしか接続できない。
チップセレクト信号はソフトウェア的に発生できる(CE0、CE1を出さないようにしておいて、他のピンにデバイスのチップセレクトをつなぐ)が、微妙なタイミング制御が必要なデバイスをつなぐことはできない。
また、接続するデバイスのクロックが一桁違うようなデバイスを一つのバスに接続した場合、スループットは遅い方のデバイスが律速段階になってしまう。
GPIOを使いやすくするいろいろなライブラリでも、カーネルドライバを利用しているWiringPiなどでは当然SPIは一つ目のバスしかサポートできないし、レジスタを直接叩くbcm2835ライブラリでも必要ないと考えたのか、やはりSPIバスのサポートは一つ目だけだった。
そもそも僕がこのpigpioライブラリを知ったきっかけは二つ目のバスが必要になったからだった。A/DとD/Aのコンバータを使って、半導体レーザをアナログの任意波形で変調ドライブしながら、光を受けたある物質の応答を観察するというものだった。
D/Aはサンプリング帯域がおよそ1MHz取れるので、方形波なら最高周波数で500kHzまで出力できる。A/Dはそこまで帯域が取れるものが手に入らないし、また観察の帯域そのものも、そこまで必要はなかった。でも、そのA/DとD/Aを同じSPIバスにつなぐとA/Dからデータをとっているあいだ、D/Aの波形が止まってしまう。これが問題になった。
それを解決するためにいろいろ試したんだけど、どれもうまくいかなかった。I2C接続のA/DやRaspberry Piの2台使いを検討して、最後にはやけくそでbcm2835ライブラリを改造して2本目のSPIを動かそうとしたんだけど、どれもダメだった。
途方にくれてた時に、pigpioを知った。問題はあっさり解決した。僕にとってpigpioのAuxiliary SPIは救世主となった。ちょっと大げさか。
まずビット0と1のmmはSPIのモード、つまりクロックに対する論理と位相を指定する。ただしふたつめのSPIバス(auxiliary SPI)ではモード0以外は動かないらしい。
p0、p1、p2はそれぞれのチップセレクト信号の論理を指定する。active low(負論理)のチップセレクトには0を指定する。p2はふたつめのバスにしかない(ふたつめのauxiliary SPIにはチップセレクトが3本あるけど、ひとつめのstandard SPIバスには2本しかない)。
u0、u1、u2はチップセレクト用のピンをその通りに使うかどうかを指定する。0にすると自動的にチップセレクト信号が出て、1にするとそのピンは他に使えることになる。これを使うと、チップセレクトにマルチプレクサ(厳密に言うとデマルチプレクサか?)を外付けしてチップセレクトをソフトウェアで生成すれば、バスにぶら下がるデバイスを増やすことができる。
ところで、本家サイトの解説ではspiOpen()のひとつめの引数はチップセレクトのことだと読めるんだけど、spiCanに対応するuビットを1にした時、同じspiChanで複数の異なるハンドルが生成できないとマルチプレクサは使えない、ということになる。この辺はソースを見るか実験するしかない。
Aはどちらのバスかを指定する。0だとひとつめ、つまりspidevやbcm2835がデフォルトにしているバス(pigpioではstandard SPI)で、1にするとふたつめのauxiliary SPIバスになる。
Wは3本線かそうでないかを指定する。1だと3本線で、0だとそれ以外だそうである。3本線のSPIってどういうのだっけ?
nnnnの4ビットはMOSIからMISOに切り替わるバイト数を指定する。当然最大15バイトということになる。これはAが1のとき、つまり3本線の場合だけ使える、と書いてある。ということはMOSIとMISOを多重化するということなんだろうか。
Linuxのシリアル通信ではRTS信号の制御が正確にできないのでRS-485などのマルチポイントのシリアルプロトコルをレベル変換だけでつなぐことはできない。チップセレクト信号がどういうタイミングで出るか、によるんだけど、それ次第ではこのSPIをRS-485用に使うことができるかもしれない。これは要研究。
ところでWとnnnnはひとつめのバス、つまりstandard SPIでしか使えない。
TはMOSIでのビット順の指定で、0だとMSBから1だとLSBからになる。RはそれがMISOでの指定。
一番上位のbbbbbb(6ビット)はワードサイズの指定。0だとデフォルトの8ビットワードになる。最大で64ビットワードのデータがやりとりできることになる。でもそんなデバイスあるの?
pigpioでワード幅を21ビットに指定すると、MCP3204/3208の最大スループットが出せるのだろうか。これは要実験である。
また、このオープンの形式だとチップセレクト別、つまりSPIデバイスごとにハンドルが作られる。例えば同じバスの上のデバイスが極端に違うクロックスピードだったときに、デバイスを指定するたびにクロックを変更するコードを書かなくて済むのがちょっとだけプログラマに優しい。
どれもSPI側が中途半端なワードサイズの場合、バイト単位に丸められて読み書きすることになる。
2.7 SPI
先にも書いたけど、pigpioのSPIはRaspberry PiのふたつめのSPIバス(pigpioではauxiliary SPIと呼んでいる)が使えるようになっている。おそらくRaspbery PiではSPIはGPIO(1ビットデジタルI/O)についで頻繁に使われるインターフェイスだろう。I2Cはどちらかというともっと小規模で低速なデバイス用で、Raspberry Piのパフォーマンスから考えるとちょっと遅くて、それに見合った高速なA/DやD/AをつなぐときはSPIになることが多い。
Raspberry PiのSPIは速いクロックでも安定に動作して、あまり引っ張りまわさない限り安心して使える。昔、苦労した2MHzクロックでもRaspberry Piではただ半田付けするだけで問題なく動いたので驚いた。
ところがSPIにはI2Cと違ってアドレス空間の概念がない。そのおかげで通信プロトコルはゆるくて、他のシリアル規格のデバイスを無理やり使い回すとかいうこともできるんだけど、複数のデバイスを同じバスに接続するためにはチップセレクト信号というハードウェアが必要になる。Raspbianのカーネルドライバ(spidevモジュール)がサポートしているSPIバスではチップセレクト信号はCE0とCE1の2本しかない。つまりそのままでは1本のバスに最大二つのデバイスしか接続できない。
チップセレクト信号はソフトウェア的に発生できる(CE0、CE1を出さないようにしておいて、他のピンにデバイスのチップセレクトをつなぐ)が、微妙なタイミング制御が必要なデバイスをつなぐことはできない。
また、接続するデバイスのクロックが一桁違うようなデバイスを一つのバスに接続した場合、スループットは遅い方のデバイスが律速段階になってしまう。
2.7.1 Auxiliary SPI
Raspberry Piの古いモデル、例えばPi 1 Model A、Model A+、Model Bには一つのSPIバスしかピンに出ていなかった。一方、Model B+以降の40ピンヘッダを持っているRaspberry Piでは二つ目のSPIバスが使えるようになった。ところが最初にも書いたようにRaspbianのカーネルドライバは二つ目のSPIバスをサポートしていなかった。GPIOを使いやすくするいろいろなライブラリでも、カーネルドライバを利用しているWiringPiなどでは当然SPIは一つ目のバスしかサポートできないし、レジスタを直接叩くbcm2835ライブラリでも必要ないと考えたのか、やはりSPIバスのサポートは一つ目だけだった。
そもそも僕がこのpigpioライブラリを知ったきっかけは二つ目のバスが必要になったからだった。A/DとD/Aのコンバータを使って、半導体レーザをアナログの任意波形で変調ドライブしながら、光を受けたある物質の応答を観察するというものだった。
D/Aはサンプリング帯域がおよそ1MHz取れるので、方形波なら最高周波数で500kHzまで出力できる。A/Dはそこまで帯域が取れるものが手に入らないし、また観察の帯域そのものも、そこまで必要はなかった。でも、そのA/DとD/Aを同じSPIバスにつなぐとA/Dからデータをとっているあいだ、D/Aの波形が止まってしまう。これが問題になった。
それを解決するためにいろいろ試したんだけど、どれもうまくいかなかった。I2C接続のA/DやRaspberry Piの2台使いを検討して、最後にはやけくそでbcm2835ライブラリを改造して2本目のSPIを動かそうとしたんだけど、どれもダメだった。
途方にくれてた時に、pigpioを知った。問題はあっさり解決した。僕にとってpigpioのAuxiliary SPIは救世主となった。ちょっと大げさか。
2.7.2 SPIバスのオープン
まずオープンはint spiOpen(unsigned spiChan, unsigned baud, unsigned spiFlags);これは、spiChanでチップセレクトの出力先を、baudでクロックスピードを設定する。
2.7.3 spiFlagsの意味
spiFlagsはややこしくて下位22ビットが21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 b b b b b b R T n n n n W A u2 u1 u0 p2 p1 p0 m mのように定義されている。
まずビット0と1のmmはSPIのモード、つまりクロックに対する論理と位相を指定する。ただしふたつめのSPIバス(auxiliary SPI)ではモード0以外は動かないらしい。
p0、p1、p2はそれぞれのチップセレクト信号の論理を指定する。active low(負論理)のチップセレクトには0を指定する。p2はふたつめのバスにしかない(ふたつめのauxiliary SPIにはチップセレクトが3本あるけど、ひとつめのstandard SPIバスには2本しかない)。
u0、u1、u2はチップセレクト用のピンをその通りに使うかどうかを指定する。0にすると自動的にチップセレクト信号が出て、1にするとそのピンは他に使えることになる。これを使うと、チップセレクトにマルチプレクサ(厳密に言うとデマルチプレクサか?)を外付けしてチップセレクトをソフトウェアで生成すれば、バスにぶら下がるデバイスを増やすことができる。
ところで、本家サイトの解説ではspiOpen()のひとつめの引数はチップセレクトのことだと読めるんだけど、spiCanに対応するuビットを1にした時、同じspiChanで複数の異なるハンドルが生成できないとマルチプレクサは使えない、ということになる。この辺はソースを見るか実験するしかない。
Aはどちらのバスかを指定する。0だとひとつめ、つまりspidevやbcm2835がデフォルトにしているバス(pigpioではstandard SPI)で、1にするとふたつめのauxiliary SPIバスになる。
Wは3本線かそうでないかを指定する。1だと3本線で、0だとそれ以外だそうである。3本線のSPIってどういうのだっけ?
nnnnの4ビットはMOSIからMISOに切り替わるバイト数を指定する。当然最大15バイトということになる。これはAが1のとき、つまり3本線の場合だけ使える、と書いてある。ということはMOSIとMISOを多重化するということなんだろうか。
Linuxのシリアル通信ではRTS信号の制御が正確にできないのでRS-485などのマルチポイントのシリアルプロトコルをレベル変換だけでつなぐことはできない。チップセレクト信号がどういうタイミングで出るか、によるんだけど、それ次第ではこのSPIをRS-485用に使うことができるかもしれない。これは要研究。
ところでWとnnnnはひとつめのバス、つまりstandard SPIでしか使えない。
TはMOSIでのビット順の指定で、0だとMSBから1だとLSBからになる。RはそれがMISOでの指定。
一番上位のbbbbbb(6ビット)はワードサイズの指定。0だとデフォルトの8ビットワードになる。最大で64ビットワードのデータがやりとりできることになる。でもそんなデバイスあるの?
2.7.4 ワードサイズの使い道
そうか、例えばA/DコンバータのMCP3204/3208ではSPIに最適化されているわけではないので、命令に4ビット、データに12ビットで、さらにスタートビット、アナログ信号のサンプリングに2ビット、そのあとデータの先頭を表すnullビット、チップセレクトをdisableしてから次の命令のために再びenableするガードに1ビット、都合最短で21ビットで変換が終わる。ところがSPIは普通バイト単位でデータをやり取りするので、最低3バイトつまり27ビット(バイト境界のガードビット含めて)必要になってしまい、最大のパフォーマンスが出せない。pigpioでワード幅を21ビットに指定すると、MCP3204/3208の最大スループットが出せるのだろうか。これは要実験である。
また、このオープンの形式だとチップセレクト別、つまりSPIデバイスごとにハンドルが作られる。例えば同じバスの上のデバイスが極端に違うクロックスピードだったときに、デバイスを指定するたびにクロックを変更するコードを書かなくて済むのがちょっとだけプログラマに優しい。
2.7.5 クローズ
終わればint spiClose(unsigned handle);を呼ぶのは礼儀である。
2.7.6 読み書き
データのやり取りのためにはint spiRead(unsigned handle, char *buf, unsigned count); int spiWrite(unsigned handle, char *buf, unsigned count); int spiXfer(unsigned handle, char *txBuf, char *rxBuf, unsigned count);を使う。最後のspiXfer()関数はSPI特有の読み書きを同時に行うためのものである。txBufのデータをMOSIから出力しながら同時にMISOからのデータを読んでrxBufに残すというものである。
どれもSPI側が中途半端なワードサイズの場合、バイト単位に丸められて読み書きすることになる。
2016-12-11 22:05
nice!(0)
コメント(0)
トラックバック(0)
コメント 0