果報は寝て待て

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コンバータです

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

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

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

測定風景

表示内容




 

 




 

2023年7月5日水曜日

7segカウンターで遊ぶ

 アマゾンで1万円ちょいで中華製の大型LEDを買った。


 

 目的は、工場のあるライン内の、ある工程で払い出されたNG品の数をメイン通路の誰でも見やすい位置に表示することです。

要求は、大きい数字で表示できる。

一台ずつカウントアップできる。

リセットで0にできる。

最後に、責任者が確認したら、OK品だった場合のカウントダウンできることです。

しかし説明書にはカウントダウンできるとはどこにも書いてません。そこでまず1台買って改造できるか調べます。

製品の裏蓋を外した状態です。


PIC12F675が中央にあり、7SEGLED専用ドライバーのTM1620が右端に見えます。

ピンの接続は、

1-VDD, 

2-デジタルI/Oでカウントアップ、

3-デジタルI/Oでカウントを0にセット

4-未使用

5-データ送信

6-クロック送信

7-ストローブ信号送信

8-Vss

そう。4番ピンが余ってる。なんとかしてみよう。

ロジアナで[24]を表示している時の信号です。上から STB,CLOCK,DATAです。


データシートや他のかたのコードを参考にして、同じ数字のときに同じ信号になるようにコードを書きます。

 結論から言うと、データシートどおりでは無いところが2箇所ありました。

1 1桁目の数字はGRID1に表示され、2桁目の数字はGRID6に表示されます。

  3桁目以降の位置は不明。

2 SEG1=a,

  SEG2=b ・・・ SEG7=g のところ

     SEG1=d   ,SEG2=常時0    , SEG3=g     ,SEG4=a     ,SEG5=b    ,SEG6=c    SEG7=f  

        SEG8=c となってます。

これを加味したのが以下のコードです。マイコンはpic12f1822。そのままポン付けできそうなので…


/*
 * File:   main.c
 * Author: Administrator
 *
 * Created on April 11, 2023, 10:48 PM
 */
// PIC12F1822 Configuration Bit Settings

// 'C' source line config statements

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF       // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable (Brown-out Reset disabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON        // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = OFF       // PLL Enable (4x PLL enabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#define _XTAL_FREQ 4000000

#define STB RA0
#define CLK RA1
#define DIO RA2
#define count_up RA5
#define reset RA4
#define count_down RA3

const char NUM[11] = {
    0b11111001,    //0
    0b10010000,    //1
    0b10101101,    //2
    0b10011101,    //3
    0b11010100,    //4
    0b01011101,    //5
    0b01111101,    //6
    0b10011000,    //7
    0b11111101,    //8
    0b11011101,    //9
    0b00000000     //void
};

void Init();
void TM_Send(char data);
void NumToArr( int n, char* arr);
void TM_ShowNum( int n){   // 0<= n <= 999999
    char arr[6];
    NumToArr(n , arr);
    
    STB = 0;
    TM_Send(0b00000011);    //6X8
    STB = 1;
    __delay_us(9);
    STB = 0;
    TM_Send(0b01000000);    //Address auto increment
    STB = 1;
    __delay_us(9);
    STB = 0;
    TM_Send(0b11000000);    //Address SET
    
    TM_Send(NUM[arr[5]]);      //Charactor Data 1
    for(char i=0; i<9; i++){
        TM_Send(0);                //Dummy
    }
    TM_Send(NUM[arr[4]]);      //Charactor Data 10
    TM_Send(0);                //Dummy
    STB = 1;
    __delay_us(9);
    STB = 0;
    TM_Send(0x8f);                 //Duty 14 /16(max)
    DIO = 0;
    STB = 1;
}

void TM_Init(){
    STB = 1;
    CLK = 1;
    
    STB = 0;
    TM_Send(0x02);                 //6x8
    STB = 1;
    __delay_us(6);
    STB = 0;
    TM_Send(0x40);                 //Address auto increment
    STB = 1;
    __delay_us(6);
    TM_ShowNum(0);
    __delay_us(6);
    STB = 0;
    TM_Send(0x8f);                 //Duty 14 /16(max)
    STB = 1;
}

