2026年10月9日金曜日

M5StickC Nixie tube Clock with NTC

M5StickC Nixie tube Clockで紹介した時計ですが、起動時にパソコンと同期する設定となっていたため再起動すると時間設定が出来ず表示がめちゃくちゃになります。これでは不便ですので、WiFi接続時はNTCと同期し、接続できないときはRTCの設定日時を使用するように変更しました。


いつもの様にろくに調べないで見切り発車したため、最初の記述は電源オンオフで+9時間されて時間が起動するたびにずれて行きました。原因を調べたところ、M5Unified.hはUTCを記述することが基本(推奨)となっているようで、日本時間で設定していると読みだした際に+9時間してしまったようです。さらっと書いていますが、これが判るまで結構時間が掛かりました。この対策で基本に則ってRTCにはUTCで記載するようにし、表示時にヘルパー関数を使用してJSTで表示するようにしています。


なお、確認用にボタンBを押すとUTC(RTC)とJSTを表示するようにしています(確認用)。ついでにボタンBが上になるように画面向きの設定を変更しています。

// M5StickC Nixie tube Clock: 2026.09.18
#include <M5Unified.h>
#include "vfd_18x34.h"
#include "vfd_35x67.h"
#include "wifi_setting.h"
#include <WiFi.h>
#include <time.h>

// NTP設定
const char* ntpServer1 = "0.pool.ntp.org";
const char* ntpServer2 = "1.pool.ntp.org";
const char* ntpServer3 = "2.pool.ntp.org";
const char* gmtOffset_sec = "JST-9"; 

int mode_ = 3; // 3:2Lines 2: 2Lines(YYMM), 1:1Line

const uint8_t* n[] =  { 
  vfd_18x34_0, vfd_18x34_1, vfd_18x34_2, vfd_18x34_3, vfd_18x34_4,
  vfd_18x34_5, vfd_18x34_6, vfd_18x34_7, vfd_18x34_8, vfd_18x34_9
  }; // vfd font 18x34
 
 const uint32_t ns[] =  { 
  sizeof(vfd_18x34_0), sizeof(vfd_18x34_1), sizeof(vfd_18x34_2), sizeof(vfd_18x34_3), sizeof(vfd_18x34_4),
  sizeof(vfd_18x34_5), sizeof(vfd_18x34_6), sizeof(vfd_18x34_7), sizeof(vfd_18x34_8), sizeof(vfd_18x34_9)
  }; // vfd font 18x34

const uint8_t* m[] =  { 
  vfd_35x67_0, vfd_35x67_1, vfd_35x67_2, vfd_35x67_3, vfd_35x67_4,
  vfd_35x67_5, vfd_35x67_6, vfd_35x67_7, vfd_35x67_8, vfd_35x67_9
  }; // vfd font 35x67

const uint32_t ms[] =  { 
  sizeof(vfd_35x67_0), sizeof(vfd_35x67_1), sizeof(vfd_35x67_2), sizeof(vfd_35x67_3), sizeof(vfd_35x67_4),
  sizeof(vfd_35x67_5), sizeof(vfd_35x67_6), sizeof(vfd_35x67_7), sizeof(vfd_35x67_8), sizeof(vfd_35x67_9)
  }; // vfd font 35x67

// 時間管理用の変数
unsigned long lastClockUpdate = 0;
unsigned long lastButtonCheck = 0;

// フェードエフェクト管理用の変数
bool isFading = false;
unsigned long lastFadeStepTime = 0;
int fadeStep = 0;
const int fadeBrightnessSequence[] = {120, 130, 140, 150, 160, 170, 180, 190, 200, 190, 180, 170, 160, 150, 140, 130, 120, 200};
const int FADE_STEPS_COUNT = 18;

// UTCからJSTのtm構造体に変換する共通ヘルパー関数
struct tm getJstTime() {
  auto dt = M5.Rtc.getDateTime();
  struct tm tm_utc;
  tm_utc.tm_year = dt.date.year - 1900;
  tm_utc.tm_mon  = dt.date.month - 1;
  tm_utc.tm_mday = dt.date.date;
  tm_utc.tm_hour = dt.time.hours;
  tm_utc.tm_min  = dt.time.minutes;
  tm_utc.tm_sec  = dt.time.seconds;
  tm_utc.tm_isdst = 0;

  time_t t_utc = mktime(&tm_utc);
  time_t t_jst = t_utc + (9 * 3600); // +9時間
  return *localtime(&t_jst);
}

// 関数のプロトタイプ宣言
void vfd_0_line();
void vfd_1_line();
void vfd_2_line();
void vfd_3_line();
void handleFade();
void triggerFade();

