Estardyn 0.96 インチ IPS LCD TFT ディスプレイモジュール 160x80 HD 65K SPI インターフェイス ST7735S2026/08/21

アリエクのEstarDyn Factory Storeで ”Estardyn 0.96 インチ IPS LCD TFT ディスプレイモジュール 160x80 HD 65K SPI インターフェイス ST7735S 3.3V Raspberry Pi DIY プロジェクト用”を買ってみました。カラーで¥300也。



Adafruit_ST7735.h を見ると 160x80ドットの初期化に、それっぽいのが2つあったので
INITR_MINI160x80_PLUGIN
INITR_MINI160x80
試してみると INITR_MINI160x80 これが良いようでした。INITR_MINI160x80_PLUGIN これは隅にスジが入ってずれて表示されました。

スケッチ
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>

//SPIピン定義
#define TFT_BLK          8   // BLK
#define TFT_CS           9   // CS
#define TFT_DC          10   // DC
#define TFT_RST         11   // Reset
#define TFT_MOSI        12   // SDA
#define TFT_SCK         13   // SCL

Adafruit_ST7735 tft = Adafruit_ST7735(&SPI, TFT_CS, TFT_DC, TFT_RST);

void setup(void)
{
  pinMode(TFT_BLK, OUTPUT);              //バックライトにGPIOを使用する
  digitalWrite(TFT_BLK, HIGH);           //バックライト オン

  //SPIピン設定
  SPI.begin(TFT_SCK, -1, TFT_MOSI, TFT_CS);

  tft.initR(INITR_MINI160x80);             //ST7735初期化
 
  tft.fillScreen(ST77XX_BLACK);               //背景の塗りつぶし

  tft.setRotation(0);                         //画面回転
  tft.setTextSize(2);                         //サイズ

  tft.setCursor(0, 0);                       //カーソル位置                      
  tft.setTextColor(ST77XX_RED);              //赤
  tft.printf("Red\n");
  tft.setTextColor(ST77XX_GREEN);            //緑
  tft.printf("Green\n");
  tft.setTextColor(ST77XX_BLUE);             //青
  tft.printf("Blue\n");
}

void loop()
{
}

スケッチの結果
赤の指定なのに青。青が赤になっています。
調べてみると同じようなディスプレモジュールでもいろいろあるようです。。。
RGB 16ビットのデータフォーマットにはRGB565とBGR565があるそうで、これを直せば良いみたいです。
ST7735Sの仕様書をみたら、RGB-BGR ORDERというものがあります。
このビットをBGRにすればよいと思うので、このレジスタを設定する箇所を探せば良さそうです。
このレジスタのアドレスと思われる ST77XX_MADCTL という定義があったので検索すると
Adafruit_ST7735.cpp のinitRとsetRotationで使われていました。
確認のためsetRotationのあとにMADCTLレジスタを書き換えてみました。
このレジスタを読み出せればRGBビットだけ1にすればよさそうなのですが、インターフェースに読み出しのピンがないようなので、
値を上書きすることにしました。ですので上位4ビットは画面の回転設定に影響するのでsetRotationの引数に合わせて値を決めました。
(どのビットを立てるかはAdafruit_ST7735.cppを参考)

変更箇所 setRotationの後
  tft.setRotation(0);                         //画面回転
  //RGB-BGR ORDER (setRotationがMADCTLを書き換えている)
  uint8_t data = 0xC8;  //0:0xC8 1:0xA8 2:0x08 3:0x68 画面の回転方向による
  tft.sendCommand(ST77XX_MADCTL, &data, 1);

変更スケッチの結果

ちなみにsetRotaionが役に立っていないようですが、ほかの設定があるようで、レジスタの値を引数に合わせて設定しないとダメでした。
もうちょっと解析して、Adafruit_ST7735.cppを別の名前にしてローカルで作り直すか、setRotationをオーバーライドするかして、
かっこよくしたほうがよさそうです。
とりあえず楽そうだったので、setRotationをオーバーライド。


#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>