void main(void) {
    Init();
    TM_Init();
    OSCCON = 0b01110000;
    WPUA = 0xFF;
    OPTION_REGbits.nWPUEN = 0;
    int count = 0;
    int up_flug = 0;
    int down_flug = 0;
    
    while(1){
        TM_ShowNum(count);
        //count ++;
        __delay_ms(10);  // 50
        if (count_up == 0){
            if(up_flug == 0){
                count ++ ;
                up_flug = 1;
            }
        }
        else{
            if(up_flug == 1){
                 up_flug = 0;
            }
        }
        
        if (count_down == 0){
             if(down_flug == 0 & count > 0 ){
                  count -- ;
                  down_flug = 1;
            }
        }
        else{
            if(down_flug == 1){
                 down_flug = 0;
            }
        }
        if (reset == 0){
            count = 0;
        }
    }
}

void Init(){
    ANSELA = 0;
    TRISA = 0b00111000;
    
    
}
void TM_Send(char data){
    for(char i=0; i<8; i++){
        CLK = 0;
        DIO = (data) & 0x1;
        data = data>>1;
        __delay_us(1);
        CLK = 1;
    }
    __delay_us(1);
    DIO = 0;
}
void NumToArr(int n, char* arr){
    for(char i=0; i<6; i++){             //sprit n into arr
        arr[5-i] = n % 10;
        n /= 10;
    }
    for(char i=0; i<5; i++){           //delete initial zero
        if(arr[i] == 0 )
            arr[i]  = 10;              //void
        else break;
    }
}

計算は6桁まで  (本当は32767まで)できるのですが、2桁しか表示されません。

そのうち上限を99にするようコードを書き換えるでしょう。  

4番ピンは期待通りカウントダウンしてくれました。 





 


2023年5月31日水曜日

7セグLEDのエンコーダー?デコーダー?

 数字の信号を7セグのLEDに表示させる記事は山のようにありますが、7セグLEDから数字を取り出すのは見かけません。

 職場で使う責任者呼び出しベル(ファミレスのあれです)を活用してるのですが、最近どこのラインから、どのような用件で呼ばれたのかも記録に残すようにとの指示がありました。面倒なことは機械にやらせようと思い、作ってみました。

使ってるのはこれです。呼び出しに特化していて無駄な機能やI/Oが一切ありません。その分安いです。

やることは、このス◯ジオの左側の2桁の7セグ表示信号から数字を取り出してARUDUINO UNOでシリアル変換してパソコン上のエクセルかメモ帳に保存する。ということです。



 ス◯ジオのカバーを外すとLEDの乗った基盤が出てきます。PICマイコンからの信号でトランジスタをオンオフしています。14個のうち左の6個が6桁のドライブ、右の8個が7がセグ+DPのドライブ、信号はトランジスタのベースの足部分に細い線をはんだ付けして取り出します。7セグのA〜Gのうち、CとDの信号はなくても数字に変換できるので今回ははんだ付けしません。


 上の写真ですが、その信号をトランジスタのゲートで受けてAruduino unoのデジタルi/oピンに入力します。

uno のコードです。

 int a_7seg = 3; // 7セグ表示ledのA
int b_7seg = 4; // 7セグ表示ledのB
int e_7seg = 5; // 7セグ表示ledのE
int f_7seg = 6; // 7セグ表示ledのF
int g_7seg = 7; // 7セグ表示ledのG
int tenline_7seg = 10; // 7セグ表示ledの2桁
int dp_7seg = 11; // 7セグ表示ledのDP
int oneline_7seg = 12; // 7セグ表示ledの1桁
int led1  = 13;
int number  = 0;
int number_ten = 0;

void setup(){
    pinMode( a_7seg, INPUT_PULLUP );
    pinMode( b_7seg, INPUT_PULLUP );
    pinMode( e_7seg, INPUT_PULLUP );
    pinMode( f_7seg, INPUT_PULLUP );
    pinMode( g_7seg, INPUT_PULLUP );
    pinMode( tenline_7seg, INPUT_PULLUP );
    pinMode( dp_7seg, INPUT_PULLUP );
    pinMode( oneline_7seg, INPUT_PULLUP );
    
    Serial.begin( 9600 );
    pinMode( 13, OUTPUT );
}

