Armadilloフォーラム

Arumadillo-840にて1msのインターバルタイマを作成できないか

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のエッジセンス、ポーリング
・ダイナミック点灯の制御
などなど

以上、アドバイス頂けたら助かります。
宜しくお願いします。


コメント

y.nakamura

2014年2月9日 1時14分

中村です。

> Armadillo-840にて
> インターバルタイマーで1msを作成したいのですが
...
> sigaction(SIGALRM....とsetitimerの組み合わせでは
> インターバル期間が全然安定しなくて困ってます。
> 狙いとしては1ms±10%です。

ちょっと長いですが、昔、220のテストに使ったソースを
貼り付けておきます。(添付の方がよかったかな?)
800シリーズでもこのままで動作します。


---------------------------------------------------------------
#include
#include
#include
#include
#include

#define REP_TIMES 10
volatile int count = 0;
struct timeval tv[REP_TIMES + 1];

struct save {
struct sigaction sa;
struct itimerval it;
};

void sig_alarm()
{
if (count <= REP_TIMES) {
gettimeofday(&tv[count++], NULL);
}
}

// interval: msec
void set_timer(int interval, struct save *save)
{
struct sigaction sa;
struct itimerval it;

memset(&sa, 0, sizeof (sa));
sa.sa_handler = sig_alarm;
sa.sa_flags = SA_RESTART;
sigaction(SIGALRM, &sa, &save->sa);

it.it_value.tv_usec = it.it_interval.tv_usec = interval * 1000;
it.it_value.tv_sec = it.it_interval.tv_sec = 0;
setitimer(ITIMER_REAL, &it, &save->it);
}

void restore_timer(struct save *save)
{
setitimer(ITIMER_REAL, &save->it, NULL);
sigaction(SIGALRM, &save->sa, NULL);
}

double tvf(struct timeval *ptv)
{
return (double)ptv->tv_sec + (double)ptv->tv_usec / 1000000;
}

void result(void)
{
double sd = 0.0;
int i;

for (i = 0; i <= REP_TIMES; i++) {
if (i == 0) {
continue;
}
double d = (tvf(&tv[i]) - tvf(&tv[i-1])) * 1000;
printf(" %8.3f[msec]\n", d);
sd += d;
}
printf(" ---------------\n");
printf(" %8.3f[msec] : mean\n", sd / REP_TIMES);
}

int main(int argc, char** argv)
{
int int_msec = 100;
struct save save;

if (argc > 1) {
int_msec = atoi(argv[1]);
}
set_timer(int_msec, &save);

int pcount = 0;
while(count <= REP_TIMES) {
if (count > pcount) {
pcount = count;
putchar('.');
fflush(stdout);
}
}
printf("\n");
restore_timer(&save);
result();

return 0;
}
---------------------------------------------------------------

コマンドライン引数に1を渡すと1msecのタイマ割り込みの
発生間隔を調べることができます。引数なしは100msec。

#これ、MLの[Armadillo:04072]でテストに使ったもの、そのままです。
#余談ですが、これが私のMLデビュー投稿でした。
#メインループでシグナルハンドラで更新されるcountを
#参照してますが、メインループが十分に早いものとして
#手を抜いてます。

手元にあるA810での実行したところ、次のようになりました。


[root@armadillo810-0 (ttySC2) /home/ftp/pub]# ./timer 1
..........
0.957[msec]
0.991[msec]
0.999[msec]
0.997[msec]
1.006[msec]
0.999[msec]
0.998[msec]
1.000[msec]
1.000[msec]
1.000[msec]
---------------
0.995[msec] : mean

> 用途は様々で、このタイマーをベースとして
> ・各簡易タイマーの管理
> ・GPIOのエッジセンス、ポーリング
> ・ダイナミック点灯の制御

上のプログラムはタイマの時間計測だけが目的なので
sigaction()でSIGALRMのハンドラを登録してやってますが、
実際のアプリではsigaction()は使用せず、
sigaddset(),sigprocmask()して、メインの無限ループで
sigwait()でSIGALRMなどを待って処理してます。
メインループなら、何でもできますから。

「など」というのは、SIGALRM以外にSIGINTやSIGTERMなども
同列で処理ということです。

具体的な例は、"sigwait"でググってみてください。
解説がいろいろ出てきます。

--
なかむら

koji_wakita

2014年2月10日 9時34分

中村様

御教示ありがとうございます。
早速、sigwaitについて調べてみます。

at_takenoshita

2014年2月10日 13時24分

こんにちは。
竹之下です。

ATDE5の環境だと、timerfdというものも使えます。

下記のようなコードだと、1msec毎にcallback()関数が呼び出されます。

#include
#include
#include
#include
#include
#include
#include

#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, &times[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

2014年2月10日 17時10分

中村です。

横道にそれますが...

> 竹之下です。
> ATDE5の環境だと、timerfdというものも使えます。

timerfdが使えると便利ですね。
A-4x0では使えないことはないがいろいろ面倒、という記事を
MLで読んでいたので、いつもメインスレッドはsigwait()で
インターバルタイマ処理、他の処理は別スレッドで...です。
先日、GPIOの割り込みをハンドルするのにSIGIO(シグナル
駆動I/O)を使ってSIGALRMと一緒にsigwait()で待てないか?
と試したのですけどダメで、結局、スレッドを分けました。

800シリーズではtimerfd使ってみようかと思います。

--
なかむら

koji_wakita

2014年2月10日 18時09分

竹之下様

御教示ありがとうございます。
早速、確認させて頂きます。