//SPIピン定義
#define TFT_BLK          8   // BLK
#define TFT_CS           9   // CS
#define TFT_DC          10   // DC
#define TFT_RST         11   // Reset
#define TFT_MOSI        12   // SDA
#define TFT_SCK         13   // SCL

class MyST7735 : public Adafruit_ST7735 {
public:
  using Adafruit_ST7735::Adafruit_ST7735;   // コンストラクタを継承

  // setRotation をオーバーライドして追加処理
  void setRotation(uint8_t m) override {
    Adafruit_ST7735::setRotation(m);    //元の実装を呼び出す
  //追加の処理 RGB->BGR
    uint8_t data = 0xC8;
    switch (m) {
      case 0:
        data = 0xC8;
        break;
      case 1:
        data = 0xA8;
        break;
      case 2:
        data = 0x08;
        break;
      case 3:
        data = 0x68;
        break;
    }
    sendCommand(ST77XX_MADCTL, &data, 1);
  }
};

//Adafruit_ST7735 tft = Adafruit_ST7735(&SPI, TFT_CS, TFT_DC, TFT_RST);
MyST7735 tft = MyST7735(&SPI, TFT_CS, TFT_DC, TFT_RST);

void setup(void)
{
  pinMode(TFT_BLK, OUTPUT);              //バックライトにGPIOを使用する
  digitalWrite(TFT_BLK, HIGH);           //バックライト オン

  //SPIピン設定
  SPI.begin(TFT_SCK, -1, TFT_MOSI, TFT_CS);

  tft.initR(INITR_MINI160x80);             //ST7735初期化
 
  tft.fillScreen(ST77XX_BLACK);               //背景の塗りつぶし

  tft.setRotation(0);                         //画面回転
  tft.setTextSize(2);                         //サイズ

  tft.setCursor(0, 0);                       //カーソル位置                      
  tft.setTextColor(ST77XX_RED);              //赤
  tft.printf("Red\n");
  tft.setTextColor(ST77XX_GREEN);            //緑
  tft.printf("Green\n");
  tft.setTextColor(ST77XX_BLUE);             //青
  tft.printf("Blue\n");
}

void loop()
{
}

一応使えそうですが、ほかにも問題が起こるかもしれません。

R36S レトロゲーム機2025/12/13

Linux のゲームエミュレータが入ってて安いということで買ってみました。
Raspberry Pi3でもBatceraとかでできましたが、ちょっとパワー不足で、かといってRaspberry Pi5
とかは高いので中華レトロゲーム機に興味が出ました。
調べるとオリジナルではないものがあるとのことで、アリエクの BOYHOM Store で購入。¥4100

ここにオリジナルかクローンの見分け方があります。
https://handhelds.wiki/R36S_Clones#Avoid_buying_a_clone

箱からはなんか違うような。。。

一応ArkOSが搭載されていました。

裏面の"Consola Retro"がさらに怪しい

電池の蓋をあけて基板のバージョンを確認。V22で今のところ一番新しいものらしい

ここのサイトでパネルの確認をしたら、Panel4と出ました
https://aeolusux.github.io/ArkOS-R3XS/tools/dtbIdentify.htm

オリジナルらしい。


●SDカードの品質が悪いとのことで、すかさずイメージバックアップ&EASYROMSをバックアップ
怪しげなSDカードです

Fedora PCでイメージバックアップ
”ディスク”アプリでSDカードを選択

右にあるメニュー(・が三つの)”ディスクイメージを作成”を選択

適当に名前を付けて作成開始
xxx.imgファイルができます。
このイメージファイルを使って、新しい同容量のSDカードにイメージを書き込み
イメージファイルを右クリックして、イメージライターを選択
”なし”のところで新しいSDカードを選択→リストア開始
これで気兼ねなく遊べます。

●WIFIでアップデート
Raspberry Pi で使っていたWIFIドングルが使えないか試してみました。
だいぶ前に買ったGW-USNano2というものです。これをセリアで買ったUSB変換アダプターでつけてみたところ、問題なく接続できました。
ダイソーで売ってる変換アダプターや¥550のType-CのUSBハブでも使えました。


