🙌

pico-jxglib で Pico ボードに RTC を接続してファイルシステムにタイムスタンプを記録する話

に公開

pico-jxglib は、ワンボードマイコン Raspberry Pi Pico の Pico SDK プログラミングをサポートするライブラリです。

https://zenn.dev/ypsitau/articles/2025-01-24-jxglib-intro

今回は、Pico ボードに RTC モジュールを接続して、ファイルシステムにタイムスタンプを記録する方法を紹介します。シェルによるファイルシステムの操作については以下の記事を参照してください。

▶️ pico-jxglib のシェルでファイルシステムを操作する話 (自動補完とヒストリ機能で入力楽々)

RTC モジュールについて

RTC(Real-Time Clock)は、リアルタイムの日時を保持するためのハードウェアです。初代 Pico (RP2040) にはチップ内に RTC が内蔵されていて、「お、これで外付けハードウェアなしに日時情報を得られるぞ」とちょっと色めきだつのですが、残念ながら電源を切ると日時情報が失われてしまうので、あまり実用的ではありません。何にせよ、後継の Pico2 (RP2350) ではこのモジュールが取り除かれてしまったので、実使用の候補にはならないでしょう。

RTC を使う実用的な方法は、Pico ボードにバックアップ電池を装備した RTC モジュールを接続することです。これで Pico ボードの電源状態にかかわらず日時情報を保持できるようになります。

電子工作でよく使われている RTC モジュールというと、I2C で接続ができる DS3231 がよく挙げられます。DS1307 という廉価版もありますが、一日に数秒もずれるらしいので、わずかな価格差で高精度な DS3231 を使うのが良いでしょう。

僕が Amazon で購入した DS3231 モジュールは、以下のようなものです。

rtc-ds3231

バックアップ電池が初めから装備されていますし、コンパクトな形状をしているのが良い感じです。基板に印刷されている信号名が分かりづらいのですが、+ = VCCD = SDAC = SCL- = GND となっています。

今回の記事では、この DS3231 モジュールを Pico ボードに接続し[1]、RTC を使った以下の機能を実現します。

  • ファイルシステムにタイムスタンプを記録する
  • シェルのプロンプトに日時を表示する
  • 時計アプリを作成する

実際のプロジェクト

開発環境のセットアップ

Visual Studio Code や Git ツール、Pico SDK のセットアップが済んでいない方は「Pico SDK ことはじめ」 をご覧ください。

GitHub から pico-jxglib をクローンします。

git clone https://github.com/ypsitau/pico-jxglib.git
cd pico-jxglib
git submodule update --init

プロジェクトの作成

VSCode のコマンドパレットから >Raspberry Pi Pico: New Pico Project を実行し、以下の内容でプロジェクトを作成します。Pico SDK プロジェクト作成の詳細や、ビルド、ボードへの書き込み方法については「Pico SDK ことはじめ」 を参照ください。

  • Name ... プロジェクト名を入力します。今回は例として rtctest を入力します
  • Board type ... ボード種別を選択します
  • Location ... プロジェクトディレクトリを作る一つ上のディレクトリを選択します
  • Stdio support .. ターミナルソフトに接続するポート (UART または USB) を選択します
  • Code generation options ... Generate C++ code にチェックをつけます

プロジェクトディレクトリと pico-jxglib のディレクトリ配置が以下のようになっていると想定します。

├── pico-jxglib/
└── rtctest/
    ├── CMakeLists.txt
    ├── rtctest.cpp
    └── ...

以下、このプロジェクトをもとに CMakeLists.txt やソースファイルを編集してプログラムを作成していきます。

ファイルシステムにタイムスタンプを記録する

RTC から日時情報を取得して、ファイルシステムにタイムスタンプを記録できるようにします。

ブレッドボードの配線イメージは以下の通りです。

circuit-rtc

CMakeLists.txt の最後に以下の行を追加します。

CMakeLists.txt
target_link_libraries(rtctest jxglib_RTC_DS323x jxglib_LFS_Flash jxglib_FAT_Flash jxglib_Serial jxglib_ShellCmd_FS)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../pico-jxglib pico-jxglib)
jxglib_configure_FAT(rtctest FF_VOLUMES 1)

ソースファイルを以下のように編集します。

