04-18-2025 02:33 AM
昨年1月にアップした記事
MicroPythonとLabVIEWで熱電対を使った温度測定
の続編です。
前編との違い
多チャンネル化
温度測定は(1)基準となる雰囲気温度と(2)対象物の温度のように多チャンネルで行うことが多いように思いますので、今回はSPI接続での多チャンネル化(実際はたった2チャンネル)の例を紹介します。多チャンネルとなるとMAX31855熱電対アンプモジュールも安価なものを使いたいのでAliexpressで300円前後のものを使ってみました。Adafruit社のものに比べるとかなり安いですが、熱電対入力のノイズ対策用10nF のコンデンサがついていません。
MicroPythonの main.py を活用し、LabVIEWからの import 文を不要に
また、前回はMicroPythonで書いた測定プログラムの関数をLabVIEWからモジュールとして使うというアイデアでしたが、よく考えればmain.pyという名前で必要な関数だけを書いておけばimport文などを送る必要がないことに気がつきました。
小型で安価なマイコンの使用
マイコンはラズパイPicoを使っていましたが、後継のRP2350が出てきてさらに安くなったWaveshare RP2040 Zero を使います。
Google AI Studioの活用
LabVIEWのダイアグラムは書いてもらえませんが、MicroPythonであれば私が書くよりもエラー処理などもきちんとしたプログラムを書いてくれます。
LabVIEW Community版という趣味や自学習用のLabVIEWを使って、自宅などで温度測定することを想定しているのですが、2チャンネルの温度測定で興味を惹きそうな測定対象を紹介してください。
なるほど、LabVIEW Community版を使った趣味や自学習用途ですね!それなら、身近なもので少し工夫すると「おっ」と思わせるような測定対象が良いですね。2チャンネルあることを活かせる、比較や関係性が見えるテーマをいくつか提案します。
1. PCの冷却性能チェック(吸気 vs 排気温度)
2. 魔法瓶 vs マグカップ(究極の保温/保冷対決!)
3. 窓の断熱性能チェック(室内 vs 室外表面温度)
4. コーヒー抽出/パン作りなどの温度プロファイル(簡易版)
5. 小さな温室/栽培ケースの環境モニタリング(上部 vs 下部/土壌)
内容、面白ポイント、アピールポイントなども書いてくれましたが省略します。
そういえば観葉植物の葉の裏表の温度測定も面白いという話を聞いたことがあります。
それは素晴らしいアイデアですね! 観葉植物の葉の裏表の温度測定は、2チャンネル測定のテーマとして非常に興味深く、教育的価値も高いです。
なぜ面白いのか?
植物の「呼吸」を可視化(蒸散): 植物は、主に葉の裏側にある気孔(きこう)という小さな穴から水分を蒸発させる「蒸散」を行っています。水が蒸発するときに気化熱を奪うため、活発に蒸散している葉(特に裏側)は、周囲の気温よりも、あるいは葉の表側よりも温度が低くなることがあります。この現象を2チャンネルの温度計で捉えることができます。
環境応答の観察: 日光の強さ、湿度、水分の供給状態、風通しなど、周りの環境が変わると、植物の蒸散活動も変化します。例えば:
日中 vs 夜間: 日中は光合成と蒸散が活発なので、裏側の温度が低くなる傾向が見られるかもしれません。夜間は気孔が閉じることが多いので、温度差は小さくなるでしょう。
水やり前後: 水分が十分にあると蒸散しやすいですが、乾燥してくると蒸散を抑制するため、葉の温度が上がるかもしれません。
光の当たり方: 葉の表は光を直接受けて温度が上がりやすいですが、裏側は蒸散で冷却されるため、その差を観察できます。
身近な生命活動の探求: 普段静かに見える観葉植物が、実は環境と相互作用しながら体温調節をしている、というダイナミックな生命活動の一端をデータで感じることができます。
LabVIEW Community版と安価なマイコンなどを使って温度測定を楽しんでみてください。
以下続きます。
来週の4/25は第2回 Knowledge Exchangeですね。今回はリアル参加はできませんが、オンラインで楽しませていただきます。
第2回 Knowledge Exchangeのご案内 (2025.04.25)
04-20-2025 12:15 AM
Waveshare RP2040 Zeroのピンアウト図は次のようになっています。
GP0からGP8がブレッドボードで使いやすいピンです。SPI0とSPI1がありますが、SPI0を使います。
安価なMAX31855ボードにはGND, VCC, DO, CS, CLKと印刷されています。複数のMAX31855ボードで共通のSPI通信バスを使いますが、DOがMISO端子、CLKがSCK端子です。CSはボードごとに異なるデジタル出力端子に接続して通信したいボードのCSピンをLOWにして通信します。
YellowとRedと印刷されたピンにK型熱電対を接続しますが、10nFのコンデンサを接続すると熱電対からのノイズに強くなると仕様書に書かれています。少しテストしている間でも実感しましたので面倒でも10nFは付けないとダメなような気がします。
ピン接続:
RP2040 Zero<--> MAX31855 (Ch1)
GND<--------------> GND
3V3<--------------> VCC
GP0<--------------> DO
GP1<--------------> CS
GP2<--------------> CLK
RP2040 Zero <--> MAX31855 (Ch2)
GND<--------------> GND
3V3<--------------> VCC
GP0<--------------> DO
GP3<--------------> CS
GP2<--------------> CLK
動作確認用のMicroPythonプログラム(tc_max31855_2ch_wLoop.py)
# tc_max31855_2ch.py
from machine import Pin, SPI
import time
DATA_POSI_MAX = 8191 # Max value of 14bit
NEGATE_CONST = -16384 ## Negate Constant for 14bit two's complement
SPI_CH = 0
RX_PIN = 0 #MISO_PIN
SCLK_PIN = 2
SPI_SPEED = 4_000_000
CS0_PIN = 1
CS1_PIN = 3
cs0 = Pin( CS0_PIN, Pin.OUT, value=1 )
cs1 = Pin( CS1_PIN, Pin.OUT, value=1 )
spi = SPI( SPI_CH, sck=Pin( SCLK_PIN ), miso=Pin( RX_PIN ), baudrate = SPI_SPEED )
def get_thermo(cs_pin):
cs_pin.value( 0 )
data = spi.read( 4 )
cs_pin.value( 1 )
temp_data = ((data[0] << 6) | (data[1]>>2))
if ( temp_data > DATA_POSI_MAX 😞 # Negate for two's complement
temp_data = temp_data + NEGATE_CONST
temp_c = temp_data * 0.25
return temp_c
def measure_ch0():
temp0 = get_thermo(cs0)
print(f'{temp0:.2f}')
def measure_ch1():
temp1 = get_thermo(cs1)
print(f'{temp1:.2f}')
def measure_all():
temp0 = get_thermo(cs0)
temp1 = get_thermo(cs1)
print(f'{temp0:.2f}, {temp1:.2f}')
while True:
measure_ch0()
measure_ch1()
measure_all()
time.sleep( 1 )
Thonny IDEを使った動作確認
このプログラムはmeasure_ch0()、measure_ch0()、measure_all()などの関数からget_thermo(cs_pin)を使って温度測定値をプリントします。プログラムの最後に「無限ループのWhileループ」があるので、電源が入ると動き続けます。
LabVIEWなど外部のプログラムから関数を使う
LabVIEWからmeasure_all()などの関数を呼び出して使うときは、「無限ループのWhileループ」を削除してマイコン内にmain.pyという名前で保存します。マイコンに電源が入ると自動的にmain.pyが動作する仕組みがあるので、main.pyが動作しますが、関数定義などを読み込んだ状態ですぐにプログラムが終了し、REPLという対話モードで待機します。そして、USBシリアル経由で関数が入力されると、REPLモードで関数を実行します。
Thonnyを終了します。
マイコンをUSBから外した後で再度USBに接続します。(マイコンのOFF-ON)
Thonnyを開いて、プロンプトに関数名を入力すると測定結果がプリントされます。
LabVIEWを使った場合でもシリアル経由で関数名を入力すると同様の応答を得ることができます。
1行目「measure_all()」
2行目「22.00, 21.75」
3行目「>>>」
raw REPLというエコーしないモードもありますが、シンプルに全て受け取って2行目を取り出して数値に戻すこととします。
すぐに終了する関数定義だけのプログラムをmain.pyという名前でマイコンに保存します。
マイコンの電源をOFF-ONします。
シリアルで「measure_all()」と書き込んで、全て受信して、2行目を取り出して数値に変換して測定結果を得る
動作確認用のMicroPythonプログラム(tc_max31855_2ch_wLoop.py)は熱電対の断線などを検知していないので、エラー処理なども含めたプログラムにGoogle AI Studioに書き直してもらいました。
# main.py (tc_max31855_2ch.py ベースの実用版)
# エラー処理とエラーコード返却機能を追加
from machine import Pin, SPI
import time
import struct # データ解釈とエラービットチェックに使用
print("Booting up... MAX31855 2ch Practical Example.")
# --- エラーコード定義 ---
ERR_OPEN_CIRCUIT = "E01" # 熱電対 断線/未接続
ERR_SHORT_GND = "E02" # GNDへのショート
ERR_SHORT_VCC = "E03" # VCCへのショート
ERR_SPI_READ = "E10" # SPI読み取りエラー or データ不足
ERR_HW_NOT_INIT = "E11" # ハードウェア未初期化
ERR_UNKNOWN = "E99" # その他の予期せぬエラー
# --- ピン設定とSPI設定 (元のコードを踏襲) ---
SPI_CH = 0
RX_PIN = 0 # MISO_PIN (元のコードの変数名)
SCLK_PIN = 2
SPI_SPEED = 4_000_000 # 元のコードの速度
CS0_PIN = 1
CS1_PIN = 3
# --- グローバル変数 ---
spi = None
cs0 = None
cs1 = None
hardware_initialized = False
# --- 初期化 ---
try:
# CSピン初期化 (元のコードに合わせて Pin オブジェクトを変数に格納)
cs0 = Pin( CS0_PIN, Pin.OUT, value=1 )
cs1 = Pin( CS1_PIN, Pin.OUT, value=1 )
# SPI初期化
spi = SPI( SPI_CH, sck=Pin( SCLK_PIN ), miso=Pin( RX_PIN ), baudrate = SPI_SPEED, polarity=0, phase=0) # Mode 0 を明示
hardware_initialized = True
print("Hardware initialized successfully.")
print(f"SPI{SPI_CH}: SCK=GP{SCLK_PIN}, MISO=GP{RX_PIN}, CS0=GP{CS0_PIN}, CS1=GP{CS1_PIN}")
except Exception as e:
print(f"!!! Hardware Initialization Failed: {e}")
hardware_initialized = False # 初期化失敗を記録
# --- 温度取得関数 (エラー処理強化版, 元の関数名を維持) ---
def get_thermo(cs_pin):
"""
指定されたCSピンの温度を測定し、温度(float)またはエラーコード(str)を返す。
structを使用してエラービットをチェックする。
"""
if not hardware_initialized:
return ERR_HW_NOT_INIT # ハードウェアが初期化されていなければエラー
# 1. SPIデータ読み取り
data = None
try:
cs_pin.value(0) # CS Low
time.sleep_us(10) # 短い待機 (重要)
data = spi.read(4) # 4バイト読み取り
time.sleep_us(10) # CS High 前の待機
except Exception as e:
# SPI通信中のエラー (タイムアウトなど)
print(f"SPI read error on CS Pin {cs_pin}: {e}") # デバッグ用にエラー表示
# finally ブロックで CS High にするので、ここではエラーコードを返す準備
data = None # データ取得失敗
finally:
# 例外発生の有無に関わらず、必ずCSをHighに戻す
if cs_pin:
cs_pin.value(1) # CS High
time.sleep_us(10) # CS High 後の待機
# 読み取ったデータが有効かチェック
if data is None or len(data) != 4:
return ERR_SPI_READ # SPI読み取り失敗 or データ不足
# 2. データ解釈とエラーチェック (structを使用)
try:
# 4バイトデータをビッグエンディアンの符号付き32ビット整数に変換
# '>i' は ビッグエンディアン (>) の 符号付き整数 (i) を示す
value = struct.unpack('>i', data)[0]
# --- エラービットのチェック (MAX31855 データシート参照) ---
# D16 (ビット16) が Fault ビット
if value & 0x10000: # D16が1ならフォルト発生
# 詳細なエラー内容をチェック
if value & 0x1: # D0 (ビット0) が1なら Open Circuit
return ERR_OPEN_CIRCUIT
elif value & 0x2: # D1 (ビット1) が1なら Short to GND
return ERR_SHORT_GND
elif value & 0x4: # D2 (ビット2) が1なら Short to VCC
return ERR_SHORT_VCC
else:
# D16は1だが、D0,D1,D2は0の場合 (通常発生しないはず)
return ERR_UNKNOWN + "(Flt)" # 不明なフォルト
# --- エラーがない場合、温度を計算 ---
# 温度データは D31 から D18 までの 14 ビット
temp_data = value >> 18 # 上位ビットを右に18シフトして抽出
# 14ビットの符号付き整数として処理 (2の補数)
# 元のコードの定数は使わず、ビット演算で判定・変換
if temp_data & 0x2000: # 14ビット目 (0x2000 = 8192) が1なら負数
# 負数に変換: 14ビットを超える部分を無視して、14ビットの最大値+1 (16384)を引く
temp_data -= 0x4000 # 0x4000 = 16384
# (元のコードの temp_data = temp_data + NEGATE_CONST と等価)
# 温度計算 (1 LSB = 0.25度)
temp_c = temp_data * 0.25
return temp_c # 正常時は温度(float)を返す
except Exception as e:
# struct.unpack や計算中の予期せぬエラー
print(f"Error processing data: {e}")
return ERR_UNKNOWN + "(Proc)" # データ処理エラー
# --- 測定・表示関数 (元の関数名を踏襲、エラーコード対応) ---
def measure_ch0():
"""CH0の温度またはエラーコードを print する"""
result = get_thermo(cs0) # 温度(float) or エラーコード(str) を取得
if isinstance(result, float):
print(f'{result:.2f}') # 温度なら小数点以下2桁で表示
else:
print(result) # エラーコードならそのまま表示
def measure_ch1():
"""CH1の温度またはエラーコードを print する"""
result = get_thermo(cs1)
if isinstance(result, float):
print(f'{result:.2f}')
else:
print(result)
def measure_all():
"""両チャンネルの温度またはエラーコードをカンマ区切りで print する"""
result0 = get_thermo(cs0)
result1 = get_thermo(cs1)
# 各結果を文字列に変換 (温度はフォーマット、エラーコードはそのまま)
str0 = f'{result0:.2f}' if isinstance(result0, float) else result0
str1 = f'{result1:.2f}' if isinstance(result1, float) else result1
# カンマ区切りで print
print(f'{str0},{str1}')
# --- main.py としての終了処理 ---
if hardware_initialized:
print("Ready for REPL commands.")
else:
print("Hardware initialization failed. Check connections/pins.")
# 元のコードにあった while True: ループは削除し、REPLからの呼び出しを待つ
これでマイコン側は終了しましたので、次はLabVIEWで温度測定プログラムを書きます。
04-21-2025 01:45 AM
LabVIEW側のプログラムはimport*****が不要なので、シンプルになりました。
エラー処理を入れたMicropythonプログラムではエラーが発生すると測定値ではなく、Exxというコードが送られてくるので、対応が必要です。
あまり面倒なことをすると複雑になっていくので、どちらかにエラーが発生したらエラーランプを光らせて両方0°Cを表示するということにしました。
チャートで2チャンネルを表示するためにクラスターで接続しました。
LabVIEWプログラムは2023Q1のMac版で作成しましたが、Windowsでも動作確認をしています。
LabVIEW CommunityのMac版もあまり問題なく使えますのでMacを持っている方は使ってみてください。
MicroPythonプログラム簡易版:tc_max31855_2ch.py
MicroPythonプログラム簡易版ループ処理付き:tc_max31855_2ch_wLoop.py
MicroPythonプログラムエラー処理付き:tc_max31855_2ch_Gais.py
LabVIEWプログラム:tc2ch.vi
04-24-2025 01:41 AM
詳細な解説記事の投稿、ありがとうございます。
インスピレーションが湧きます。
AI による LabVIEW コーディング支援につきまして、以前からChagGPT がテキストベースでは教えてくれていたのですが、ついにダイアグラムを描きはじめました。
まだ生成に時間はかかりますし、あまり正確ではないのですが、驚愕しました。
デバッグのためにこちらが描いたダイアグラムの画像を貼ると、かなりの精度で読み取ってくれます。
04-24-2025 03:09 AM
ysm2019さんコメントありがとうございます。
LabVIEWもAIでコーディングができるんですか。すごいですね。
遊んでみよっと!