前回のGnuplotアニメーションに続いて、今回はX軸をプロットする毎に16秒分変化させて、以前より滑らかに動くようにしてみました。

動画にした方法は、MacのVNC上で画面録画しただけです。録画時間は約5時間35分、これを2分10秒まで縮めました。

 

↑ offset値が頻繁に上下していますが、これは生活ノイズ(ここでは電化製品のオンオフなど電気的ノイズを表現)で上下しています。多分、裸のGPSモジュールとラズパイの配線がワニ口クリップの長い配線のせいかも知れません。また、ラズパイのアップグレード中も30μs位変化した所を見たので、そんな要因も含まれます。

↓ 生活ノイズが無いと、このように落ち着いています。

 

↓ この動画の元になったGnuplotのスクリプト(C言語)です。なぜC言語なのかと言うと、Gnuplotのスクリプトだけで現時刻に応じてX軸の範囲を設定する方法が分からなかったからです。

/*
 * loopstats-plot.c
 *
 * gcc -Wall -o loopstats-plot loopstats-plot.c
 * 2020/02/10
 *
 * loopstatsのoffset値をGnuplotでグラフ化する。
 *
 * X軸は現時刻から-3600秒前の値を使用。
 * X軸は16秒毎に16秒分変化する。
 *
 */

#include <stdio.h>
#include <time.h>
#include <unistd.h>

#define YOHAKU 150

int main(void)
{
    FILE *gp;
    int s1, s2;
    
    gp = popen("gnuplot -persist", "w");
    
    fprintf(gp, "set encoding iso_8859_1\n");
    fprintf(gp, "set grid y2tics\n");
    fprintf(gp, "set y2range [-0.00004:0.00004]\n");
    fprintf(gp, "set y2tics ('40' 0.00004, '30' 0.00003, '20' 0.00002, '10' 0.00001, '5' 0.000005, '0' 0.00000, '-5' -0.000005, '-10' -0.00001, '-20' -0.00002, '-30' -0.00003, '-40' -0.00004)\n");
    fprintf(gp, "set xzeroaxis lt -1\n");
    fprintf(gp, "set timestamp\n");
    fprintf(gp, "unset ytics\n");
    fprintf(gp, "set title 'loopstats offset' font 'Helvetica,16'\n");
    fprintf(gp, "set xlabel '-3600 (s)' font 'Helvetica,14'\n");
    fprintf(gp, "set y2label 'offset ({\265}s)' font 'Helvetica,14'\n");
    fprintf(gp, "plot '< tail -225 /var/log/ntpstats/loopstats' using 2:3 with points axis x1y2\n");
    
    while (1) {
        time_t t = time(NULL);
        s1 = t % 86400 + YOHAKU;
        s2 = t % 86400 - 3600 - YOHAKU;
        
        // X軸設定 [現時刻−3600秒:現時刻]
        fprintf(gp, "set xrange [%d:%d]\n", s2,s1);
        fprintf(gp, "replot\n");
        fflush(gp); // ここで描画
        
        sleep(16); // 再描画まで16秒待つ
    }
    fprintf(gp, "unset output\n");
    fprintf(gp, "exit\n");
    pclose(gp);
    
    return 0;
}
$ gcc -Wall -o loopstats-plot loopstats-plot.c
$ ./loopstats-plot

 

このフローチャートに従って、共有メモリに書き込むプログラムAのその部分 ↓

    while (1) { // 無限ループ
        if ((fp=popen("ntpq -c rv", "r"))==NULL) {
            string s = "N"; // ntpp -c rv 実行失敗時 N を共有メモリに書き込む
            sprintf(shared_memory, s.c_str());
        } else {
            fgets(str, 512, fp); // 一行読み込む
            ptr = strstr(str, "sync_ntp");
            if (ptr != NULL) {
                string s = "S"; // S を共有メモリに書き込む
                sprintf(shared_memory, s.c_str());
                sleep(16);
            } else {
                ptr = strstr(str, "sync_unspec");
                if (ptr != NULL) {
                    ptr = strstr(str, "no_sys_peer");
                    if (ptr != NULL) {
                        string s = "M"; // M を共有メモリに書き込む    
                        sprintf(shared_memory, s.c_str());
                    } else {
                        string s = "N"; // N を共有メモリに書き込む
                        sprintf(shared_memory, s.c_str());
                    }
                } else {
                    string s = "N"; // N を共有メモリに書き込む    
                    sprintf(shared_memory, s.c_str());
                }
            }
        }
        pclose(fp);
        sleep(1);
    }

 

