ページ

2021年2月21日日曜日

ロードセル用アンプHX711を1つのクロック線で複数台とやりとりするためのArduino向けライブラリを作ってみた


背景

HX711とは、ロードセルという重量センサの値を読み取るのに適したアンプです。
独自の通信規格でクロック線とデータ線の2本を利用してマイコンなどで値を読み取れます。

秋月電子のHX711の販売ページで公開されているプログラムは1つの装置から値を読み取る処理になっています。
そのため、そのまま複数台に利用すると、クロックをそれぞれに向けて生成するため、読み取り時間が台数倍になります。

1つのクロック線で読み取れば台数が増えても読み取り時間はほとんど変わりませんし、かつ値の解釈処理は分裂していると便利そうだったので、自分好みのライブラリを作ってみました。
利用方法を紹介します。

使ったもの

  • ESP32にプログラムを書き込めるPC
    Arduino IDEかPlatformIOを利用して書き込めます。
  • ESP32開発ボード
    WROOMが載っているものを利用しました。
  • USB microケーブル
    ESP32開発ボードとPCを接続します。
  • HX711モジュール x4
    ロードセルの値を読み取るためのアンプモジュールです。
  • ロードセル 2kg x4
    HX711モジュールを通して状態を読み取ります。
  • レベル変換基板
    ESP32は3.3VですがHX711は5Vで動作するため、信号線にレベル変換基板を介在させて電位を変換します。
  • ブレッドボード x2
    開発ボードやモジュールの信号線を引き出します。
  • ジャンパワイヤ
    ブレッドボードに引き出した信号線を接続します。
  • はんだ + はんだごて
    HX711モジュールの実装に利用しました。
  • 木板、板ナット、ゴム足、M4ネジ
    ロードセルの設置に利用しました。
    購入先はホームセンターです。
  • のこぎり、電動ドリル
    木板の加工に利用しました。
  • ワイヤストリッパー
    ジャンパワイヤの適当な長さへの切断に利用しました。

回路

HX711は3.3Vでも動くのですが、ロードセルの定格電圧が5V〜12Vなので、HX711は5Vで駆動し、出力された信号をレベル変換基板で5Vから3.3Vに変換してESP32に入力します。

下記回路図のように接続しました。
回路図はクリックで拡大できます。


木の板を加工してロードセルと回路を取り付ける様子を画像で紹介します。


板とロードセル間のスペーサーとして利用したM8板ナットの寸法が丁度良かったです。


HX711モジュール説明書に従い、J3とJ4のジャンパを盛りました。


ロードセルにゴム足を付けました。


回路を組み上げたブレッドボードの両面テープを剥がして板に貼り付けます。


ケーブルがバラけないようホットボンドで固定しました。


裏返して完成です。


ライブラリ HX711_asukiaaa をインストール

Arduino IDEの場合は、ライブラリマネージャからインストールできます。


PlatformIOの場合はplatformio.iniのlib_depsで指定すれば利用可能です。
platformio.ini
lib_deps = HX711_asukiaaa

プログラムの説明

ライブラリのサンプルプログラムに対して、ESP32用にピンを変更し、ロードセルに合わせて設定値を変更したものです。
全体像を共有後に要所を解説します。
#include <HX711_asukiaaa.h>

int pinsDout[] = {32, 33, 34, 35};
const int numPins = sizeof(pinsDout) / sizeof(pinsDout[0]);
int pinSlk = 23;

HX711_asukiaaa::Reader reader(pinsDout, numPins, pinSlk);

//---------------------------------------------------//
// Load cell SC133 2kg
// https://akizukidenshi.com/catalog/g/gP-13041/
//---------------------------------------------------//
#define LOAD_CELL_RATED_VOLT 0.001f
#define LOAD_CELL_RATED_GRAM 2000.0f

//---------------------------------------------------//
// Resistors for HX711 module AE-HX711-SIP
// https://akizukidenshi.com/catalog/g/gK-12370/
//---------------------------------------------------//
#define HX711_R1 20000.0
#define HX711_R2 8200.0

HX711_asukiaaa::Parser parser(LOAD_CELL_RATED_VOLT, LOAD_CELL_RATED_GRAM, HX711_R1, HX711_R2);
float offsetGrams[numPins];

void setup() {
Serial.begin(115200);
Serial.println("start");
reader.begin();
while(reader.read() != 0) {
Serial.println("Failed initial reading. Retry.");
delay(500);
}
for (int i = 0; i < reader.doutLen; ++i) {
offsetGrams[i] = parser.parseToGram(reader.values[i]);
}
}