void loop(){
    //  tenline_7segがLOWならば0.3ms後に2桁めの数字として取り込む
  if(digitalRead(tenline_7seg) == LOW ){
      delayMicroseconds(50);
      if (digitalRead(dp_7seg) == HIGH){
        if(digitalRead(e_7seg) == LOW){
          // 8,6,2,0
          if(digitalRead(f_7seg) == LOW){
            // 8,6,0
            if(digitalRead(b_7seg) == LOW){
              // 8,0
              if(digitalRead(g_7seg) == LOW){
                number_ten = 8;
              }else{
                number_ten = 0;
              }
            }else{
              number_ten = 6;
            }
          }else{
            number_ten = 2;
          }
        }else{
          // 9,7,5,4,3,1
          if(digitalRead(f_7seg) == LOW){
            // 9,5,4
            if(digitalRead(a_7seg) == LOW){
              // 9,5
              if(digitalRead(b_7seg) == LOW){
                number_ten = 9;
              }else{
                number_ten = 5;
              }
            }else{
              number_ten = 4;
            }
          }else{
            // 7,3,1
            if(digitalRead(a_7seg) == LOW){
              // 7,3
              if(digitalRead(g_7seg) == LOW){
                number_ten = 3;
              }else{
                number_ten = 7;
              }
            }else{
              if(digitalRead(b_7seg) == LOW){
                number_ten = 1;
              }else{
                number_ten = 0;
              }
            }
          }
        }
      }

 //  1000msec後の7セグの状態を1桁目の数字として取り込む
    
      delayMicroseconds(1000);
      if (digitalRead(dp_7seg) == HIGH){
        if(digitalRead(e_7seg) == LOW){
          // 8,6,2,0
          if(digitalRead(f_7seg) == LOW){
            // 8,6,0
            if(digitalRead(b_7seg) == LOW){
              // 8,0
              if(digitalRead(g_7seg) == LOW){
                number = 8;
              }else{
                number = 0;
              }
            }else{
              number = 6;
            }
          }else{
            number = 2;
          }
        }else{
          // 9,7,5,4,3,1
          if(digitalRead(f_7seg) == LOW){
            // 9,5,4
            if(digitalRead(a_7seg) == LOW){
              // 9,5
              if(digitalRead(b_7seg) == LOW){
                number = 9;
              }else{
                number = 5;
              }
            }else{
              number = 4;
            }
          }else{
            // 7,3,1
            if(digitalRead(a_7seg) == LOW){
              // 7,3
              if(digitalRead(g_7seg) == LOW){
                number = 3;
              }else{
                number = 7;
              }
            }else{
              if(digitalRead(b_7seg) == LOW){
                number = 1;
              }
            }
          }
        }
      }
    
  }
    //  numberが0でなければシリアルポートへ出力 
    if(number != 0 ){
      Serial.println( "" );
      //Serial.print( 1 );
      Serial.print( number_ten );
      Serial.print( number );
      Serial.print( "  call  " );
       
      digitalWrite(led1,HIGH);
      delay(32000 );
      digitalWrite(led1,LOW);
      number_ten = 0;
      number = 0;
    }

}

 

 unoから teratermでパソコンに取り込みます。

受信した時刻を表示させるため、”call”と受信したら、時刻を送り返すマクロをループさせて、エコーをオンにして、あたかも 時刻も受信したように見せています。


 マクロのコードです。

 

 do
    wait 'call'
    gettime time_now_str "%m/%d %H:%M"
    sendln  time_now_str

    ;doへ戻る
loop

めちゃ簡単です。



 


 

2023年1月15日日曜日

フォルダ内にある同一の画像ファイルを整理する

長年、いろんなデジタルカメラで写真を撮って、それをHDDに溜め込んでると、いつの間にか同じ画像が複数の名前で保存されてる、ってこと無いですか?

 10枚20枚くらいなら、目視で、これとこれダブってるわ、と削除できるのですが、数千枚が一つのディレクトリに溜め込んでると人間ではどうしようもありません。

今回、PYTHONを使って、HDDに溜め込んでた、JPGファイルで同じ画像を1つだけ残してあとは削除するコードを作成して、ファイルを整理してみました。

同一かどうかを判定する方法にはいくつかあるのですが、今回は、pyexiv2というモジュールを使って撮影日時が同一かどうかを調べて判定します。

 

以下の流れとなります

1 PYTHONCODEと同じディレクトリに対象の画像ファイルを置く
2  jpgファイルがあればJPGに変換する
3  JPGファイルの更新日付を読み込む
4 フォルダ内の1枚めのファイルと同じ更新日付のJPGファイルを探しファイル名をリストにする
5 リスト内に1枚残し、あとは削除用リストにコピーする