void setup(void){ 
  auto cfg = M5.config();
  M5.begin(cfg);

  // 画面の向きを設定 (1: 横向きなど)
  M5.Display.setRotation(1);
  M5.Display.fillScreen(TFT_BLACK);
  M5.Display.setBrightness(200);
  M5.Display.setTextSize(1);

  // Wi-Fi 接続
  WiFi.begin(ssid, password);
  M5.Display.print("Connecting to WiFi");

  // 最大5秒間、Wi-Fi接続を待つ
  int timeout = 0;
  while (WiFi.status() != WL_CONNECTED && timeout < 20) {
    delay(500);
    M5.Display.print(".");
    timeout++;
  }
  Serial.println();

  // Sync NTC when connecting Wi-Fi
  if (WiFi.status() == WL_CONNECTED) {
    M5.Display.println("WiFi Connected.");
    // Get time data from NTP
    M5.Display.println("WiFi Connected!\nSyncing NTP...");

    //  時差を設定してNTPから時刻を取得
    configTzTime(gmtOffset_sec, ntpServer1, ntpServer2, ntpServer3);

    // NTPから正しい現在時刻が届くまで最大15秒間ループして待つ
    struct tm timeinfo;
    int retry = 0;
    while (!getLocalTime(&timeinfo, 1000) && retry < 30) {
      delay(500);
      M5.Display.print(".");
      retry++;
    }

    if (retry <= 30) {
      time_t t = time(nullptr) + 1;  // Advance one second
      while (t > time(nullptr))   ;  // Synchronization in seconds
      M5.Rtc.setDateTime(gmtime(&t));

      delay(1000);
      M5.Display.println("RTC updated via NTP.");
    } else {
      M5.Display.println("\nNTP Error! Rebooting...");
      delay(3000);
      ESP.restart(); // 再起動してやり直す
    }

    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);
  } else {
    Serial.println("WiFi not available. Skip NTP sync.");
  }
  delay(2000);
  M5.Display.clear();
}

void loop(void) { 
  M5.update();
  unsigned long currentMillis = millis();

  // 1. ボタン入力の監視(200msごとにサクサク判定)
  if (currentMillis - lastButtonCheck >= 200) {
    if (M5.BtnA.isPressed()) {
      lastButtonCheck = currentMillis;
      if (mode_ == 3)      { mode_ = 1; }
      else if (mode_ == 2) { mode_ = 3; }
      else if (mode_ == 1) { mode_ = 2; }
      else if (mode_ == 0) { mode_ = 1; }
      M5.Display.fillScreen(TFT_BLACK); // モード変更時に画面クリア
    } else if (M5.BtnB.isPressed()) {
      lastButtonCheck = currentMillis;
      mode_ = 0;
      M5.Display.fillScreen(TFT_BLACK); // モード変更時に画面クリア
    }
  }

  // 2. 時計の定期描画(500msごと)
  if (currentMillis - lastClockUpdate >= 500) {
    lastClockUpdate = currentMillis;
    
    if (mode_ == 3)      { vfd_3_line(); } // hh:mm:ss
    else if (mode_ == 2) { vfd_2_line(); } // y:m:d hh:mm:ss
    else if (mode_ == 1) { vfd_1_line(); } // mm:ss
    else if (mode_ == 0) { vfd_0_line(); } // text
  }

  // 3. フェードエフェクトの進行管理(バックグラウンド処理)
  if (isFading) {
    handleFade();
  }
}

void vfd_3_line(){
  // 時刻を保持する構造体
  struct tm jst = getJstTime();
  int h1 = int(jst.tm_hour/ 10 );
  int h2 = int(jst.tm_hour- h1*10 );
  int i1 = int(jst.tm_min / 10 );
  int i2 = int(jst.tm_min - i1*10 );
  int s1 = int(jst.tm_sec / 10 );
  int s2 = int(jst.tm_sec - s1*10 );

  M5.Display.drawPng (m[h1], ms[h1],   3,  0, 35, 67);
  M5.Display.drawPng (m[h2], ms[h2],  41,  0, 35, 67);
  M5.Display.drawPixel( 79, 22, ORANGE); M5.Display.drawPixel( 79, 48, ORANGE); 
  M5.Display.drawPixel( 79, 21, YELLOW); M5.Display.drawPixel( 79, 47, YELLOW); 
  M5.Display.drawPng (m[i1], ms[i1],  83,  0, 35, 67);
  M5.Display.drawPng (m[i2], ms[i2], 121,  0, 35, 67);
  M5.Display.drawPng (n[s1], ns[s1], 120, 45, 18, 34);
  M5.Display.drawPng (n[s2], ns[s2], 140, 45, 18, 34);

  // フェード中でない、かつ00秒のときだけ1回だけ起動する
  if (s1 == 0 && s2 == 0 && !isFading) { triggerFade(); }
}