プログラムAによって共有メモリに書き込まれた文字により、表示を変化させるラズパイ7桁時計のその部分 ↓

    static int segdata[22][8] = { // 7セグメントデータ
      // a  b  c  d  e  f  g  dp
	{H, H, H, H, H, H, L, L}, // 0
	{L, H, H, L, L, L, L, L}, // 1
	{H, H, L, H, H, L, H, L}, // 2
	{H, H, H, H, L, L, H, L}, // 3
	{L, H, H, L, L, H, H, L}, // 4
	{H, L, H, H, L, H, H, L}, // 5
	{H, L, H, H, H, H, H, L}, // 6
	{H, H, H, L, L, L, L, L}, // 7
	{H, H, H, H, H, H, H, L}, // 8
	{H, H, H, H, L, H, H, L}, // 9
        {H, H, H, H, H, H, L, H}, // 0. 小数点付き
        {L, H, H, L, L, L, L, H}, // 1. 小数点付き
        {H, H, L, H, H, L, H, H}, // 2. 小数点付き
        {H, H, H, H, L, L, H, H}, // 3. 小数点付き
        {L, H, H, L, L, H, H, H}, // 4. 小数点付き
        {H, L, H, H, L, H, H, H}, // 5. 小数点付き
        {H, L, H, H, H, H, H, H}, // 6. 小数点付き
        {H, H, H, L, L, L, L, H}, // 7. 小数点付き
        {H, H, H, H, H, H, H, H}, // 8. 小数点付き
        {H, H, H, H, L, H, H, H}, // 9. 小数点付き
	{L, L, L, L, L, L, L, L}, // Blank
	{L, L, L, L, L, L, L, H}, // dp
    };

    // 中略

    while (1) { // 無限ループ 1サイクル14.8ミリ秒 1秒間に約67回繰り返す

        // 共有メモリの文字により表示を変える
        switch (*shared_memory) {
            case 'S': // +++++ 時計表示 + 同期LED点灯 +++++
                disp = 2000;  // 2ミリ秒表示維持
                LEDstate = 1; // 同期LED点灯
                break;
            case 'M': // +++++ 時計表示 ++++++++++++++++++
                disp = 2000;  // 2ミリ秒表示維持
                LEDstate = 0; // 同期LED消灯
                break;
            case 'N': // +++++ チラツキ表示 ++++++++++++++
                disp = 10000; // 10ミリ秒表示維持
                LEDstate = 0; // 同期LED消灯
                break;
        }

        gpioWrite(SYNC_LED, LEDstate); // 共有メモリに応じて同期LED点灯・消灯

        // 時刻の取得
        clock_gettime(CLOCK_REALTIME, &ts);
        localtime_r(&ts.tv_sec, &lotm);  // ローカル時間に変換
        num[0] = ts.tv_nsec / 100000000; // 1/10秒の桁取得
        num[1] = lotm.tm_sec % 10 + 10;  //  1. 秒の桁取得(小数点付き)
        num[2] = lotm.tm_sec / 10;       // 10  秒の桁取得
        num[3] = lotm.tm_min % 10;       //  1  分の桁取得
        num[4] = lotm.tm_min / 10;       // 10  分の桁取得
        num[5] = lotm.tm_hour % 10;      //  1  時の桁取得
        num[6] = lotm.tm_hour / 10;      // 10  時の桁取得

        // 7桁ダイナミック表示ルーチン
        for (j = 0; j < 7; j++) {
            for (i = 0; i < 9; i++) { // 7セグメントデータセット
                gpioWrite(nanaseg[i], segdata[num[j]][i]);
            }
            gpioWrite(common[j], SEG_ON); // 7セグLED表示
            gpioDelay(disp);             // 共有メモリに応じて表示維持時間変化
            gpioWrite(common[j], SEG_OFF);// 7セグLED消灯
        }

    }

 

↓ 7セグLEDのコモンをオシロで観測した画像。

なんと、私のブログで「共有メモリ」やら「プロセス間通信」なる言葉を使うとは、恐れを知らない素人プログラマー。だって趣味で自分使用のラズパイの話ですから、お構いなしです。

 

早めにお詫びして起きます。この投稿は無知な私が書いた物なので、全てが誤りかも知れません。

 

 

 

/*
 * zeroClock.cpp
 * 
 * プロセス間通信 共有メモリテスト
 * NTPチェック用プログラム(プロセス)が書き込んだ共有メモリに応じて表示を変える。
 * 
 * その別プロセスがntpq -p 実行結果に応じて
 * NTPサーバー参照同期中ならGを共有メモリに書き込み
 * そうでなければWを書き込む。
 * 
 * このプログラムは共有メモリを読み込んで
 * Gなら正常時計表示、Wの場合全桁ドット表示させる。
 *
 */
 