void loop() {
reader.read();
for (int i = 0; i < reader.doutLen; ++i) {
float gram = parser.parseToGram(reader.values[i]) - offsetGrams[i];
Serial.print("sensor" + String(i) + ": " + String(gram/1000) + " kg ");
Serial.print("offset: " + String(offsetGrams[i]));
Serial.println("");
}
Serial.println("at " + String(millis()));
Serial.println("");
delay(1000);
}

ESP32は1桁と10の位のピンは役割を割り振られて意図しない動作になることがあるため、20と30の位のピンを割り当てました。
今回はHX711の出力を読み取る用途なので、34と35の入力専用ピンも問題なく利用できました。
定義したGPIOピンを渡しつつReaderを定義します。
int pinsDout[] = {32, 33, 34, 35};
const int numPins = sizeof(pinsDout) / sizeof(pinsDout[0]);
int pinSlk = 23;

HX711_asukiaaa::Reader reader(pinsDout, numPins, pinSlk);

今回は2kgのロードセルを利用するため、該当する定格値を有効にしています。
秋月電子のHX711モジュールを利用するため、それで利用されている抵抗値も定義します。
ロードセルの定各値とHX711モジュールの抵抗値を渡しつつParserを定義します。
//---------------------------------------------------//
// Load cell SC133 2kg
// https://akizukidenshi.com/catalog/g/gP-13041/
//---------------------------------------------------//
#define LOAD_CELL_RATED_VOLT 0.001f
#define LOAD_CELL_RATED_GRAM 2000.0f

//---------------------------------------------------//
// Resistors for HX711 module AE-HX711-SIP
// https://akizukidenshi.com/catalog/g/gK-12370/
//---------------------------------------------------//
#define HX711_R1 20000.0
#define HX711_R2 8200.0

HX711_asukiaaa::Parser parser(LOAD_CELL_RATED_VOLT, LOAD_CELL_RATED_GRAM, HX711_R1, HX711_R2);
このライブラリは読み取りと値の解釈がクラスとして別れているため、値をまとめて読み取った後に適切な解釈処理を行うことで、異なる種類のロードセルやモジュールを同時に利用可能です。

ロードセルは初期化に数秒かかることがあるため初回読み取りが成功するまで読み取りを繰り返し、成功した値をオフセットとして保持します。
void setup() {
reader.begin();
while (reader.read() != 0) {
delay(500);
}
for (int i = 0; i < reader.doutLen; ++i) {
offsetGrams[i] = parser.parseToGram(reader.values[i]);
}
}

1秒ごとに読み取りと、オフセットを考慮した現在の重量をシリアルで出力しています。
void loop() {
reader.read();
for (int i = 0; i < reader.doutLen; ++i) {
float gram = parser.parseToGram(reader.values[i]) - offsetGrams[i];
Serial.print("sensor" + String(i) + ": " + String(gram/1000) + " kg ");
Serial.print("offset: " + String(offsetGrams[i]));
Serial.println("");
}
Serial.println("at " + String(millis()));
Serial.println("");
delay(1000);
}

動作の様子

プログラムを動かしシリアル出力を見てみます。

何も置いていないときは、オフセットが効いて、どのロードセルも0kgとなりました。
sensor0: -0.00 kg offset: 8017.81
sensor1: 0.00 kg offset: 8153.21
sensor2: -0.00 kg offset: 7640.58
sensor3: 0.00 kg offset: 8281.51
at 18497

台の端に1Lのペットボトルを置いてみます。


1つのロードセルにほとんどの負荷がかかるため下記のような出力になり、合計約1.2kgの物が置かれたことになりました。
sensor0: 0.06 kg offset: 8017.81
sensor1: 0.95 kg offset: 8153.21
sensor2: 0.10 kg offset: 7640.58
sensor3: 0.11 kg offset: 8281.51
at 101896

今度は台の中央に1Lのペットボトルを置いてみます。


出っ張った対角のロードセルで主に負荷を受けるため、下記のように2つのロードセルに負荷が分散され、合計約1.2の物が置かれたことになりました。
sensor0: 0.10 kg offset: 8017.81
sensor1: 0.49 kg offset: 8153.21
sensor2: 0.21 kg offset: 7640.58
sensor3: 0.42 kg offset: 8281.51
at 140786

期待通りに値を読めているようです。