void vfd_2_line(){
  struct tm jst = getJstTime();
  int yearFull = jst.tm_year + 1900;
  int monthFull = jst.tm_mon + 1;
  int y1 = int( yearFull   / 1000 );
  int y2 = int((yearFull  - y1*1000 ) / 100 );
  int y3 = int((yearFull  - y1*1000 - y2*100 ) / 10 );
  int y4 = int( yearFull  - y1*1000 - y2*100 - y3*10 );
  int j1 = int(monthFull  / 10);
  int j2 = int(monthFull  - j1*10 );
  int d1 = int(jst.tm_mday   / 10 );
  int d2 = int(jst.tm_mday   - d1*10 );
  int h1 = int(jst.tm_hour  / 10) ;
  int h2 = int(jst.tm_hour  - h1*10 );
  int i1 = int(jst.tm_min / 10 );
  int i2 = int(jst.tm_min - i1*10 );
  int s1 = int(jst.tm_sec / 10 );
  int s2 = int(jst.tm_sec - s1*10 );

    // 日時表示
  M5.Display.drawPng (n[y1], ns[y1],   0,  0, 18, 34);
  M5.Display.drawPng (n[y2], ns[y2],  19,  0, 18, 34); 
  M5.Display.drawPng (n[y3], ns[y3],  38,  0, 18, 34);
  M5.Display.drawPng (n[y4], ns[y4],  57,  0, 18, 34);
  M5.Display.drawPixel(  77, 13, ORANGE); M5.Display.drawPixel(  77, 23, ORANGE); 
  M5.Display.drawPng (n[j1], ns[j1],  80,  0, 18, 34);
  M5.Display.drawPng (n[j2], ns[j2], 100,  0, 18, 34);
  M5.Display.drawPixel( 118, 13, ORANGE); M5.Display.drawPixel( 119, 23, ORANGE); 
  M5.Display.drawPng (n[d1], ns[d1], 120,  0, 18, 34);
  M5.Display.drawPng (n[d2], ns[d2], 140,  0, 18, 34);

  // 時間表示
  M5.Display.drawPng (n[h1], ns[h1],   0, 40, 18, 34);
  M5.Display.drawPng (n[h2], ns[h2],  20, 40, 18, 34); 
  M5.Display.drawPixel(  48, 54, ORANGE); M5.Display.drawPixel(  48, 64, ORANGE); 
  M5.Display.drawPng (n[i1], ns[i1],  60, 40, 18, 34);
  M5.Display.drawPng (n[i2], ns[i2],  80, 40, 18, 34);
  M5.Display.drawPixel( 108, 54, ORANGE); M5.Display.drawPixel( 108, 64, ORANGE); 
  M5.Display.drawPng (n[s1], ns[s1], 120, 40, 18, 34);
  M5.Display.drawPng (n[s2], ns[s2], 140, 40, 18, 34);

  // フェード中でない、かつ00秒のときだけ1回だけ起動する
  if (s1 == 0 && s2 == 0 && !isFading) { triggerFade(); }
}

void vfd_1_line(){
  struct tm jst = getJstTime();
  int i1 = int(jst.tm_min / 10 );
  int i2 = int(jst.tm_min - i1*10 );
  int s1 = int(jst.tm_sec / 10 );
  int s2 = int(jst.tm_sec - s1*10 );

  // 時間表示
  M5.Display.drawPng (m[i1], ms[i1],   2,  6, 35, 67);
  M5.Display.drawPng (m[i2], ms[i2],  41,  6, 35, 67);
  M5.Display.drawPixel( 79, 28, ORANGE); M5.Display.drawPixel( 79, 54, ORANGE); 
  M5.Display.drawPixel( 79, 27, YELLOW); M5.Display.drawPixel( 79, 53, YELLOW); 
  M5.Display.drawPng (m[s1], ms[s1],  83,  6, 35, 67);
  M5.Display.drawPng (m[s2], ms[s2], 121,  6, 35, 67);

 // フェード中でない、かつ00秒のときだけ1回だけ起動する
 if (s1 == 0 && s2 == 0 && !isFading) { triggerFade(); }
}

void vfd_0_line(){
    M5.Display.setCursor(0, 10);
    M5.Display.setTextColor(WHITE, BLACK);

    // RTCから生データ(UTC)を取得して表示
    auto dt = M5.Rtc.getDateTime();
    M5.Display.printf("UTC : %04d/%02d/%02d %02d:%02d:%02d\r\n", 
                      dt.date.year, dt.date.month, dt.date.date, 
                      dt.time.hours, dt.time.minutes, dt.time.seconds);

    // 計算したJST(日本時間)を表示
    struct tm jst = getJstTime();
    M5.Display.printf("JST : %04d/%02d/%02d %02d:%02d:%02d\r\n", 
                      jst.tm_year + 1900, jst.tm_mon + 1, jst.tm_mday, 
                      jst.tm_hour, jst.tm_min, jst.tm_sec);
}

// フェードを開始させるフラグ制御
void triggerFade() {
  if (!isFading) {
    isFading = true;
    fadeStep = 0;
    lastFadeStepTime = millis();
  }
}