#include <iostream>
#include <pigpio.h>
#include <unistd.h>
#include <sys/shm.h>

#define H 0x1
#define L 0x0
#define SEG_ON 0x1
#define SEG_OFF 0x0

using namespace std;

int mainLoop(void);

int main(void)
{
    if(daemon(0, 0) == 0) { // デーモン関数
        mainLoop();
    } else {
        cout << "error" << endl;
    }
    return 0;
}

int mainLoop(void)
{    
    int i, j, disp;
    int num[7];
    struct timespec ts;
    struct tm lotm;
                       // 0.1秒1秒 10秒 1分 10分 1時 10時
                  // Common 1   2   3   4   5   6   7
    static int common[7] = {25, 24, 23, 18, 22, 27, 17};
                    // GPIO 25  24  23  18  22  27  17

                     // 7seg a   b   c   d   e   f  g  dp  NTP
    static int nanaseg[9] = {21, 20, 16, 26, 19, 6, 13, 5, 12};
                     // GPIO 21  20  16  26  19  6  13  5  12

    static int segdata[12][9] = { // 7セグメント & NTP用LED データ
    // a  b  c  d  e  f  g  dp NTP
      {H, H, H, H, H, H, L, L, H}, // 0
      {L, H, H, L, L, L, L, L, H}, // 1
      {H, H, L, H, H, L, H, L, H}, // 2
      {H, H, H, H, L, L, H, L, H}, // 3
      {L, H, H, L, L, H, H, L, H}, // 4
      {H, L, H, H, L, H, H, L, H}, // 5
      {H, L, H, H, H, H, H, L, H}, // 6
      {H, H, H, L, L, L, L, L, H}, // 7
      {H, H, H, H, H, H, H, L, H}, // 8
      {H, H, H, H, L, H, H, L, H}, // 9
      {L, L, L, L, L, L, L, L, H}, // Blank
      {L, L, L, L, L, L, L, H, L}, // dp
    };
    
    // pigpioライブラリ初期化
    if (gpioInitialise() < 0) exit(1);
    
    // コモン用GPIOピンをアウトプット設定
    for (i = 0; i < 7; i++) {
      gpioSetMode(common[i], PI_OUTPUT);
    }
    
    // セグメント用GPIOピンをアウトプット設定(NTP用LEDを含む)
    for (i = 0; i < 9; i++) {
      gpioSetMode(nanaseg[i], PI_OUTPUT);
    }
    
    /*
     *
     * 共有メモリ設定
     *
     */
    // 作成済みの共有メモリのIDを取得する
    const string file_path = "/home/pi/ntpCheck.dat";
    const int id = 50;

    const key_t key = ftok(file_path.c_str(), id);

    const int seg_id = shmget(key, 0, 0);
    if(seg_id == -1){
        cerr << "Failed to acquire segment" << endl;
        return EXIT_FAILURE;
    }

    // 共有メモリをプロセスにアタッチする
    char* const shared_memory = 
                reinterpret_cast<char*>(shmat(seg_id, 0, 0));
    
    
    /*
     *
     * 時刻取得及びダイナミック表示ルーチン(無限ループ)
     *
     * 時刻取得、1桁2ミリ秒表示×7、時刻取得、1桁2ミリ秒表示×7の繰り返し
     *
     * 共有メモリの文字が
     * G の場合はNTPサーバと参照同期中
     * W の場合はNTPサーバと参照同期していない
     *
     */
    while (1) {
      // 共有メモリから読み込んだ文字により表示を変える
      switch (*shared_memory) {
        case 0x47: // G (Good) G ASCII Code // G の場合正常時計表示
          clock_gettime(CLOCK_REALTIME, &ts); // 時刻の取得
          localtime_r(&ts.tv_sec, &lotm); // ローカル時間に変換
          num[0] = ts.tv_nsec / 100000000;    //0.1秒の桁取得
          num[1] = lotm.tm_sec % 10;          //  1秒の桁取得
          num[2] = lotm.tm_sec / 10;          // 10秒の桁取得
          num[3] = lotm.tm_min % 10;          //  1分の桁取得
          num[4] = lotm.tm_min / 10;          // 10分の桁取得
          num[5] = lotm.tm_hour % 10;         //  1時の桁取得
          num[6] = lotm.tm_hour / 10;         // 10時の桁取得
          disp = 2000; // 2ミリ秒
          break;
        case 0x57: // W (Wait) 0x57 ASCII Code // W の場合ドット表示
          for (i = 0; i < 7; i++) {
            num[i] = 11; // 全桁 D.P & NTP用LED消灯 セット
          }
          disp = 100000; // 100ミリ秒
          break;
      }
      for (j = 0; j < 7; j++) { // 7桁ダイナミック表示ルーチン
        for (i = 0; i < 9; i++) { // 7セグメントデータセット
          gpioWrite(nanaseg[i], segdata[num[j]][i]);
        }
        gpioWrite(common[j], SEG_ON);   // 7セグLED点灯
        gpioDelay(disp);                // 点灯維持
        gpioWrite(common[j], SEG_OFF);  // 7セグLED消灯
      }
    }
    //  ここから以降は無限ループに含まれないので不要?
    
    // pigpioライブラリ終了処理
    gpioTerminate();

    // 共有メモリをプロセスから切り離す
    shmdt(shared_memory);
    
    return 0;
}

