【Arduino】SDカードのファイルのタイムスタンプを取得、変更する
- SDカードのファイルにタイムスタンプをちゃんと入れたい
- 使用ライブラリ
- タイムスタンプの書き込み
- コールバックを使った方法
- 作成日時と更新日時に違う値を書き込む
- 専用のメソッドで書き込む方法
- コールバックを使った方法
- タイムスタンプの取得
- FAT16/32システムのタイムスタンプのフォーマット
SDカードのファイルにタイムスタンプをちゃんと入れたい
Arduino(あるいはESP32など)のスケッチから、SDカード上のファイルのタイムスタンプを取得したり変更する方法です。
SDカードにファイルを作成するのはとっても簡単なんですが、タイムスタンプをどうこうという情報はあまり見つからないのでまとめておきたいと思います。
情報が少ないってことは要するに、SDカード上のファイルのタイムスタンプなんて誰も気にしてないのかもしれません(笑)。実際、SDカード上のファイルに更新日時が無いからと言って特に困ることはなく、 “エクスプローラーでSDカードを見た時にスッキリする”ぐらいの効果しか無いです。でもまー、ちゃんとした作成日時、更新日時が入ってると精神衛生上とても気持ちが良いので……
使用ライブラリ
SdFat by Bill Greiman
SDカードを扱うライブラリはいくつかあるようなのですが、昔調べた時にタイムスタンプを書き込めたのがこのライブラリだったので、軸の秤で使っています。Arduino IDEのライブラリマネージャからインストールできます。
今現在(2022年1月)も頻繁に更新されてて、迂闊《うかつ》にアップデートしちゃうと既存のコードがちょいちょい動かなくなるのが玉に瑕《きず》。
今回使っているバージョンは 2.1.2 です。
GitHub - greiman/SdFat: Arduino FAT16/FAT32 exFAT Library
https://github.com/greiman/SdFat
タイムスタンプの書き込み
コールバックを使った方法
FsDateTime
のstaticメソッドsetCallback
を使って日付時刻を返すコールバックを登録しておく方法です。setup
関数などで一度登録しておけば後は特に意識しなくてもファイル作成時にタイムスタンプが書き込まれるので、通常はこの方法で十分かもしれません。
#include <SdFat.h>
static void dateTimeCallback(uint16_t *date, uint16_t *time, uint8_t *ms10)
{
// dateに西暦, 月, 日をFS_DATEマクロで変換して渡す。
*date = FS_DATE(2022, 1, 25);
// timeに時, 分, 秒をFS_TIMEマクロで変換して渡す。
// ただしFATの時刻の解像度が2秒単位なので、下記だと10秒になってしまう。
*time = FS_TIME(23, 30, 11);
// ミリ秒は10ms単位だが、そもそもFAT16/32では意味無し。
*ms10 = 0;
}
void setup()
{
// コールバックメソッドを登録
FsDateTime::setCallback(dateTimeCallback);
}
コールバック関数はミリ秒も返せるようになっていますが、そもそもFAT16/32のタイムスタンプは秒まで(しかも、秒も2秒単位)なので通常は意味がなさそうでした。exFAT用なのかな?FS_DATE, FS_TIME
マクロが何をやっているかは後述。
作成日時と更新日時に違う値を書き込む
上記の方法だと作成日時、更新日時に同じ値が書き込まれてしまいますが、下記のようにすることで一応別々の時刻の書き込みに成功しました。sync
メソッドのタイミングで更新日時が書き込まれるようです。
#include <SdFat.h>
void dummyMethod()
{
// 作成日時を返すコールバックメソッドを登録
FsDateTime::setCallback(createdDateTimeCallback);
File *file = new File();
file->open("testfile.txt", FILE_WRITE);
file->println("dummy text");
// 更新日時を返すコールバックメソッドを登録
FsDateTime::setCallback(modifiedDateTimeCallback);
file->sync();
file->close();
delete file;
}
ただまあ、ファイルオープンの途中でコールバックを変更したりとあまり気持ちの良い方法ではないので、下記の専用メソッドを使う方が良いと思います。
専用のメソッドで書き込む方法
File
のtimestamp
メソッドを使います。ファイルを開いている状態で呼ぶ必要がありますが、作成日時と更新日時それぞれを直感的に書き込めるのでわかりやすいです。
#include <SdFat.h>
void dummyMethod()
{
File *file = new File();
file->open("testfile.txt", FILE_WRITE);
if (!file->timestamp(T_CREATE, 2022, 1, 25, 23, 30, 55))
Serial.println("Created timestamp fail.");
if (!file->timestamp(T_WRITE, 2023, 12, 25, 0, 15, 25))
Serial.println("Modified timestamp fail.");
file->close();
delete file;
}
ファイルをオープンする必要はありますが、既存のファイルのタイムスタンプを変更する時はFILE_READ
モードでも問題なく変更できる模様。
また、T_CREATE, T_WRITE
の他にT_ACCESS
というアクセス日時を指定する定数もあるんですが、ファイルのアクセス日時を変更したりはできませんでした。ディレクトリ用かも?
タイムスタンプの取得
反対にファイルのタイムスタンプ取得は作成日時、更新日時、アクセス日毎《ごと》に専用のメソッドが用意されています。ちなみにSdFatライブラリのバージョンが2.1.2での方法です。少し古いバージョンではこれらのメソッドは無く、dirEntry
メソッドなるものを使ってました(ちなみにそのメソッドはもう無いみたいです)。
#include <SdFat.h>
void dummyMethod()
{
bool creationDateExists = false;
File *file = new File();
file->open("testfile.txt", FILE_READ);
uint16_t date, time;
// 作成日時
if (file->getCreateDateTime(&date, &time))
Serial.println("Create Date:" + String(date) + " Time:" + String(time));
else
Serial.println("Read created timestamp fail.");
// 更新日時
if (file->getModifyDateTime(&date, &time))
Serial.println("Modified Date:" + String(date) + " Time:" + String(time));
else
Serial.println("Read modified timestamp fail.");
// アクセス日時
if (file->getAccessDate(&date, &time))
Serial.println("Access Date:" + String(date) + " Time:" + String(time));
else
Serial.println("Read access timestamp fail.");
file->close();
delete file;
file = NULL;
}
ちなみに取得できる値は日付、時刻それぞれuint16_t
になっていますが、これはFAT16/32システム上でのタイムスタンプの格納形式そのままの模様。フォーマットは下記。
FAT16/32システムのタイムスタンプのフォーマット
これまでのコードに時々出てきたuint_16
型の日付と時刻ですが、これはそのままFAT16/FAT32システムのタイムスタンプの格納方式になっています。実際の年月日、時分秒の値がどのように格納されているかと言うと……
日付
上位から西暦7bit、月4bit、日5bitの16bitワード値です。西暦は1980年を0とする値で、7bitの最大値が127なので1980 + 127 = 2107年が最大値っぽいです。案外短いと言うか、今若い人は割と余裕でFAT32問題を体験できそう。月と日はそのままの値。
ビットの並びはこんな感じ。
↓16---+----+8---+----+
|yyyy yyym|mmmd dddd|
+----+----+----+----+
時刻
上位から時5bit, 分6bit, 秒5bitで格納されています。ただし秒は0~29の値で、実際の秒数に変換するには2倍する必要があります。
時刻のビットの並び。
↓16---+----+8---+----+
|hhhh hmmm|mmms ssss|
+----+----+----+----+
というわけでデコード処理をコードにまとめるとこんな感じ。FS_DATE, FS_TIME
マクロはこの逆を行っているのだと思われます。
void decodeFatDateTime(uint16_t date, uint16_t time, uint16_t *year, uint8_t *month, uint8_t *day, uint8_t *hour, uint8_t *min, uint8_t *sec)
{
// 日付を西暦、月、日に分解
*year = 1980 + (date >> (5 + 4)); // 1980年を0とする値になっている
*month = (date >> 5) & 0x0F;
*day = date & 0x1F;
// 時刻を時、分、秒に分解
*hour = (time >> (5 + 6));
*min = (time >> 5) & 0x3F;
*sec = (time & 0x1F) * 2; // 2秒単位なので2倍する
}
いじょ!
ここまで書いといてアレですが、そもそもマイコンのプロジェクトってRTCモジュール使わないと現在時刻わからないので、タイムスタンプ書き込めても書き込む値がわからないと言う(笑)。
軸の秤では一応手動で日付時刻を設定できるようにしてあって、設定されている場合はできるだけファイルに更新日時を書き込むようにしてあります。
ま、再起動すると忘れちゃうんだけどな!
この記事はここで終わりです。
読んでいただきありがとうございました。
良かったらシェアしてね!
That's all for this article. Thank you for your reading.
Please share this if you like it!