果報は寝て待て: 2024

2024年1月17日水曜日

ロードセルで重芯計を作ってみた

 aruduino uno    と秋月電子のロードセルを使って、重芯計(今後はバランサーと呼びます)を作ってみた。

うちの工場では、ライン内で、加工した製品のアンバランス量を測定し、ドリルでバランス修正をして、アンバランス量を規定値以下にして出荷してます。

ライン内に設置してる機械は1000万円近くする◯浜製作所のバランシングマシンです。時々ライン外の製品のアンバランス量の測定依頼が来ます。

そこで、精度はそこそこでも簡単にアンバランス量の測定をできる装置を安くできないかと相談があり、制作してみることにしました。

 原理は3つの体重計を正三角形に並べて上に板を置き、中心からどれだけずれてるかを3つの体重計の測定値から計算しようというものです。

使うもの aruduino uno

秋月電子 ロードセル シングルポイント(ビーム型) 2kg x3個

HX711使用ロードセル用ADコンバーターモジュール基板 x3個


 

これらを使って作成します。

aruduino uno用のサンプルコードは、たくさんあったのですが、複数のロードセルの測定値を表示するコードを、「あすき」様のブログより使わせていただきました。

https://asukiaaa.blogspot.com/2021/02/hx711-multiple-in-single-clock-line.html

ロードセルから、重量に変換するところはそのまま使わせていただいてます。

詳しい原理です。

① 正三角形の頂点ABCのそれぞれにロードセル(体重計)を設置します。その上に同じ正三角形の板をおいたとします。

距離AO=70ミリメートルとします。

② ABCのそれぞれにロードセルにそれぞれのオフセットを入れて0グラムにします。

③ 中心Oに20グラムの重りをおいた時、理論値はABCそれぞれ20/3=6.67グラムになります。その場合アンバランス量は0gmm(グラムミリメートル)となります。


 ④ 頂点B上に20グラムの重りをおいたとします。するとACはそれぞれ0グラム、Bは20グラムとなります。その場合アンバランス量は20gX70mm=1400gmm 、角度は120度となります。

角度は上を0度として時計方向に回します。 

 


⑤ また、直線ABと,CとOを通る直線との交点に20グラムの重りを置いたとします。その場合ABはそれぞれ10グラム、Cは0グラムとなりアンバランス量は20gx70mm/2=700gmm、角度は60度となりますね。

こんな具合でアンバランスの量と角度を測定します。

実際にワークを置くと1000グラム近く増えるので、ABCの最小値min(ABC)を求めて、ABCそれぞれから引いてやります。

 A' = A - min(ABC)         B' = B - min(ABC)      C' = C - min(ABC) 

となり、A'B'C'の合成ベクトルがアンバランス量と角度を表します。

補正についてです。

補正には偏芯補正と目盛り補正があります。

偏芯補正

 ワークをセットさせる治具のアンバランス量や、わずかな傾きが大きくアンバランス量に影響してくるので、偏芯補正をします。治具には正三角形の頂点の位置に位置決めピンがたっており、製品に開いてる3点のアナをそこに入れてセットします。

ですから、120度回転、240度回転させても同じようにセットできます。それを利用します。

0度にセットした時、120度にセットした時、240度にセットした時それぞれのアンバランス量と角度から偏芯補正値を算出して補正します。計算式はコードを見てください。

 目盛り補正

 ロードセルにそのまま20gの重りを載せた場合と1kgの治具を載せた上に20gの重りを載せた場合の重りの表示値の違いを補正します。ロードセルが優秀で、目盛り補正値は、いつも0.98くらいです。今の使用目的ならば目盛り補正は必要なさそうです。 

aruduino unoのコードです。

 

#include <HX711_asukiaaa.h>
#include <LiquidCrystal.h>

LiquidCrystal lcd(2, 3, 4, 5, 6, 7);


int pinsDout[] = {11, 12, 13};
const int numPins = sizeof(pinsDout) / sizeof(pinsDout[0]);
int pinSlk = 10;
HX711_asukiaaa::Reader reader(pinsDout, numPins, pinSlk);
const int memori_hosei_mode_Pin = 8; // 目盛り補正モードに入るピン番号
const int hensin_hosei_mode_Pin = 9; // 偏芯補正モードに入るピン番号
int correctionMode = 0; // 0:測定、1:目盛り補正、2:偏芯補正

//---------------------------------------------------//
// Load cell SC301A 100kg
// https://akizukidenshi.com/catalog/g/gP-12036/
//---------------------------------------------------//
// #define LOAD_CELL_RATED_VOLT 0.002f
// #define LOAD_CELL_RATED_GRAM 100000.0f