自作中のラズパイ7桁時計に関して、欲しかった機能をテスト中です。

それは、ntpq -p 実行結果からNTPサーバー参照同期中の印である *(アスタリスク)を検索して、見つかればLEDを点灯させたいのです。

 

時計表示用のプログラムとは別なプログラムとして作ります。理由は実行に時間がかかる、約0.6秒くらいかかるのでダイナミック表示に支障があるからです。

今回はGPIOを動かすのにWiringPiを使用しました。先に時計表示にpigpioを使用しているのですが、このプログラムでもpigpioライブラリを使用しようとすると「既に使用中で使えません」みたいな事になってしまいましたので。

本当はプロセス間通信とか共有メモリを利用して、LED点灯は時計表示用のプログラムに任せたいのですが、私には難しくて組めません。

 

↓ このソースコードは無知の私が書いた物なので、全てが間違いかも知れません。その事を承知の上で参考にして下さい。

 

/*
 * ntpCheck.cpp
 *
 * ntpq -p の実行結果から参照同期中の文字 * を検索
 * 見つからない場合は見つかるまで、5秒毎にntpq -p 実行し検索
 * 見つかった場合はLEDを10分間点灯させて、最初に戻り繰り返す。
 *
 */

#include <iostream>
#include <string.h>
#include <wiringPi.h>
#include <unistd.h>

#define PIN 5
// using namespace std;

int mainLoop(void);

int main(void)
{
    if (daemon(0, 0) == 0) { // デーモン関数
        mainLoop();
    } else {
        // cout << "error" << endl;
    }
    return 0;
}

int mainLoop(void)
{
    char str[512], *ptr;
    FILE *fp;

    if (wiringPiSetupGpio() == -1) return 1;
    pinMode(PIN, OUTPUT);

    // (A) 無限ループ
    while (1) {
        if ((fp=popen("ntpq -p", "r"))==NULL) {
            fprintf(stderr, "error!!!\n");
            exit(1); // 異常終了
        }

        while (1) {
            fgets(str, 512, fp); // 一行毎に読み込む
            if (feof(fp)) {
                sleep(5);
                break; // 検索文字が無くファイルの終わりに達して
                       // ループを抜ける->(B)
            }
            ptr=strchr(str, '*'); // 検索文字は参照同期中の*
            if (ptr!=NULL) {
                // cout << "OK ----------" << endl;
                digitalWrite(PIN, HIGH); // 検索文字有りLED点灯
                sleep(600);              // 10分間点灯維持
                digitalWrite(PIN, LOW);  // LED消灯
                break; // ループを抜ける->(B)
            }
            // cout << "NG" << endl;
        }
        // (B)
        pclose(fp);// ->(A)

    }
    // return 0;
}
$ g++ -Wall -o ntpCheck ntpCheck.cpp -lwiringPi
$ sudo ./ntpCheck

ラズパイ7桁時計(自作中)の1/10秒表示とGPSの1ppsを比べてみました。

↓ 右端の7セグLEDが1/10秒で、GPSの赤LEDのフラッシュ時間も1/10秒です。

 

Stratum 1 のNTPサーバーと参照同期中のラズパイ7桁時計の1/10秒と、GPSの1PPSを比べる為に、高速度カメラ(iPhoneのスローモーション240fps)で撮影した動画です。

1/10秒単位の表示もバッチリ合っていますね。

このラズパイとGPSは繋げていません。別々に作動しています。

 

↓ ラズパイ7桁時計のソースコード