6 すべてのファイルに対して同じ操作を行う

7 削除用リストで重複が無いよう、SET型に変更する

8 削除用リスト内のファイル名のファイルを削除する

 

 

 # -*- coding: utf-8 -*-
"""
Spyder Editor

1 PYTHONCODEと同じディレクトリに対象の画像ファイルを置く
2  jpgファイルをJPGに変換する
3  jpgファイルの更新日付を読み込む
4 フォルダ内のファイルと同じ更新日付のjpgファイルを探す
5 同じならば、そのファイルを削除する
6 すべてのファイルに対して同じ操作を行う

"""
import pyexiv2
import glob
import os

"""
jpgファイルをJPGに変換する
"""
all_list_before = glob.glob('*.jpg') #
for list_before in all_list_before :
    print(list_before)
    basename_without_ext = os.path.splitext(os.path.basename(list_before))[0]
    print(basename_without_ext)
    os.rename(basename_without_ext+".jpg",basename_without_ext+".JPG")
    print(basename_without_ext+".JPG")


def date_time(jpg_file): # jpgファイルの更新日付を読み込む
    pyexiv2.set_log_level(4)
    img = pyexiv2.Image(jpg_file)
    
    metadata = img.read_exif()
    #print(metadata)
    if metadata == {} :
        time_jpg = None
    else :
        time_jpg = metadata['Exif.Photo.DateTimeOriginal']

    #print("DateTimeOriginal:", time_jpg)
    img.close()
    return time_jpg



start_list = glob.glob('*.JPG') # フォルダ内のjpgファイル名を読み込む
all_delete_list =[]
for jpg_file in start_list:
    delete_list =[]
    time_a = date_time(jpg_file)
    #print("tume_a = ",jpg_file,time_a)
    if time_a == None :
        continue
    for serch_file in start_list :
        #print("serch_file = ",serch_file)
        time_b = date_time(serch_file)
        #print("time_b  = ",serch_file,time_b)
        if time_b == None :
            continue
        if time_a == time_b :
            delete_list.append(serch_file)
    print(delete_list)
    if len(delete_list) > 1 :
        for i in range(len(delete_list)-1) :
            print(delete_list[i+1])
            all_delete_list.append(delete_list[i+1])
print(all_delete_list)
all_delete_list = list(set(all_delete_list)) # 2枚以上あれば1枚にする
print(all_delete_list)
for delete_file in all_delete_list :
    os.remove(delete_file)

完璧ではありません。

メタデータがもともと削除されてるとそのまま残ります。

またもともと壊れてるJPGファイルは手で取り除く必要があります。

でも手作業よりははるかに助かりました。

 

 


2022年7月22日金曜日

中華製の扇風機の修理

 職場の人から、1年前に買った扇風機が動かないから見てほしいとのことで、軽い気持ちで引き受けた。一見、東芝製にみえました。


 

 開けてびっくり、フィルムコンデンサが爆発している。 

ここは軽い気持ちで交換してはいけないところなので、「ホームセンターに持っていってクレーム言った方がいいよ」といったのですが、「新しいの買ったし、レシートが分からないから捨てる」とのこと。

持ち主へ返却の必要がなくなったのでじっくり爆発の原因を調べてみました。

このコンデンサはトランスの代わりにAC100vからACの低圧に下げているものです。その中の金属化樹脂(METARIZED POLIMER) が溶けて噴出して、固まってます。その時オープンになったので何事もなかったのですが、火災につながった可能性もあります。

 
基板全体の写真です。


             マイコンまでの回路図です

VDDはマイコンの1ピンに、VSSは20ピンに入ってます。マイコンはSONIXのSN8F5702という汎用のマイコンで、データシートでは5.5~1.7Vくらいだったと思います。そのためツェナーダイオードは5Vくらいでクランプしてるはずです。試しに単三電池3本をツェナーダイオードの両端につないで動作させたところ数時間何事もなかったように、扇風機は正常に動いてくれました。待機時で8mA,動作時で12mAくらいです。

上の回路図の部品は1.5μ以外すべて異常ありませんでした。基板も熱が加わったようにも見えません。

1か所気になるのが、 破損した1.5μのコンデンサに直列に抵抗が入っていないことです。これならばコンセントを差し込むタイミングが悪ければ、このコンデンサに大電流が流れるので早期に劣化するのかもしれないということです。設計する人はそんなことくらいわかってるはずですし、他のユーザーからもクレームが来ると思います。

  それ以外、私の技量ではどうしても原因がわかりません。どなたか心当たりありませんか?

 




