「えだまめ」しているラズパイ

30年ぶりに半田ごて握ってラズパイ勉強中。

ラズパイでメッシュWiFiネットワークを構築する(2) Gateway NODEの準備

◯やりたいこと

(1)で準備したラズパイにホームネットワークとメッシュネットワークをつなぐGatewayをインストールする。

◯やったこと

3. Gateway NODE(GW) の作成

ゲートウェイはホームネット側で言うルーターと同じ事をします。ルーターがインターネットとホームネットをルーティングするように、ゲートウェイはホームネットとメッシュネットをルーティングします。NODEをバケツリレーしてやってきたパケットは NAT変換されホームネット側に渡されます。

NODOがアシストしてきたボール(パケット)を堂安(ゲートウェイ)がゴール(ホームネットへ渡す)するイメージです。ただしメッシュ側からホーム側へのアクセスはインターネットを含めて自由にできるのですがホーム側からメッシュ側へのアクセスはできません。日本側が必ず勝つ設定になっています。もちろん日本チーム同志でのボールのやりとりはオールオーケーですが…。そのためメッシュ側にアクセスするにはゲートウェイのホーム側 IPへ SSHでログインし、そこから2段接続でメッシュ側にアクセスする必要があります。

今回はゲートウェイを以下の通りで設定していきます。
・インターフェース : eth0-ホームネット側、wlan0-メッシュネット側
・ホームネット側アドレス : ホームネットのDHCPからもらう
・メッシュネット側アドレス:192.168.199.1
・メッシュネットワーク:192.168.199.0
・メッシュネットマスク:255.255.255.0

3-1. ラズパイの準備

今回ゲートウェイにはラズパイ 3B+を使用します。できれば新型の 4B+を使用したかったのですが、昨今のラズパイはひどい入手難。仕方がないので引退していた3B、3B+を引っ張り出してきてパッシブ型のアルミケースに入れて利用します。

(1)の要領で作成したラズパイをホームネット側にLANケーブルで接続して起動し、TeratermPuttyなどのターミナルソフトを使って sshゲートウェイにログインしておきます。

ssh pi@192.168.1.xxx(ゲートウェイにホームネット側から付与された IPアドレス)
password:

3-2. batctl ツールのインストール

最初にbatctl ツールをインストール。

sudo apt-get install -y batctl

インストール後以下の内容で設定ファイルを作成します。

nano ~/start-batman-adv.sh

・ファイル内容

#!/bin/bash
# batman-adv interface to use
sudo batctl if add wlan0
sudo ifconfig bat0 mtu 1468
# Tell batman-adv this is an internet gateway
sudo batctl gw_mode server
# Enable port forwarding
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo iptables -A FORWARD -i eth0 -o bat0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i bat0 -o eth0 -j ACCEPT
# Activates batman-adv interfaces
sudo ifconfig wlan0 up
sudo ifconfig bat0 up
sudo ifconfig bat0 192.168.199.1/24

最下行のアドレスがゲートウェイのメッシュ側 IPアドレスです。
作成後は忘れずに chmodでファイルに実行権限を付与し実行可能な状態にしておきます。

chmod +x ~/start-batman-adv.sh

 

3-3. wlan0のネットワーク定義の設定

続いてwlan0インターフェースのネットワーク定義をroot権限で作成していきます。

sudo nano /etc/network/interfaces.d/wlan0

・ファイル内容

auto wlan0
iface wlan0 inet manual
wireless-channel 1
wireless-essid call-code-mesh
wireless-mode ad-hoc
wireless-key 1234567890

大元の記事ではネットワークにセキュリティキーを使用していなかったのでここでは別に追加しています。キーは16進数で10桁などの制限がありますので注意して設定してください。

・wireless-channnel 1〜12チャンネル
・wireless-essid YOUR-SSID

などは任意の値をセットできます。日本ではチャンネルを13まで利用可能なようなのですが、ラズパイでは12までしか設定できないようです。モードはアドホック固定です。このファイル内容はメッシュネットワーク内全てのデバイスで同一である必要があります。

3-4. batman-advモジュールのロード

echo 'batman-adv' | sudo tee --append /etc/modules

3-5. wlan0をDHCPプロセスの管理から外す

echo 'denyinterfaces wlan0' | sudo tee --append /etc/dhcpcd.conf

3-6. 起動時実行スクリプトの定義

起動時にstart-batman-adv.shを実行するため rc.localにスクリプトを定義します。

sudo nano /etc/rc.local

ファイル内の exit 0 の前に

/home/pi/start-batman-adv.sh &

の1行を挿入しておきます。

3-7. DHCPサーバーのインストール

ここからは Gateway専用の設定になります。まずはメッシュ側のDHCPサーバーとして「dnsmasq」をインストールします。

sudo apt install dnsmasq -y

インストール後以下のファイルを開き、

sudo nano /etc/dnsmasq.conf

RollDownキーを使って最下行までスクロールしてそこへ貸し出す IPアドレスの範囲を記述しておきます。

interface=bat0
dhcp-range=192.168.199.2,192.168.199.99,255.255.255.0,12h

最後に iptablesの情報を永続化するためのツールをインストールしておきます。

sudo apt install iptables-persistent -y

ルールを保存するか ip4と ip6で2度聞いてくるのでどちらも「はい」を押してください。

3-8. ゲートウェイの動作確認

ここでいったんリブートします。

sudo reboot

リブートすると wlan0はメッシュネットワーク側での動作に変わりますので以後の操作は eth0(ホームネット側)からログインして操作することになります。
ssh でログイン後以下のコマンドを実行してみて

ifconfig

次の内容が表示されれば正常に動作しています。

