2022年5月15日日曜日

Arduinoで水位計(燃料計)を使う


背景

浮きの位置によって抵抗が0~190Ωに変化する水位計を使う場面がありました。
それには下記2種類の処理が必要だったので、出来上がった処理をライブラリにまとめて公開しました。

備忘録を兼ねて利用した水位計の特徴とライブラリの使い方を記事を残します。

使ったもの

  • 水位計(燃料計)
    aliexpressで購入しました。


    購入したのは長さ425mmです。


    刻印として425mm、0~190Ω、210Wが分かる情報が記されていました。


    今回はArduinoで使うのでセンサ部分だけ買いましたが、表示装置付きのもあります。
  • 抵抗
    220Ω 1%
    水位計の抵抗を読み取るために直列に接続する抵抗です。
    分解能を大きめに確保するため、水位計と同程度の抵抗を利用するのが良いと思います。
    今回は手元にあった220Ωの抵抗を利用しました。
    例として示す画像は炭素皮膜(誤差5%)の抵抗を利用していますが、精度の高い金属皮膜(誤差1%)を利用するのが良いです。
  • Arduino
    アナログ入力ピンがあるものなら、記事の内容を適用できると思います。
    今回はRaspberry Pi Picoを利用しました。
  • 太い線をブレッドボードに引き込むための変換基板
    水位計の信号線が太くてブレッドボードに直接差し込めなかったので、変換器版を利用しました。
  • マイナスドライバー
    変換基板への信号線固定に利用しました。
  • Arduinoの開発環境
    Arduino IDEやPlatformIOでプログラムを作ります。
  • ブレッドボード
    各装置の信号線を引き出します。
  • ジャンパワイヤ
    信号線を繋ぎます。
  • テスター
    水位計の抵抗確認に利用しました。
  • ワニ口クリップ
    テスターと水位計の一時的な固定に利用しました。

ライブラリを準備

Arduino IDEの場合

ライブラリマネージャ(スケッチ -> ライブラリをインクルード -> ライブラリを管理で開けます)で下記の2つのライブラリをインストールしてください。



PlatformIOの場合

プロジェクトのplatform.iniのlib_depsに今回必要なライブラリ2つを追加してください。
lib_deps =
ResistorReader_asukiaaa
Gauge_asukiaaa

水位計の抵抗を確認

測定してみると、抵抗は線形ではなく段階的に変化していると分かりました。


先端からの黒い浮きの位置と抵抗の関係をグラフにするとこうなりました。
浮きの移動範囲は0~365mm、抵抗の変化範囲は0.2~187.2Ωの17段階でした。


移動範囲の最初と最後は中央に比べると抵抗の変化が大きくなっていました。
抵抗の切り替わりが右下がりになっている理由を117.5Ωの境目で説明します。


110.8Ωの範囲に浮きがあります。


上記の状態から117.5Ωの印の上端を超えると、抵抗が117.3Ωになりました。


抵抗は一度変わると5mmほど切り替わりに距離が必要となるようで、117.8Ωの印が隠れるほど下げても117.3Ωを維持します。
下記の写真から数mm下げると11.8Ωになります。


117.8Ωから124Ωの切り替わりも同様に、124Ωの印ギリギリまで117.8Ωが維持されます。


124Ωの印を超えると124Ωになります。


一度124Ωになると、124Ωの印が隠れるほど下げても124Ωが維持されます。


上記の余裕がグラフが右に下がっている理由です。

回路作成

Ardinoで水位計の抵抗を読み取るために下記の回路を組みます。
ADC_VREF(3.3V)、水位計、220Ω抵抗、GNDの順に繋ぎ、水位計と抵抗の間の電圧をADC2で読取る回路です。


組むとこうなりました。



抵抗を読むプログラム

ResistorReaderのサンプルコードに下記の変更を加えて書き込みます。
  • 読込ピンはA2
  • 読込ピンの接続は可変抵抗の低い側
  • 低い側の抵抗は220Ω
  • 高い側の抵抗は0Ω

#include <Arduino.h>
#include <ResistorReader_asukiaaa.hpp>