まとめ

HX711を1本の信号線で複数台扱うライブラリを利用することで、複数のロードセルの値を1台利用時とほぼ変わらない時間で読み取りました。

HX711とロードセルを複数利用する際に、よかったらご利用ください。

https://github.com/asukiaaa/arduino-HX711

13 件のコメント :

  1. 大変興味深く読まさせていただきました。初歩的な質問ですが
    1)今回はESP32を使っておられますがArduino UnoやNanoでも使えますか? 2)delayを100にすれば10回/秒程度にサンプリングを増やせますか? 3)ESP32の場合Wi-FiでPCに飛ばすようにすることはできますか?

    返信削除
    返信
    1. > 今回はESP32を使っておられますがArduino UnoやNanoでも使えますか
      はい、多分使えます。
      5Vで動くMCUを使う場合、3.3Vと5Vのレベル変換装置が不要になります。

      > delayを100にすれば10回/秒程度にサンプリングを増やせますか?
      delayを小さくすると測定回数を増やせるものの、正確な時間は覚えていないのですが測定時間がかかるためめ、どこかの周期で頻度をそれ以上高められなくなります。

      > ESP32の場合Wi-FiでPCに飛ばすようにすることはできますか?
      はい、可能です。

      削除
  2. この記事で大変勉強させてもらってます
    ロードセルを個別校正するのはどうされてますか?

    返信削除
  3. > ロードセルを個別校正するのはどうされてますか?

    setup関数内でoffsetGramsに各ロードセルの初期値を代入しているのですが、これとは別の処理を意味しますか?

    返信削除
    返信
    1. ゼロ点調整ではなく
      重さを乗せた時の値を校正するのに
      #define LOAD_CELL_RATED_VOLT 0.001f
      を変更すればいいのかなと思いますが4つ個別にはできないですか?

      削除
  4. 応答ありがとうございます。
    多種類のロードセルを同時に使いたいと認識しました。
    解釈(parser)のインスタンスを複数種類定義して利用すれば可能です。
    複数種類を扱うサンプルプログラムreadMultiKindsを追加しましたのでご覧ください。

    返信削除
    返信
    1. 詳しく回答ありがとうございます
      参考になりました
      因みにセンサー0,1,2,3のgramをlcdに同時に表示させたいのですが
      String(gram/1000)を入れると一秒毎にセンサー0,1,2,3と切り替わり表示してしまいうまくいきませんどうしたら良いですか?

      削除
  5. 参考になったということで良かったです。

    > String(gram/1000)を入れると一秒毎にセンサー0,1,2,3と切り替わり表示してしまいうまくいきません

    それはライブラリは使えた上で応用プログラムの組み立て方の質問になっていますが、お答えします。
    forの中でdelayを呼び出してgramを利用しているのだと思いますが、gramをforの外でも使える配列として
    float grams[numPins];
    などとして定義し、forの中でgrams[i]に解釈した重さ情報を代入して、forの外でgramsの情報(記事の内容ならgrams[0]〜grams[3])を扱えばlcdに複数の解釈済みの情報を表示できると思います。
    そうした場合、forの中のdelayは削除してください。

    解決しない場合は利用しているコードを共有してください。
    コードの共有はコメント欄よりgithubの方が見やすいので、よければこちらをご利用ください。
    https://github.com/asukiaaa/arduino-HX711/issues

    返信削除
  6. 下記のissueにやりとりの場を移し、解決しました。
    https://github.com/asukiaaa/arduino-HX711/issues/3

    返信削除
  7. 初めてコメントさせていただきます。あすき様のコードを使わせていただいて3台のロードセル使用の重芯計を作成しました。あすき様のコードと明記した上で、私のブログに掲載させていただきたいと思いますがよろしいでしょうか?差し支えがあればご連絡ください。

    返信削除
  8. 掲載は問題ありません
    しかし、「思っている」のに既に記事に引用されているので、公開前に問い合わせるか、公開済みと分かる文「掲載しています」で問い合わせていただけると、疑念を抱かずに済んで良いです
    また、ブログの紹介のところをurlの貼りつけだけでなくaタグを利用してリンクにしていただけると嬉しいです

    返信削除
  9. 不快な思いをさせてしまい、申し訳ありませんでした。今後注意いたします。またaタグでのリンクもさせていただきました。今後もよろしくおねがいします。

    返信削除
  10. 気をつけていただけることとタグの変更ありがとうございます

    返信削除