趣味の開発ノート

ITの学習やプログラミング・ノーコードアプリ開発のことなど。

【M5Stack】スケッチ例「TFT_Clock_Digital」を読み解く02. __TIME__定数でコンパイル時刻を取得

M5Stackで電子工作ができるようになりたくて、練習中。
一定間隔でセンサ値をデータベース(Spreadsheet)に送るような仕組みを作ることを目的に、タイマー周りの機能を調べている。

サンプルスケッチ「TFT_Clock_Digital」について読解しながら、どういうスケッチの構造になっているのかを学んでいる。
(このスケッチは、Arduino IDEのメニューから、「ファイル」>「スケッチ例」>「M5Stack」>「Advanced」>「Display」>「TFT_Clock_Digital」から開くことができる。)

前回は、setup()関数の中身を見てみた。

nouka-it.hatenablog.com

今回はloop()関数の中身を見ていきたいんだけど、その前にグローバルで定義されている変数と関数を理解する必要がありそうなので、そちらを見ていく。

グローバル定義

▼サンプルスケッチ TFT_Clock_Digitalのグローバル定義

#include <M5Stack.h>

#define TFT_GREY 0x5AEB

uint32_t targetTime = 0;                    // for next 1 second timeout

static uint8_t conv2d(const char* p); // Forward declaration needed for IDE 1.6.x

uint8_t hh = conv2d(__TIME__), mm = conv2d(__TIME__ + 3), ss = conv2d(__TIME__ + 6); // Get H, M, S from compile time

byte omm = 99, oss = 99;
byte xcolon = 0, xsecs = 0;
unsigned int colour = 0;

この中で、今回見ていきたいのが以下の3種類の変数。

  • uint32_t型 変数 targetTime
  • uint8_t型 関数 conv2d
  • uint8_t型 変数 hh, mm, ss

uint32_t, uint8_t型について

まず、uint32_t, uint8_t型については、C99(C言語の規格)から使える比較的新しい(?)整数型らしい。
旧来のchar, short, intとそれぞれの型の符号無しバージョンunsigned char, unsigned short, unsigned intなどの型の代わりに使えて、一目でわかりやすい型名になっているのが利点らしい。

例としてuint32_tについてみてみると、

  • uintとuがついているので符号無しの整数型(intだと符号有りの整数型)
  • 数字32はビット数を表す

という意味。つまり、

  • uint32_tは32ビット(4バイト)の符号無し整数型 = 旧来のint型を表している
  • uint8_tは8ビット(1バイト)の符号無し整数型 = 旧来のchar型を表している

blog.tafetafe.work

変数targetTime

▼サンプルスケッチより

uint32_t targetTime = 0;                    // for next 1 second timeout

「for next 1 second timeout」・・・直訳すると「次の1秒間のタイムアウト」?
おそらくなんだけど、loop関数での使われ方を見ると「前回の時計表示から1秒経っているかどうかを判断して、もし1秒経っていたら表示秒数を進める」という処理をするために使われているように見える。

関数conv2dと変数hh, mm, ss

▼サンプルスケッチより

static uint8_t conv2d(const char* p); // Forward declaration needed for IDE 1.6.x
uint8_t hh = conv2d(__TIME__), mm = conv2d(__TIME__ + 3), ss = conv2d(__TIME__ + 6); // Get H, M, S from compile time

conv2dコンパイルされた時刻を取得するための関数、だそう。

▼conv2dの中身

// Function to extract numbers from compile time string
static uint8_t conv2d(const char* p) {
  uint8_t v = 0;
  if ('0' <= *p && *p <= '9')
    v = *p - '0';
  return 10 * v + *++p - '0';
}

kyokucho1989.chillout.jp

そして、この関数と何やら__TIME__という値を使って、コンパイルされた時間、分、秒をそれぞれ変数hh, mm, ssに格納している。 となると、この__TIME__というものの中に時刻が入っていたりするのかな?

TIME定数でコンパイル時刻を取得できる

ということで、時刻表示している仕組みを理解。

まず、C/C++ではコンパイル時に__TIME__という定数にコンパイル時間が格納される。
そしてコンパイルからの経過時間mills()を加算することで、現在時刻を出すことができる。

というような理屈かな?

確かに、これなら通信を使わずに時刻表示が実現できる。毎時00分に処理する、という動作もできそうだ。
(正確な時刻ではないけど、まあ運用上問題はなさそう。)

実際の__TIME__定数には1061159211のような数値の文字列が入っている。
どういうルールかイマイチわかんないけど、これをconv2d関数で時間、分、秒に変換しているらしい。

▼画面上にコンパイル時間を表示するスケッチ

#include <M5Stack.h>

void setup() {
  M5.begin();         // M5初期化
  M5.Power.begin();   // M5の電源の初期化

  // 画面の設定
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextSize(3);
  M5.Lcd.setTextColor(TFT_YELLOW, TFT_BLACK);

}

void loop() {

  // 画面の表示
  M5.Lcd.clear();
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.printf("__TIME__:\n");
  M5.Lcd.printf("%u", __TIME__);

  delay(10000);
}

f:id:massa_potato:20220220142254j:plain

今日のまとめ

サンプルスケッチ「TFT_Clock_Digital」のグローバルで定義されている変数と関数を見てみた。
コンパイラ時刻が__TIME__で数値文字列として取得できて、それをconv2d関数で時刻表示に変換することができることがわかった。

次こそはloop()関数の中身を見ていこう。