rtctest.cpp
#include <stdio.h>
#include "pico/stdlib.h"
#include "jxglib/Serial.h"
#include "jxglib/Shell.h"
#include "jxglib/LFS/Flash.h"
#include "jxglib/FAT/Flash.h"
#include "jxglib/RTC/DS323x.h"

using namespace jxglib;

int main()
{
    ::stdio_init_all();
    LFS::Flash driveA("A:",  0x1010'0000, 0x0004'0000); // Flash address and size 256kB
    LFS::Flash driveB("B:",  0x1014'0000, 0x0004'0000); // Flash address and size 256kB
    LFS::Flash driveC("*C:", 0x1018'0000, 0x0004'0000); // Flash address and size 256kB
    FAT::Flash driveD("D:",  0x101c'0000, 0x0004'0000); // Flash address and size 256kB
    Serial::Terminal terminal;
    Shell::AttachTerminal(terminal.Initialize());
    ::i2c_init(i2c0, 400'000);
    GPIO16.set_function_I2C0_SDA().pull_up();
    GPIO17.set_function_I2C0_SCL().pull_up();
    RTC::DS323x rtc(i2c0);
    for (;;) {
        // :
        // any other jobs
        // :
        Tickable::Tick();
    }
}

このソースファイルは「pico-jxglib のシェルでファイルシステムを操作する話 (自動補完とヒストリ機能で入力楽々)」で紹介したものをベースにしており、Pico ボードのフラッシュメモリ上に LittleFS と FAT ファイルシステムのドライブを作成します。I2C の初期化と、RTC::DS323x インスタンスの生成を追加すると、ファイルシステムにタイムスタンプを記録できるようになります。

上のプログラムを実行すると、シェルが起動します。シェルの操作は UART または USB で接続したホスト PC のターミナルソフトから行います。

RTC の日時を設定するには rtc コマンドを日付や時刻を引数にして実行します。

C:?>rtc 2025-06-22 12:34:56
2025-06-22 12:34:56.000

RTC の現在日時を確認するには rtc コマンドを引数なしで実行します。

C:?>rtc
2025-06-22 12:35:32.000

タイムスタンプが記録できることを確認するために、ファイルを作成してみます。最初の状態だとドライブはまだフォーマットされていないので。以下のコマンドを実行してフォーマットしてください。

C:?>format a: b: c: d:
drive a: formatted in LittleFS
drive b: formatted in LittleFS
drive c: formatted in LittleFS
drive d: formatted in FAT12
C:/>ls-drive
 Drive  Format           Total
 A:     LittleFS         256K
 B:     LittleFS         256K
*C:     LittleFS         256K
 D:     FAT12            256K

C: ドライブは LittleFS でフォーマットされています。このドライブ上に cat でファイルを作成します。

C:/>cat > test.txt
This is a test file.
^C

ls コマンドを実行してみると:

C:/>ls
-a--- 2025-06-22 12:36:39     21 test.txt

タイムスタンプが記録されていることが分かります。LittleFS はタイムスタンプを持たないファイルシステムですが、pico-jxglib ではファイルのアトリビュート領域に 64 ビット UNIX 時刻の形式で日時情報を記録しています。

touch コマンドを使うと、ファイルのタイムスタンプを更新できます。

C:/>touch test.txt
C:/>ls
-a--- 2025-06-22 12:37:38     21 test.txt

シェルのプロンプトに日時を表示する

シェルのプロンプトに日時を表示することができます。ブレッドボードの配線とプログラムは前のセクションと同じものを使用します。

シェルのプロンプトを変更するには prompt コマンドを使用します。%Y や %M などのフォーマット文字列を指定することで、日時を表示することができます。

C:/>prompt "%Y-%M-%D %h:%m:%s>"
2025-06-22 12:45:07>
2025-06-22 12:45:08>
2025-06-22 12:45:13>
2025-06-22 12:45:14>

prompt で指定できるフォーマット文字列は以下の通りです。

フォーマット文字 意味
%d カレントドライブ C:
%w カレントディレクトリ /work/images
%Y 年(4桁) 2025
%y 年(下2桁) 25
%M 月(2桁) 06
%D 日(2桁) 22
%h 時(2桁, 24h) 12
%H 時(2桁, 12h) 12
%m 分(2桁) 45
%s 秒(2桁) 07
%a AM/PM AM, PM

時計アプリを作成する

RTC モジュールから日時情報を取得して、TFT LCD ST7789 に表示する時計アプリを作成します。

ブレッドボードの配線イメージは以下の通りです。

circuit-rtc-st7789

CMakeLists.txt の最後に以下の行を追加します。

CMakeLists.txt
target_link_libraries(rtctest jxglib_ST7789 jxglib_RTC_DS323x)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../pico-jxglib pico-jxglib)