WIFIの設定はOPTIONのWIFIで。
UPDATEもここから
WIFIが使えるようになると WIFI:up になります。

以前、DSの脳トレを散々やってDS本体が壊れてしまって放置していたのですが、これでやってみたのですが、マウスで文字を書くのは至難の業です。
ダイソーのハブにWIFI、マウスを繋いで動作することは確認できました。USB DACも使えました。


●ARKOSを最初からインストール
ゲームを入れるSDカードとOSを入れるカードは分けたほうが良いと聞いたので、新しいSDカードにArkOSをインストール。

おすすめの microSD カード
https://handhelds.wiki/R36S_Compatibility_Lists

ですが、試しに¥550のダイソー32GBmicroSDで作成

ARKOSダウンロード
https://github.com/AeolusUX/ArkOS-R3XS?tab=readme-ov-file

imgファイルをFedora のディスクアプリでSDカードに書き込んで、R36Sに戻して起動

WindowsではRaspberry Pi imager を使ってみました
今回はRaspberryPiのイメージは作らないので "No Filterig" を選択して次に 

"カスタムイメージを使う" を選択して書き込むimgファイルを選択して次に

書き込むSDカードを選択して次に、Writingに進むので書き込み

本体に戻して起動するとインストールされます。といってもSDカード内に展開されるので、SDカードを変えればいろいろ試せます。

OPTIONSでデバイスを選択

※V22ボードは以下からdtbファイルを入れ替えないとUSB OTGが動かない問題がありました。
以下の対処でWIFIなどの機器が動作するようになりました。

https://handhelds.wiki/R36S_Overview#R36S-V22_2024-12-18

dtb files
https://github.com/AeolusUX/R36S-DTB/tree/main/R36S/Panel%204%20-%20V22

別のSDカードにゲームを入れる
新しいSDカードをexFATでフォーマットして、EASYROMSフォルダの中身(元のゲームが入っているSDカードのフォルダ)をコピーします。
これをR36SのTF2-GAMEスロットに挿して、OPTIONS→ADVANCED→SWITCH TO MAIN SD FOR ROMSを選択して切り替え。

●portmaster install
https://portmaster.games/installation.html
Downloadから以下をゲット

Install.Full.PortMaster.sh

SDカードのtoolsフォルダにコピーする
ネットから必要ファイルがダウンロードされるので、WIFIドングルを繋ぐ
本体に戻してOPTIONS->Tools->INSTALL.FULL.PORTMASTER インストール

PORTMASTER起動 OPTIONS->Tools->PORTMASTER

Optionで日本語も選べます

●kodi install
この記事にKODIに関するものがあったので試してみました。
https://www.reddit.com/r/R36S/comments/1c1mzz4/kodi_on_r36s/?tl=ja

kodi-Installer.rar を取ってきて解凍。
中身の
kodi Installer.sh
kodi.tar.gz
2つのファイルをゲームを入れてあるSDカードにコピー。
コピー先フォルダ: ports
(この2つのファイルはインストール後に消える)
R36Sを起動してメニューのPORTからKODI Installを実行するとインストールが開始される。
結構時間がかかる。
再起動するとKODIの項目がメニューにできる。

KODIを起動するとスペイン語なので設定を変更。
まずスペイン語が分からないので、なんとか英語にする。
左の三つあるアイコンの真ん中が設定(画像はもうすでに英語になってます)
Interface選択
Interface Langage -> English

いきなり日本語にしない理由は以前Raspberry PiでKODIを使っていた時にフォントを”Arial”にしておかないと文字が豆腐になってなんだかわからなくなったので、念のためSkinからFontsをArial にします。

Interface Langageが英語のままでもフォントをArialにすると日本語が表示できます。


DACを繋げばより良い音で聴けます。メディアプレイヤーとしても使えるかも。。。

YouTube埋め込みテスト2025/12/05

YouTubeの埋め込みのテストです。 ブログをHTMLで書かないといけないのは面倒ですね。

画像

2023年のゴールデンウィークの前後に松之山温泉でおてつたびをして、休日観光した際に撮った動画 おてつたび先の”ひなの宿ちとせ”はとても良い人たちと出会えていい経験をしました。

