外国のYouTuberの動画と、そこからのリンク先のページが参考になります。

外国のYouTuberの動画
Use Raspberry Pi 5 GPIO – Push button input – LED output – ChatGTP for code – GPIOD [PART 2 OF 2]

リンク先のページ
GPIO Programming: Exploring the libgpiod Library

 

以下、素人の私が書く事ですので、何処かに誤りがあるかも知れません。私本人は以下のプログラムが動くので、取り敢えずこれで良しと思っていますが。それを承知の上で参考にして下さい。

 

ラズパイ5のGPIOはgpiodで使うみたい。ラズパイ4まではpigpioでLチカ出来ていた。pigpioを使用したソースコードでもラズパイ5でコンパイルは成功する。だけど、そのソフトを走らせると不動作で「ラズパイでは無いようです」みたいに言われてしまう。

↓ この画像は、ラズパイ5にgpiod等をインストールして、/dev/gpiochip4を利用してLチカしている所です。

 

↓ C言語でGPIOを使用したい時に必要。

$ sudo apt install gpiod libgpiod2 libgpiod-dev

 

↓ chronyc sources

↑ PPSはこのラズパイ5。pi4やzero2にpi2は同じLAN内のラズパイで、どれもNTPサーバーです。PPSのラズパイ5、pi4、zero2の3つのラズパイは各々に繋がれたGPSの1PPSに同期中。pi2はLAN内のNTPサーバーを参照しています。

↓ 3つのLEDをchronyc sourcesの結果に応じて点灯させるプログラムのソースコード。

// Raspberry Pi 5
// pi@pi5:~ $ g++ -Wall -o chronyCheck chronyCheck.cpp -l gpiod

#include <err.h>
#include <cstring>
#include <iostream>
#include <gpiod.h>
#include <unistd.h>
#include <stdio.h>

#define BUF 256
#define H 1
#define L 0
#define PPS 10
#define NTP 9
#define NG 11

using namespace std;

enum kekka{
    pps_hantei = 10,
    ntp_hantei,
    ng_hantei
};

int mainLoop(void);

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