// delayを使わずにバックグラウンドで輝度をじわじわ変化させる処理
void handleFade() {
  unsigned long currentMillis = millis();
  // 25msごとに輝度を1段階変化させる
  if (currentMillis - lastFadeStepTime >= 25) {
    lastFadeStepTime = currentMillis;
    
    M5.Display.setBrightness(fadeBrightnessSequence[fadeStep]);
    fadeStep++;
    
    // すべてのステップが終了したらエフェクトを終了
    if (fadeStep >= FADE_STEPS_COUNT) {
      isFading = false;
    }
  }
}

2026年9月4日金曜日

M5StickC Nixie tube Clock

マイコンボード系を入れいている箱の奥深くから、久々にM5StickCを見つけました。結構長いこと使用していなかったので、M5StickCでよくある起動しないモードになっていました。とりあえず、純正ケーブルと某端子のショートで復活!

使い道が思いつかないのでネットを検索していたところ、GitHubにCarlos Orts氏の M5StickC_Nixie_tube_Clockを見つけました。ニキシー管時計は持っていますが、ちょっと書き込んでみようかと思ったのが間違い!

コンパイル、書き込みともに正常なのに、画像が表示されない(バックライトが消えているように見える)。マイコンボードの設定、フェード処理などいろいろ書き換えても治りません。挙句には画像データをただ表示しただけでも現象が変わらない。とりあえず頭を冷やすために数日放置しちゃいました

で、復活したんですよ。もう開き直って大筋そのままで画像表示に関する内容を全部書き換えます。そのためにライブラリもM5StickC.hからM5Unified.hに変更ですよ。ちなみにM5StickC.hからM5Unified.hは混在するとエラーになります

最初に画像データに関しては、デジタルゴチゾウを作成した際に使用した画像データImageData化サイト(Row Dampを選択)にて画像データを再作成しました(vfd_18x34.h, vfd_35x67.h)。drawPng関数を使用して画像表示するように変更(テスト表示で問題なく表示されました)

次にM5Unified.h に変更したことで、スクリーンの初期設定、RTCなどいくつかの関数が使用出来なくなっていますので全て置き換え。おまけでボタン監視やフェードイン/アウトのエフォート部分も処理の安全化を行いました

NTC接続もと考えましたが、とりあえずオリジナル状態を再現するだけに止めました






#include <m5unified .h>
#include "vfd_18x34.h"
#include "vfd_35x67.h"

m5::rtc_time_t RTC_TimeStruct;
m5::rtc_date_t RTC_DateStruct;

int mode_ = 3; // 3:2Lines 2: 2Lines(YYMM), 1:1Line

const uint8_t* n[] =  { 
  vfd_18x34_0, vfd_18x34_1, vfd_18x34_2, vfd_18x34_3, vfd_18x34_4,
  vfd_18x34_5, vfd_18x34_6, vfd_18x34_7, vfd_18x34_8, vfd_18x34_9
  }; // vfd font 18x34
 
 const uint32_t ns[] =  { 
  sizeof(vfd_18x34_0), sizeof(vfd_18x34_1), sizeof(vfd_18x34_2), sizeof(vfd_18x34_3), sizeof(vfd_18x34_4),
  sizeof(vfd_18x34_5), sizeof(vfd_18x34_6), sizeof(vfd_18x34_7), sizeof(vfd_18x34_8), sizeof(vfd_18x34_9)
  }; // vfd font 18x34

const uint8_t* m[] =  { 
  vfd_35x67_0, vfd_35x67_1, vfd_35x67_2, vfd_35x67_3, vfd_35x67_4,
  vfd_35x67_5, vfd_35x67_6, vfd_35x67_7, vfd_35x67_8, vfd_35x67_9
  }; // vfd font 35x67

const uint32_t ms[] =  { 
  sizeof(vfd_35x67_0), sizeof(vfd_35x67_1), sizeof(vfd_35x67_2), sizeof(vfd_35x67_3), sizeof(vfd_35x67_4),
  sizeof(vfd_35x67_5), sizeof(vfd_35x67_6), sizeof(vfd_35x67_7), sizeof(vfd_35x67_8), sizeof(vfd_35x67_9)
  }; // vfd font 35x67

const char *monthName[12] = {
  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};


// 時間管理用の変数
unsigned long lastClockUpdate = 0;
unsigned long lastButtonCheck = 0;

// フェードエフェクト管理用の変数
bool isFading = false;
unsigned long lastFadeStepTime = 0;
int fadeStep = 0;
const int fadeBrightnessSequence[] = {120, 130, 140, 150, 160, 170, 180, 190, 200, 190, 180, 170, 160, 150, 140, 130, 120, 200};
const int FADE_STEPS_COUNT = 18;

// 関数のプロトタイプ宣言
void vfd_1_line();
void vfd_2_line();
void vfd_3_line();
void handleFade();
void triggerFade();

