koji_wakita
2014年2月8日 17時48分
初めまして
脇田と申します。
Armadillo-840にて
インターバルタイマーで1msを作成したいのですが
良い方法はないでしょうか?
(C言語を使用しています。)
sigaction(SIGALRM....とsetitimerの組み合わせでは
インターバル期間が全然安定しなくて困ってます。
狙いとしては1ms±10%です。
イメージとしては
1ms毎にハンドラ等がコールされ
プログラムが実行されるようなもの。
スリープとループでは実行時間分が誤差として累積するためNGです。
timer_create
timer_settime
はクロスコンパイル時にエラーとなり使えなませんでした。
(環境はATDE5 i386でQt-Creatorを使用しています)
参考
用途は様々で、このタイマーをベースとして
・各簡易タイマーの管理
・GPIOのエッジセンス、ポーリング
・ダイナミック点灯の制御
などなど
以上、アドバイス頂けたら助かります。
宜しくお願いします。
コメント
koji_wakita
at_takenoshita
こんにちは。
竹之下です。
ATDE5の環境だと、timerfdというものも使えます。
下記のようなコードだと、1msec毎にcallback()関数が呼び出されます。
#include <sys/timerfd.h> #include <time.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <unistd.h> #define LOOPS 100 static int timer_fd; static int count = 0; struct timespec times[LOOPS]; void set_timer(void) { struct itimerspec ts; int ret; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 1000*1000; ts.it_value = ts.it_interval; timer_fd = timerfd_create(CLOCK_MONOTONIC, 0); if (timer_fd == -1) { perror("timerfd_create"); exit(1); } ret = timerfd_settime(timer_fd, TFD_TIMER_ABSTIME, &ts, 0); if (ret != 0) { perror("timerfd_settime"); exit(1); } } static void show_result(void) { int i; long long diff_nsec = 0; printf("diff\n"); for (i = 0; i < LOOPS; i++) { if (i != 0) { diff_nsec = (times[i].tv_sec*1000*1000*1000+times[i].tv_nsec) - (times[i-1].tv_sec*1000*1000*1000+times[i-1].tv_nsec); } printf("%03d: %03lld.%06lld[msec]\n", i, diff_nsec/1000/1000, diff_nsec%(1000*1000)); } } static void callback(void) { clock_gettime(CLOCK_MONOTONIC, ×[count]); } int main(void) { fd_set rfds, rfds_org; int nfds; int ret; set_timer(); FD_ZERO(&rfds_org); FD_SET(timer_fd, &rfds_org); nfds = timer_fd + 1; do { rfds = rfds_org; ret = select(nfds, &rfds, NULL, NULL, NULL); if (ret < 0) { if (errno == EINTR) continue; perror("select"); exit(1); } if (FD_ISSET(timer_fd, &rfds)) { uint64_t expired; ret = read(timer_fd, &expired, sizeof(expired)); if (ret != sizeof(expired)) { perror("read"); exit(1); } callback(); } } while(count++ < LOOPS); show_result(); return 0; }
コンパイル時には、以下のように"-lrt"をつけてlibrtをリンクしてください。
gcc -Wall -lrt timerfd.c
実行結果は、下記のようになります。
diff 000: 000.000000[msec] 001: 000.971413[msec] 002: 000.991983[msec] 003: 000.998399[msec] 004: 000.998483[msec] 005: 000.999899[msec] 006: 000.999399[msec] 007: 001.000315[msec] 008: 000.995817[msec] 009: 001.009899[msec] 010: 000.994911[msec] ...
timerfdの他に、signalfdやeventfdというものもあり、シグナルやイベントなどもselect系関数(pollやepoll)で扱うことができるようになっていますので、シグナルハンドラを使わずに、統一的に扱うことができるようになっています。
もう一点、他のプロセスが動いている時に周期が安定しないという場合は、スケジューリングポリシーとプライオリティを設定してあげると良いかもしれません。
スケジューリングポリシーとプライオリティはsched_setscheduler()関数で設定できます。
y.nakamura
中村です。
横道にそれますが...
> 竹之下です。
> ATDE5の環境だと、timerfdというものも使えます。
timerfdが使えると便利ですね。
A-4x0では使えないことはないがいろいろ面倒、という記事を
MLで読んでいたので、いつもメインスレッドはsigwait()で
インターバルタイマ処理、他の処理は別スレッドで...です。
先日、GPIOの割り込みをハンドルするのにSIGIO(シグナル
駆動I/O)を使ってSIGALRMと一緒にsigwait()で待てないか?
と試したのですけどダメで、結局、スレッドを分けました。
800シリーズではtimerfd使ってみようかと思います。
--
なかむら
koji_wakita
y.nakamura
2014年2月9日 1時14分
中村です。
> Armadillo-840にて
> インターバルタイマーで1msを作成したいのですが
...
> sigaction(SIGALRM....とsetitimerの組み合わせでは
> インターバル期間が全然安定しなくて困ってます。
> 狙いとしては1ms±10%です。
ちょっと長いですが、昔、220のテストに使ったソースを
貼り付けておきます。(添付の方がよかったかな?)
800シリーズでもこのままで動作します。
コマンドライン引数に1を渡すと1msecのタイマ割り込みの
発生間隔を調べることができます。引数なしは100msec。
#これ、MLの[Armadillo:04072]でテストに使ったもの、そのままです。
#余談ですが、これが私のMLデビュー投稿でした。
#メインループでシグナルハンドラで更新されるcountを
#参照してますが、メインループが十分に早いものとして
#手を抜いてます。
手元にあるA810での実行したところ、次のようになりました。
> 用途は様々で、このタイマーをベースとして
> ・各簡易タイマーの管理
> ・GPIOのエッジセンス、ポーリング
> ・ダイナミック点灯の制御
上のプログラムはタイマの時間計測だけが目的なので
sigaction()でSIGALRMのハンドラを登録してやってますが、
実際のアプリではsigaction()は使用せず、
sigaddset(),sigprocmask()して、メインの無限ループで
sigwait()でSIGALRMなどを待って処理してます。
メインループなら、何でもできますから。
「など」というのは、SIGALRM以外にSIGINTやSIGTERMなども
同列で処理ということです。
具体的な例は、"sigwait"でググってみてください。
解説がいろいろ出てきます。
--
なかむら