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

初めまして
脇田と申します。

Armadillo-840にて
インターバルタイマーで1msを作成したいのですが
良い方法はないでしょうか?
(C言語を使用しています。)

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

 イメージとしては
 1ms毎にハンドラ等がコールされ
 プログラムが実行されるようなもの。

スリープとループでは実行時間分が誤差として累積するためNGです。

timer_create
timer_settime
はクロスコンパイル時にエラーとなり使えなませんでした。
(環境はATDE5 i386でQt-Creatorを使用しています)

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

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

製品: 
Armadillo-840

中村です。

> 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"でググってみてください。
解説がいろいろ出てきます。

--
なかむら

中村様

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

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

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()関数で設定できます。

中村です。

横道にそれますが...

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

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

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

--
なかむら

竹之下様

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