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年1月31日土曜日

ロボホンの前掛け交換

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

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

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

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

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

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


2026年1月30日金曜日

ジャンクのPlayStation2(SCPH-70000)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2025年12月15日月曜日

NUC7i5BNKを25H2にアップデート

Windows 11非対応CPU を搭載したPCにWindows 11をインストールした場合、OSの自動アップデートが出来ないようです。Windows 11のインストール方法に関しては、「NUC7i5BNK にWindows11をインストール」をご覧ください。そのため、マニュアルでのアップデートが必要になるようです

今回は23H2から25H2にアップデートします。24H2など他バージョンにしたい場合は、これから記載する方法で出来るかは保証できませんので、あしからず

さて、マニュアルアップデートの方法はいくつかあるようですが、今回はWindows Serverインストールを使用する方法でアップデートしました。

具体的には、最初にMicrosoftからWindows 11のインストーラ(ISOファイル)をダウンロードして、インストール用のUSBドライブを作成します

次にコマンドプロンプトから、インストーラを起動します

> setup.exe /product server 

「Windows Serverのインストール」とありますが、無視してください

このウィンドウで「セットアップでの更新プログラムのダウンロード方法の変更」をクリックします。「更新プログラム、ドライバー、オプション機能の入手」ウィンドウが表示されます


このウィンドウでは「今は実行しない」を選択し、次へをクリックします。「引き継ぐ項目を選んでください」ウィンドウが表示されます

「ファイル、設定、アプリを保持する」を選択し、次へをクリックします

「インストール準備完了」ウィンドウ以下の様になっていればインストールをクリックしてください(Home エディションでの実行の場合です)

上記は設定した画面だけですので、途中でライセンス条件などの承認画面があったと思います(この辺は通常インストールと同じなので、キャプチャしていませんでした)

あとはアップデートが完了するまで待つだけです

アップデート完了後、システムを確認すると無事にアップデートが出来ています

この後、少し使用してみましたが、問題なく使用できています

2025年11月14日金曜日

NUC7i5BNK にWindows11をインストール

 一昔前なら5万くらいしていたNUC7i5BNK ですが、現在は中古ですが1万円以下で購入できます。第7世代Intel CPUなのでWindows11の正式サポートから外れていますから、お安くなりますね。ま、衝動買いしましたが...

状態は、動作確認済みでSSD は非搭載となっていました。メモリは手持ちがありましたので、M.2 SSD と一緒に組み込むことにします。なお、開腹は簡単で裏のネジを4本外すだけです

SATAマウントは配線のみで固定されていませんので、持ち上げて作業します。本当は外した方が良いのかもしれませんが、邪魔だったら外そうというスタンスで作業しています

ラッキーなことに前オーナーは4GBですがメモリを付けてくれていました。手持ちの8GBと合わせて12GBになります

奥にM.2のスロットが見えますので、5年位前に購入して放置してあった500GB SSDを搭載します(サブ機になるので、大規模容量は不要と判断)

あとは組み立ててOSのインストールになります。なお、ローカルアカウントのみにしたかったため、Windows10をインストールし設定、ドライバを入れてから、Windows11にアップグレードしました。なお、Windows11へのアップグレードは、\sources\appraiserres.dllを空ファイル化して行いました

なお、Windows11の詳細バージョンは23H2になります。更新プログラムのチェックでは、24H2以降が表示されないので、外部ソフトを使うなどを使うなどの方法が必要そうです。実施した際は、またアップします

2025年10月13日月曜日

iiyam LS140MUのSSD増設

仕事で使用しているiiyam LS140MUのSSD容量が心もとなくなってきました(100GB強)。AMAZONのセールもありましたので、空いているM.2スロットに増設してみることにしました

M.2 SSDは、KingSpec NX SSD M.2 NVMe 2280 (1TB) をチョイス。使ったことないメーカーですが、口コミと価格でこれにしました

早速、分解していきます。開腹は裏のネジを12本外すだけです。なお、全てのネジは同じ長さでした


裏蓋は爪など無く、簡単に開きました。このPCは楽で良いです

折角開けたので、メモリやSSDをチェックします

メモリはDDR4 SODIMM 8GB 1枚のみです。搭載メモリ数は16GBのため、8GBは表面実装のようです。実績は不明ですが32GB SODIMMを搭載できれば、最大40GBのメモリになりますね。さすがに仕事用ではそこまで必要ないですが、16GB メモリは試してみたい気がします



メインのSSDは、Intel 670p シリーズ 1.0TBを搭載していました。なかなか良さげなSSDです

さて話を戻して、M.2 SSDを増設します。と、言っても刺して、ネジで止めるだけですが...

CMOS用の電池の上に取り付けるのはどうかと思いましたが、とりあえず様子を見ます

Windows上では、パーティションテーブルをGPTにして、フォーマットしました。しばらくは容量は気にせずに使用出来そうです

2025年10月6日月曜日

きちゃないジャンクプロコン

値段が安かったのでプロコンジャンク2台セットを購入しました。両方ともスティックがボロボロと明記されており、動作に関しては1台は動きそうな感じで記載さrていました

普段なら最初に動作テストをしますが余りにきちゃないので、分解清掃を一番に行いました。最初にグリップ部分のネジを2本外し、グリップを外します

赤丸のネジを外すと、バッテリーを外すことが出来ます。開けるときに爪があるので折らないように注意してください。写真を撮り忘れましたが、次に5本ネジを外します(バッテリー箇所のネジは長いドライバが必要です)

バイブレータが両面テープで張り付けてありますので、開けるときに少し力が要ります。また、フラットケーブルがメイン基板とボタン側の基板にあるので無理し過ぎないように!

あまりに汚かったので清掃後の写真になります

真ん中のフラットケーブルを外して、赤丸ネジを外すとLRボタン部分が外れます。LRボタンは金属棒で止めてあるので、引き抜くと簡単に外れます

ボタン側はさらに赤丸ネジを2本外すと基板が外れます


ここまで来たら中性洗剤を入れた水につけて、その後は水洗いです


スティックはボロボロなので、念のためスティックモジューツ付きのものをスアマゾンでポチリました

逆の手順で組み立てて、これでやっと動作テストが出来ます。なお、1つはバッテリーが膨らんでいたため、バッテリーは処分しました(各自治体のルールに則って処分してください)

動作に関しては、バッテリーが膨らんでいた方は電源が入りません(もう片方のバッテリーを使用)。もう片方は問題なく使用出来ました。動作する方はスティックモジュール部に接点復活スプレーを散布、スティックをクリクリして完了です。この後の動作チェックではドリフトなく無事に使用できています

さて動かなかったコントローラですが基板を全て外し、チェックしたところ緑青のようなものが付いていました



バッテリーは膨らんでいましたが液漏れなどは無い状態、さらにバッテリー接点近くでは無く、基板の真ん中です。とりあえず無水エタノールで清掃しましたが、残念ながら電源は入りませんでした。時間があるときにテスター片手にチェックしようと思います