閉じる

UI言語[UI Language]

記事自体は翻訳されません! 記事によって英語版があったりなかったりします。翻訳がある記事は文頭に記載があるよ!
Each articles themselves will not be translated by this setting. Some of article has translation and some of them doesn't. You will notice if the article has its translation by its preamble!

テーマ[Theme]


アイキャッチ画像

【Arduino】SDカードのファイルのタイムスタンプを取得、変更する


SDカードのファイルにタイムスタンプをちゃんと入れたい

Arduino(あるいはESP32など)のスケッチから、SDカード上のファイルのタイムスタンプを取得したり変更する方法です。
SDカードにファイルを作成するのはとっても簡単なんですが、タイムスタンプをどうこうという情報はあまり見つからないのでまとめておきたいと思います。

情報が少ないってことは要するに、SDカード上のファイルのタイムスタンプなんて誰も気にしてないのかもしれません(笑)。実際、SDカード上のファイルに更新日時が無いからと言って特に困ることはなく、 “エクスプローラーでSDカードを見た時にスッキリする”ぐらいの効果しか無いです。でもまー、ちゃんとした作成日時、更新日時が入ってると精神衛生上とても気持ちが良いので……

エクスプローラー
エクスプローラー

Arduinoで使ったSDカードのファイル、タイムスタンプ無いがち。


使用ライブラリ

SdFat by Bill Greiman

SDカードを扱うライブラリはいくつかあるようなのですが、昔調べた時にタイムスタンプを書き込めたのがこのライブラリだったので、軸の秤で使っています。Arduino IDEのライブラリマネージャからインストールできます。
今現在(2022年1月)も頻繁に更新されてて、迂闊うかつにアップデートしちゃうと既存のコードがちょいちょい動かなくなるのが玉にきず

SdFat
SdFat

今回使っているバージョンは 2.1.2 です。


タイムスタンプの書き込み

コールバックを使った方法

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;
}

ただまあ、ファイルオープンの途中でコールバックを変更したりとあまり気持ちの良い方法ではないので、下記の専用メソッドを使う方が良いと思います。


専用のメソッドで書き込む方法

Filetimestampメソッドを使います。ファイルを開いている状態で呼ぶ必要がありますが、作成日時と更新日時それぞれを直感的に書き込めるのでわかりやすいです。

#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モジュール使わないと現在時刻わからないので、タイムスタンプ書き込めても書き込む値がわからないと言う(笑)。
軸の秤では一応手動で日付時刻を設定できるようにしてあって、設定されている場合はできるだけファイルに更新日時を書き込むようにしてあります。
ま、再起動すると忘れちゃうんだけどな!

この記事のタグ[This article is filed under]: Arduino[Arduino] | ESP32[ESP32] | 電子工作[Electronics] | C++言語[C++]


この記事はここで終わりです。
読んでいただきありがとうございました。
良かったらシェアしてね!

That's all for this article. Thank you for your reading.
Please share this if you like it!

Twitter | Reddit | Facebook | Pinterest | Pocket

前の記事[Prev Post]

前の記事のアイキャッチ画像

【軸の秤】はじめの一歩

次の記事[Next Post]

次の記事のアイキャッチ画像

【軸の秤】ファームウェアのダウンロード