//---------------------------------------------------//
// Load cell SC601 120kg
// https://akizukidenshi.com/catalog/g/gP-12035/
//---------------------------------------------------//
//#define LOAD_CELL_RATED_VOLT 0.002f
//#define LOAD_CELL_RATED_GRAM 120000.0f

//---------------------------------------------------//
// Load cell SC133 20kg
// https://akizukidenshi.com/catalog/g/gP-12034/
//---------------------------------------------------//
// #define LOAD_CELL_RATED_VOLT 0.002f
// #define LOAD_CELL_RATED_GRAM 20000.0f

//---------------------------------------------------//
// 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
#define LOAD_CELL_RATED_GRAM 2830.0f
//---------------------------------------------------//
// Load cell SC616C 500g
// https://akizukidenshi.com/catalog/g/gP-12532/
//---------------------------------------------------//
// #define LOAD_CELL_RATED_VOLT 0.0008f
// #define LOAD_CELL_RATED_GRAM 500.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];    // 各ロードセルのオフセット値
float sokutei_gram[numPins];   // 各ロードセルの測定値(オフセット値を引いたもの)
float sokutei_gram_before[numPins];   // 目盛り校正時、各ロードセルの測定値(オフセット値を引いたもの)
float sokutei_gram_after[numPins];   // 目盛り校正時、おもりを乗せた各ロードセルの測定値(オフセット値を引いたもの)


float min_sokutei_gram;        // 各ロードセルの測定値の中の最小値
float relative_gram[numPins];  // 各ロードセルの相対的測定値(最小値を引いたもの)
float relative_gram_of_X;      // 合計相対的測定値のⅩ軸成分
float relative_gram_of_Y;      // 合計相対的測定値のÝ軸成分
float relative_hensin_of_X;      // 合計相対的測定値のⅩ軸成分(偏心補正済)
float relative_hensin_of_Y;      // 合計相対的測定値のÝ軸成分(偏心補正済)


float angle;                   // アンバランス角度
float unbalance_value;         // アンバランス量(gmm)
float unbalance_value_hensin;         // アンバランス量(gmm)(偏心補正済)

float hensen_hosei_X[numPins]; // []回目の偏芯補正時の合計相対的測定値のⅩ軸成分
float hensen_hosei_Y[numPins]; // []回目の偏芯補正時の合計相対的測定値のÝ軸成分
float hensin_offset_X = 0.0;         // 偏芯補正用の合計相対的測定値のⅩ軸成分
float hensin_offset_Y = 0.0;         // 偏芯補正用の合計相対的測定値のÝ軸成分
float memori_offset = 1.0;         // 目盛り補正用の係数、初期値は1.0



void setup() {
  pinMode(memori_hosei_mode_Pin, INPUT_PULLUP);
  pinMode(hensin_hosei_mode_Pin, INPUT_PULLUP);
  lcd.begin(16, 2); // LCDの初期化 16文字x2行のLCDモジュールの場合
  Serial.begin(115200);
  Serial.println("start");
  lcd.setCursor(0, 0); // 表示位置を指定
  lcd.print("start");
  reader.begin();
  for (int i = 0; i < reader.doutLen; ++i) {
    offsetGrams[i] = parser.parseToGram(reader.values[i]);
  }
}