/*
 * clock2.cpp
 * 
 * 1/10秒表示テスト
 * 
 * 7桁ダイナミック表示対応
 * 時刻 08:34:27.6 の場合
 * 表示順序は 6 -> 7 -> 2 -> 4 -> 3 -> 8 -> 0
 *
 */
 
#include <iostream>
#include <pigpio.h>
#include <unistd.h>

#define H 0x1
#define L 0x0
#define COM_ON 0x1
#define COM_OFF 0x0

using namespace std;

void mainLoop(void);

int main(void)
{
    if (daemon(0, 0) == 0) { // デーモン関数
        mainLoop();
    } else {
        cout << "error" << endl;
    }
    return 0;
}

void mainLoop(void)
{    
    int i, j;
    int num[7];
    struct timespec ts;
    struct tm lotm;
		       // .1秒 1秒 10秒 1分 10分 1時 10時
	          // Common 1   2   3   4   5   6   7
    static int common[7] = {25, 24, 23, 18, 22, 27, 17};
                    // GPIO 25  24  23  18  22  27  17

	             // 7seg a   b   c   d   e   f  g   dp
    static int nanaseg[8] = {21, 20, 16, 26, 19, 6, 13, 12};
                     // GPIO 21  20  16  26  19  6  13  12

    static int segdata[12][8] = { // 7セグメントデータ
      // a  b  c  d  e  f  g  dp
	{H, H, H, H, H, H, L, L}, // 0
	{L, H, H, L, L, L, L, L}, // 1
	{H, H, L, H, H, L, H, L}, // 2
	{H, H, H, H, L, L, H, L}, // 3
	{L, H, H, L, L, H, H, L}, // 4
	{H, L, H, H, L, H, H, L}, // 5
	{H, L, H, H, H, H, H, L}, // 6
	{H, H, H, L, L, L, L, L}, // 7
	{H, H, H, H, H, H, H, L}, // 8
	{H, H, H, H, L, H, H, L}, // 9
	{L, L, L, L, L, L, L, L}, // Blank
	{L, L, L, L, L, L, L, H}, // dp
    };
    
    // pigpioライブラリ初期化
    if (gpioInitialise() < 0) exit(1);
    
    // コモン用GPIOピンをアウトプット設定
    for (i = 0; i < 7; i++) {
	gpioSetMode(common[i], PI_OUTPUT);
    }
    
    // セグメント用GPIOピンをアウトプット設定
    for (i = 0; i < 8; i++) {
	gpioSetMode(nanaseg[i], PI_OUTPUT);
    }
    
    /*
     *
     * 時刻取得及びダイナミック表示ルーチン(無限ループ)
     *
     * 時刻取得、1桁2ミリ秒表示×7、時刻取得、1桁2ミリ秒表示×7の繰り返し
     *
     */
    while (1) {
	
	clock_gettime(CLOCK_REALTIME, &ts); // 時刻の取得
	localtime_r(&ts.tv_sec, &lotm); // ローカル時間に変換
	num[0] = ts.tv_nsec / 100000000; // 0.1秒の桁取得
	num[1] = lotm.tm_sec % 10;       //   1秒の桁取得
	num[2] = lotm.tm_sec / 10;       //  10秒の桁取得
	num[3] = lotm.tm_min % 10;       //   1分の桁取得
	num[4] = lotm.tm_min / 10;       //  10分の桁取得
	num[5] = lotm.tm_hour % 10;      //   1時の桁取得
	num[6] = lotm.tm_hour / 10;      //  10時の桁取得
	
	for (j = 0; j < 7; j++) { // 7桁ダイナミック表示ルーチン

	    for (i = 0; i < 8; i++) { // 7セグメントデータセット
		gpioWrite(nanaseg[i], segdata[num[j]][i]);
	    }
	    gpioWrite(common[j], COM_ON);   // 7セグLED点灯
	    gpioDelay(2000);                // 2ミリ秒点灯維持
	    gpioWrite(common[j], COM_OFF);  // 7セグLED消灯
	}
    }
    
    // pigpioライブラリ終了処理
    gpioTerminate(); //  これは無限ループに含まれないので不要?
}
$ g++ -Wall -pthread -o clock2 clock2.cpp -lpigpio -lrt
$ sudo ./clock2

 

ラズパイをPi4からZeroに変えました。CPU使用率の変化は

Pi4:約8.0%
Zero:約10.5%

 

このプログラムの表示遅延は最大で約14.8ミリ秒(0.0148秒)、最小は限り無くゼロのはず。その理由は約14.8ミリ秒置きに時刻取得して、表示を更新しているからです。

↓ 0.1秒桁用7セグLEDのコモン端子に繋がるオンオフを、オシロスコープで観測した画像です。