void setup(void){ 
  auto cfg = M5.config();
  M5.begin(cfg);

  // 画面の向きを設定 (1: 横向きなど)
  M5.Display.setRotation(1);
  M5.Display.fillScreen(TFT_BLACK);
  M5.Display.setBrightness(200);

  // rtc setup start ---------------------------------------------
  String pt = (__DATE__ " " __TIME__); // PC DATE TIME READ
  //000000000011111111112  Read data
  //012345678901234567890
  //Jun  6 2019 07:20:41

  // ----- 月 (Month) の取得 -----
  char m1[4]; // 3文字 + 終端文字('\0') 
  (pt.substring(0, 3)).toCharArray(m1, 4);
  String mStr = pt.substring(0, 3);
  int m2 = 1; // 該当しない場合のデフォルト値(例: 1月)を設定
  for (int mx = 0; mx < 12; mx ++) {
    if (mStr == monthName[mx]){ // 安全なString比較に変更
      m2 = mx + 1; 
      break;
    }
  }

  m5::rtc_date_t DateStruct;         // Month, Date, Year 
  DateStruct.month   = m2;
  // ----- 日 (Day) の取得 -----
  // 注意: 1桁の日は " 6" のようにスペースが含まれるため、
  // substring(4, 6) で切り出した後、toInt() を使えば自動で数値化されます。
  DateStruct.date    = (pt.substring(4, 6)).toInt();
  // ----- 年 (Year) の取得 -----
  DateStruct.year    = (pt.substring(7,11)).toInt();
  M5.Rtc.setDate(&DateStruct);
  m5::rtc_time_t TimeStruct;
  // ----- 時・分・秒 (Time) の取得 -----
  TimeStruct.hours   = (pt.substring(12,14)).toInt();
  TimeStruct.minutes = (pt.substring(15,17)).toInt();
  TimeStruct.seconds = (pt.substring(18,20)).toInt();
  M5.Rtc.setTime(&TimeStruct);
  // rtc setup end -----------------------------------------------
}
 
void loop(void){ 
  unsigned long currentMillis = millis();
  M5.update();
  // 1. ボタン入力の監視(200msごとにサクサク判定)
  if (currentMillis - lastButtonCheck >= 200) {
    if (M5.BtnA.isPressed()) {
      lastButtonCheck = currentMillis;
      if (mode_ == 3)      { mode_ = 1; }
      else if (mode_ == 2) { mode_ = 3; }
      else if (mode_ == 1) { mode_ = 2; }
      M5.Display.fillScreen(TFT_BLACK); // モード変更時に画面クリア
    }
  }

  // 2. 時計の定期描画(500msごと)
  if (currentMillis - lastClockUpdate >= 500) {
    lastClockUpdate = currentMillis;
    
    if (mode_ == 3)      { vfd_3_line(); }
    else if (mode_ == 2) { vfd_2_line(); }
    else if (mode_ == 1) { vfd_1_line(); }
  }

  // 3. フェードエフェクトの進行管理(バックグラウンド処理)
  if (isFading) {
    handleFade();
  }
}

void vfd_3_line(){
  M5.Rtc.getTime(&RTC_TimeStruct);
  M5.Rtc.getDate(&RTC_DateStruct);
  int h1 = int(RTC_TimeStruct.hours / 10 );
  int h2 = int(RTC_TimeStruct.hours - h1*10 );
  int i1 = int(RTC_TimeStruct.minutes / 10 );
  int i2 = int(RTC_TimeStruct.minutes - i1*10 );
  int s1 = int(RTC_TimeStruct.seconds / 10 );
  int s2 = int(RTC_TimeStruct.seconds - s1*10 );

  M5.Display.drawPng (m[h1], ms[h1],   3,  0, 35, 67);
  M5.Display.drawPng (m[h2], ms[h2],  41,  0, 35, 67);
  M5.Display.drawPixel( 79, 22, ORANGE); M5.Display.drawPixel( 79, 48, ORANGE); 
  M5.Display.drawPixel( 79, 21, YELLOW); M5.Display.drawPixel( 79, 47, YELLOW); 
  M5.Display.drawPng (m[i1], ms[i1],  83,  0, 35, 67);
  M5.Display.drawPng (m[i2], ms[i2], 121,  0, 35, 67);
  M5.Display.drawPng (n[s1], ns[s1], 120, 45, 18, 34);
  M5.Display.drawPng (n[s2], ns[s2], 140, 45, 18, 34);

  // フェード中でない、かつ00秒のときだけ1回だけ起動する
  if (s1 == 0 && s2 == 0 && !isFading) { triggerFade(); }
}

