MSX IoT BASIC 失敗学 – MAX30100で気づいた「BASIC向けセンサー」の選び方

みなさん、こんにちは。

前回はSGP30(ガスセンサー)を使い、MSX0で実用的な空気質モニタリングシステムを構築する様子をお届けしました。

「センサーとMSX IoT BASICの相性は抜群だ!」と盛り上がった私ですが、エンジニアの世界には「成功」と同じくらい、あるいはそれ以上に価値のある記録があります。それは「失敗」の記録です。

今回は、私が意気揚々と挑戦して見事に玉砕したMAX30100心拍センサユニットを使ったパルスオキシメーターの実装記録を共有します。

SGP30はあんなに上手くいったのに、なぜMAX30100は実用的にならなかったのか?

その理由は、センサーが要求する「時間精度」と、BASICという言語の「実行速度」の間に、埋められない深い溝があったからです。

 


 

MSX0で血中酸素濃度を測りたい

 

SGP30の成功に味を占めた私は、次なるステップとして生体情報の取得に挑戦しました。用意したのは、指先をかざすだけで脈拍と血中酸素飽和度(SpO2)を測定できるMAX30100心拍センサユニット

M5Stackなどのマイコン開発では定番の、非常に安価で高性能なセンサーで、Arduino IDEを使った実装例も豊富です。実際、私も実装しました。

データシートとにらめっこしながら、I2Cコマンドを精査し、GitHub Copilotを相棒に書き上げたのが以下のBASICプログラムです。

実装したプログラム(HEARTRATE.BAS)

10 CLS
20 _IOTFIND("device/i2c_a",C)
30 PRINT "Found I2C devices: ";C
40 IF C=0 THEN 80
50 _IOTFIND("device/i2c_a",A$(0),C)
60 FOR I=0 TO C-1:IF A$(I)="57" THEN 100 ELSE NEXT I
70 PRINT "MAX30100 not found..."
80 PRINT "Device not found":END
100 PRINT "MAX30100 detected at address: ";A$(I)
110 N$="device/i2c_a/"+A$(I)
120 '--- 変数の初期化 ---
130 HR=0:SP=95:BC=0:TM=0:LI=0
170 '--- MAX30100の初期化 ---
180 GOSUB 1000
190 '--- メインループ ---
210   GOSUB 2000:'FIFOデータの読み込み
230   IF SS > 0 THEN GOSUB 3000:'サンプルの処理
250   TM=TM+1:IF TM >= 100 THEN GOSUB 6000:'1秒ごとに表示更新
270   FOR J=1 TO 2:NEXT J
280   GOTO 200
1000 '--- 初期化ルーチン ---
1010 PRINT "Initializing MAX30100..."
1030 C$=CHR$(&H06)+CHR$(&H03):_IOTPUT(N$,C$):'SPO2モード
1050 FOR K=1 TO 500:NEXT K
1070 C$=CHR$(&H07)+CHR$(&H27):_IOTPUT(N$,C$):'100Hz
1090 FOR K=1 TO 500:NEXT K
1110 C$=CHR$(&H0C)+CHR$(&H33):_IOTPUT(N$,C$):'LED電流
1130 FOR K=1 TO 500:NEXT K
1140 CLS:RETURN
2000 '--- FIFO読み取り ---
2020 C$=CHR$(&H02):_IOTPUT(N$,C$)
2040 _IOTGET(N$,S$):WRP=ASC(MID$(S$,1,1)):SS=WRP
2070 IF SS > 16 THEN SS=16
2080 RETURN
3000 '--- データのパースとビート検出 ---
3010 C$=CHR$(&H05):_IOTPUT(N$,C$):FOR K=1 TO 5:NEXT K
3050 _IOTGET(N$,DT$)
3060 IF LEN(DT$)>0 THEN IH=ASC(MID$(DT$,1,1)) ELSE IH=0
3130 IR=(IH*256)+IL:RD=(RH*256)+RL
3150 '脈動の簡易判定
3160 IF IR > 500 AND LI <= 500 THEN BC=BC+1
3170 LI=IR
3180 'SpO2の簡易計算式
3190 IF IR > 200 AND RD > 0 THEN SC = 110 - 25 * ((RD/IR)-0.5) ELSE SC = 80
3210 SP=INT(SC)
3220 RETURN
6000 '--- 表示更新 ---
6020 HR=BC:BC=0:TM=0:CLS
6090 PRINT "Heart rate: ";HR;"bpm"
6110 PRINT "SpO2: ";SP;"%"
6120 RETURN

 


 

MSX0での実装結果 – Basicでは動作が不安定

 

結論から言いましょう。このプログラムは「壊滅的に動作が不安定」でした。

