Raspberry Pi用pigpio Library - その5 [Raspberry Pi]
pigpioライブラリの概観の続き(その1、その2、その3、その4)。最近僕はずっとこれで遊んでばかりいる。今日はこのライブラリのいちばんの特徴である高精度ソフトウェアPWMとコールバックについて。
前も書いたかもしれないけど、PWM制御のサーボモータを持っている人はぜひこのpigpioのPWMを試してみてほしい。Raspberry Piのどのピンからでも安定した信号が出せるのに驚くから。
そしてPWMもすごいけど、ソフトウェア的にはこのコールバックはめちゃ便利である。デジタルなイベントを監視する必要がある人にはこれもぜひ試してもらいたい。退屈なポーリングのコードなのに応答性やパフォーマンスで苦労した人ならこの便利さはわかると思う....
gpioServo()とgpioGetServoPulsewidth()はサーボモータ信号用に特化したPWM発生関数である。pulsewidth引数には1500$\mu$secが0°で、500$\mu$secから2500$\mu$secまで、PWM方式のサーボモータを知ってる人ならすぐわかる数字が使えるようになっている。
また、周波数はどんな値でも選べるわけではなくて、18通りのキリのいい数字に丸められる。その18通りの周波数はサンプリングレートによって違っていて、1μsecサンプリングだと最高40kHzまで指定できる。詳細は gpioSetPWMfrequency にある表を参照してもらいたい。
もともとあるハードウェアによるPWM信号は
ただし、pigpioライブラリを使えば、サーボモータの制御ぐらいではハードウェアPWMを使わなければいけない理由はなくなる。ハードウェアPWMのアプリケーションとしては、40kHzより高い周波数や1$\mu$sec以下の微妙な制御の必要な、例えばPWMによるアナログ音声の発生などくらいしか思い浮かばない(Raspberry Piではかなり厳しいけど)。
コールバック関数fにNULLを渡すとキャンセル動作になる。
このコールバック関数呼び出しは最初に書いたようにハードウェア割り込みではなく、専用スレッドでのポーリングによっている。ポーリングのサンプリングレートより短いパルスは見失う可能性がある。サンプリングレートはデフォルトで5μsecである。
また、コールバックは1000回/secの頻度でしか呼ばれない。従って最大で1msec程度のレイテンシが発生しうる、ということになる。
こう言われると、なんだか使い道が限定されるような気がするけど、ハードウェア割り込みとは違ってコールバック関数内部での制約は一切ない、というのが利点である。タイミングクリティカルなアプリケーションでなければずっと使いやすいはずである。
ちなみに、pigpioのコールバックはOS X(macOS)のNSRunLoop、CFRunLoopで入力の監視やタイマを書くのと同じ感覚で使えるのでわかりやすい。呼ばれる粒度も似たようなものなのでユーザインターフェイスから制御するには間延びもせずせわしなくもならずに使いやすい。
1msec以内に複数回のレベル変化があったときには、それに対応する回数だけコールバックが呼ばれないといけないけど、そうなっているか、そのときどういうタイミングで呼ばれるか、はまだよくわからない。
ほんとに簡単なものでない限りはRefConは必要なので、普通はExの方を呼ぶことになるだろう。
gpioTickはSoCのシステムクロックタイマの下位32ビットの値を読んでいるだけのものになっている。SoCは上位32ビットも持っているので、64ビット幅のタイムスタンプにしてくれると有難かった(どうやらこのライブラリの作者はlong longを使うのがい嫌いらしい。このライブラリでは64ビット幅のデータは一切使われない)。
長時間動作させるプログラムを書くときは、unixのtime_tなんかと組み合わせてラップしないタイムスタンプを作った方が後々の面倒がなくていいだろう。
前も書いたかもしれないけど、PWM制御のサーボモータを持っている人はぜひこのpigpioのPWMを試してみてほしい。Raspberry Piのどのピンからでも安定した信号が出せるのに驚くから。
そしてPWMもすごいけど、ソフトウェア的にはこのコールバックはめちゃ便利である。デジタルなイベントを監視する必要がある人にはこれもぜひ試してもらいたい。退屈なポーリングのコードなのに応答性やパフォーマンスで苦労した人ならこの便利さはわかると思う....
2.5 GPIOのPWM
int gpioPWM(unsigned user_gpio, unsigned dutycycle); int gpioGetPWMdutycycle(unsigned user_gpio); int gpioSetPWMrange(unsigned user_gpio, unsigned range); int gpioGetPWMrange(unsigned user_gpio); int gpioSetPWMfrequency(unsigned user_gpio, unsigned frequency); int gpioGetPWMfrequency(unsigned user_gpio); int gpioServo(unsigned user_gpio, unsigned pulsewidth); int gpioGetServoPulsewidth(unsigned user_gpio);このライブラリの動機だと作者本人が言っている、高精度ソフトウェア制御のPWM関数群である。周波数とデューティ比のレンジを指定して、gpioPWM()を呼べばPWM信号が出る。周波数とレンジを指定しなくてもgpioPWM()を呼べばデフォルトの値で信号が出ることになっている。デフォルトではレンジは256刻み(0から255まで)になっている。
gpioServo()とgpioGetServoPulsewidth()はサーボモータ信号用に特化したPWM発生関数である。pulsewidth引数には1500$\mu$secが0°で、500$\mu$secから2500$\mu$secまで、PWM方式のサーボモータを知ってる人ならすぐわかる数字が使えるようになっている。
また、周波数はどんな値でも選べるわけではなくて、18通りのキリのいい数字に丸められる。その18通りの周波数はサンプリングレートによって違っていて、1μsecサンプリングだと最高40kHzまで指定できる。詳細は gpioSetPWMfrequency にある表を参照してもらいたい。
もともとあるハードウェアによるPWM信号は
int gpioHardwarePWM(unsigned gpio, unsigned PWMfreq, unsigned PWMduty);で操作できる。ただし当然だけど、ピン番号はモデルごとに正しい指定になっていないといけない。他のライブラリと同じように周波数とデューティの指定の仕方は基本クロックとハードウェアの構成に依存する。
ただし、pigpioライブラリを使えば、サーボモータの制御ぐらいではハードウェアPWMを使わなければいけない理由はなくなる。ハードウェアPWMのアプリケーションとしては、40kHzより高い周波数や1$\mu$sec以下の微妙な制御の必要な、例えばPWMによるアナログ音声の発生などくらいしか思い浮かばない(Raspberry Piではかなり厳しいけど)。
2.6 コールバック設定
pigpioライブラリの一番の特徴であると僕が思っている、GPIOピンの状態変化でのコールバック呼び出し。2.6.1 GPIOピンのレベル変化でのコールバック
まず一番基本的なもの。typedef void (*gpioAlertFunc_t) (int gpio, int level, uint32_t tick); int gpioSetAlertFunc(unsigned user_gpio, gpioAlertFunc_t f);gpioSetAlertFunc()の第1引数で指定したピンのレベルが変化したときにコールバックfが呼ばれる。コールバックにはピン番号と、変化後のレベルと、変化したタイムスタンプ(後述)が渡される。
コールバック関数fにNULLを渡すとキャンセル動作になる。
このコールバック関数呼び出しは最初に書いたようにハードウェア割り込みではなく、専用スレッドでのポーリングによっている。ポーリングのサンプリングレートより短いパルスは見失う可能性がある。サンプリングレートはデフォルトで5μsecである。
また、コールバックは1000回/secの頻度でしか呼ばれない。従って最大で1msec程度のレイテンシが発生しうる、ということになる。
こう言われると、なんだか使い道が限定されるような気がするけど、ハードウェア割り込みとは違ってコールバック関数内部での制約は一切ない、というのが利点である。タイミングクリティカルなアプリケーションでなければずっと使いやすいはずである。
ちなみに、pigpioのコールバックはOS X(macOS)のNSRunLoop、CFRunLoopで入力の監視やタイマを書くのと同じ感覚で使えるのでわかりやすい。呼ばれる粒度も似たようなものなのでユーザインターフェイスから制御するには間延びもせずせわしなくもならずに使いやすい。
1msec以内に複数回のレベル変化があったときには、それに対応する回数だけコールバックが呼ばれないといけないけど、そうなっているか、そのときどういうタイミングで呼ばれるか、はまだよくわからない。
2.6.2 ユーザデータ付きコールバック
さらにユーザデータ、いわゆるRefCon引数つきであとは同じのtypedef void (*gpioAlertFuncEx_t)( int gpio, int level, uint32_t tick, void *userdata); int gpioSetAlertFuncEx(unsigned user_gpio, gpioAlertFuncEx_t f, void *userdata);がある。名前がExで終わる関数は他にもあるけどすべてRefCon付きのものを表している。
ほんとに簡単なものでない限りはRefConは必要なので、普通はExの方を呼ぶことになるだろう。
2.6.3 タイムスタンプ
タイムスタンプはuint32_t gpioTick(void);で返されるμsec単位の整数と同期している。このタイムスタンプは32ビット幅で、1時間ちょっとで0に戻ってしまうので注意が必要である(過去のタイムスタンプの値が常に小さいと思っていると、一晩動かさなければいけないアプリで痛い目にあうということ。そういうのってたいていデバグの時には気がつかなくて本当に痛い。経験者は語る)。
gpioTickはSoCのシステムクロックタイマの下位32ビットの値を読んでいるだけのものになっている。SoCは上位32ビットも持っているので、64ビット幅のタイムスタンプにしてくれると有難かった(どうやらこのライブラリの作者はlong longを使うのがい嫌いらしい。このライブラリでは64ビット幅のデータは一切使われない)。
長時間動作させるプログラムを書くときは、unixのtime_tなんかと組み合わせてラップしないタイムスタンプを作った方が後々の面倒がなくていいだろう。
2016-10-29 21:48
nice!(0)
コメント(1)
トラックバック(0)
共有していただきありがとうございます。
by yazılım (2023-06-27 01:57)