void vfd_2_line(){
  M5.Rtc.getTime(&RTC_TimeStruct);
  M5.Rtc.getDate(&RTC_DateStruct);

  int y1 = int(RTC_DateStruct.year    / 1000 );
  int y2 = int((RTC_DateStruct.year   - y1*1000 ) / 100 );
  int y3 = int((RTC_DateStruct.year   - y1*1000 - y2*100 ) / 10 );
  int y4 = int(RTC_DateStruct.year    - y1*1000 - y2*100 - y3*10 );
  int j1 = int(RTC_DateStruct.month   / 10);
  int j2 = int(RTC_DateStruct.month   - j1*10 );
  int d1 = int(RTC_DateStruct.date    / 10 );
  int d2 = int(RTC_DateStruct.date    - d1*10 );
  int h1 = int(RTC_TimeStruct.hours   / 10) ;
  int h2 = int(RTC_TimeStruct.hours   - h1*10 );
  int i1 = int(RTC_TimeStruct.minutes / 10 );
  int i2 = int(RTC_TimeStruct.minutes - i1*10 );
  int s1 = int(RTC_TimeStruct.seconds / 10 );
  int s2 = int(RTC_TimeStruct.seconds - s1*10 );

  // 日時表示
  M5.Display.drawPng (n[y1], ns[y1],   0,  0, 18, 34);
  M5.Display.drawPng (n[y2], ns[y2],  19,  0, 18, 34); 
  M5.Display.drawPng (n[y3], ns[y3],  38,  0, 18, 34);
  M5.Display.drawPng (n[y4], ns[y4],  57,  0, 18, 34);
  M5.Display.drawPixel(  77, 13, ORANGE); M5.Display.drawPixel(  77, 23, ORANGE); 
  M5.Display.drawPng (n[j1], ns[j1],  80,  0, 18, 34);
  M5.Display.drawPng (n[j2], ns[j2], 100,  0, 18, 34);
  M5.Display.drawPixel( 118, 13, ORANGE); M5.Display.drawPixel( 119, 23, ORANGE); 
  M5.Display.drawPng (n[d1], ns[d1], 120,  0, 18, 34);
  M5.Display.drawPng (n[d2], ns[d2], 140,  0, 18, 34);

  // 時間表示
  M5.Display.drawPng (n[h1], ns[h1],   0, 40, 18, 34);
  M5.Display.drawPng (n[h2], ns[h2],  20, 40, 18, 34); 
  M5.Display.drawPixel(  48, 54, ORANGE); M5.Display.drawPixel(  48, 64, ORANGE); 
  M5.Display.drawPng (n[i1], ns[i1],  60, 40, 18, 34);
  M5.Display.drawPng (n[i2], ns[i2],  80, 40, 18, 34);
  M5.Display.drawPixel( 108, 54, ORANGE); M5.Display.drawPixel( 108, 64, ORANGE); 
  M5.Display.drawPng (n[s1], ns[s1], 120, 40, 18, 34);
  M5.Display.drawPng (n[s2], ns[s2], 140, 40, 18, 34);

  // フェード中でない、かつ00秒のときだけ1回だけ起動する
  if (s1 == 0 && s2 == 0 && !isFading) { triggerFade(); }
}

void vfd_1_line(){
  M5.Rtc.getTime(&RTC_TimeStruct);
  M5.Rtc.getDate(&RTC_DateStruct);
  int i1 = int(RTC_TimeStruct.minutes / 10 );
  int i2 = int(RTC_TimeStruct.minutes - i1*10 );
  int s1 = int(RTC_TimeStruct.seconds / 10 );
  int s2 = int(RTC_TimeStruct.seconds - s1*10 );

  // 時間表示
  M5.Display.drawPng (m[i1], ms[i1],   2,  6, 35, 67);
  M5.Display.drawPng (m[i2], ms[i2],  41,  6, 35, 67);
  M5.Display.drawPixel( 79, 28, ORANGE); M5.Display.drawPixel( 79, 54, ORANGE); 
  M5.Display.drawPixel( 79, 27, YELLOW); M5.Display.drawPixel( 79, 53, YELLOW); 
  M5.Display.drawPng (m[s1], ms[s1],  83,  6, 35, 67);
  M5.Display.drawPng (m[s2], ms[s2], 121,  6, 35, 67);

  // フェード中でない、かつ00秒のときだけ1回だけ起動する
 if (s1 == 0 && s2 == 0 && !isFading) { triggerFade(); }
}

// フェードを開始させるフラグ制御
void triggerFade() {
  if (!isFading) {
    isFading = true;
    fadeStep = 0;
    lastFadeStepTime = millis();
  }
}

// delayを使わずにバックグラウンドで輝度をじわじわ変化させる処理
void handleFade() {
  unsigned long currentMillis = millis();
  // 25msごとに輝度を1段階変化させる
  if (currentMillis - lastFadeStepTime >= 25) {
    lastFadeStepTime = currentMillis;
    
    M5.Display.setBrightness(fadeBrightnessSequence[fadeStep]);
    fadeStep++;
    
    // すべてのステップが終了したらエフェクトを終了
    if (fadeStep >= FADE_STEPS_COUNT) {
      isFading = false;
    }
  }
}