void loop() {
 
  int hensin_hosei_1 = 0;
  int hensin_hosei_2 = 0;
  int hensin_hosei_3 = 0;
  int hensin_hosei_4 = 0;
  int buttonState1 = digitalRead(memori_hosei_mode_Pin);
  int buttonState2 = digitalRead(hensin_hosei_mode_Pin);
 
  if (buttonState1 == LOW && buttonState2 == HIGH) {
    correctionMode = 1;  // 1:目盛り補正、2:偏芯補正
  }
  else if (buttonState1 == HIGH && buttonState2 == LOW) {
    correctionMode = 2;  // 1:目盛り補正、2:偏芯補正
  }
 
  if (correctionMode == 1 ){
    // 1:目盛り補正モードに入る
    Serial.println("memori_hosei_Mode   ");
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("memori_hosei_Mode ");

    delay(1000);
    Serial.println("Set MasterWork at 0 degree! ");
    lcd.setCursor(0, 1);
    lcd.print("Set Work 0 deg");
    delay(7000);
   
    auto readState = reader.read();
    Serial.println("ReadState: " + HX711_asukiaaa::getStrOfReadState(readState));
    if (readState == HX711_asukiaaa::ReadState::Success) {
      for (int i = 0; i < reader.doutLen; ++i) {
        sokutei_gram_before[i] = (parser.parseToGram(reader.values[i]) - offsetGrams[i]) ;
        Serial.print("sokutei_gram_before" + String(i) + ": " + String(sokutei_gram_before[i]) + " g ");
        Serial.print("offset: " + String(offsetGrams[i]) + " g ");
        Serial.print("voltage: " +
                    String(parser.parseToMicroVolt(reader.values[i])) + " uV ");
        Serial.print(String(parser.parseToMicroVolt(reader.values[i]) / 1000) +
                    " mV ");
        Serial.println("");
      }
      Serial.println("Set 20g_Weight arround center! ");
      lcd.setCursor(0, 1);
      lcd.print("                 ");
      lcd.setCursor(0, 1);
      lcd.print("Put 20g center");
      delay(5000);
    }

    readState = reader.read();
    Serial.println("ReadState: " + HX711_asukiaaa::getStrOfReadState(readState));
    if (readState == HX711_asukiaaa::ReadState::Success) {

      for (int i = 0; i < reader.doutLen; ++i) {
        sokutei_gram_after[i] = (parser.parseToGram(reader.values[i]) - offsetGrams[i]) ;
        Serial.print("sokutei_gram_after" + String(i) + ": " + String(sokutei_gram_after[i]) + " g ");
        Serial.print("offset: " + String(offsetGrams[i]) + " g ");
        Serial.print("voltage: " +
                    String(parser.parseToMicroVolt(reader.values[i])) + " uV ");
        Serial.print(String(parser.parseToMicroVolt(reader.values[i]) / 1000) +
                    " mV ");
        Serial.println("");
      }


      memori_offset = 20.0 / (sokutei_gram_after[0] + sokutei_gram_after[1] + sokutei_gram_after[2] - sokutei_gram_before[0] - sokutei_gram_before[1] - sokutei_gram_before[2]);
      Serial.print("memori_offset: " + String(memori_offset) );
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("memori_offset: ");
      lcd.setCursor(0, 1);
      lcd.print(String(memori_offset));


      delay(5000);
      for (int i = 0; i < reader.doutLen; ++i) {
        Serial.print("sensor" + String(i) + ": " + String(sokutei_gram[i]) + " g ");
        Serial.println("");
      }
    }
    delay(5000);
    correctionMode = 0;
    lcd.clear();
  }

  if (correctionMode == 2 ){
    // 2:偏芯補正モードに入る
    Serial.println("hensin_hosei_Mode   ");
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("hensin_hosei_Mod ");

    delay(1000);
    Serial.println("Set MasterWork at 0 degree! ");
    lcd.setCursor(0, 1);
    lcd.print("Set Master 0 \xDF");


    delay(7000);
    
    for (int j = 0; j < 3; ++j) {

      delay(1000);

      auto readState = reader.read();
      Serial.println("ReadState: " + HX711_asukiaaa::getStrOfReadState(readState));
      if (readState == HX711_asukiaaa::ReadState::Success) {
        for (int i = 0; i < reader.doutLen; ++i) {
          sokutei_gram[i] = (parser.parseToGram(reader.values[i]) - offsetGrams[i]) * memori_offset;
          Serial.print("sensor" + String(i) + ": " + String(sokutei_gram[i]) + " g ");
          Serial.print("offset: " + String(offsetGrams[i]) + " g ");
          Serial.print("voltage: " +
                      String(parser.parseToMicroVolt(reader.values[i])) + " uV ");
          Serial.print(String(parser.parseToMicroVolt(reader.values[i]) / 1000) +
                      " mV ");
          Serial.println("");
        }
        min_sokutei_gram = min(sokutei_gram[0], min(sokutei_gram[1], sokutei_gram[2]));
        Serial.println("min_sokutei_gram = " + String(min_sokutei_gram)) + " g ";
        for (int i = 0; i < reader.doutLen; ++i) {
          relative_gram[i] = sokutei_gram[i] - min_sokutei_gram;
          Serial.print("relative_gram: " + String(relative_gram[i]) + " g ");
        }
        hensen_hosei_X[j] = 0.866 * relative_gram[1] - 0.866 * relative_gram[2];
        hensen_hosei_Y[j] = relative_gram[0] - 0.5 * relative_gram[1] - 0.5 * relative_gram[2];
        Serial.println("");
        Serial.print("hensen_hosei_X"+String(j)+": " + String(hensen_hosei_X[j]) + " g ");
        Serial.println("hensen_hosei_Y"+String(j)+": " + String(hensen_hosei_Y[j]) + " g ");

        if (j < 2 ){
          Serial.println("Turn MasterWork plus 120 degree! ");
          lcd.setCursor(0, 1);
          lcd.print("                   ");
          lcd.setCursor(0, 1);
          if (j == 0){
            lcd.print("Set Master 120 \xDF");
          }
          if (j == 1){
            lcd.print("Set Master 240 \xDF");
          }


        }
        delay(8000);
      }
    }
    hensin_offset_Y =  (hensen_hosei_Y[0] + hensen_hosei_Y[1] + hensen_hosei_Y[2]) / 3 ;
    hensin_offset_X =  (hensen_hosei_X[0] + hensen_hosei_X[1] + hensen_hosei_X[2]) / 3 ;
    Serial.print("hensin_offset_X : " + String(hensin_offset_X) + " g ");
    Serial.println("hensin_offset_Y : " + String(hensin_offset_Y) + " g ");
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("hensin_offset");
    lcd.setCursor(0, 1);
    lcd.print("X= "+String(hensin_offset_X,1)+"g,Y= "+String(hensin_offset_Y,1)+"g");


    delay(5000);

    correctionMode = 0;
  }

  if (correctionMode == 0 ){
    // 0:測定モードに入る


    auto readState = reader.read();
    Serial.println("ReadState: " + HX711_asukiaaa::getStrOfReadState(readState));
    if (readState == HX711_asukiaaa::ReadState::Success) {
      for (int i = 0; i < reader.doutLen; ++i) {
        sokutei_gram[i] = (parser.parseToGram(reader.values[i]) - offsetGrams[i]) * memori_offset;
        Serial.print("sensor" + String(i) + ": " + String(sokutei_gram[i]) + " g ");
        Serial.print("offset: " + String(offsetGrams[i]) + " g ");
        Serial.print("voltage: " +
                    String(parser.parseToMicroVolt(reader.values[i])) + " uV ");
        Serial.print(String(parser.parseToMicroVolt(reader.values[i]) / 1000) +
                    " mV ");
        Serial.println("");
      }
      min_sokutei_gram = min(sokutei_gram[0], min(sokutei_gram[1], sokutei_gram[2]));
      Serial.println("min_sokutei_gram = " + String(min_sokutei_gram)) + " g ";
      for (int i = 0; i < reader.doutLen; ++i) {
        relative_gram[i] = sokutei_gram[i] - min_sokutei_gram;
        Serial.print("relative_gram: " + String(relative_gram[i]) + " g ");
      }
      relative_gram_of_X = 0.866 * relative_gram[1] - 0.866 * relative_gram[2];
      relative_gram_of_Y = relative_gram[0] - 0.5 * relative_gram[1] - 0.5 * relative_gram[2];
      Serial.println("");
      Serial.print("relative_gram_of_X: " + String(relative_gram_of_X) + " g ");
      Serial.println("relative_gram_of_Y: " + String(relative_gram_of_Y) + " g ");
      angle = atan2(relative_gram_of_X, relative_gram_of_Y) * 180.0 / PI;
      // 角度を0から360度に正規化
      if (angle < 0) {
        angle += 360.0;
      }
      Serial.println("angle =  " + String(angle) + " °");
      unbalance_value = (relative_gram[0] + relative_gram[1] + relative_gram[2]) * 90.0 ;
      Serial.println("unbalance_value " + String(unbalance_value) + " gmm");
            
      relative_hensin_of_Y = relative_gram_of_Y - hensin_offset_Y;
      relative_hensin_of_X = relative_gram_of_X - hensin_offset_X;
      Serial.print("relative_hensin_of_X: " + String(relative_hensin_of_X) + " g ");
      Serial.println("relative_hensin_of_Y: " + String(relative_hensin_of_Y) + " g ");
      angle = atan2(relative_hensin_of_X, relative_hensin_of_Y) * 180.0 / PI;
      // 角度を0から360度に正規化
      if (angle < 0) {
        angle += 360.0;
      }
      Serial.println("angle =  " + String(angle) + " °");
      unbalance_value_hensin = (sqrt(sq(relative_hensin_of_Y) + sq(relative_hensin_of_X) ) ) * 90.0 ;
      Serial.println("unbalance_value(hensin) " + String(unbalance_value_hensin) + " gmm");
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("angle=" + String(angle,1) + " \xDF");
      lcd.setCursor(0, 1);
      lcd.print("unbal=" + String(unbalance_value_hensin,1) + " gmm");


      Serial.println("at " + String(millis()));
      Serial.println("");
      
    }


  }
  delay(1000);
}

あすき様のライブラリ HX711_asukiaaa と LCDキャラクタディスプレイの LiquidCrystal.h を使用してます。

ADコンバータです

ロードセルを取り付けたところ

治具を取り付けるアダプタを取り付けたところです。

その上にこのような治具を載せます。

測定風景

表示内容