ソースファイルを以下のように編集します。

rtctest.cpp
#include <stdio.h>
#include "pico/stdlib.h"
#include "jxglib/RTC/DS323x.h"
#include "jxglib/ST7789.h"
#include "jxglib/Font/shinonome18.h"

using namespace jxglib;

int main()
{
    stdio_init_all();
    ::spi_init(spi1, 125'000'000);
    GPIO14.set_function_SPI1_SCK();
    GPIO15.set_function_SPI1_TX();
    ST7789 display(spi1, 240, 320, {RST: GPIO10, DC: GPIO11, CS: GPIO12, BL: GPIO13});
    display.Initialize(Display::Dir::Rotate90);
    ::i2c_init(i2c0, 400'000);
    GPIO16.set_function_I2C0_SDA().pull_up();
    GPIO17.set_function_I2C0_SCL().pull_up();
    RTC::DS323x rtc(i2c0);
    DateTime dtPrev;
    for (;;) {
        DateTime dt;
        RTC::Get(&dt);
        if (dt != dtPrev) {
            int ySeparator = display.GetHeight() * 2 / 5;
            dtPrev = dt;
            char str[32];
            display.SetFont(Font::shinonome18).SetFontScale(2, 2).SetColor(Color(192, 192, 192));
            ::snprintf(str, sizeof(str), "%04d-%02d-%02d", dt.year, dt.month, dt.day);
            Size size = display.CalcStringSize(str);
            display.DrawString((display.GetWidth() - size.width) / 2, ySeparator - 8 - size.height, str);
            display.SetFont(Font::shinonome18).SetFontScale(4, 4).SetColor(Color(192, 192, 255));
            ::snprintf(str, sizeof(str), "%02d:%02d:%02d", dt.hour, dt.min, dt.sec);
            size = display.CalcStringSize(str);
            display.DrawString((display.GetWidth() - size.width) / 2, ySeparator + 8, str);
            display.Refresh();
        }
        Tickable::Tick();
    }
}

clock-app

おまけ: Pico の RTC を利用する

RTC::Pico インスタンスを生成することで、Pico ボードの内蔵 RTC を利用することもできます。初代 Pico (RP2040) 専用です。Pico2 (RP2350) では、コンパイルに必要なインクルードファイル hardware/rtc.h がないためエラーになります。

CMakeLists.txt の最後に以下の行を追加します。

CMakeLists.txt
target_link_libraries(rtctest jxglib_RTC_Pico jxglib_LFS_Flash jxglib_FAT_Flash jxglib_Serial jxglib_ShellCmd_FS)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../pico-jxglib pico-jxglib)
jxglib_configure_FAT(rtctest FF_VOLUMES 1)

ソースファイルを以下のように編集します。

rtctest.cpp
#include <stdio.h>
#include "pico/stdlib.h"
#include "jxglib/Serial.h"
#include "jxglib/Shell.h"
#include "jxglib/LFS/Flash.h"
#include "jxglib/FAT/Flash.h"
#include "jxglib/RTC/Pico.h"

using namespace jxglib;

int main()
{
    ::stdio_init_all();
    LFS::Flash driveA("A:",  0x1010'0000, 0x0004'0000); // Flash address and size 256kB
    LFS::Flash driveB("B:",  0x1014'0000, 0x0004'0000); // Flash address and size 256kB
    LFS::Flash driveC("*C:", 0x1018'0000, 0x0004'0000); // Flash address and size 256kB
    FAT::Flash driveD("D:",  0x101c'0000, 0x0004'0000); // Flash address and size 256kB
    Serial::Terminal terminal;
    Shell::AttachTerminal(terminal.Initialize());
    RTC::Pico rtc;
    for (;;) {
        // :
        // any other jobs
        // :
        Tickable::Tick();
    }
}

rtc コマンドなどで日時を確認した時点で RTC が動き始めます。

脚注
  1. DS1307 も I2C アドレスやデータフォーマットなどが DS3231 と共通しているので、同じように扱うことができると思います。 ↩︎

GitHubで編集を提案

Discussion