#define PIN_RESISTOR A2
#define PIN_RESISTOR_POSITION ResistorReader_asukiaaa::PinPosition::Downer
#define RESISTOR_FIXED_UPPER 0
#define RESISTOR_FIXED_DOWNER 220

ResistorReader_asukiaaa::Core resistorReader(PIN_RESISTOR,
PIN_RESISTOR_POSITION,
RESISTOR_FIXED_UPPER,
RESISTOR_FIXED_DOWNER);

void setup() {
Serial.begin(115200);
}

void loop() {
resistorReader.update();
Serial.println("adc: " + String(resistorReader.getCurrentAdc()));
Serial.println("resistor: " + String(resistorReader.getCurrentResistor()));
Serial.println("at " + String(millis()));
Serial.println();
delay(1000);
}

プログラムを書き込んでください。

水位計の抵抗を読む

シリアルモニタを開いて、書き込んだプログラムで期待通りに抵抗を読めているか確認します。

先端の0.2Ωを1.08Ωと認識。


76Ωを75.74Ωと認識。


117.5Ωを117.93Ωと認識。


187.2Ωを187.72Ωと認識。


どの場所でも1Ω前後の誤差で認識できていました。

水位計を設置して、抵抗値と水位の関係を調べる

水位計を対象となる容器に設置して水位と抵抗の関係を調べます。
今回は100Lタンクに設置したので、10L毎に抵抗の値を調べました。
結果はこちらです。

L   ohm
0 0.2
10 38
20 68
30 76
40 84
50 90.7
60 104
70 111.8
80 117.5
90 130.7
100 145.6

タンクの底が狭くなっているため、先端は容量に対して抵抗の変化が大きい関係となりました。

調べた抵抗と水位の値を組み込む

設置して注水して得た抵抗と水位の関係をGaugeライブラリに渡して抵抗から水位を取得します。

#include <Arduino.h>
#include <Gauge_asukiaaa.hpp>
#include <ResistorReader_asukiaaa.hpp>

#define PIN_RESISTOR A2
#define PIN_RESISTOR_POSITION ResistorReader_asukiaaa::PinPosition::Downer
#define RESISTOR_FIXED_UPPER 0
#define RESISTOR_FIXED_DOWNER 220

ResistorReader_asukiaaa::Core resistorReader(PIN_RESISTOR,
PIN_RESISTOR_POSITION,
RESISTOR_FIXED_UPPER,
RESISTOR_FIXED_DOWNER);

Gauge_asukiaaa::Point arrPoint[] = {
// clang-format off
{0.2, 0},
{38, 10},
{68, 20},
{76, 30},
{84, 40},
{90.7, 50},
{104, 60},
{111.8, 70},
{117.5, 80},
{130.7, 90},
{145.6, 100},
// clang-format on
};
size_t lenPoint = sizeof(arrPoint) / sizeof(Gauge_asukiaaa::Point);
Gauge_asukiaaa::Core gauge(arrPoint, lenPoint);

void setup() {
Serial.begin(115200);
}

void loop() {
resistorReader.update();
float resistor = resistorReader.getCurrentResistor();
Serial.println("adc: " + String(resistorReader.getCurrentAdc()));
Serial.println("resistor: " + String(resistor));
Serial.println("L: " + String(gauge.convert(resistor)));
Serial.println("at " + String(millis()));
Serial.println();
delay(1000);
}

プログラムを書き込んで動作を確認します。   

動作確認

浮きの位置を変化させつつ、シリアルモニタで処理内容を確認します。

浮きが先端にある場合、抵抗1.08Ωで推定0.23L


浮きが先端から全体の1/5ほどの場所にある場合、抵抗52.47Ωで推定14.82L


浮きが中央にある場合、104.29Ωで推定60.38L


浮きが先端から4/5の場所にある場合、抵抗145.36Ωで推定99.84L


浮きが最も高い部分にある場合、187.72Ωで推定128.27L
ライブラリによって推定容量は算出できましたが、タンクの計上からしてここまでは水が来ない想定です。


期待通りに容量を推定できました。

おわり

段階的に抵抗が変わる水位計の抵抗値を読み取って容量を推定できました。

余談: ESP32のanalogReadは誤差が大きめ