2022年7月3日日曜日

TWELITE DIPで透過モードを使ってみる



 TWELITE DIP REDを透過モードにして使ってみたので、手順を覚書にします。

  パソコンとの通信はいつものやつ、秋月電子の AE-FT231Xを使用します。

 

① TWELITE DIPと秋月電子のAE-FT231Xを接続する。

  透過モードだけどほとんど送るだけの方を送信側とほとんど受けるだけを受信側としてます。 

  送信側

送信時のLEDをつけるのと、ハードウェア制御に似たことをするため、TWELITEのデジタルI/Oの⑤とFT231XのCTSを繋ぎます。

  受信側

LEDは受信側につけるのと、受信のみなのでハードウェア制御はつないでません。


②ファームウェアの書き換え

       1. TWELITE STAGE SDK をダウンロードし、展開する

           https://mono-wireless.com/jp/products/stage/index.html

  2. TWELITEプログラマーをダウンロードし、展開する

       https://mono-wireless.com/jp/products/TWE-APPS/LiteProg/index.html

  3.  TWELITE DIPをプログラムモードで立ち上げる

  

   sw1とsw2を同時にオンにする。次にsw1をオフにし、次にsw2をオフにする

  これでプログラムモードになります。

  4. TWELITEプログラマーを立ち上げる。

     詳しくはTWELITEプログラマーの説明のページで。

  5. 下のbinファイルをドロップします。

    場所は、MWSTAGE\MWSDK\Wks_TweApps\App_Uart\App_Uart\build\App_Uart_RED_L1305_V1-4-6.bin です。

   間違ったファイルをドロップしたら、違いますよと教えてくれました。
 






2022年6月9日木曜日

pythonでラインの稼働状況を棒グラフに表示

 目的は、ちまたでよく見る、工場の自動ラインの稼働状況をモニタで表示することです。当然お金をかければなんぼでもあります。

今のところこんな風に表示されます。

 

 

 

やりたいことです。

① 1時間当たりの出来高を棒グラフで表示します。

② とりあえず48時間前まで表示して1時間ごとに更新します。

③ 棒の下には、月/日/時を表示します。

④真ん中の青い線は、出来高の目標線で、プログラムの立ち上げ時に入力します。

 

次に構成です。

① 自動ラインの最終工程から、完品が1台出るたびに2秒間の信号を出力します。

  三菱シーケンサーから、24vで取り出してます。

② 24vをフォトカプラで受けてaruduino unoで取り込みます。写真ではフォトカプラが6個並んでますが、今回は1個しか使ってません。



aruduino のプログラムです。


// data logger
//degital2~degital7 の6本のi/oを0.5秒毎にpythonへ送信する
//

int signal_1 = 2; // degital入力2 を signal_1に取り込む
int signal_2 = 3; // 
int signal_3 = 4; // 
int signal_4 = 5; // 
int signal_5 = 6; // 
int signal_6 = 7; // 



int led1  = 13;

int keep_sig1_on = 0;  // signal_1がonの状態を読み、ダブルカウント防止
int keep_sig2_on = 0;  //
int keep_sig3_on = 0;  //
int keep_sig4_on = 0;  //
int keep_sig5_on = 0;  //
int keep_sig6_on = 0;  //



void setup(){
    pinMode( signal_1, INPUT_PULLUP );
    pinMode( signal_2, INPUT_PULLUP );
    pinMode( signal_3, INPUT_PULLUP );
    pinMode( signal_4, INPUT_PULLUP );
    pinMode( signal_5, INPUT_PULLUP );
    pinMode( signal_6, INPUT_PULLUP );
    pinMode( 13, OUTPUT );
        
    Serial.begin( 9600 );

}