Spotpear ESP32C3-1.442025/11/12

Spotpear ESP32C3-1.44

カラーLCDの使い方を調べるのに”Spotpear ESP32C3-1.44inch” をアリエクで買ってみました。¥1000くらい。 

とりあえず買ったままの状態で起動すると説明が出てくるので、
スマホでテザリング設定で"Spotpear" , password "12345678"
でWifiに接続して一応通常画面が出る。が、時間は合わないし、天気情報もでませんでした。

●Demo  CodeをArduino IDEでコンパイルする。
コンパイル方法

コードの在処
https://github.com/Spotpear/ESP32C3_1.44inch

1】以下のライブラリをインストール
 TFT_eSPI
 TJpg_ Decoder
 ArduinoJson
 Time
 HTTPClient
 lvgl

2】ライブラリ内のTFT_eSPIフォルダのUser_Setup.h を編集する
以下の設定にする
-------------------------------------------------------------------------------------------------------
#define ST7735_DRIVER

#define TFT_WIDTH  128
#define TFT_HEIGHT 128

#define ST7735_GREENTAB3

#define TFT_RGB_ORDER TFT_BGR  // Colour order Blue-Green-Red

#define TFT_INVERSION_ON

#define TFT_MOSI  4
#define TFT_SCLK  3
#define TFT_CS    2  // Chip select control pin
#define TFT_DC    0  // Data Command control pin
#define TFT_RST   5  // Reset pin (could connect to RST pin)
#define TFT_MISO -2 //dummy これ追加(重要)

#define SPI_FREQUENCY  40000000
-------------------------------------------------------------------------------------------------------
Webのspotpearの説明にはありませんが、TFT_MISO に値を入れないとTFT_INITが失敗します。
TFT_eSPI_ESP32_C3.h に不具合があります。TFT_eSPIのバージョンは2.5.43。
出所
https://github.com/Bodmer/TFT_eSPI/issues/3772

・TFT_eSPIのバグ
1,User_Setup.hでTFT_MISOを定義しない、または-1を設定すると TFT_MISI == TFT_MISO となって初期化できない。画面白いまま。
2,TFT_eSPI/Processors/TFT_eSPI_ESP32_C3.h で
#define SPI_PORT_SPI2_HOST
 #define SPI_PORT 2
にしないとCore Dumpが出ます。

TFT_MISO に-2を入れてますが、-1ではなく、存在しないPin番号にしたかったので適当に設定しました。これが良いか不明。

--------------------------------------------------------------------------------------------------------------------------------------------
User_Setup.h
//                            USER DEFINED SETTINGS
//   Set driver type, fonts to be loaded, pins used and SPI control method etc.
//
//   See the User_Setup_Select.h file if you wish to be able to define multiple
//   setups and then easily select which setup file is used by the compiler.
//
//   If this file is edited correctly then all the library example sketches should
//   run without the need to make any more changes for a particular hardware setup!
//   Note that some sketches are designed for a particular TFT pixel width/height

// User defined information reported by "Read_User_Setup" test & diagnostics example
#define USER_SETUP_INFO "User_Setup"

// Config for two ST7735 128 x 128 displays for Animated_Eyes example
#define USER_SETUP_ID 47

#define ST7735_DRIVER     // Configure all registers

#define TFT_WIDTH  128
#define TFT_HEIGHT 128

// #define ST7735_INITB
// #define ST7735_GREENTAB
// #define ST7735_GREENTAB2
 #define ST7735_GREENTAB3
// #define ST7735_GREENTAB128    // For 128 x 128 display
// #define ST7735_GREENTAB160x80 // For 160 x 80 display (BGR, inverted, 26 offset)
// #define ST7735_REDTAB
//#define ST7735_BLACKTAB
// #define ST7735_REDTAB160x80   // For 160 x 80 display with 24 pixel offset

//#define TFT_RGB_ORDER TFT_RGB  // Colour order Red-Green-Blue
#define TFT_RGB_ORDER TFT_BGR  // Colour order Blue-Green-Red