int mainLoop(void)
{
    FILE *fp;
    char *ret;
    char str[512], *ptr;
    const char* cmdline = "chronyc sources";
    
    enum kekka hantei;
    
    gpiod_chip *chip;
    gpiod_line *line_PPS, *line_NTP, *line_NG;
    //For Raspberry Pi 5 use gpiochip4 (For Raspberry Pi 4 use gpiochip0)
    const char *chipname = "gpiochip4";
        
    // Open GPIO chip
    chip = gpiod_chip_open_by_name(chipname);

    // Open GPIO lines
    line_PPS = gpiod_chip_get_line(chip, PPS);
    line_NTP = gpiod_chip_get_line(chip, NTP);
    line_NG = gpiod_chip_get_line(chip, NG);

    // Open LED lines for output
    gpiod_line_request_output(line_PPS, "chronyCheck", 0);
    gpiod_line_request_output(line_NTP, "chronyCheck", 0);
    gpiod_line_request_output(line_NG, "chronyCheck", 0);
    
   
    // 無限ループ
    while (1) {
        
        // コマンドの chronyc sources 実行
        if ((fp=popen(cmdline,"r")) == NULL) {
            err(EXIT_FAILURE, "%s", cmdline);
        }
        
        // chronyc sources 実行結果を1行づつチェックしながら
        // 最後の行まで繰り返す。その1行毎に応じて i の値を足し算する
        // 但し、377を最優先して検索行に * と 177 や 277 等が有っても同期判定しない
        do {
            ret = fgets(str, BUF, fp); // 1行読み取り
            ptr = strstr(str, " 377 "); //  377 を検索(377前後のスペースも含む)
            if (ptr != NULL) {
                ptr = strstr(str, "#*"); // #* を検索
                if (ptr != NULL) {       // 検索行に #* と 377 があった時だけ1PPS同期判定とする
                    hantei = pps_hantei;
                    // cout << "Synchronizing to 1PPS & 377" << endl;
                    ret = NULL; // ここに来たらdo-whileから抜けて1行読み取りを終了する
                } else {
                    ptr = strstr(str, "^*");
                    if (ptr != NULL) {
                        hantei = ntp_hantei;
                        // cout << "Synchronizing to NTP" << endl;
                        ret = NULL; // ここに来たらdo-whileから抜けて1行読み取りを終了する
                    } else {
                        ptr = strstr(str, "^+");
                        if (ptr != NULL) {
                            hantei = ntp_hantei;
                            // cout << "Synchronizing to NTP" << endl;
                            ret = NULL; // ここに来たらdo-whileから抜けて1行読み取りを終了する
                        } else {
                            ptr = strstr(str, "^-");
                            if (ptr != NULL) {
                                hantei = ntp_hantei;
                                // cout << "Synchronizing to NTP" << endl;
                                ret = NULL; // ここに来たらdo-whileから抜けて1行読み取りを終了する
                            } else {
                                hantei = ng_hantei; // ここに来た場合は、1PPSにもNTPにも同期せず
                                // cout << "NG" << endl;
                            }
                        }
                    }
                }
            } else {
                hantei = ng_hantei; // 377が無い場合1PPSにもNTPにも同期せずとする
                // cout << "NG" << endl;
            }
        } while (ret != NULL);
    
        // 全LEDを消灯
        gpiod_line_set_value(line_PPS, L);
        gpiod_line_set_value(line_NTP, L);
        gpiod_line_set_value(line_NG, L);
    
        // hanteiに応じてLEDを点灯
        switch (hantei) {
            case pps_hantei:
                gpiod_line_set_value(line_PPS, H);
                sleep(70); // 1PPS同期判定時は70秒休憩
                break;
            case ntp_hantei:
                gpiod_line_set_value(line_NTP, H);
                sleep(16); // NTP同期判定時は16秒休憩
                break;
            case ng_hantei:
                gpiod_line_set_value(line_NG, H);
                sleep(4); // NG判定時は4秒休憩
                break;
            default: // ここに来るはず無いので意味合いとしては異常表示
                gpiod_line_set_value(line_PPS, H);
                gpiod_line_set_value(line_NTP, H);
                gpiod_line_set_value(line_NG, H);   
        }

        pclose(fp);
        
    }

    // 以下は無限ループ外なので不要?
    gpiod_line_release(line_PPS);
    gpiod_line_release(line_NTP);
    gpiod_line_release(line_NG);
    gpiod_chip_close(chip);
    
    return 0;
}

↑ デーモン関数はOS起動と共に起動して常に作動する為の物。それにはユニットファイル作成等も必要だけど。ここには記載していません。

コンパイル

$ g++ -Wall -o chronyCheck chronyCheck.cpp -l gpiod

RPi5(ラズパイ5)にGNSSモジュールを繋げました。そのモジュールなんですが、欲しいGNSSモジュールが秋月電子に無いので、今回はアマゾンでポチりました。

ラズパイ5をNTPサーバーにする為のインストールは。↓

$ sudo apt update
$ sudo apt install chrony
$ sudo apt install gpsd gpsd-clients pps-tools

↓ 三次元測位中で赤LEDが点滅して1PPS出力しています。これで一応、Stratum 1相当のNTPサーバーになっています。

↑ 白色のケースやねじ類は、3DプリンターEnder-3 V3 SEとPLAフィラメントで造形しました。

3D model-viewer

 

アマゾンで入手したのは3点で、GNSSモジュールGY-NEO-6MV2(税込999円)とGPS外付けアンテナ線長3m(税込980円)とSMA延長ケーブル5m(税込799円)です。

NEO-6MのVCCにラズパイ1番ピン3.3Vを印加。他の配線は秋月電子のGNSSモジュールと同じ。外部アンテナは延長ケーブル込みの長さ8mのケーブルを繋げて外に出しています。

↓ ラズパイ5とNEO-6Mの配線は画像の通り。There is a wire that looks like it might be tangled in the picture, but we have not misplaced the connection, so there is nothing for you to worry about.

それに、秋月電子で販売されていたGNSSモジュール、K-13849やK-09991などの1PPSはアクティブLowだけど、NEO-6Mの1PPSは、それとは反対のアクティブHighみたいです。なのでconfig.txtの設定が一部異なる。
dtparam=assert_falling_edge=true が不要。↓