数値はデタラメ、脈拍は検知したりしなかったり、時には全く反応しない。最大の理由は、BASICという「インタプリタ言語」が持つ実行速度の揺らぎが、センサーの要求する「ミリ秒単位の厳密なサンプリング」を破壊してしまったからだと考えています。

致命的だった「サンプリングの不均一」

MAX30100は、内部で100Hz(10msごと)に新しいデータを生成します。脈拍を測るには、この「10msごとのデータ」を正確に並べて波形として解析する必要があります。

しかし、MSX0のBASICで _IOTPUT_IOTGET を実行すると、通信1回につき数msのオーバーヘッドが発生します。さらに、BASICは1行ずつコードを解釈しながら動くため、ループのたびに処理時間が微妙に変化します。

  • 理想(100Hz): 10ms → 20ms → 30ms → 40ms (一定の間隔)
  • 現実(BASIC): 12ms → 25ms → 31ms → 48ms (ガタガタの間隔)

この「ガタガタ」のせいで、波形の形が崩れ、心拍数を計算するためのビート検出ロジックが正常に機能しなくなったと考えられるのです。

 


 

考慮すべきはセンサーの分類 – スナップショット型 vs タイムシリーズ型

 

今回の失敗から得た最大の知見は、BASICで扱うべきセンサーの選別基準です。私はこの基準を「スナップショット型」と「タイムシリーズ型」という2つの概念で整理しました。

1. スナップショット型センサー(BASIC向き ✓)

「今の気温は?」「今のガス濃度は?」と尋ねた際、その瞬間の値を1つ返してくれるタイプです。

  • 代表例: SGP30(空気質)、BME280(気圧・温湿度)、照度センサー、距離センサー
  • 特徴: データの更新が数秒単位で良く、1回の測定値で完結する。
  • BASICとの相性: 非常に良い。100ms程度の遅延が起きても「今の温度」としての意味は揺らがないからです。

2. タイムシリーズ型センサー(BASIC不向き ✗)

短い時間に連続してデータを取得し続け、その「波形の変化(時系列)」を解析して初めて意味を成すタイプです。

  • 代表例: MAX30100(脈拍)、加速度計(振動解析)、マイク(音声)
  • 特徴: ミリ秒精度の一定間隔サンプリングが必須。100個以上の連続したデータをセットで扱う。
  • BASICとの相性: 非常に悪い。タイマー割り込みが使えないBASICでは、サンプリング間隔を一定に保つことが困難だからです。

 


 

I2C通信の重み

 

MSX IoT BASICにおける _IOT* 命令は、非常に便利ですが、実は内部で複雑な処理を行っている「重い」命令です。

実行のたびに、実測では、1回のループで合計5〜8ms程度の時間を消費します。さらに、血中酸素飽和度(SpO2)を計算するには本来、以下のような物理的な比率計算が必要です。

SpO2=11025×(RAC/RDCIRAC/IRDC0.5)SpO_2 = 110 – 25 \times \left(\frac{R_{AC} / R_{DC}}{IR_{AC} / IR_{DC}} – 0.5\right)

このような浮動小数点演算を伴う処理を、ミリ秒単位の高速ループ内で行うのは、BASICインタプリタにとって酷な要求だったと言えます。ArduinoのようなC++環境であれば、コンパイル済みのバイナリがマイクロ秒単位で動くため問題になりませんが、BASICの「1行ずつ解釈する」という美学が、ここでは仇となってしまいました。

 


 

まとめ – MSX0向けセンサー選びの黄金律

 

今回の失敗を経て、私はMSX0でのIoT開発における「センサー実装指針」を立てました。

BASICで実装すべきセンサー(スナップショット型)

  • 気象・環境: SGP30, DHT20, BME280(数秒おきの計測で十分)
  • 光学・距離: BH1750, M5TOF(「今、何cmか」を知るだけで目的を達せる)
  • スイッチ: ホールセンサー, タッチセンサー(ON/OFFの状態確認)

BASICを避けるべきセンサー(タイムシリーズ型)

  • 生体計測: 脈拍、心電図(ミリ秒のズレが致命傷)
  • 動的解析: 振動、衝撃、音声(周波数解析が必要なもの)
  • 高精度制御: ドローンの姿勢制御など(フィードバック速度が不足)

もし、どうしてもタイムシリーズ型をMSX0で使いたいなら、

  1. MSXべーしっ君でプリコンパイルして高速化する
  2. ロギングに徹して、解析は後でPCで行う

といった戦略が必要です。

「BASICで簡単に作れる」と言われるMSX0のIoT BASICですが、その限界を正しく知ることで、より効率的で楽しい開発ができるようになります。SGP30のような「スナップショット型」のセンサーなら、MSX0はリモート管理が可能で設定変更に強い最強のIoT向け開発プラットフォームだと思います!

 

本日も最後までお読みいただきありがとうございました。

それでは、よいMSXライフを!

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール