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 <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>
#include <string.h>
 
#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 <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, &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分

竹之下様

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