ニキシー管表示は良い感じなので、他でこの画像データを使用して表示させるのも面白いかもと思いながら、M5StickCにはメモリ容量的にキツかなって感じです。小さい画像データだと8桁表示できますので、ドットの画像を作ってダイバージェンスメーター(古)にしても面白いかも(桁数の関係でπ(パイ)世界線、ρ(ロー)世界線は無理ですねw)

2026年2月12日木曜日

iOS 26.3 リリース

iOS 26.3がリリースされました

対象デバイスは、iPhone 11以降, iPad(8代目), iPad mini(5代目以降), iPad Air(3代目), iPad Pro(12.9インチ 3代目以降、11インチ 初代以降)になります

アップデート内容は、以下の様になっています

このアップデートには重要なセキュリティアップデートが含まれます。

Apple セキュリティアップデートを確認すると以下のセキュリティアップデート内容が記載されています

Accessibility

CVE-2026-20645: Loh Boon Keat

CVE-2026-20674: Jacob Prezant (prezant.us)

Bluetooth

CVE-2026-20650: jioundai

Call History

CVE-2026-20638: Nils Hanff (@nils1729@chaos.social) of Hasso Plattner Institute

CFNetwork

CVE-2026-20660: Amy (amys.website)

CoreAudio

CVE-2026-20611: Anonymous working with Trend Micro Zero Day Initiative

CoreMedia

CVE-2026-20609: Yiğit Can YILMAZ (@yilmazcanyigit)

CoreServices

CVE-2026-20617: Gergely Kalman (@gergely_kalman), Csaba Fitzl (@theevilbit) of Iru

CVE-2026-20615: Csaba Fitzl (@theevilbit) of Iru and Gergely Kalman (@gergely_kalman)

CVE-2026-20627: an anonymous researcher

dyld

CVE-2026-20700: Google Threat Analysis Group

Game Center

CVE-2026-20649: Asaf Cohen

ImageIO

CVE-2026-20675: George Karchemsky (@gkarchemsky) working with Trend Micro Zero Day Initiative

CVE-2026-20634: George Karchemsky (@gkarchemsky) working with Trend Micro Zero Day Initiative

Kernel

CVE-2026-20654: Jian Lee (@speedyfriend433)

CVE-2026-20626: Keisuke Hosoda

CVE-2026-20671: Xin'an Zhou, Juefei Pu, Zhutian Liu, Zhiyun Qian, Zhaowei Tan, Srikanth V. Krishnamurthy, Mathy Vanhoef

LaunchServices

CVE-2026-20663: Zhongcheng Li from IES Red Team

libexpat

CVE-2025-59375

libxpc

CVE-2026-20667: an anonymous researcher

Live Captions

CVE-2026-20655: Richard Hyunho Im (@richeeta) at Route Zero Security (routezero.security)

Messages

CVE-2026-20677: Ron Masas of BreakPoint.SH

Photos

CVE-2026-20642: Dalibor Milanovic

Sandbox

CVE-2026-20628: Noah Gregory (wts.dev)

Sandbox Profiles

CVE-2026-20678: Óscar García Pérez, Stanislav Jelezoglo

Screenshots

CVE-2026-20682: Viktor Lord Härringtón

Shortcuts

CVE-2026-20653: Enis Maholli (enismaholli.com)

Spotlight

CVE-2026-20680: an anonymous researcher

StoreKit

CVE-2026-20641: Gongyu Ma (@Mezone0)

UIKit

CVE-2026-20606: LeminLimez

CVE-2026-20640: Jacob Prezant (prezant.us)

VoiceOver

CVE-2026-20661: Dalibor Milanovic

WebKit

CVE-2026-20652: Nathaniel Oh (@calysteon)

CVE-2026-20608: HanQing from TSDubhe and Nan Wang (@eternalsakura13)

CVE-2026-20676: Tom Van Goethem

CVE-2026-20644: HanQing from TSDubhe and Nan Wang (@eternalsakura13)

CVE-2026-20636: EntryHi

CVE-2026-20635: EntryHi

Wi-Fi

CVE-2026-20621: Wang Yu of Cyberserval

なおアップデートファイルの大きさは、iOS 26.2.1をインストールしているiPhone 16 proで11.36GB、iPhone 11で3.76GBになります


スマホ・携帯ランキング

2026年2月4日水曜日

watchOS 10.6.2 リリース

  watchOS 10.6.2がリリースされました。Apple Watch Series 4 ,5 ,SEが対象になります

アップデート内容は、以下の様になっています

このアップデートにより、iMessage、FaceTime、およびデバイスのアクティベーションなどの機能に必要な証明書の有効期限が延長され、2027年1月以降も引き続きご利用いただけるようになります。

Apple セキュリティアップデートを確認しましたが、セキュリティアップデート内容はありませんでした