void loop(){
  //  signal_1がlowになったら keep_sig1_onを 1にする
    if(keep_sig1_on == 0){                    
      if(digitalRead(signal_1) == LOW ){
        keep_sig1_on = 1;
      }
    }
    else{
       if(digitalRead(signal_1) == HIGH ){  
         keep_sig1_on = 0;
        }
    }
  //  signal_2がlowになったら keep_sig2_onを 1にする
    if(keep_sig2_on == 0){                    
      if(digitalRead(signal_2) == LOW ){
        keep_sig2_on = 1;
      }
    }
    else{
       if(digitalRead(signal_2) == HIGH ){  
         keep_sig2_on = 0;
        }
    }
  //  signal_3がlowになったら keep_sig3_onを 1にする
    if(keep_sig3_on == 0){                    
      if(digitalRead(signal_3) == LOW ){
        keep_sig3_on = 1;
      }
    }
    else{
       if(digitalRead(signal_3) == HIGH ){  
         keep_sig3_on = 0;
        }
    }
  //  signal_4がlowになったら keep_sig4_onを 1にする
    if(keep_sig4_on == 0){                    
      if(digitalRead(signal_4) == LOW ){
        keep_sig4_on = 1;
      }
    }
    else{
       if(digitalRead(signal_4) == HIGH ){  
         keep_sig4_on = 0;
        }
    }
  //  signal_5がlowになったら keep_sig5_onを 1にする
    if(keep_sig5_on == 0){                    
      if(digitalRead(signal_5) == LOW ){
        keep_sig5_on = 1;
      }
    }
    else{
       if(digitalRead(signal_5) == HIGH ){  
         keep_sig5_on = 0;
        }
    }
  //  signal_6がlowになったら keep_sig6_onを 1にする
    if(keep_sig6_on == 0){                    
      if(digitalRead(signal_6) == LOW ){
        keep_sig6_on = 1;
      }
    }
    else{
       if(digitalRead(signal_6) == HIGH ){  
         keep_sig6_on = 0;
        }
    }
    

    
//  シリアルポートへ出力 bou_ok_count,ng_count,nc_count
    Serial.flush();
    
    Serial.print( keep_sig1_on );
    Serial.print( keep_sig2_on );
    Serial.print( keep_sig3_on );
    Serial.print( keep_sig4_on );
    Serial.print( keep_sig5_on );
    Serial.println( keep_sig6_on );
    

    

// 各トリガーが掛かれば、LED1を点燈する

    if(digitalRead(signal_1) == LOW){
      digitalWrite(led1,HIGH);
    }
    else if( digitalRead(signal_2) == LOW){
      digitalWrite(led1,HIGH);
    }
    else if( digitalRead(signal_3) == LOW ){
      digitalWrite(led1,HIGH);  
    }  
    else if ( digitalRead(signal_4) == LOW ){
      digitalWrite(led1,HIGH);
    }
    else if ( digitalRead(signal_5) == LOW){
      digitalWrite(led1,HIGH);
    }
    else{
      digitalWrite(led1,LOW);
    }
    
    delay(500 );
}

 

aruduinoは、ただ0.5秒ごとに 000000 をusbでpythonに送ってて、完品の信号が入力されたら 000001 を送信して、2秒たったらまた 000000 に戻すだけの仕事です。

③ LINUXMINT32BIT版に入ってるpython3 で、aruduinoからの信号を受け取り、1時間ごとに集計してリストに加え、tkinterで棒グラフ表示します。

 

# -*- coding: utf-8 -*-
"""
Created on Fri Apr 22 21:52:16 2022
7.1 for windows
@author: osada
"""

import tkinter as tk
import serial as sl
import time
import re
import datetime as dt

signal_1 = 0
signal_2 = 0
signal_3 = 0
signal_4 = 0
signal_5 = 0
signal_6 = 0

old_signal_1 = 1
old_signal_2 = 1
old_signal_3 = 1
old_signal_4 = 1
old_signal_5 = 1
old_signal_6 = 1

deki__count1 = 0
deki__count2 = 0
deki__count3 = 0
deki__count4 = 0
deki__count5 = 0
deki__count6 = 0
ave_774 = int(input("774Lの時間当たりの平均数を入力してください"))
data = []
#data= [10,15,20,15,10,14,12,10,5,7,15,16,9,14,12,11]
data_hour = []
#data_hour = ["04/27 21","04/27 20","04/27 19","04/27 18","04/27 17","04/27 16","04/27 15","04/27 14","04/27 13","04/27 12","04/27 11","04/27 10","04/27 09","04/27 08","04/27 07","04/27 06"]
now_time_hour = dt . datetime . now ( ).strftime("%m/%d %H")
data_hour.insert(0,now_time_hour) # data_hourリストの末尾に now_time_hour を追加する
triger_flug = 0
old_line = None
clear_count_triger = None
#ウインドを作成
root=tk.Tk()
root.geometry('1000x700')
root.title('canvasの使い方')