#define TFT_INVERSION_ON
//#define TFT_INVERSION_OFF

// Generic ESP32 setup

//add for spotpear esp32-c3 1.44
#define TFT_MOSI  4
#define TFT_SCLK  3
#define TFT_CS    2  // Chip select control pin
#define TFT_DC    0  // Data Command control pin
#define TFT_RST   5  // Reset pin (could connect to RST pin)
#define TFT_MISO -2 //dummy

//#define TFT_MISO 19
//#define TFT_MOSI 23
//#define TFT_SCLK 18
////#define TFT_CS    21 // Not defined here, chip select is managed by sketch
//#define TFT_DC    2
//#define TFT_RST   4  // Connect reset to ensure display initialises

#define LOAD_GLCD   // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2  // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4  // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_FONT6  // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
#define LOAD_FONT7  // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:.
#define LOAD_FONT8  // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
//#define LOAD_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT
#define LOAD_GFXFF  // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts

#define SMOOTH_FONT


//#define SPI_FREQUENCY  27000000
#define SPI_FREQUENCY  40000000

#define SPI_READ_FREQUENCY  20000000

#define SPI_TOUCH_FREQUENCY  2500000

// #define SUPPORT_TRANSACTIONS
--------------------------------------------------------------------------------------------------------------------------------------------

3】lvglライブラリの lv_conf.h の作成
 lvglライブラリは9.4.0(現時点で最新)から8.4.0にダウングレードした。lv_conf.h がだいぶ違う。9.x.xはエラーがたくさんでて面倒なので。

lv_conf_template.h をコピーして名前をlv_conf.hにしてlibrariesフォルダーに入れる
lv_conf.hを編集する
(編集されたものがDemo codeに入ってるのでそのまま使える)
●---Modify the parameter to change 0 to 1 here
/* clang-format off */
#if 1 /*Set it to "1" to enable content*/ <- 1に 

#ifndef LV_CONF_H
#define LV_CONF_H

#include <stdint.h>

/*====================
   COLOR SETTINGS
 *====================*/

/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 16

/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/
#define LV_COLOR_16_SWAP 0 <- 付属のヘッダーファイルは0だが、Webの説明では1 

●--- Change 30 to 16 and 0 to 1 here
/*====================
   HAL SETTINGS
 *====================*/

/*Default display refresh period. LVG will redraw changed areas with this period time*/
#define LV_DISP_DEF_REFR_PERIOD 16      /*[ms]*/ <- 16に

/*Input device read period in milliseconds*/
#define LV_INDEV_DEF_READ_PERIOD 30     /*[ms]*/

/*Use a custom tick source that tells the elapsed time in milliseconds.
 *It removes the need to manually update the tick with `lv_tick_inc()`)*/
#define LV_TICK_CUSTOM 1 <- 1に
#if LV_TICK_CUSTOM
    #define LV_TICK_CUSTOM_INCLUDE "Arduino.h"         /*Header for the system time function*/
    #define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis())    /*Expression evaluating to current system time in ms*/
#endif   /*LV_TICK_CUSTOM*/

/*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings.
 *(Not so important, you can adjust it to modify default sizes and spaces)*/
#define LV_DPI_DEF 130     /*[px/inch]*/

※ライブラリのバグにたどり着くまで、1日あまり費やしました。githubの皆さんに感謝。
これでTFT_eSPIライブラリが使えるようになりました。


Freenove Arduino用六脚ロボットキット2025/10/31

アリエクで Freenove  6脚ロボットキットを買って組み立てました。(¥12,638コントローラ付き)
箱が結構つぶれていましたが、中身は大丈夫でした。


とりあえず動きました。動画では超音波センサーがついていますが、キットには含まれません。
Wifi経由でスマホアプリやパソコン用のProcessingというアプリでコントロールできます。

ロボット本体のCPUは ATNEGA2560-16AU 
GPIOのヘッダーがあるのでセンサーなどが取り付けられます。