なおアップデートファイルの大きさは、watchOS 10.6をインストールしているSeries 4で160MBになります 


スマホ・携帯ランキング

2026年2月3日火曜日

iPadOS 16.7.14 リリース

iPadOS 16.7.14がリリースされました

対象デバイスは、iPhone 8、iPhone 8 Plus、iPhone X、iPad 第5世代、iPad Pro (初代 9.7/12.9)になります

アップデート内容は、以下の様になっています

このアップデートには重要なセキュリティアップデートが含まれ、すべてのユーザに推奨されます。

Apple セキュリティアップデートを確認しましたが、セキュリティアップデート内容はありませんでした


スマホ・携帯ランキング

2026年1月31日土曜日

ロボホンの前掛け交換

 初代ロボホン(SR-01M-W)を所有していますが、フリマサイトで前掛けを見つけたので購入しました。ロボホンのオプションパーツって意外に高く、過去に出ていたものんは再販などないんですよ。そのためか、中古があまり出回っていません

早速、交換します。ロボホンの前掛けはねじ止めされていますが、そのネジはシールで隠れています。最初のこのシールを剥がします

このシールは再利用しますので、折り曲げたりしないように気を付けて剥がす必要があります。剥がすとしたにネジが2本ありますので、これを外すと前掛けが取れます

左胸のハートは「ロボホンハート」になります。スワロフスキー製のハート型のジルコニアなんだそうです。胸当てを外さないとみることがないですね

で、入手した胸当てを逆の手順で取り付けます

ちょっと引き締まった感じがしますが、どうですかね。本当は耳当ても欲しいのですが、胸当て以上に入手性が低いです


2026年1月30日金曜日

ジャンクのPlayStation2(SCPH-70000)

 近所の中古ゲーム販売店のジャンクコナーで、PlayStation2(SCPH-70000)を見つけました。ジャンクの理由は電源が入らないそうです

PlayStation2(SCPH-10000)は持っていますが、大きさが気になっていましたので購入しました。電源が入らない理由は、いくつか思いついていたのもありますが...

早速、自宅で電源をつないでみましたが起動どころか、うんともすんとも言いません。ちなみにコンポジットケーブルはPlayStation用では無いため使えません(任天堂のものです)。そのため、手持ちのケーブルを使用しました

それでは、早速分解していきます。このタイプは、裏側の足の下にネジが隠れています

このネジを外すだけで開腹できちゃいます(このころのハードウェアは、保護シールなどないですからね)。ネジの長さもすべて共通になります

蓋を開けたら、赤丸箇所のネジを外します。これで基板をシャーシから外すことが出来ます。

電源コネクタを見た瞬間に問題個所が見つかりました。

コネクタの半田付けが変になっていますね。おそらく、前オーナーが修理に失敗したものですね。半田不良個所の半田を除去して基板を見てみました

半田を除去したところ、見事に基板配線が切れています。電源抜き差しによるコネクタの負担が下人ですね。なお作業がしにくいので、放熱板のネジをいくつか外して浮かせています

基板表面を削って半田付けする方法もありますが、電源コネクタのためジャンパすることにしました(電流がそれなりに流れますからね)

奥に見える白いチップはヒューズになります。そこにジャンパーを付けるのもいかがなものかと思いましたので、奥のスルーホールにジャンパ付けしました

半田付けするために乱暴ですが棒を刺して開いています。テスターチェックをして問題なさそうだったので電源を再度入れてみました

この状態で電源は無事に入りました(LED点灯)

この後、再度組み立てて実際にゲームを起動して遊ぶこともできました。ひさびさにサンソフトの「へべれけのぽぷーん」を遊んで、凄くヘタッピになっていたことにショックを受けましたよ.....

2026年1月28日水曜日

watchOS 26.2.1リリース

 watchOS 26.2がリリースされました。Apple Watch Series 6 以降が対象になります

アップデート内容は、以下の様になっています

このアップデートには、AirTag(第2世代)の対応に加え、バグ修正が含まれています。

Apple セキュリティアップデートを確認しましたが、セキュリティアップデート内容はありませんでした

なおアップデートファイルの大きさは、watchOS 26.1をインストールしているSeries 7で193MBになります 


スマホ・携帯ランキング

2026年1月27日火曜日

iOS 26.2.1 リリース

iOS 26.2.1がリリースされました

対象デバイスは、iPhone 11以降, iPad(8代目), iPad mini(5代目以降), iPad Air(3代目), iPad Pro(12.9インチ 3代目以降、11インチ 初代以降)になります

アップデート内容は、以下の様になっています

このアップデートには、AirTag(第2世代)の対応に加え、バグ修正が含まれています。

Apple セキュリティアップデートを確認しましたが、セキュリティアップデート内容はありませんでした

なおアップデートファイルの大きさは、iOS 26.2をインストールしているiPhone 16 proで704.6MB、iPhone 11で503.9MBになります


スマホ・携帯ランキング