この記事は最初ESP32で回路を作っていたのですが、抵抗の測定結果に30Ω(15%)ほど誤差があり、許容できない精度だったので利用を止めてraspi picoで回路を組みました。

187Ωと出て欲しいところで223Ωと表示しているESP32。


ESP32のADCは0.15~0.24Vでないと測定精度が低かったり、esp32固有の関数を呼び出して補正後の測定電圧を呼び出す必要があったりするので、利用には注意が必要です。(参考: ESP32のADCについて
それらに気をつけても自分の試した範囲ではESP32で期待する精度を出せなかったので、ESP32でのアナログ値の扱いは一旦止めました。

参考

Raspberry Pi Pico Pinout
ResistorReader_asukiaaa
Gauge_asukiaaa

変更履歴

2023.12.24
センサの長さを475mmと記述していましたが、正しくは425mmだったので修正しました

7 件のコメント :

辻田 さんのコメント...

>ESP32のADCは0.15~0.24Vでないと測定精度が低かったり

減衰させないで裸で使用した場合、フルスケール0-1.1Vのうち、有効な範囲は100mV-950mV(0.1V - 0.95V)

11dBで減衰させて使用した場合、フルスケール0-3.9Vのうち、有効な範囲は150mV-2450mV(0.15V - 2.45V)

ということではないかと思いました。

Rail to railとうたっていない限り、ADやアンプは上と下の性能が劣るのは仕方ないので、できるだけレンジの中心付近で使用するのがいいかと思います。
また、ESP32のdatasheetを見ると、12-bitのADCのDNLは-7~7LSBと書いてあるので、実質8-bitのADCと思わないといけないです。INLは-7~7LSBとかでもいいですが、普通はDNLは+/- 1LSBです。

辻田 さんのコメント...

>ADC connected to an external 100 nF capacitor; DC signal input;
>ambient temperature at 25 °C;
>Wi-Fi&BT off

追加です。ESP32のdatasheetに、ADCの特性の条件として上記の記載があるので、外付けキャパシタが必要なのと、WifiやBTの無線回路の動作の影響があるのではないかと思いました。

アナログ回路は、データシートをよく読まないといろいろひっかかると思いますよ。

Asuki Kono さんのコメント...

コメントありがとうございます。

dbやフルスケールは認識しつつもまだ理解できていませんが、100nf(0.1uf)のコンデンサの設置が求められているのは知りませんでした。
情報ありがとうございます。

ADC2(D25以下のADCポート)は無線を使うと動かなくなると認識しています。
その記述がADC1にも影響するのを意図しているか気になるところです。

辻田 さんのコメント...

ADCは、アナログ回路部分とディジタル回路部分があります。12-bitと書いてあるので、ディジタル回路は12-bitの設計になっているのだと思います。ただし、DNLが-7LSB~7LSBと書かれているので実物を評価した時の測定値がそのまま記載されていると思うのですが、これはアナログ回路の特性が不十分だということを意味しています。たぶん、Espressifにはアナログ設計者がいないか能力不足で、購入してきたADCコアをそのまま使用しているのだと思います。
無線回路を動作させると他の部分に比べて消費する電流(特にピーク電流)が大きいので、ピーク電流の影響で電源の電位が大きく変動します。うまく設計されていないアナログ回路はその影響を大きく受けてしまうのでまともな性能は出ないだろうと思います。
ただ、ソフトウェアを組むときに、WifiやBTの動作とセンサの動作・測定とが被らないようにしてやれば、ある程度回避できると思います。

Asuki Kono さんのコメント...

解説ありがとうございます。

> ソフトウェアを組むときに、WifiやBTの動作とセンサの動作・測定とが被らないようにしてやれば、ある程度回避できると思います。
ADC2は無線利用時も動きはしますが、回路の設計が不十分なために、そのような工夫が必要になるのですね。
ESP32のADCはなるべく利用を避けたい心持ちですが、使わざるを得なくなった場合は気をつけます。

ファーザーQ さんのコメント...

センサーの値を読み取る方法を探しておりました。
ボードは違いますが参考にさせて頂きますmm

Asuki Kono さんのコメント...

情報が役に立てば嬉しいです