$ sudo vi /boot/firmware/config.txt

[all]

dtparam=uart0=on

# GPS 1PPS /dev/serial0 -> ttyAMA0
dtparam=uart0_console
enable_uart=1
dtoverlay=pps-gpio
dtparam=gpioin=18
#dtparam=assert_falling_edge=true

M3サイズの雄ねじを造形して使用したら折れました。

↑ 指先で締めていたら1本だけ折れた。造形直後(冷えてから)M3サイズの雌ねじに雄ねじを入れようと「少しきついかなぁ」と思いながら指先で少し強目に回していたら折れました。折れた雄ねじの先は雌ねじに残ったまま。

この雄ねじは立てて造形したので、余計に折れやすい。ねじの設計は緩めがいい?

または、造形物同士の雄ねじ雌ねじをいきなりねじ込むのは良くないかも。ザラザラした造形物なので金属のナットやビスで、すり合わせしてから使用すれば少しは改善するかも。

 

電子工作で良く使う、このサイズのねじが自分好みで造形出来る便利さは計り知れない。でもこんな簡単に折れては、期待感が半減してしまう。

最初から分かってはいるけど、3Dプリンターで造形したねじは、強度や耐久性が求められる部分には使え無い。

電子工作で良く使うM2.5のビスやナット。それを3Dプリンターで造形出来たらいいなぁ〜。と言う事でやってみました。

何度か設計を見直して造形。雄ねじ雌ねじ共に妥協出来る造形が出来ました。と言っても樹脂製ですし、小物の基板用なら使える程度ですが。でも好きな長さでスペーサーが作れるので、電子工作には3Dプリンターは便利です。↓

造形条件はノズル0.4mm、レイヤー高さ0.12mm、スライス公差は中間です。

ねじの出来具合はと言うと。いつものレイヤー高さが0.2mmでは、少々不安だけど、ねじにはなっています。1番良いのは0.12mmです。最も0.12mmに合わせた設計なので、良いはずなんだけど。0.16mmも良いけど少し寸法が合わない感じで、製品の金属ナットが少し緩いです。

ねじ造形は、あるレイヤー高さに合わせて設計すると、他のレイヤー高さに合わなくなりますね。また、スライス公差の中間、排他、包括でも寸法が合わなくなるので、スライス公差も注意項目です。

↓ そのレイヤー高さによる違いを、スライサーソフト(Cura)のプレビューで比較。ノズルは0.4mm使用。この中で良い0.12mmでも、ねじが小さ過ぎて再現性があまり良く無いです。趣味の電子工作で使うにしても、M2.5がギリギリ限界でしょうか。

↑ スライス公差は中間です。

↓ M2.5雄ねじ(造形物)の設計値。

 

↓ Onshapeのドキュメントリスト。今回作成した3つの3Dデータ。

↑ 造形物はM2.5雄雌ねじスペーサー。他〇〇タップや〇〇ダイスは、ブーリアン演算で使用しました。

ラズパイ5に、余っているGPS(GNSS)モジュールを繋げてみようと思い、ケースをOnshapeで設計中。そのケースは3Dプリンター(Ender3 V3 SE)で造形します。本当に3Dプリンターは便利。

使用3D CADはOnshapeです。先日よりFreeCADからOnshapeに変えたんですが、FreeCADより使い心地が良いです。

↓ 画像にある3Dモデルは、全て私がOnshapeで作りました。今はOnshape上にて最後の確認、ねじの収まり具合や、GNSSモジュールが他と干渉しないか確認している所です。断面図もいい感じに見れて、なかなか良いです。

 

GNSSモジュールが収まる所の設計と造形は出来ました。

 

RPi5にGNSSモジュールを繋げましたが、RPi4やzero2の時と同じ様になりません。

何がならないかと言うと、GNSSモジュールとNTPが同期出来ないんです。RPi5はUART端子が新たに増えて、その影響なのか、ttyS0からttyAMA0に変更したりググって色々試しましたが、私には解決出来ませんでした。

誰かが、それを解決してくれるのを待ちましょう。