bat0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1468
        inet 192.168.199.1  netmask 255.255.255.0  broadcast 192.168.199.255
        inet6 fe80::f1e8:b920:3a95:cdee  prefixlen 64  scopeid 0x20<link>
        ether b6:fb:9f:de:90:f6  txqueuelen 1000  (イーサネット)
        RX packets 57  bytes 2394 (2.3 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1  bytes 110 (110.0 B)
        TX errors 0  dropped 42 overruns 0  carrier 0  collisions 0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.82  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 fe80::fed7:b320:764f:f01f  prefixlen 64  scopeid 0x20<link>
        inet6 240d:1a:8d4:8500:231a:25e5:4a64:820b  prefixlen 64  scopeid 0x0<global>
        ether b8:27:eb:51:6b:02  txqueuelen 1000  (イーサネット)
        RX packets 4445  bytes 348477 (340.3 KiB)
        RX errors 0  dropped 2  overruns 0  frame 0
        TX packets 119  bytes 14678 (14.3 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (ローカルループバック)
        RX packets 29  bytes 4779 (4.6 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 29  bytes 4779 (4.6 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet6 fe80::ba27:ebff:fe04:3e57  prefixlen 64  scopeid 0x20<link>
        ether b8:27:eb:04:3e:57  txqueuelen 1000  (イーサネット)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 508  bytes 56697 (55.3 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

チェック点
・eth0がホームネット側から割り当てられたIPアドレス(ここでは192.168.1.82)であること
・bat0が192.168.199.1のアドレスであること
・wlan0にはIPアドレスが割り当てられていないこと

続いて以下のコマンドで、デバイスのワイヤレスインタフェースを確認します。

iwconfig

 
表示例

lo        no wireless extensions.
eth0      no wireless extensions.
wlan0     IEEE 802.11  ESSID:"call-code-mesh"
          Mode:Ad-Hoc  Frequency:2.412 GHz  Cell: FE:9E:13:0E:98:71
          Tx-Power=31 dBm
          Retry short limit:7   RTS thr:off   Fragment thr:off
          Power Management:on
bat0      no wireless extensions.

チェック点
SSIDが「/etc/network/interfaces.d/wlan0」で規定した「call-code-mesh」となっている
・モードはAd-Hoc(アドホック)になっている

続いてwlan0がメッシュネットワークでアクティブになっているか確認します。

sudo batctl if

activeと表示されれば正常に動作しています。

wlan0: active

〜 続く 〜

WiFiのチャンネルについて

パッシブ型のアルミケースでラズパイを包み込んでしまうとやはり内臓ワイヤレスLANの電波強度が弱くなるようです。(下図では7ch)


現在稼働しているメッシュネットのブリッジには WiFiドングルを増設しているのですが内臓に比べると電波強度が強く出ています。(図の 1ch)

NODEが場所々々で電波強度を維持しているので、混信を意識し3チャンネルおき5チャンネルで運用してます。ホーム側のWiFiはラズパイでセットできない13チャンネルを設定してチャンネルを有効利用中。さらに混んできたら2チャンネルおき7チャンネルまで増やす予定です。

ラズパイでメッシュWiFiネットワークを構築する(1) OS用SDカード・ラズパイの準備

◯やりたいこと

家の中に増殖しまくっているラズパイ群、

設置場所が広がりすぎてひとつのWiFiアクセスポイントだけでは家内全部をカバーしきれなくなってきました。そこでバケツリレー方式でデータを転送してくれるメッシュネットワークを利用し家内全てにWiFiが届くネットワークの再構築に向け準備を始める事にしました。

今回参考にさせていただいたのはこの記事。
ラズパイでメッシュネットワークを作成する 準備編その1:名刺サイズの超小型PC「ラズパイ」で遊ぶ(第62回) - ITmedia NEWS

記事はとても参考になりました。感謝です。
さらにこの記事の大元である
WiFiMeshRaspberryPi/README.md at master · binnes/WiFiMeshRaspberryPi · GitHub

を翻訳ページ
DeepL翻訳:世界一高精度な翻訳ツール

で訳しながら様々な利用シーンを想定していろいろと実験していきたいと思います。

◯やったこと

1. メッシュネットワーク全体の把握

今回構築するメッシュネットワークの第一到達点は原本記事内にある1番最初のネットワーク図。


(原本記事を和訳したものをワードに貼り付けて印刷、にらめっこしてます)

インターネットがつながるホームネットワークとは別のサブネットでメッシュネットワークを構築し、そのネットワーク内に
 ・ゲートウェイ(Gateway)
 ・ブリッジ(Bridge)
 ・ノード(NODE)
の3つをラズパイで立ち上げられれば第一段階が完成です。
インターネットにつながるホームネットワークが既にあることが前提条件です。

2. メッシュネットワーク構築用ラズパイの準備

2.-1. 使用するラズパイOS

メッシュネットワークに利用するラズパイのOSは新しく構築するということもあり最新版のbullseyeを使用します。あえて新規にインストールせず現在稼働中のラズパイにメッシュをインストールしても大丈夫なのですが、負荷のかかるゲートウェイとブリッジに関しては新規に専用マシンを準備する事にしました。OSの種類は32Bit版Lite、これをCUIでインストールします。(私のラズパイ開発環境は相変わらずiPadで階段下のWindowsマシンにリモートログインする一本指打法なので、実はCUI以外のOSのインストール方法が取れないというのが実情です)。

2-2. SDカード書込み機

SDカードにOSを書き込む方法は Windowsマシンで「Raspberry Pi Imager」を使うのが一般的ですが、やたらと遅いので私の場合ラズパイを利用して作成しています。

OSイメージを一度ダウンロードしてしまえば後は1〜2分で書込みが終了しますので開発時に何度もSDカードを作り直す際はとても重宝します。ここではWindowsマシンではなくあえてラズパイでSDカードを作る方法を記載しておきます。

2-3. OSの書込み

書込み用のラズパイはフルバージョンをインストールしてあり、iPadからリモートログインしてGUI操作ができるようになっています。ただ使用するのは XLTerminal で結果的に CUI での書込みとなります。

2-3-1. OSのダウンロード・解凍

32Bit Lite版 OS 「2022-09-22-raspios-bullseye-armhf-lite.img」をカレントディレクトリにダウンロードします。

wget https://ftp.jaist.ac.jp/pub/raspberrypi/raspios_lite_armhf/images/raspios_lite_armhf-2022-09-26/2022-09-22-raspios-bullseye-armhf-lite.img.xz

ダウンロードが完了したらファイルを解凍しOSイメージをカレントディレクトリに準備しておきます。

xz -dv 2022-09-22-raspios-bullseye-armhf-lite.img.xz


2-3-2. OSの書込み

空のSDカード(空でなくても問題ありません)をUSBカードリーダーにセットし書込み機に差し込みます。この時ディスクをマウントするか聞いてきますのでキャンセルして未マウントの状態を維持します。

dfコマンドで他のSDカードのマウントがない事を確認し、挿入したSDカードのデバイス名が/dev/sdaになる事を確認して以下の書込みコマンドを実行します。

sudo dd bs=4M if=2022-09-22-raspios-bullseye-armhf-lite.img of=/dev/sda status=progress conv=fsync

1分半ほどで書込みが終了します。


2-3-3. bootフォルダのセットアップ

書込みが終わったらこのSDカードで bootする際キー入力が不要となるよう必要なファイルを作成しておきます。
ここでカードリーダーを一度抜き差しします。すると再びマウントするか聞いてきますので今回は暗証番号を入力しSDカードをマウントします。

dfコマンドを実行すると /dev/sda1/(=/media/pi/boot)、/dev/sda2(=/media/pi/rootfs)の2つがマウントされているのが確認できます。

この状態で以下の3つの作業を行います。

a.起動後 SSHでログインできるようsshディレクトリを作成

sudo mkdir /media/pi/boot/ssh

b.WiFi接続用の設定ファイルを作成

sudo nano /media/pi/boot/wpa_supplicant.conf

ファイル内容

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=JP
network={
    ssid="YOUR-SSID"
    psk ="YOUR-PASSWORD"
}


c.ログインユーザーを登録するファイルを作成
前もってログインユーザーのパスワードをハッシュ化しておきます。

echo 'YOUR-PASSWORD' | openssl passwd -6 -stdin

結構長いアルファベット・数字の文字列が出力されますが、これを以下のファイル内で設定するパスワードとして使用します。

sudo nano /media/pi/boot/userconf.txt

ファイル内容

pi:ハッシュ化されたYOUR-PASSWORD


2-3-4. マウントの解除

最後にマウントを解除して完成したSDカードを抜きます。

umount /dev/sda1
umount /dev/sda2


2-4. 作成したSDカードでラズパイを起動

いよいよ 2-3 で準備したSDカードをラズパイにセットして起動します。bootフォルダに書込んでおいた.ssh、wpa_supplicant.confなどのファイルは一度起動してしまうと消去されてしまいますのでセットアップは一発勝負です。セットアップに失敗した場合は2-3に戻って起動ファイルの再設定が必要になりますので注意してください。

2-4-1. IPアドレスの確認

ここからがCUIインストールのむずかしいところです。起動したラズパイにはモニターがつながっていませんのでキチンと起動したかどうかが不明です。そこで NetEnumなどの IPアドレス検索ツールを使ってラズパイが WiFiにつながり DHCPから IPアドレスをきちんともらっているのかどうかを確認します。順調に進んでいれば新しいアドレスが割り振られメーカー名などが表示されます。
この時保険として LANケーブルもつないでおけば WiFiで接続に失敗しても有線で確実に IPアドレスを取得できるので安心です。
割り振られた ipアドレスは次の sshログイン時に必要になりますので別にひかえておきます。

2-4-2. SSHでログイン

WindowsマシンからTeraTermPuttyなどの SSHターミナルを使い、2-4-1 で取得した IPアドレスに 2-3-3.c でセットしたユーザー・パスワードを使ってログインします。

2-4-3. ラズパイのセットアップ

無事ログインできたら以下のコマンドを入力してラズパイのセットアップを行います。
a. 次回起動時に sshでログインできるようホームディレクトリに .sshフォルダを作成

mkdir -m 700 .ssh

b. ファイルシステムの再構築

sudo raspi-config nonint do_expand_rootfs

c. ローカル言語を日本語にセット

sudo raspi-config nonint do_change_locale ja_JP.UTF-8

d. ローカル時間を東京にセット

sudo raspi-config nonint do_change_timezone Asia/Tokyo

e. rootのパスワードを設定

echo -e "YOUR-PASSWOR\nYOUR-PASSWORD\n" | sudo passwd root

YOUR-PASSWORDの部分を任意のパスワードに置き換えて入力します。
f. カーネルのアップデート

echo y | sudo rpi-update ; sudo reboot

ここで再起動しますので再び sshでログインしたら

g. パッケージのアップデート

sudo apt update; sudo apt -y full-upgrade
sudo apt -y autoremove; sudo apt autoclean

でセットアップが完了です。

2-4-4. セットアップおまけ

以下のコマンドは必要であれば実行しておきます。
h. ホストネームの変更

sudo raspi-config nonint do_hostname hoge

g. SDカードを長寿命化するため /tmpフォルダを RAMディスク化

echo tmpfs  /tmp tmpfs defaults,size=256m,noatime,mode=1777 0 0 | sudo tee -a /etc/fstab

h. 同上 /var/logフォルダを RAMディスク化

echo tmpfs  /var/log  tmpfs defaults,size=16m,noatime,mode=0755 0 0 | sudo tee -a /etc/fstab

/var/logフォルダをRAMディスク化する場合は、一度作られたフォルダがないとアプリケーションによっては動作不良になってしまう場合があるので注意が必要です(sanba等)。再起動で消えてしまったフォルダを起動時にスクリプトで再作成しておく必要があります。

以上のコマンドは再起動すると有効になります。

〜 続く 〜

◯現在快調に動作中

本来WiFi電波が届かないところに電波を届けるため、中間地点くらいにバケツリレー用のラズパイ(NODE)をおき

NTPを利用した時計を表示させるラズパイ(NODE)をおくとあら不思議。

インターネット通信してNTPからキチンと正しい時刻を取得し表示しています。中継機の電源を切るとネットが途切れ時間が狂うのでバケツリレーは正しく動作しているようです。メッシュネットワーク、かなり便利なツールの予感がし始めています。

CCS811・BME280・BH1750を使ったCO2データロガーの製作(3) マメタン実験編

◯やりたいこと

動作実験中のCO2ロガーで、実際に豆タンアンカを使用した際のCO 2濃度を測ってみる。

◯やったこと

豆タンを使っている寝室に測定器を置いて実際に一晩測ってみました。その結果驚きの事実が…。

使用する豆炭アンカはメジャーなミツウロコ製。それにワンタッチで火が付くマメタンを利用して火をセットします。もちろん熾火になるまで外に置いておくのは鉄則です。

それを布団の中に入れ脇にCO2ロガーをセットしておいたところ

なんか量子的な変化。センサーの癖なのか布団をバフバフしたせいでCO2がポンと上がったのか。
いずれにしてもしばらくおいておいたらCO2濃度が2600ppmを超えてきましたので

24時間換気のスイッチを入れました。(ちなみに住宅内の推奨CO2濃度は1000ppm以下です。)
その時の減少推移はこんな感じ。

1時間後 CO2は 200ppm 、VOCは350ppbの減少です。

昔のスカスカな家ではこれが天然で行われていたんだろうなあとヤボな事を考えながら換気スイッチを切り就寝。現金なものでスイッチを切ったらしっかりCO2が上昇し始めていました。

そして翌朝、驚きのサムネ写真。

CO2は3700ppm、VOCは4000ppbを超えグラフを振り切ってしまっていました。おそろしい。
CO2濃度は事務所内規定値の 5000ppmは超えていませんでしたがVOCの高濃度には愕然。豆炭独特のにおいの元はこれだったんでしょうか。
この値が正確かどうかはまだわかりませんが、何気に使っている豆炭の危険性を改めて認識。CO2ロガーを作ってみようと思ったのは正解、てな感じでしょうか。
h

◯やってみて

身近な豆炭の危険性を再認識。ロガーは未だブレッドボード上にバラックで組み立てた状態ですので測定値の正確性を検証するためにも早いとこケースに入れてしまおうと考え始めました。次回ケース化のレポートができればと考えています。

◯その他雑談

焼き上がったマメタン、今年は手あぶり火鉢なるものを買ってみたのでその灰になってくれています。

テレビを見ながら火ばしでサクサク細分化。

いい「ながら作業」アイテムです。

◯その後

測定データの加工方法でグラフがどう変化するかをチェックしています。

1つ目の山 1回あたり5回測定しその平均値を記録
2つ目の山 1回あたり10回測定しその中央値を記録
3つ目の山  同上 で電池切れのため異常値を記録
4つ目の山 測定毎に行なっていた温度補正を温湿度固定で測定

◯その後2 (2023.07.15)

ハードウェアが結構バージョンアップしています。

その上でソフトウェアを確定させようとジタバタしているのですが、未だWROOM02タイマー起動とCCS起動との連携がうまくいかず難儀しています。
WROOMがディープスリープに入ってもCCSの電源は切らずに60秒測定の低消費電力モードでゆっくり待機させ、手動リセット起動時以外はそのままの状態のCCSからデータを読むだけ、という作戦を立てているのですがなかなか成功しません。
現在は単3電池2本で1.5日程度しか動作しない超燃費の悪い測定器になっています。

省電力型の測定器完成に向けもう少々粘りが必要そうです。

CCS811・BME280・BH1750を使ったCO2データロガーの製作(2) 回路の動作実験

◯やりたいこと

(1)で作成した回路をブレッドボード上に組み、とりあえずセンサーで実際に測定してみる。

◯やったこと

・CCS811モジュールの準備

今回はCCS811モジュールとして動作電源が 5Vのタイプと

チップ端子が直接出力されていて 3.3V動作用タイプの

2つを準備しました。5V動作用のモジュールはarduino用として売っていたものですが、パターンを追ってみるとレギュレータを通して内部 3Vで動作しており、単に出力信号線を 5Vへプルアップしているだけのいわゆる 5Vトレラント回路となっていました。そのためレギュレータのドロップアウトを考えてもなんとか 3.3Vで動作させる事が出来そうな感じでしたので、どちらも同じ回路で実験してみる事にします。

・実験回路組立て

いつも通りブレッドボードに実験する回路を組んでいきます。
まずは(1)で実験済のリセット回路と配線。

その上にCCS811 とOLED、

ESP-WROOM-02

BME280をセットすると

組立てが完了です。

・動作確認

実を言うとうまく動作するまでには相当苦労させられました。何たってライブラリに付属しているサンプルスケッチではうまく動作してくれなかったのです。WDTを利用したハードウエアリセットを多用するスケッチなのですがどうもバグが潜んでいるようです。困ったもんです。

先輩諸氏の組まれたスケッチやライブラリを読み込んであーでもないこーでもないとあちこちいじり回しやっと動作した写真がこちら。

deepSleepせずに 1分おきにループして測定を続け、データをOLEDに表示・WiFi送信するという単純なスケッチでついに測定に成功しました。

グラフは夜0時過ぎから測定を開始し、朝起きて足元が寒かったのでカセットガスストーブを焚いて消すまでのデータを記録しています。記録されたCO2濃度は概ねその行動パターンに追付いしていますので無事動作しているのは間違い無いでしょう。

起動から2分間の電流波形は

となっており、起動時にWROOM02がフルで電流を消費、その後CCS811が起動してさらに消費電流が増えています。一定時間経過後 wroomが MODEM_SLEEPに入ると消費電流が約 50mAへと減少し WiFi接続継続用に瞬間的に消費電流が増えるというパターンを繰り返しています。

ちなみに電源投入時には 342mAを記録しており、

コンデンサの突入電流も加味されているとはいえ電源周りをしっかりと組む事を改めて意識です。

◯やってみて

とりあえず CO2測定には成功しましたがこのままでは消費電力が大きくとても電池動作というわけにはいきません。USBから給電しながら電池も充電しながらという事で電源周りの回路追加は必須な様です。省電力化という事でCCS811 の立ち上げまわりのノウハウ取得もいろいろと実験で確かめていく必要がありそうです。今回も少々長丁場になりそうです。

・その後の測定1

午後の部屋のCo2変化グラフ。

部屋に人が入るとポンとCO2濃度が上がりますが、その後不在になると24時間換気の影響で徐々に濃度が下がっていきます。3山目以降は複数人が部屋に常にいたため濃度上昇度合いが高くなっていますがやはり換気の影響で徐々に濃度が下がっていきます。24時間換気ってしっかり活躍しているんですね。

最後の大きな山は台所で夕食の準備が始まった時。急激に濃度が上昇し調理中は高濃度を維持しています。IHなのでガスは出ないはずなんですが食材からでもVOCが出ているんでしょうか。
とはいえ調理後は順調に濃度は下がり、途中換気扇をつけたところ下がり方が急になって 1時間で900ppmほど下げています。さすが換気扇。ただ温度も 0.5℃ほど下がり足下がスースーしたのも事実ですが…。
まだ実験段階ですが、面白いデータが取れてます。

ちなみに電流計の測定精度を下げ(16Bit->14Bit)、サンプリングレートを60SPSに上げた電流計でプロットした電流グラフ。

ふむふむ、なるほど…。
スケッチのバグを発見です。

CCS811・BME280・BH1750を使ったデータロガーの製作(1) 回路図作成・リセット回路の実験

◯やりたいこと
最近やたら値段が上がっているマメタンアンカ、

中国の大キャンプブームのせいかと思っていたらウクライナの越冬向けにも寄付を募っているニュースが出たりして世界的に需要が高まっているよう。我が家にも現役アンカがあるのですが、今年もぬくぬくに備えそろそろ準備を始める事にしました。

そこで必要になるのが酸欠防止用のCO2センサー。あちこちで暖房に活躍してもらうため普段使い用にCO2センサー(CCS811)+温湿度・気圧センサー(BME280)+照度センサー(BH1750)を組みあわせた携帯型データロガーを作ってみる事にしました。

主な機能は以下の通り。
1. 一定間隔でデータをWiFi転送するロガー機能
2.リセットスイッチによる手動起動機能を持たせその際はOLEDにデータを表示
3. CO2濃度によるブザー警報機能
4. 電池動作

◯やったこと

・回路設計

各センサーはESP-WROOM-02のI2C通信を利用して制御し各々の省電力モードに期待して常時通電を基本にします。特にCCS811は常時通電しておかないと正確な値が出ないようです 。ただOLEDは常時点灯が不要ですので起動時にリセットスイッチをセンスしオンであれば電源を入れるようにします。

リセットパルスはSW入力を微分してワンショットパルスを作成しマイナス側の電圧をカットするためFETで受けてWROOMに入力しています。リセットパルスの幅は必要最低限にしてWROOMを素早く起動させ、スイッチが離される前にその状態を読み込んで手動起動かを確認するという作戦です。これに関してはうまくいくかどうか後ほど実験してみます。

以上の点を検討して作成した回路図は以下の通りです。

・リセット回路

まずはリセット回路が予想通りに動作するか実験してみます。

この間作った電源がさっそく活躍してくれています。
回路図ではコンデンサが1μFですが写真は0.1μF(時定数10ms)で行っている時の様子です。この時の微分回路出力とFET出力をハンディオシロで確認してみると


(水色:微分回路出力 黄色:FET出力)

SWを相当早く押してみましたがリセット起動後SWがOFFになるまで50ms以上ありましたので普通に押してる分には問題なく手動起動を検知してくれると思います。リセットパルスの幅の実測値は 9msほど。まあまあいい感じです。

この回路で起動後 delay()5秒、deepSleep()10秒を繰り返すスケッチを組み

タイマー起動時はLED消灯、リセットスイッチによる起動はLEDを点灯させてみると

バッチリ。
予定通りうまく動作してくれています。

この時の電流計のログ。
1番右の山がdeepSleep()中にリセットスイッチで起動した部分。定期起動部分に比べ LEDが点灯した分電流値が増えています。

◯やってみて

起動方法が確定したので次回実際にセンサーをつないで動作試験を行なってみます。

MCP3425(16Bit ADC)を使った電流計の製作(5) 実機の製作 完成編

◯やりたいこと

(3)、(4)で製作したボードとケースを組立て、以下の仕様でスケッチを作成し電流計を完成させます。
○仕様
・供給電圧 3.3V(max 800mA)
・電流測定範囲 (トグルSW切替)
  High:1024mA〜10mA
  Low:256mA〜1mA
・電流ロギング機能 (WiFi転送)
  10sec〜6min (プッシュSW切替/長押しスタート)
・単3電池 3本

電流の測定範囲はシャント抵抗の影響もあり現実的には最大値の 1/10程度です。あくまでも「測定可能範囲」です。

◯やったこと

・組立て

(3)で作成したメインボードと(4)で作成したケースを合体させます。

組み立てる前にまずWROOM-02のピンヘッダ加工をしておきます。ピンヘッダは丸ピンソケットに対し足が長いためそのままだとムダにボードの高さが高くなるためです。

1.5mmのボード2枚を使って足の長さを確保しフラットカットニッパでバチバチ切断。

これで1.5mm程高さを低くすることができます。

OLEDは付いていたピンソケットを外して配線を引き出しておき、

ケースに実装して必要ヶ所をつなげば

電源付き電流計の完成です。

・スケッチの作成

仕様に従い(2)のスケッチに
WiFiクライアント動作
・電流表示モードと電流記録(ロギング)モードを作成
・ロギング時間設定/モード開始用にSWを追加

を追加しました。

/**
*
*    MCP3245(16Bit ADC)電流計 
*               WR02_WiFicli_ADC_OLED v0.1.1 (ok)
*
*/
// Standerd Library (IDE v1.8.57.0)
#include              <Arduino.h>
#include              <Wire.h>
 
// Board Library (ESP8266 v2.7.2)
/* ESP8266 Boards(v2.7.2)/Generic ESP8266 Module */
#include              <ESP8266WiFi.h>
#include              <WiFiClient.h>        // wifi_set_sleep_type()

// Install Library
/* OLED */
#include              <Adafruit_SSD1306.h>  // v2.5.7

// Global Value
/* WiFi */
#define CONN_SSID     "YOUR SSID"
#define CONN_PASS     "YOUR PASSWORD"
boolean wifi_mode =   false;

/* My ID,IP,MAC */
IPAddress ip          (YOUR IP);
IPAddress gateway     (YOUR GATEWAY);  // ex. 192,168,0,1
IPAddress dnServer    (YOUR DNS);
IPAddress netmask     (YOUR SUBNET);   // ex. 255,255,255,0

/* UART */
#define TX            1         // D1
#define RX            3         // D3

/* I2C */
#define I2C_SDA       2         // D2
#define I2C_SCK       0         // D0
#define I2C_CLK       100000    // fSCL=100KHz

/* SW */
#define SW            TX 
#define SW_on         LOW
#define SW_off        HIGH

/* OLED(SSD1306) */
#define OLED_ADR      0x3C      // I2C Address
#define SCREEN_WIDTH  128       // OLED display width, in pixels
#define SCREEN_HEIGHT 64        // OLED display height, in pixels
#define OLED_RESET    -1        // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306      display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
unsigned long
        dsptime   =   0;

/* ADC(MCP3245) */
#define ADC_ADR       0x68      // I2C Address
#define ADC_REF       2.048     // [V]:Voltage Reference
float   curr_max  =   0;
float   curr_min  =   0;
boolean curr_peek =   false;

/* logging 
*   log_max 5000:341sec 3000:212sec 1000: 70sec
*            450: 30sec  300: 20sec  150: 10sec */
boolean log_mode  =   0;        // 0:表示中 1:ロギング中
uint8_t log_time  =   0;        // 記録時間 10s,30s,1,2,3,4,5,6m
int     log_max[] =   {150,450,900,1800,2700,3600,4500,5400};
int     cunt      =   0;
/* Tag name:log_struct, member:mill,curr */
struct  log_struct {
  long    mill;
  float   curr;    };
struct  log_struct    logg[5400]; // 5400:RAM 70964(86%)

///// ハードウエア 初期設定
void setup() {
  /* Sleep Mode */
  wifi_set_sleep_type(MODEM_SLEEP_T);
  Serial.end();

  /* Pin Mode */
  pinMode(TX, INPUT_PULLUP);
  pinMode(RX, INPUT_PULLUP);
  
  /* I2C */
  Wire.begin   (I2C_SDA, I2C_SCK);
  Wire.setClock(I2C_CLK);

  /* OLED(SSD1306) */
  display.begin(SSD1306_SWITCHCAPVCC, OLED_ADR);
  /* OLED 起動表示(splash.h) */
  display.display();

  /* WiFi */
  WiFi.config(ip, gateway, netmask, gateway);
  unsigned long timeout = millis();
  while(millis() - timeout < 5000) {
    if(wifi_connect()) {wifi_mode = true; break;}
  }
}

///// メインルーチン
void loop() {
  /// 変数 初期設定
  /* レンジ切替変数 */
  float   SHUNT, GAIN, MAX;
  uint8_t DIGIT;
  byte    CMD;
  /* 測定用変数 */
  float   curr, shnt;
  byte    conf;
  
  /// 測定レンジ設定 (SWのチャタリング未チェック)
  if (digitalRead(RX)) {
    // HIGH Range(max:1.024A)
    GAIN  = 2;          // [倍]:PGA Gain
    SHUNT = 0.1;        // [Ω]:Shunt Resistor
    DIGIT = 2;          // [桁]:表示する小数の桁数
    CMD   = 0b10001001; // ADC 設定コマンド
    MAX   = 2047;       // 最大値
  }
  else {
    // LOW Range(max:256mA)
    GAIN  = 8;
    SHUNT = 1;
    DIGIT = 3;
    CMD   = 0b10001011;
    MAX   = 511;
  }

  /// ADC 設定 コマンド送信 (Configuration Register)
  /*   Bit  7: _RDY
  /*   Bit6,5: channel
  /*   Bit  4:  1=連続    0=ワンショット測定
  /*   Bit3,2: 00=12Bit 01=14Bit 10=16Bit 11=18Bit
  /*   Bit1,0: 00=x1    01=x2    10=x4    11=x8 */
  Wire.beginTransmission(ADC_ADR);
  Wire.write(CMD);
  Wire.endTransmission();
  
  /// 測定完了待ち
  /*   18Bit: 3.75SPS=270ms
  /*   16Bit:   15SPS= 67ms
  /*   14Bit:   60SPS= 17ms
  /*   12Bit:  240SPS=  5ms */
  delay(67);

  /// ADC data,config 受信(1sec以内)
  conf = 0x80;
  unsigned long timeout = millis();
  
  while(millis() - timeout < 1000) {
    // データ(3Byte)要求
    Wire.requestFrom(ADC_ADR, 3);
    // 3Byte未確定は 10mS待機を j回だけ実施
    for (uint8_t j=0; j<3; j++) {
      if(Wire.available() >= 3) {
        // ADC i2cデータ -> 数値変換
        shnt =(Wire.read()<< 8)+ Wire.read();
        conf = Wire.read();
        break;
      }
      delay(10);
    }
    // 受信データ確定(conf Bit7=0)で測定終了
    if (conf<0x80) break;
    delay(10);
  }

  /// 電圧->電流値変換 基準電圧:ADC_REF
  /*   ADC:12Bit 1mV       240SPS (-2048~2047) 
  /*       14Bit 250uV      60SPS (-8192~8191) 
  /*       16Bit 62.5uV     15SPS (-32768~32767)
  /*       18Bit 15.625uV 3.75SPS (-131072~131071) */
  shnt = shnt/32767;      // 測定精度 16Bit
  shnt*= ADC_REF/GAIN;    // 測定倍率 PGA Gain x2,x8
  shnt*= 1000;            // 表示単位 mA
  if (shnt>MAX) shnt=0;   // MAXを超えた値はエラー
  curr = shnt*(1/SHUNT);  // シャント抵抗倍率

  /// max,min計算
  if (curr_max < curr) {
    // ピーク値更新
    curr_max = curr;
    curr_min = curr;
  }
  else {
    // 最小値更新 チェック
    if (curr_min>curr && curr!=0) curr_min = curr;
  }

  /// Display/logging 判定
  if (!log_mode) {
    // データ表示 modeの場合 key check
    switch(sw_in()) {
      case 0: break;            // key入力なし
      case 1: {                 // 短押し
        // 記録時間変更
        uint8_t ele = sizeof(log_max)/sizeof(int); // 要素数
        log_time = (log_time<=ele-1)? log_time+1 : 0;
        // curr_min,max 初期化
        curr_max = 0; curr_min = 0; curr_peek = false;
        break;
      }
      case 2: log_mode = true;  // 長押し
    }
    // データ表示 (500ms毎)
    if ((millis()-dsptime)>=500) {
      dsp_curr_data(curr,shnt,DIGIT);
      dsptime = millis();
    }    
  }
  else {
    // データ収集(logging) mode 
    if (cunt==0) {
      // logging表示
      display.fillRect(  0,  1,128, 16,BLACK);
      display.setTextSize(1);
      display.setCursor( 32, 5);
      display.print("Logging...");
      display.display();
    }
    // データ格納
    if (cunt<log_max[log_time]) {
      logg[cunt].mill = millis();
      logg[cunt].curr = curr;
      cunt          += 1;
    }
    // データ送信
    else {
      if (wifi_mode) {
        // 送信モード 表示
        display.fillRect (  0, 1, 82, 16,BLACK);
        display.setCursor( 32, 5);
        display.print("Sending");
        cunt = 0;
      
        // データ送信
        for (int i=0; i<log_max[log_time]; i++) {
          // 送信状況表示
          display.fillRect ( 82, 1,128, 16,BLACK);
          display.setCursor( 82, 5);
          display.print(i);
          display.display();
        
          // DATA送信
          /*
          *    ここに 
          *      logg[i].mill
          *      logg[i].curr
          *    をWiFi転送するスケッチを書込む
          */
        }
      }
      log_mode = false;
    }
  }
}

///// OLED Control /////
/* 電流計表示 */
void dsp_curr_data(float cur, float shn, uint8_t dig) {
  char    buf1[9], buf2[21];
  
  if(true) {
    display.clearDisplay();
    display.setTextColor(WHITE);
    
    /* Current */
    display.drawRect   (0, 0, 127, 18, WHITE);
    display.setTextSize(2);
    display.setCursor  (  14, 2);
    dtostrf            (cur, 7, dig, buf1);
    sprintf            (buf2, "%s", buf1);
    display.print      (buf2);
    display.setCursor  (102, 2);
    display.println    ("mA");

    /* log_modeing mode */
    display.setTextSize(1);  
    display.setCursor  (  2, 6);
    display.print      (log_time+1);
    
    /* Bus-V */
    display.setCursor  ( 12, 22);
    display.println    ("Bus-V :    3.30  V");
    /* Shunt-V */
    display.setCursor  ( 12, 30);
    dtostrf            (shn,8,2,buf1);
    sprintf            (buf2, "ShuntV:%s mV", buf1);
    display.println    (buf2);
    /* Power */
    display.setCursor  ( 12, 38);
    dtostrf            ((cur*3.3),8,2,buf1);
    sprintf            (buf2, "Power :%s mW", buf1);
    display.println    (buf2);
    
    /* max current */
    display.setCursor  ( 12, 48);
    dtostrf            (curr_max,8,2,buf1);
    sprintf            (buf2, "max   :%s mA", buf1);
    display.println    (buf2);
    /* min current */
    display.setCursor  ( 12, 56);
    dtostrf            (curr_min,8,2,buf1);
    sprintf            (buf2, "min   :%s mA", buf1);
    display.println    (buf2);

    display.display();
  }
}

///// WiFi Control /////
/* WiFi接続開始処理 -> True:WiFi接続完了, False:WiFi接続失敗 */
boolean wifi_connect() {
  // WiFi 接続サイクルスタート(接続->確認->再接続) 3トライ
  for(int i = 0; (i < 2); i++) {
    // WiFi接続 接続が記憶されていない場合接続情報を記憶させ次回自動接続にする。
    if (WiFi.SSID() != CONN_SSID) {  
      WiFi.persistent(true);
      WiFi.mode(WIFI_STA);
      WiFi.setAutoConnect(true);
      WiFi.begin(CONN_SSID, CONN_PASS); 
    }

    // 5秒間 0.5秒毎に接続確認
    unsigned long timeout = millis();
    while(millis() - timeout < 10000) {
      // 接続できた場合 Trueリターン
      if(WiFi.status() == WL_CONNECTED) return true;
      // 未接続は 500ms待ちしてループ
      delay(500);
    }
  }
  // WiFi 接続不可 エラーリターン
  return false;
}

/* WiFi サーバ接続・DATA送信・切断処理 */
boolean data_send(String sendData, String ip, int port) {
  WiFiClient client;

  // DATA 送信先サーバー接続 (3秒間 接続確認)
  unsigned long timeout = millis();
  while (millis() - timeout < 3000) {
    if (client.connect(ip, port)) {
      delay(1);
      // 接続したら DATAはすぐに送信
      //client.println(sendData);  // (CR+LF)が2パケット目で送信される
      client.print(sendData+"\n"); // 1パケット送信で済む

      // 送信完了後切断
      client.stop();
      delay(1);

      // Trueリターン
      delay(100);
      return true;
    } 
    // 接続できなかった場合,0.3-0.5秒おきに再接続
    else delay(250+random(50,250));
  }
  // 接続できなかったので エラーリターン
  delay(100);
  return false;
}

///// SW Control /////
/* SW Scan (チャタリング中はリターンしない) */
boolean sw_scan() {
  while (true) {
    int8_t c = digitalRead(SW);
    delay(50);
    if (c==digitalRead(SW)) {
      return (c==SW_on)? true:false;
    }
  }
}

/* SW入力 未入力:0 短押し:1 長押し(2sec):2 */
uint8_t sw_in() {
  if(!sw_scan()) return 0;
  for(uint8_t i=0; i<40; i++) {
    if(!sw_scan()) return 1;
  }
  return 2;
}

(2022.10.06 v0.1.1b改定 1.最小電流値はピーク値記録後の値に変更 2.ロギング時間変更でmin,maxクリア)

起動時の OLEDには初期設定値の Adafruitロゴを表示させていますが、Adafruit_SSD1306ライブラリ内のsplash.hのデータを変えると任意のオープニング表示が可能です。

またサンプリングレートを向上させるため
・ロギングモード中は電流値の表示をせず値の収集に専念
・収集データは structを利用してメモリ上に記録し、収集時間終了後一気にWiFiで送信

という処理を行なっています。その結果サンプリングレートは 14.7SPSと ADCの 15SPSに近い値になっており測定中に電流値を見られないという不便もガマンの範囲内かなというところです。

測定時間は log_max[ ] 内に定義すればロギング時間切替SWで順次切替ができ、SWの長押しでロギングがスタートします。今回のスケッチでは10s,30s,1〜6minの8つの時間を登録しており、測点数は5400個(6min)分を確保しています。5400測点でメモリ使用率が86%まで増加しコンパイル時に「スケッチが使用できるメモリが少なくなっています。動作が不安定になる可能性があります。」と警告が出てきますのでこの辺で止めています。

・動作試験

さっそく電子負荷をつないでいろいろな条件を与えてみます。
LOWモードでは 256mAでメーター振り切れ。

HIGHモードでは 500mAを流してみました。

電子負荷のLED表示にはかなり誤差があり写真ではだいぶズレた数値が表示されていますが、電流が多めの範囲ではやはりシャント抵抗による電圧降下は大きめです。

そしてこの電流計のアピールポイントである電流のロガー機能。収集データは我が家の「えだまめ」ネットワーク(ラズパイ)内SQLサーバーに転送しており、そのデータをグラフにしてみました。

まずは10secのロギング。

測点が少ないのでドット間のスキマガ大きく電流の変化がわかりやすいですね。グラフ左上に表示されているようにサンプリングレートは 14.69SPS。逆算して1回あたり68msの測定時間。ADCの変換時間以外は1msしか使っていない勘定です。
グラフで電子負荷の出力が100%になるまでの測点を数えると10個。1点68msとすると負荷の立ち上がりに700ms近くかかっているのがわかります。

続いて 30sec、60secのロギング結果。

電子負荷の立ち上がり・立ち下がり時間がかろうじてわかるかな〜という感じです。
そしてロギング時間最大の 6分。

負荷状態をいろいろ変えてみましたがキチンと追従しているようです。
どうやら3.3V電源供給型電流計、簡単に言うと3.3V電源(電流計付き)が無事完成したようです。

◯やってみて

これでブレッドボードの実験時に簡単に電源を供給することができるので「超便利!」と気分が高揚していたのですが、実は現状で大きなミスをしている事を発見。それは何かというと…

「電源スイッチのつけ忘れ」

なんと、しょうもない。

この際だから OLEDの電源を FETで電源制御して deelSleep()を利用したオートパワーオフ機能を追加しようかと考えてました。起動はリセットスイッチです。電流計 ver1.1の仕様がさっそく決定です。

MCP3425(16Bit ADC)を使った電流計の製作(4) 実機の製作 ケース編

やりたいこと

(3)で製作したボードを実装するケースを加工する。

◯やったこと

・ケース選定と部品配置

今回使用するケースはTAKACHI の SW-85。

このケースにこんな感じで部品を配置します。


・加工

プラスチックケースは加工が簡単に済むので助かります。ドリルは乾電池式で十分加工が可能ですし刃も木工・プラスチック用の刃先で用が済むので安上がり。

穴を拡大するシャーシリーマも切れ味抜群で気持ちがいいほど。

ただヤスリは目づまりしやすいのでこまめな掃除が必要です。

必要な径より少し小さめの穴をドリルで開け、

リーマで必要サイズまで大きくします。

OLED用の四角い穴はドリルで何カ所か穴を開けた後リーマでギリギリまで穴を大きくしヤスリで仕上げていきます。いつものことですが四角い穴は加工が難しいですね。

全部の穴を開けたあとはヤスリでバリ取りをし消しゴムでケガキ線を消すとケース加工は終了。

外装部品を取り付けるとケースが完成です。

OLEDに関しては配線接続後接着剤で固定する予定。
最終的なフロント裏面の内部実装はこんな感じになります。


◯やってみて

ケースサイズの割に端子が大きめで、ケースの厚さもあるため少々ずんぐりむっくりな電源になりそうです。

・トグルスイッチ…

トグルスイッチを取り付ける際に共回りを防止するフック付きワッシャー、

表側につけた方がカッコいいと思うのは私だけでしょうか…。

MCP3425(16Bit ADC)を使った電流計の製作(3) 実機の製作 ボード編

◯やりたいこと

(1)、(2)の動作実験で得られたデータを元に実機の最終回路を決定しユニバーサルボード上にその回路を組む。

◯やったこと

・作成回路図の最終決定

作成する電流計付き電源の最終回路図を決めます。(1)に比べて以下の点を追加しました。

1. 電源として供給する3.3Vと、電流計を動作させる3.3Vを2つのレギュレータで別々に供給
2. シャント抵抗を2種類にして電流測定レンジをHIGH,LOWの2つに拡大
3. ロギング専用モードのトリガースイッチを増設

以上3点をグレードアップした回路図が以下の通り。

WiFi 電流計ロガーに変身です。

・ケース選定

利用するケースによって回路を組むボードのサイズが変わりますので最初に使うケースを決めておきます。今回は TAKACHIの SW-85を使うことにしました。

配置はこんな感じ。

フロントはパカッと外せるフタ側に組み立てることにし、電池は奥のケース側に収めます。
ちなみにこのケース2は 18650 2本も収納可能で、

電源の2系統化も可能です。

・回路作成

回路を組み上げるユニバーサルボードは秋月電子のタイプDボードを使い、それを前提に部品を集めます。

(1)で紹介した
ESP-WROOM-02
・16Bit ADC MCP3425
・3端子レギュレータ NJM2845
・0.96インチ 128×64ドット有機ELディスプレイ(OLED) 白色

の他、
秋月電子 タイブDボード
・100mΩ シャント抵抗
・10KΩ 抵抗
・100uF、0.1uF チップコンデンサ
・ピンソケット、ピンヘッダ

を準備します。全て秋月電子で入手可能です。
その他実験時にかなり熱くなったレギュレータの放熱対策として、ラズパイ用のヒートシンク

熱伝導シールでチップに貼り付けることにします。

んでもって組立て。

裏面。

組立て直後はフラックスやハンダクズで相当汚れていますので

フラックス除去剤、無水エタノールを使って

歯ブラシなどでゴシゴシキレイに仕上げます。

ただ安い中華製のモジュールなどではあまり強くこすると表面がはがれてしまう場合がありますので注意が必要です。
あとはイモハン部分にハンダを盛ったりしてお化粧すればサッパリ。

テスト動作用に電源取出しピンソケットを追加して

MCP3425,WROOM-02を取り付けると

本体ボードの完成です。

・動作試験

さっそくボードに周辺部品を仮付けして動作試験を行います。

ん〜、順調。
電子負荷もつないでみて実際に電流負荷をかけてみると

500mA流してもいい感じ。一安心です。
次回このボードをケースに組み込んでいきたいと思います。

◯やってみて

いたずらでWiFiを動かして測定電流値をロギングしてみました。
ラズパイネットワークのSQLサーバーにWiFiで転送したデータを matplotlibでグラフ化してみると

それなりにうまく動いてくれているようです。
現状では250〜300ms/回程度のサンプリングレートになっています。
しばらく動かしてみてハード面のデバッグを進めていきます。

MCP3425(16Bit ADC)を使った電流計の製作(2) スケッチの作成

◯やりたいこと

(1)で製作した電流計付き電源のスケッチを作成する。

◯やったこと

シャント抵抗の電圧をAD変換し電流値に変換してOLEDに次のように表示します。

スケッチは以下の通り。

/*
*
*    MCP3245(16Bit ADC)電流計 WR02_ADC_OLED v0.0.4b
*
*/
// Standerd Library (IDE v1.8.19)
#include              <Arduino.h>
#include              <Wire.h>

// Board Library
/* ESP8266 Boards(v2.7.2)/Generic ESP8266 Module */
#include              <WiFiClient.h>        // wifi_set_sleep_type()

// Install Library
/* OLED */
#include              <Adafruit_SSD1306.h>  // v2.5.7

// Global Value
/* UART */
#define TX            1         // D1
#define RX            3         // D3

/* I2C */
#define I2C_SDA       2         // D2
#define I2C_SCK       0         // D0
#define I2C_CLK       100000    // fSCL=100KHz

/* OLED */
#define OLED_ADR      0x3C      // I2C Address
#define SCREEN_WIDTH  128       // OLED display width, in pixels
#define SCREEN_HEIGHT 64        // OLED display height, in pixels
#define OLED_RESET    -1        // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306      display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
unsigned long dsptime = 0;

/* ADC */
#define ADC_ADR       0x68      // I2C Address
#define ADC_REF       2.048     // [V]:Voltage Reference
#define SHUNT         0.1       // [Ω]:Shunt Resistor
float curr_max =      0;
float curr_min =      9999.9;

void setup() {
  // ハードウエア 初期設定
  /* Sleep Mode */
  wifi_set_sleep_type(MODEM_SLEEP_T);
  Serial.end();
  
  /* I2C */
  Wire.begin   (I2C_SDA, I2C_SCK);
  Wire.setClock(I2C_CLK);

  /* OLED(SSD1306) */
  display.begin(SSD1306_SWITCHCAPVCC, OLED_ADR);
  //display.begin();
 
  /* OLED画面初期表示 */
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(2);
  display.setCursor(  0,  0);
  display.println("SSD1306");
  display.setTextSize(1);
  display.setCursor(  0, 19);
  display.println("OLED Start.");
  display.display();
  delay(1000);
}

// 
void loop() {
  // 初期設定
  char  buf1[9],  buf2[21];
  float shnt=0.0, curr=0.0;
 
  // ADC 設定 コマンド送信 (Configuration Register)
  /*   Bit  7: _RDY
  /*   Bit6,5: channel
  /*   Bit  4: 1=連続 0=ワンショット測定
  /*   Bit3,2: 00=12Bit 01=14Bit 10=16Bit 11=--
  /*   Bit1,0: 00=x1    01=x2    10=x4    11=x8 */
  Wire.beginTransmission(ADC_ADR);
  Wire.write(0b10001001);
  Wire.endTransmission();

  // データ確定待ち(16Bit:15SPS=67ms)
  delay(100);

  // ADC data,config 受信(1sec以内)
  unsigned long timeout = millis();
  while(millis() - timeout < 1000) {
    // データ(3Byte)要求
    Wire.requestFrom(ADC_ADR, 3);

    // 3Byte未確定は 10mS待機を 3回だけ実施
    byte  conf=0x80;
    for (uint8_t j=0; j<3; j++) {
      if(Wire.available() >= 3) {
        // ADC i2cデータ -> 数値変換
        shnt =(Wire.read()<< 8)+ Wire.read();
        conf = Wire.read();
        break;
      }
      delay(10);
    }
    // 受信データ確定(Bit7=0)で測定終了
    if (conf<0x80) break;
    delay(10);
  }

  // 電圧->電流値変換 基準電圧:ADC_REF shunt抵抗値:SHUNT
  /*   ADC:12Bit 1mV   240SPS (-2048~2047) 
  /*       14Bit 250uV  60SPS (-8192~8191) 
  /*       16Bit 62.5uV 15SPS (-32768~32767) */
  shnt = shnt/32767;      // 測定精度 16Bit
  shnt*= ADC_REF/2;       // 測定倍率 2倍(max1.024A)
  shnt*= 1000;            // 表示単位 mA
  if (shnt>2047) shnt=0;  // 2047を超えた値はerr
  curr = shnt*(1/SHUNT);  // シャント抵抗倍率

  // max,min計算
  if (curr_max<curr)            curr_max=curr;
  if (curr_min>curr && curr!=0) curr_min=curr;

  // 表示
  if ((millis()-dsptime)>=100) {
    display.clearDisplay();
    display.setTextColor(WHITE);
    
    /* Current */
    display.drawRect(0, 0, 127, 18, WHITE);
    display.setTextSize(2);
    display.setCursor(  2, 2);
    dtostrf(curr,8,2,buf1);
    sprintf(buf2, "%s", buf1);
    display.print(buf2);
    display.setCursor(102, 2);
    display.println("mA");
      
    /* Bus-V */
    display.setTextSize(1);  
    display.setCursor( 12, 22);
    display.println("Bus-V :    3.30  V");
    /* Shunt-V */
    display.setCursor( 12, 30);
    dtostrf(shnt,8,2,buf1);
    sprintf(buf2, "ShuntV:%s mV", buf1);
    display.println(buf2);
    /* Power */
    display.setCursor( 12, 38);
    dtostrf((curr*3.3),8,2,buf1);
    sprintf(buf2, "Power :%s mW", buf1);
    display.println(buf2);
    
    /* max current */
    display.setCursor( 12, 48);
    dtostrf(curr_max,8,2,buf1);
    sprintf(buf2, "max   :%s mA", buf1);
    display.println(buf2);
    /* min current */
    display.setCursor( 12, 56);
    dtostrf(curr_min,8,2,buf1);
    sprintf(buf2, "min   :%s mA", buf1);
    display.println(buf2);
   
    display.display();
    dsptime = millis();
  }
}

こまめにコメントを記入していますのでプログラムの流れは大体はつかめると思います。ADCとデータをやり取りする部分には無限ループしないよう3つほど制限をかけており、

1. ADCから読込んだconfigデータのBit7(RDYフラグ)が0になった(=確定値を受信)
2. ADCからの返信データ(3byte)未着待ちは1回10msを3回まで
3. 測定時間は最大1sec

でループから抜け出てきます。

◯やってみて

写真のハードウエアは(1)からちょっとグレードアップしていて、電源供給用に専用のレギュレータが追加されています。そのため少々安心して大きめの電流を流す事が出来ています。

とはいえレギュレータはかなり熱くなりますので、本番の実機では放熱対策を取ろうと考えてます。

MCP3425(16Bit ADC)を使った電流計の製作(1) 回路図の決定

◯やりたいこと

16BitのADコンバーターMCP3425を使って3.3V電源供給型電流計(要は電流計付電源)を作る。

◯やったこと

・回路図作成

I2C通信のMC3425とOLEDをWROOM-02にぶら下げると言う簡単な回路です。WROOM-02のブレイクボードはシンプル版を意識してます。


・部品集め

部品は全て秋月電子で購入しました。主要部品を以下に載せておきます。

ADC MCP3425

主な仕様
・分解能:16bit、ΔΣ
・信号入力:差動入力
・基準電圧源:2.048V ± 0.05%
・アンプゲイン:×1、×2、×4、×8
・変換データレート:15SPS(16bit時)~240SPS(12bit時)
・制御/出力インターフェース:I2C
・電源電圧範囲:2.7V~5.5V
・消費電流:145μA(VDD=3V、連続変換時)

秋月電子DIP変換モジュールを利用します。

細ピンのピンヘッダをはんだ付けしときます。

3端子レギュレータ NJM2845

主な仕様
・出力方式:シリーズ
・出力正負:正電源
・入力電圧:~12.3V
・出力電圧:3.3V
・最大出力電流:800mA
ドロップアウト電圧:0.18V
・許容損失:10W
・パッケージ:TO-252

チップ上の表示は「45 330」 、これをTO-252用ピッチ変換基盤にはんだ付けして利用します。

ちなみに基盤裏側に0.1uFのチップコンデンサを取り付けてあります。

0.96インチ 128×64ドット有機ELディスプレイ(OLED) 白色

主な仕様
・電源電圧:3.3~5V
・制御方式:I2C(IIC)
・制御IC:SSD1306
・解像度:128×64
・表示色:白
・アドレス:0x78(既定)または0x7A(抵抗の付け替えによる切り替え)
・視野角:160°
・使用温度範囲:-20~60℃
・ピンヘッダ:□0.64mm

表示には定番の便利なOLED表示器を利用します。

ESP-WROOM-02

主な仕様
・電源電圧:3.0~3.6V
・消費電流:平均80mA
・対応WiFiプロトコル802.11b/g/n(2.4GHz)
・サイズ:18mm×20mm×3mm
・端子ピッチ:1.5mm
Wi-Fiモード:station/softAP/SoftAP+station
・セキュリティ:WPA/WPA2
・暗号化:WEP/TKIP/AES
・内蔵フラッシュは4メガバイト(32メガビット)です。
・工事設計認証(技適)番号:206-000519

漢字表示も意識してフラッシュメモリが4MBタイプのモジュールを使い、シンプルタイプのブレークボードにハンダ付けしています。RES,ENピンはボード上のパターンに10KΩのチップ抵抗を取り付けてあります。

・製作

まずは回路がキチンと動作するかどうかブレッドボード上に組んで実験してみます。回路自体は簡単ですのでミニブレッドボードにササッと組み上げてしまいます。

最初にディスクリート部品と配線。

ADCを載せ

3端子レギュレータ、

ESP-WROOM-02

最後にOLEDを載せると完成。

ブレッドボードは実験までの手順が簡単で助かります。

・動作試験

前にINA219を使って作った電流計のスケッチを焼き直して動作試験を行ってみます。
まずは電源オン。

お〜、無事動作しているようです。
続いて電子負荷をつないでの実験。

無負荷なのにもう16mAも流れています。電子負荷はゼロでも電気を食うようです。
50mA,100mAと負荷を増加させて電流計の表示をチェック。

初期状態に流れていた16mAを加算すると概ね近場の数値を示していますのでうまい具合に動作しているようです。ただ max,minの表示にバグがあるようです。

◯やってみて

どうやら回路図的には問題はなさそうです。ただスケッチにバグを発見しましたので、しばらくバグ取りして次回完成版のスケッチを紹介したいと思います。

WROOM-02で静電容量式水分計を作る (8)実機製作 本体編

◯やりたいこと

(7)で決めた回路図をもとに測定器本体の製作を行います。プローブのタイマーICをC-MOS化する予定があるため、プローブの本体への取付けは気密性を高める接着を行わず仮組みで止めておきます。

◯やったこと

・回路の製作

本体を格納するケースは単4電池3本の電池ケースがちょうど入る aitendoの防水ケースを使う事にしました。

うわブタが透明なので照度も測れて便利です。
回路はミニブレッドボードサイズのユニバーサル基板に組み上げ、ミニカードスペーサー(MPS-04-0 秋月電子)で電池ケース裏に接着剤で固定する段取りで進めます。
一通り部品を準備したら

組立て。

今回は準備した電解コンデンサやセラミックコンデンサでなく初めてチップコンデンサを使ってみました。小さく収まって便利なのですが、老眼の目には少々取扱いが難しい部品でした。
ソケットにWROOM-02 やおまけで付けた BME280(温湿度・気圧)、BH1750(照度)センサーを取り付けると

完成。

電池ケース裏に基板を固定して一体化します。

ちなみに防水ケースには基板固定用のネジ受けが付いていて

このままでは作成した回路を格納できないので単4電池3本が入る様にリューターで削ってしまいます。

4カ所削ると単4電池が入ります。

全部削ると単3電池も入りますが

高さが取れずフタが閉められなくなってしまうので単4電池で妥協しておきます。
削った後は電池と一体化した回路を格納して完了。

測定器本体の完成です。

・プローブの取付け

測定器は外での利用も想定しているためケースの気密性を確実にしなければなりません。当初は防水コネクタで通信線を引込もうとも考えていたのですが、あまり大げさにするのも何でしたし接触抵抗も気になったので単純にケーブルをゴムブッシュに通してシリコンゴムで固定・防水する事にしました。

ケースにゴムブッシュサイズの穴を開けて

内側にシリコンゴムを塗り

ケーブルを通せば完成。

最終的にケーブルもシリコンゴムで固定化します。温湿度・気圧測定センサーは小穴を開けてのぞき口にセンサーを露出させ同じくシリコンゴムで固める予定ですが、現時点ではまだ「ケース内温度の測定」にとどめておきます。

この実機で本番のロングラン試験に入る事にします。

◯やってみて

実機で6日ほど実稼働してみたところ、温度や稼働時間に関して測定電圧の補正が必要なことが判明しました。

試行錯誤して補正係数を求めたレポートはその(9)で紹介します。

WROOM-02で静電容量式水分計を作る (9)計測値の補正

◯やりたいこと

静電容量式水分計プローブの出力電圧減少(水分率上昇)現象の対策として温度、時間経過の2つをターゲットに補正係数を求める。

◯やったこと

・温度補正

(8)で作成した水分計、1日走らせてみたら水分率が突然ヒョイと上がる現象が起きました。

朝4:40頃。

実はこれ朝起きて部屋の中が暑かったので窓全開で室内の空気を入れ替えた時に起きた現象。

この日の朝は外の方が気温や湿度が低くカラッとした空気。気持ちいい風が室内に入ってきて湿度も急減、温度もあっという間に3℃程下がったのですが同時に水分率も急上昇。

室温が下がると水分率が上がる、水分率が室温と密接に関係していることをヒョンなことから発見です。
最初は湿度が関係するのかなとも考えたのですが、次の日の朝のデータ。


(水分計グラフと温湿度グラフを並べてみました)

5:30にまた水分率がヒョイと上がっていますがこれももちろん朝の換気による室温の低下。

この日は気温が下がりましたが湿度は上昇。やはり関連しているのは温度の様です。
さらに3日分グラフを並べてみるとその差がよく出てきて、

ガチャガチャと変化している湿度ではなく、上に凸している温度と下に凹している水分率に相関関係があるのは間違いなさそう。水分計プローブだと思っていたのが実は温度計プローブだったと言うオチです。もちろん温度はもとに戻るのに水分率はもとに戻らないと言う問題のある温度計ではあるのですが…。

いろいろ調べてみたら、誘電式の水分計に関してまとめた「筑波大学水理実験センター報告 No8 誘電式キャリブレーション」と言う報告書の中に「プローブの出力電圧は温度に比例する」とのレポートがありました。今回のウチの測定結果と一致しますので温度によりプローブ出力が変化するのは多分間違い無いのでしょう。

そこで今回は25℃を補正値ゼロとして実測値から求めた ±8mV/℃の温度補正値を測定電圧に加える事にしました。気温が1℃下がるとプローブ電圧に8mV加算します。これで温度変化に対応する様にします。

・経過時間補正

問題なのが長期的な水分率上昇傾向。6日ほど走らせただけで水分率が14%も上昇しています。

プローブ周りの土の誘電率が安定するまで時間がかかるのか、プローブの中に水が染み込んで実際に水分率が上がっているのか…。さすがに永遠に上がり続けることはないと思いますのでしばらく置いて様子見してみる事にします。当面はここまでの上昇率から求めた実測値 30mV/日を経過時間に応じて加算してみようと考えてました。

…とジタバタしていたら遂に計測水分率が安定化してきてくれました。グラフを記録し損なったのですが、水分率が給水して飛び上がる前の半日程は上昇が止まり温度変化に対応しているだけになっています。

最新2日分だけピックアップ。

水分率もプローブ電圧もほぼ水平で推移しています。電圧で見ると±10mV程度の変化です。ちょっと一安心。
でもウォーミングアップに6日もかかってしまうとは随分とスロースターターな水分計。静電容量式水分計は測定開始までにたっぷり時間がかかる様です。

◯やってみて

実験により以下の知見が得られました。
1. プローブの測定電圧は温度に比例する。測定にあたっては温度も同時に測定して温度補正する必要がある。
2. プローブの測定電圧が安定化するには数日単位の時間を要する。

プローブ電圧の安定化時間に関しては、土質を変化させてみるのははもちろん、変更する事によって結果に変化が出ていた測定時間間隔、ADC変換の精度なども変化させいろいろなパターンを試してみたいと考えています。

○その後

計測を開始して3週間たったグラフがこれ。

測定開始時には漸増傾向があった水分率も6日目に安定化してからはその傾向がなくなりました。水やりをして水分率が急上昇した後は概ね期待通りの漸減傾向。温度補正値をいろいろと変化させながら現在は±12.5mVに設定しています。全体的に思ったほど水分率が減少していないのが正解なのか不明な点です。実験のため週一行っていた水やりを2週間やらずに観察していたのですが、さすがに植物の方が心配になってきたので「水やらず」実験は一旦ここで終了します。

電池電圧の変化に関しては
1. 電池交換による電圧上昇
2. データ送信先がオフライン時に無限ループに入ってしまうバグがありディープスリープに入れずに急速に消耗
3. 送信データ量を減らしたら電圧がポンと上がった

などによるものです。今後は省電力化の実験も進めていきますが、単4電池の供給能力、WROOM02 に対して少々不安を感じてきてます。

○その後2

長期実験3か月、13週目のグラフです。

寒くなって来たせいか乾燥が進んで水分率が元に戻るまで4週間かかることがわかりました。毎週水まきしていた鉢植えの水、冬は一月に一度でいい事を確認です。
現在電池の持ちをよくするため10分おきの測定を30分おきに変更して様子を見ています。

○その後3

長期実験6ヶ月弱のグラフです。

電池が切れて異常値を記録していたので電池交換のタイミングで現状のレポートしておきます。

○水分率(緑色の折れ線)

順調に測定を開始してからはキチンとしたノコギリグラフが出ています。4山目の刃が丸いのはセンサーから離れたところに水をまいた時の変化で、遠くからジワジワと染みてき来るせいか測定水分率もジワジワと上昇しています。その後他と同様減少に転じています。

○電池の持ち具合(紫色の折れ線)

電池に関しては4種類の実験が終了しています。
1. 最初の小さなトンガリ放電カーブ 単4エネループ3本 10分おき測定→持続日数 2週間
2. 2つ目の放電カーブ 単3エネループ3本 10分おき測定→持続日数 4週間

どうも電池の持ちが悪いので測定時間を30分おきに伸ばしてみる事に。
3. 3つ目の放電カーブ 単3エネループ 30分おき測定→持続日数 2ヶ月

試しにアマゾンのアルカリ電池4本(電池電圧の測定上限を超えてしまいますが)でどれくらい持つかを試してみたところ
4. 4つ目の放電カーブ 単3アルカリ電池(アマゾン) 10分おき測定→持続日数 2ヶ月

ありゃ、電池を4本にして総エネルギー量を増やしたつもりだったのですが持続時間があまり増えていません。アマゾンの電池容量はエネループの4分の3だったようです。5番目としてエネループ4本での持続日数チェックに入っています。

最近いたずらでグラフをWebブラウザ(ラズパイ+Django)で見られるようにしていて、

チェックが楽で助かっています。

WROOM-02で静電容量式水分計を作る (7)実機回路決定・実証編

◯やりたいこと

(6)のプローブ製作に引き続き測定器本体の製作に入ります。製作は以下の手順で回路の実証をしながら進めていきます。
1. (5)までの実験に基づき水分計測定器本体の最終回路を決定する。
2. 収納するケースの都合上WROOM02 用にピンソケットを使えずユニバーサル基板直付けとなる。スケッチの修正が出来なくなるのでブレッドボードに組んだ実機回路でロングランテストを行いバグ取りを確実に行う。
3. 実験段階で発生していたプローブ出力電圧の漸減減少対策
4. 気温変化によるプローブ出力の変化確認
5. アルカリ電池での動作確認
6. 本体を防水ケースに収納

◯やったこと

・実機回路図の決定

プローブから来たI2C信号をWiFiで飛ばすだけなので回路的には ESP-WROOM-02モジュール単体のみでいいのですが

1. 省電力化のためプローブ電源をFETで制御
2. AOUTを利用して電池残量を監視するための分圧回路

の2つを追加するのでコンパクト版のブレークボードを利用します。プローブ電源制御用の FETは電圧3.3V、電流10mA程度の制御ですのでプラスチックモールド型のPch FETであればほぼ何でも使えると思います。私はオン抵抗の低いものを探し秋月で売っていた2SJ497を使用する事にしました。
I2CのSCL,SDAはIO0,IO2を使い、起動モード設定用とI2C用のプルアップ抵抗を兼用にして部品点数を減らしています。

という事で製作する実機の最終回路図は以下の通りとなります。


(※この回路はプルアップ抵抗付きのモジュールをつなぐとその抵抗から電源が供給されVcc制御ができないと言う欠点があります。後程GND制御に変更した回路も掲載します。)

・プローブ出力の漸減減少対策

上記回路をブレッドボードに組み立てさっそく測定テストに入ります。

3日ほど走らせてみました。

 赤:プローブ出力電圧
 青:プローブ出力電圧の百分率ポジション(乾燥1.85V:0%、水中0.7V:100%)
 黄:動作用電池電圧をWROOM02 のTOUTで計った数値

電池電圧も10回測定して平均を取るようにし、測点の有効桁数を3桁にしてグラフをなめらかにしてみました。

結果にはやはり測定電圧の漸減(水分率漸増)傾向が出ています。途中水やりをしたため水分率が上がっていますが基本的な漸増傾向は変わりません。電池電圧は3.5Vありますし電源まわりの強化とかシールド線のアースとかいろいろと対策をとってみたのですがいっこうに効果が出ません。行き詰まってしまって「よし、心機一転、電池満タンで最初からやり直し!」と電池交換したところグラフの水分率にポンと変化が...。

「そっか!!」

ひらめきました。原因判明。
単純に電源に使っている3端子レギュレータのドロップ電圧を検討していなかったのです。なんたる凡ミス。

当初使っていたレギュレータはNJU7223という新日本無線製の3端子レギュレータ。

30uAという低消費電流が気に入って使用していたのですが、ドロップ電圧が低いながらも0.4Vあります。プローブに3.3Vを供給するためには電池電圧が3.7V以上ないとダメだったと言う当たり前のミスでした。3.7Vを下回ると電池電圧の変化がそのままプローブに出力されプローブの発振周波数に影響を与えていたのが原因。しかし理由がわかれば対策は簡単。ドロップ電圧の低いレギュレータを探します。
結果見つけたのがこれ。

同じく新日本無線製のNJM2845。消費電流が400uAとちょっと大食いなのが気になるのですがドロップ電圧は0.18V。これだと電池電圧が3.42Vまで下がっても動作させられます。
さっそくこのレギュレータを使って実験回路を組立てて実験してみます。

温度補正の検討もしたかったのでBME280も追加して温湿度も測ってみました。
完全に乾燥するまで測定。

結果はと言うと…。

やり〜。予想通りです。3.7Vを切ってもプローブ出力の漸減現象がありません。
測定環境の温湿度はクーラーのおかげかほとんど変化がなく測定にはなかなかいい環境でした。

濡らし方もいろいろ変えてテストしてみたのですが

漸減減少は現れず。

途中満タンの電池に交換してもプローブ出力に影響はなし。完璧です。

・気温の変化によるプローブ電圧の変化

測定器は外部での使用も想定しています。そこで低温環境内でプローブ出力がどのように変化するのかを確認するため実験機をタッパに入れプローブを濡らして冷蔵庫に入れてみる事にしました。
プローブを湿らせて4時間程常温に置き、

乾燥が始まった頃に冷蔵庫へ。
その後1日の経緯をグラフで見てみると

一旦乾燥し始めたプローブが冷蔵庫の調湿機能?のせいなのか乾燥が間延びしているように見えます。一緒に測定した庫内の温湿度を見てみると

温度はサッと下がっているのに対し湿度は上がったり下がったりのガチャガチャ。最近の冷蔵庫ってこんな機能があるんですね。
とは言え乾燥は着実に進んでプローブのキムワイプはすっかりカラカラ。
水分計と冷蔵庫が活躍しているなかなか面白いデータが取れました。

結果として気温10℃程度への15℃程の温度変化はグラフで見る限りそれほど影響はないように見えます。まあ水分率の絶対値はそもそも当てにしておらず変化を監視するのが主目的ですので、趣味レベルで測る分には常温帯での温度変化を余り気にしなくていいようです。氷点下付近の本格的な低温実験は冬期間実際に実機を外に置いてやってみようと思ってます。

という事でいよいよ測定器本体の製作に入ります。
ちょっと長くなりましたので続きは(8)に持ち越しです。

WROOM-02で静電容量式水分計を作る (6)実機製作 プローブ編

◯やりたいこと

(5)までの実験に基づき実機の製作に入ります。今回はプローブの製作です。
プローブ出力はプローブ内ですぐにAD変換しI2Cで測定データを送信する事にしてますので、ADC-I2C変換モジュールを内部に組込んだインテリジェントなプローブを製作します。

◯やった事

ADCモジュールのI2C出力はバッファリングせずに転送できる事が(4)で確認できています。ですので追加する部品は変換モジュール1個のみ。XHコネクタを外した後のスペースにうまい具合に乗りそうですのでそれほど大きくならずにプローブを作る事ができそうです。

プローブ回路とADCモジュールのショート防止は熱収縮チューブと接着絶縁系シリコンで行う事にします。

プローブ側をシリコンと収縮ゴムでガチガチに固めたあとその上にADC回路を構築。

そこへ4芯USBケーブルを接続してさらにシリコンゴム、熱収縮チューブで固定すると新型プローブの完成です。

◯やってみて

さっそく新型プローブを使って測定の開始です。

プローブ出力を直接引き回していた時は測定データが非常にクリチカルで出力線の配置を変えるだけでデータが変化してしまいましたが、今回はその部分をガチガチに固めたので安定した値を収集できそうです。

プローブ出力は温度が低くなると同じ水分量でも電圧が低くなりますし

電源が常時入っている時の出力電圧と測定時のみ電源を入れる場合とでも出力電圧に差が出ます。発振の安定化に時間がかかるせいでしょうか。

またプローブ内にジワジワ水分が浸透していくせいなのか、はてまた電池電圧が測定のたびに減っていくせいなのか毎回電圧減少(水分量増加)方向に偏移があります。

原因追求にはまだまだ実験が続きそうです。
次回測定器本体も完成させてこの辺の原因追求をしていこうと考えてます。

◯その後

今後製作する予定の実機回路を使いこのプローブで1日弱測定した結果をラズパイの matplotlib でグラフ化してみました。

赤がプローブの出力電圧、青が乾燥空気中1.85V:0%、水中0.7V:100%とした場合のプローブ電圧の百分率ポジション、黄色が動作用電池の電圧をWROOM02 のTOUTで計った数値です。
アナログ出力を直接引き回していた時に比べると圧倒的に安定感があります。やはり測定結果をI2Cで送信する事にしたのは正解だった様です。

◯その後2

左がI2C付きプローブの2日分の出力グラフ、右がアナログ出力をそのままケーブルで引き回したプローブの出力グラフ。

青が水分率の測定値を示していますが出力の安定具合はその差歴然。アナログ値をそのまま引き回すと非常に不安定な出力になっているのがわかります。

WROOM-02で静電容量式水分計を作る (5)正10分おきに起動して測定する

◯やりたいこと

(4)まででハードウエアの構成がほぼ決まったので動作仕様を決めてスケッチの作成をします。動作仕様は以下の通り。
1. 測定結果をWiFiで定期的(正10分)に送信する。
2. 時刻はNTP(Network Time Protocol)を利用する。
3. 電池動作とするためデータ送信時以外はスリープして省電力化を図る。
4. 1回の起動で1秒ごとに10回測定しその平均値を測定値とする。

◯やったこと

・ハードウエアの追加

WROOM-02(ESP8266)は省電力スタンバイモードとしていくつかのスリープモードを持ちますが、1番電力を消費しないのがディープスリープモード。今回はこのディープスリープモードを利用して必要時以外は極力電池を消費しないエコな水分計を作成します。
ディープスリープからWROOM-02を起動させるにはIO16に出力される起動用のリセットパルスでリセットをかける必要がありますので、あらかじめIO16とリセット端子を接続しておきます。

1KΩの抵抗をはさんであるのはIO16が出力設定でH出力時にリセットスイッチでショートするのを防ぐための電流制限抵抗です。

・スケッチの作成

動作仕様に基づきスケッチを作成しました。

/**
*
*    WiFi 容量式水分計 WR02_CSMS v0.0.9b
*
*      Module : ESP-WROOM-02(ESP8266)
*      Pin Function
*        IO0  : SCL(pull_up)
*        IO1  : TX
*        IO2  : SDA(pull_up)
*        IO3  : RX
*        IO4  : -
*        IO5  : -
*        IO12 : -
*        IO13 : -
*        IO14 : -
*        IO15 : -
*        IO16 : Reset出力(OnBord)
*        TOUT : -
*
**/
// Standerd Library
#include               <Arduino.h>
#include               <time.h>
#include               <Wire.h>

// Include Library
/* WiFi (ESP8266) */
#include               <ESP8266WiFi.h>
#include               <WiFiClient.h>

// Global Value
/* WiFi */
#define CONN_SSID     "SSID"
#define CONN_PASS     "PASSWORD"

/* My ID,IP,MAC */
#define MY_ID         63

IPAddress ip          (192,168,  1,MY_ID);  // センサーIPアドレス
IPAddress gateway     (192,168,  1, 1);     // ルーターIPアドレス
IPAddress dnServer    (192,168,  1, 1);
IPAddress netmask     (255,255,255, 0);

/* UART */
#define TX            1         // D1
#define RX            3         // D3

/* I2C */
#define I2C_SDA       2         // D2
#define I2C_SCK       0         // D0
#define I2C_CLK       50000     // fSCL=50KHz

/* ADC */
#define ADC_ADDR      0x68

/* NTP DATA */
#define JST           (3600 * 9)
#define DAYLIGHTOFFSET_JST (0)
#define NTP_SERVER1   "ntp.nict.jp"         // NTP1
#define NTP_SERVER2   "ntp.jst.mfeed.ad.jp" // NTP2

void setup() {
  // シリアル 開始
  Serial.begin(9600);

  // I2C 開始
  Wire.begin   (I2C_SDA, I2C_SCK);  // set I2C pins, default clock is 100kHz
  Wire.setClock(I2C_CLK);           // set I2C clock(fSCL)

  // WiFi スタティック接続 (IP,GateWay,SubnetMask,DNS)
  WiFi.config(ip, gateway, netmask, gateway);
  if (!wifi_connect()) wifi_end();

  // NPT 接続
  configTzTime("JST-9", NTP_SERVER1, NTP_SERVER2);
  if (!ntp_connect()) wifi_end();
}

void loop() {
  // 現在時刻取得
  time_t tmUnix    = time(NULL);           // unix
  struct tm* tmNow = localtime(&tmUnix);   // unix

  // 正10分(or11分)でなければsleep
  if ((tmNow->tm_min % 10)>2) wifi_end();

 // 変数設定
  float   adcd = 0;
  float   ratio= 0;
  uint8_t cunt = 0;
  byte    conf = 0;

  // 測定(10回平均値)
  for (uint8_t i=0; i<10; i++) {
    // ADC コマンド送信 (ワンショット測定,16Bit,ゲイン1倍)
    Wire.beginTransmission(ADC_ADDR);
    Wire.write(0b10001000);
    Wire.endTransmission();
    delay(10);

    // ADC data,config 受信
    unsigned long timeout = millis();
    while(millis() - timeout < 1000) {
      // データ(3Byte)要求
      Wire.requestFrom(ADC_ADDR, 3);

      // 3Byte未確定は3回まで待つ
      for (uint8_t j=0; j<3; j++) {
        if(Wire.available() >= 3) {
          // ADC i2cデータ -> 数値変換
          adcd +=(Wire.read()<< 8)+ Wire.read();
          conf  = Wire.read();
          cunt += 1;
          break;
        }
        else delay(100);
      }

      // 確定データか確認
      if (conf>=0x80) break;
      delay(250);
    }
    delay(1000);
  }
  // 平均値計算
  if (cunt!=0) adcd = (adcd/cunt)*2.048/32768;

  // プローブ乾燥時:2.048V 水中時:0.800V で比率計算
  ratio = 0;
  if (adcd >= 0.800)
    ratio = ((adcd-2.048)/(2.048-0.800))*100;
    if (ratio<0) ratio *= -1;
  
  // 結果表示
  data = String(MY_ID)+ ","
       + String(adcd,3)+ ","
       + String(ratio,0);
  Serial.print("-> "+ data+ "%");

/* 
* 〜 ここにWiFiでデータを転送するスケッチを作成  〜
*/
  
  // 計測終了 sleep
  wifi_end();
}

///// WiFi Control /////
/* WiFi接続開始処理 -> True:WiFi接続完了, False:WiFi接続失敗 */
boolean wifi_connect() {
  // WiFi 接続サイクルスタート(接続->確認->再接続) 3トライ
  for(int i = 0; (i < 2); i++) {
    // WiFi接続 接続が記憶されていない場合接続情報を記憶させ次回自動接続にする。
    if (WiFi.SSID() != CONN_SSID) {  
      WiFi.persistent(true);
      WiFi.mode(WIFI_STA);
      WiFi.setAutoConnect(true);
      WiFi.begin(CONN_SSID, CONN_PASS); 
    }

    // 5秒間 0.5秒毎に接続確認
    unsigned long timeout = millis();
    while(millis() - timeout < 10000) {
      // 接続できた場合 Trueリターン
      if(WiFi.status() == WL_CONNECTED) return true;
      // 未接続は 500ms待ちしてループ
      delay(500);
    }
  }
  // WiFi 接続不可 エラーリターン
  return false;
}

/* WiFi 終了・切断処理 */
void wifi_end() {
  // 現在時刻取得
  time_t tmUnix    = time(NULL);           // unix
  struct tm* tmNow = localtime(&tmUnix);   // unix

  // WiFi切断
  if (WiFi.status() == WL_CONNECTED) {
    WiFi.persistent(false);
    WiFi.disconnect(); // ステーションモード維持,ssid・passwordをクリアしない
    delay(10);
  }

  // 現在から正10分までの時間を計算
  int minNow = tmNow->tm_min % 10;
  int minSlp = 10 - minNow;
  int secSlp = 0;

  // 30秒過ぎは起動を30秒早める
  if (tmNow->tm_sec>30) {
    minSlp = minSlp-1; 
    secSlp = 30;
  }
  
  ESP.deepSleep((minSlp*63+ secSlp)*1000*1000);
  delay(500);
}

///// NTP Control /////
/* NTP 同期処理 */
boolean ntp_connect() {
  // NTP サーバー同期確認 (max 5sec)
  unsigned long timeout = millis();
  while (millis() - timeout < 10000) {
    // 現在時刻読込
    time_t timeNow   = time(NULL);
    struct tm* tmNow = localtime(&timeNow);

    // NPT同期完了は正常リターン
    if(tmNow->tm_year>=100) return true;

    // 同期未了は0.25秒おきに再確認
    delay(250);
  }
  // 接続できなかったので エラーリターン
  return false;
}


大まかな流れとしては以下の通りとなっています。

1. 起動・初期設定
2. (setup)
 UARTスタート
 I2Cスタート
 WiFi接続
 NTP確認
3. (loop)
 現在時刻読込
 正10分でなければスリープ(11分もok)
 10回測定し平均値を求める
 測定データを出力(今回は単純にUART出力しています)
 スリープ

起動して測定して終了するだけの簡単な一本道スケッチですが、ポイントはその終わり方(wifi_end()のdeepSleep処理)です。終了時の時刻から次の正10分までの時間を求めその時間分スリープさせる方式をとっています。
ESP8266の内部時計はかなりラフで、10分単位になると分単位で簡単にずれてしまいます。私の場合は1分あたり63秒で計算すると実時間に近づきましたがさすがにこれを計測時間のあてにはできません。そこでNTPとの連携で正10分の記録ができる様いろいろといじくってみました。
一回の起動で3回NTPにアクセスしますので1時間あたり18回、1時間平均20回以下のアクセスというNTPの要求事項はクリアしています。

◯やってみて

しばらく走らせてみましたが、どうやら無事動作してくれているようです。

d1が測定電圧、d2が測定回数で測定開始から順調に正10分・測定回数10回で動作しています。
測定電圧が微妙に上昇しているのは水温の変化によるもので

プローブが出力する最低電圧がどれくらいになるのかテストしてみたものです。
ちなみにドライアイス入りでも実験。

当たり前ですがドライアイス入りでもキチンとデータを測定してくれました。

これでスケッチも無事動作しましたので次回いよいよ本体の製作に入ります。