macOSからPi Picoを使う - その33 [Pi Pico]
僕の場合、とりあえずmacOS側はlibusbの同期転送でPi Picoとのやりとりはできている。core1がcore0とほとんど独立に動いているにもかかわらず、core0で動いているTinyUSBがデータを取りこぼす現象は治ってないけど、適度にお休みを入れるというworkaroundでやり過ごしている。大きな声で人に言えないけど。
libusbもだいたいわかってきた。動作が軽くてよくできている。非同期転送の標準的な使い方のサンプルコードでも用意してくれるとありがたいけど。
というわけでそのlibusbでの非同期転送のやりかた。前回つぎのlibusb_handle_events()呼び出しまでの時間を知ることがなぜか僕の環境でできなかったので、その代わりをどうするか考える....
USBコントローラとドライバがどのくらいバッファを持っているかによるけど、呼び出し頻度はUSB1.1では1msec、USB2.0では1/8msecが目安だろう。そう考えるとかなり頻繁に呼ばないといけない、ということになる。
libusb_handle_events()内部での作業は一般的にはかなり軽いものなので、呼び出しのオーバオヘッドが大きいと無駄になる可能性がある。
例えばObjective-Cで
Swiftでは単なるこのラッパ
とすると、引数interval秒間隔でブロック内部が繰り返し呼ばれる(zerotvは時間間隔0のtimeval構造体)。
実際にtimerがどのくらいの頻度で呼ばれるか確認してみる。Objective-Cで
みたいなコードをmain threadで実行してみる。1つ目のタイマは待ち時間0ででただcount変数をカウントアップするだけで、2つ目のタイマが1秒待って1つ目のタイマを止めている。最後にcount変数がいくつになったか出力する、というだけのもの。
だいたい1万回前後で、つまりRunLoop一回まわすのに100μsecぐらいということになる。しかしこれはほとんどからっぽなのでmain threadでの作業が増えるとこれはどんどん遅くなるはずである。特にウィンドウの内容を頻繁に描き変える、例えばムービーを表示するようなアプリでは軽く一桁違ってくるはずである。
Pi PicoのUSB1.1に対してはこのタイマで1msec間隔でlibusb_handle_events_timeout()関数などをタイムアウト0で呼べばまったく問題ない、ということになる。
しかしUSB2〜3ではバッファサイズによってはこれでは厳しいかもしれない。例えばUSB3 Visionカメラのデータをこのタイマで受けるとバッファを溢れさせる可能性がある。USB3 Visionを受けるのは専用threadを立ててそこで同期呼び出し関数か、libusb_handle_events_completed()を呼ぶ方が安全だろう。
そう考えると、データ量や頻度によらず汎用的なlibusb_handle_events()関数呼び出しをmacOSで実現しようとすると、main threadのRunLoopに組み込むのは難しいかもしれない。
どうするのが簡単で安全なんだろうか。
libusbもだいたいわかってきた。動作が軽くてよくできている。非同期転送の標準的な使い方のサンプルコードでも用意してくれるとありがたいけど。
というわけでそのlibusbでの非同期転送のやりかた。前回つぎのlibusb_handle_events()呼び出しまでの時間を知ることがなぜか僕の環境でできなかったので、その代わりをどうするか考える....
17.6.6 どのくらいの頻度でlibusb_handle_events()を呼ぶか
ということで、適切な呼び出し頻度(タイムアウト)は今のところわからない。interrupt転送の場合、USB1.1では1フレームは1msec周期で、1フレームに1回ずつ、となっている。USB2.0では1/8msec(=125μsec)周期のマイクロフレームで、その中に最大3回入っていいことになっている。USBコントローラとドライバがどのくらいバッファを持っているかによるけど、呼び出し頻度はUSB1.1では1msec、USB2.0では1/8msecが目安だろう。そう考えるとかなり頻繁に呼ばないといけない、ということになる。
libusb_handle_events()内部での作業は一般的にはかなり軽いものなので、呼び出しのオーバオヘッドが大きいと無駄になる可能性がある。
17.6.7 タイマを使う
macOSにはタイマがある。これはRunLoopから呼ばれるので非同期転送のsubmitとコールバックの終了処理(つまりはlibusb_handle_events()によるイベント処理)を同じthreadで行うことができる。例えばObjective-Cで
[NSTimer scheduledTimerWithTimeInterval:interval repeats:YES block:^(NSTimer * _Nonnull timer) { libusb_handle_events_timeout(ctx, &zerotv); }];
scheduledTimer(withTimeInterval interval:interval, repeats: true, block:^(NSTimer * _Nonnull timer) { libusb_handle_events_timeout(ctx, &zerotv); })
実際にtimerがどのくらいの頻度で呼ばれるか確認してみる。Objective-Cで
__block NSInteger count = 0; __block NSTimer *timer; timer = [NSTimer scheduledTimerWithTimeInterval:0.0 repeats:YES block:^(NSTimer * _Nonnull timer) { count ++; }]; [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:NO block:^(NSTimer * _Nonnull timer) { [timer invalidate]; NSLog(@"count = %ld", count); }];
だいたい1万回前後で、つまりRunLoop一回まわすのに100μsecぐらいということになる。しかしこれはほとんどからっぽなのでmain threadでの作業が増えるとこれはどんどん遅くなるはずである。特にウィンドウの内容を頻繁に描き変える、例えばムービーを表示するようなアプリでは軽く一桁違ってくるはずである。
Pi PicoのUSB1.1に対してはこのタイマで1msec間隔でlibusb_handle_events_timeout()関数などをタイムアウト0で呼べばまったく問題ない、ということになる。
しかしUSB2〜3ではバッファサイズによってはこれでは厳しいかもしれない。例えばUSB3 Visionカメラのデータをこのタイマで受けるとバッファを溢れさせる可能性がある。USB3 Visionを受けるのは専用threadを立ててそこで同期呼び出し関数か、libusb_handle_events_completed()を呼ぶ方が安全だろう。
そう考えると、データ量や頻度によらず汎用的なlibusb_handle_events()関数呼び出しをmacOSで実現しようとすると、main threadのRunLoopに組み込むのは難しいかもしれない。
どうするのが簡単で安全なんだろうか。
2021-10-17 21:25
nice!(0)
コメント(0)
コメント 0