写真にあるヘッダーに超音波センサーを付けて、障害物をよけるようにしました。
●配線
HC-SR04 -> Header pin
Trig -> 2pin
Echo -> 3pin
Vcc -> 3.3V
Gnd -> GND
●動作
1,前足右を2回振る
2,前進
3,物が20cm以内なら左に回転
4,5回左に回転しても物がある場合、スリープ

謎な点
センサープログラムをロボットに組み込んでみたら、1回目に値がおかしい場合がある。ワークアラウンド:とりあえず2回読む。

●スケッチ
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
#ifndef ARDUINO_AVR_MEGA2560
#error Wrong board. Please choose "Arduino/Genuino Mega or Mega 2560"
#endif

// Include FNHR (Freenove Hexapod Robot) library
#include <FNHR.h>

// ====================== 設定 ======================
const uint8_t TRIG_PIN = 2;   // 出力 (TRIG)
const uint8_t ECHO_PIN = 3;   // 入力 (ECHO)

//#define DEBUG_SERIAL

FNHR robot;

void setup() {
  // Custom setup code start
#ifdef DEBUG_SERIAL
  Serial.begin(9600);
#endif
  // 測距センサー
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  digitalWrite(TRIG_PIN, LOW);          // まず LOW にしておく

  // Custom setup code end
  // Start Freenove Hexapod Robot
  robot.Start();
  //前足を振る
  leg_move();
  leg_move();

}

void loop() {
  // Custom loop code start
  static unsigned int count = 0;

  long distance = measureDistance();
  delay(5);
  distance = measureDistance();
#ifdef DEBUG_SERIAL
  if (distance >= 0) {                 // -1 はエラー
    Serial.print("Distance: ");
    Serial.print(distance);
    Serial.println(" cm");
  } else {
    Serial.println("Out of range / timeout");
  }
#endif

  if (distance > 20) {  
  // Crawl forward
  crowl_forward(4);
  count = 0;
  }
  else {
    // Turn left
    turn_left(4);
    delay(1000);

    ++count;
    if (count > 4) {
      robot.SleepMode();
      count = 0;
      while (true); //stop
    }
  }
}

//動作
void leg_move() {
  // Active mode
  robot.ActiveMode();
  delay(500);

  robot.LegMoveToRelatively(1, 10, 60, 50);
  delay(200);

  robot.LegMoveToRelatively(1, -20, -10, -10);
  delay(100);
  robot.LegMoveToRelatively(1, 40, 10, 10);
  delay(100);
  robot.LegMoveToRelatively(1, -40, -10, -10);
  delay(100);
  robot.LegMoveToRelatively(1, 20, 10, 10);
  delay(200);

  robot.LegMoveToRelatively(1, -10, -60, -50);

}

void crowl_forward(unsigned int count) {
  for (unsigned int i = 0;i < count;i++) {
    // Crawl forward
    robot.CrawlForward();
  }
}

void crowl_backward(unsigned int count) {
  for (unsigned int i = 0;i < count;i++) {
    // Crawl Backward
    robot.CrawlBackward();
  }
}

void crowl_left(unsigned int count) {
  for (unsigned int i = 0;i < count;i++) {
    // Crawl Backward
    robot.CrawlLeft();
  }
}

void crowl_right(unsigned int count) {
  for (unsigned int i = 0;i < count;i++) {
    // Crawl Backward
    robot.CrawlRight();
  }
}

void turn_left(unsigned int count) {
  for (unsigned int i = 0;i < count;i++) {
    robot.TurnLeft();
  }
}

// ====================== 測定関数 ======================
long measureDistance() {
  // 1. TRIG を 10µs HIGH にする
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  // 2. ECHO パルス幅を取得(max 30 ms = 30000 µs)
  //    timeout_ms を設定して無限待ちにならないように
  const uint32_t timeout_us = 30000;   // 30 ms
  uint32_t pulse = pulseIn(ECHO_PIN, HIGH, timeout_us);

  if (pulse == 0) return -1;           // タイムアウト

  // 3. 距離を cm に変換
  long distance_cm = pulse / 58;       // 1cm ≈ 58µs(往復)
  return distance_cm;
}