finFlag = True
serial = None

serial = sl.Serial('/dev/ttyACM0', 9600, timeout=0)
#serial = sl.Serial('COM5', 9600, timeout=0)  #

while( serial.is_open is False):
    time.sleep(100)

#図形を壁画するキャンバスをウインド上に作成
canvas = tk.Canvas(root, width = 1000, height = 650)
canvas.grid(column=1, row=3, columnspan=1,rowspan=8)
now_time_hour = dt . datetime . now ( ).strftime("%m/%d %H")
#stop_time=3600 #目標終了時間

#無限ループ
while True:
    #  シリアルポートの操作
    line = None
    if ( serial.is_open):
        line = serial.readline()
        new_line = line.strip().decode('utf-8')
        #print(new_line)
        
    if len(new_line) == 13 and old_line != new_line :
        
        now_time = dt . datetime . now ( ).strftime("%Y/%m/%d %H:%M:%S")
        #print(now_time)

        print("new_line ",new_line)
        #print("old_line ",old_line)
        signal_1 = re.search(r'A(.+)B',new_line).group(1)
        #print("signal_1 ",signal_1)
        signal_2 = re.search(r'B(.+)C',new_line).group(1)
        #print("signal_2 ",signal_2)
        signal_3 = re.search(r'C(.+)D',new_line).group(1)
        #print("signal_3 ",signal_3)
        signal_4 = re.search(r'D(.+)E',new_line).group(1)
        #print("signal_4 ",signal_4)
        signal_5 = re.search(r'E(.+)F',new_line).group(1)
        #print("signal_5 ",signal_5)
        signal_6 = re.search(r'F(.+)G',new_line).group(1)
        #print("signal_6 ",signal_6)
        print(now_time,"      ",signal_1,signal_2,signal_3,signal_4,signal_5,signal_6)
        old_line = new_line
        
    if signal_1 != old_signal_1 : # シグナル1に変化があった
        if signal_1 == "1" :
            deki__count1 = deki__count1 + 1 # 出来高カウントに1をプラス
            print("deki__count1  ",deki__count1)
        old_signal_1 = signal_1
        print(clear_count_triger,data)
    
    clear_count_triger = dt . datetime . now ( ).strftime("%M%S") #"%M%S" 毎時0分0秒がトリガー
    #print(clear_count_triger,data) # 0000
        
    if clear_count_triger == "0000" and triger_flug == 0 :
        now_time_hour = dt . datetime . now ( ).strftime("%m/%d %H")
        data.insert(0,deki__count1) # dataリストの末尾に deki__count1 を追加する
        data_hour.insert(0,now_time_hour) # data_hourリストの末尾に now_time_hour を追加する
        
        if len(data) > 48 : # dataリストが48要素を超えたら先頭を削除する
            data.pop()
        if len(data_hour) > 49 : # dataリストが48要素を超えたら先頭を削除する
            data_hour.pop()
        deki__count1 = 0 # deki__count1をクリアー
        triger_flug = 1
        now_time = dt . datetime . now ( ).strftime("%Y/%m/%d %H:%M:%S")
        print(now_time,data)        
        print(now_time,data_hour)
    if clear_count_triger != "0000" and triger_flug == 1:
        triger_flug = 0    
   
    canvas.delete("rect") # タグ"rect"を消去する
    canvas.create_text(500, 40, text= "774Lの過去48hの時間当たり生産数  " ,tag="rect",fill="black",font=('Helvetica 40 bold'))
    canvas.create_line(960-20*len(data), 600-(ave_774)*20, 1000 ,600-(ave_774)*20,tag="rect",fill="Blue",width = 2)
    for i in range(len(data)):
        canvas.create_rectangle(980-20*i,600-20*data[i],995-20*i,600,tag="rect",fill = 'green')
        canvas.create_text(988-20*i, 590-20*data[i], text= str(data[i]),tag="rect",fill="black",font=('Helvetica 8 bold'))
        canvas.create_text(988-20*i, 630, text= str(data_hour[i+1]),tag="rect",fill="black",font=('Helvetica 8 bold'), angle=90)
    root.update()
    

root.mainloop()

6ライン分の稼働状況を1台で管理できるよう6桁の文字列を送れるようにしてあります。

でもこれ以上拡張することはなさそうです。