工場改善 IoT Arduino開発

現場の悩みに解決手段を

【アソビ】フォトリレー使って24v制御してみた

社内でIoT事情としてはセキュリティガーによってなかなか幅が絞られてます。
とりあえずWiFiはグレー、Bluetoothはマウスとかにも入ってるしOK?
Raspberry PiはSD入れる口やLANポートあるからなんか...
マイコンはマウスにも入ってるし、一緒だよね!うん。

そんなわけでちょっと進まないので番外編として親戚のおじさんと
ドローン✕エアガン✕マイコンをやってみました。

事の始まり
親戚のおじさんがJDIのドローン持ってて、実家の敷地で飛ばしてたのは知ってたのです...
久々に連絡来たのですが、
「メカリレーモジュール(13g)をもっと軽くする方法ある?」

どういう事??

聞いてみるとドローンに合わせて作られた自作エアガン?を搭載しているではないですか(笑)
この前布教したarduino使って圧縮空気をエアバルブで制御してるし!
ライトを光らせると射出!

うーんガチやんけ!

f:id:kesokonpatata:20211208202725j:plain
f:id:kesokonpatata:20211208202727j:plain
f:id:kesokonpatata:20211208202723j:plain
f:id:kesokonpatata:20211208202832j:plain

要するともっとカッコ良くしたいから、軽く小さくしたいとのこと。

小さいは正義!
軽いは最高!


要件整理
H/W
①メカリレーを軽く、フォトリレーに変更。
マイコンarduino NANO→Seeeduino XINOに変更。
③バッテリーは7.2v一つで5v降圧と24v昇圧の小さいモジュールにする。
④基板に実装するのはマイコン、変圧モジュール、フォトリレー、ボタン。
⑤バッテリー、エアバルブ、光センサーモジュールは基板の端子と電線で繋ぐ。

S/W
マイコンの制御はドローンのライトを光らせた時、光センサーモジュール使って信号を受けたら、フォトリレーを1回だけ10秒間ONにして、エアバルブのソレノイドを解放。
マイコンに安全装置としてボタンを接続し、そのボタンを押すと光センサーによる④の制御がされるようにする。

そこでモジュール買ってフォトリレーの回路でわからないところは会社の回路設計してる友人に聞いたりと、なんやかんやで基板作って総重量13gに収まった!あと動いた!

まずはブロック図書いて
f:id:kesokonpatata:20211209053800j:plain

部品選定とフォトリレー周りの回路図は知り合いの回路設計とかやってた人に教えてもらって
f:id:kesokonpatata:20211209053758j:plain

部品リスト作って
f:id:kesokonpatata:20211209053751j:plain

配線図作りたいんですが、まだスキルが無くてとりあえずExcelで引いてみるという愚策でやってみる。
f:id:kesokonpatata:20211209053734j:plain

Amazonから届いた部品を並べて
f:id:kesokonpatata:20211209053737j:plain

重量測ったらいけそうですね!
f:id:kesokonpatata:20211209053809j:plain

Seeeduino XINOに光センサ反応したらエアバルブを制御するフォトリレーへの信号ONにするプログラム書いてみる。
f:id:kesokonpatata:20211209053744j:plain

はんだ付けして
f:id:kesokonpatata:20211209053753j:plain

直流安定化電源ないので、電池を使って7.2vくらいの電圧作って出力電圧の調整してみる。
フォトリレー:24v
マイコン :5v
光センサ反応して電圧出てるかチェックしたら動いたー
やったぜ!
f:id:kesokonpatata:20211209053755j:plain

はんだ付け面はセリアの100均でレジンとブラックライトを買って固めようとしたけど、あまり固まらなかった...
f:id:kesokonpatata:20211209053746j:plain

結局翌日の朝からお日様に当てて固めました。
f:id:kesokonpatata:20211209053739j:plain

あとはおっちゃんに任せた!
f:id:kesokonpatata:20211209053741j:plain

マビックエアー2に乗せるところの設計からは任せた!
f:id:kesokonpatata:20211209053654j:plain

おぉ〜飛んだ〜
f:id:kesokonpatata:20211209053748j:plain

風船狙って割れました‼︎
うおー
BB弾の威力は法律に引っかからないのは確認済み。
f:id:kesokonpatata:20211209053805j:plain


楽しかったなぁ〜

24vの電圧のスイッチがマイコンでできるようになったので
シーケンサとかで制御してた機器とかにもつかえるかもしれませんね。
どう使うかはもうすこしかんがえてみます。

おわり

M5StakCでBluetoothストップウォッチ作ってみた2

前回の記事から500件のメモリ機能とまとめてキーボード入力追加してみました。
現場に持っていくことを想像したらPC持っていくのが手間だと思ったからです。
だんだん凝り始めて高性能なストップウォッチになってきました。


3000円で結構な時間遊べてコスパいいっす。

趣味なら時間もお金も、労力もある限り費やして探求できるし、おもしろいですね。

そもそも趣味と仕事って金になるかならないかという指標があると思いますが、色々考えるそうと言えるものもあれば、そうでも無いような事があるし、境目って曖昧だと私は思うのですよ。

あとワーク・ライフ・バランスとか聞いたことありますが、ワークもライフだろって突っ込みたいのです。
なんでも分けたがる人には理解されないですよね。なんかブラック企業の経営者っぽいし。


この記事を立てたのも趣味です。でも仕事にも生かして、誰かが喜んでくれたら満足なのです。
そして結果的にお金になったらさらに幸せです。




さて本質の追求はここまでにして本題です。

機能は以下としました。
Aボタン:スタート(+Bluetoothキーボード入力)、ストップ
Bボタン:ストップ(2度押しで保存したメモリをすべてBluetoothキーボードで入力)
Cボタン:リセット(メモリのカウントもリセット)

f:id:kesokonpatata:20210703002416j:plain

【プログラム】

/*
  キーボードライブラリをインストール
  https://github.com/asterics/esp32_mouse_keyboard
  ・最大約49日まで
  ・件数は500まで
*/

#include <BleKeyboard.h> //Bluetooth HIDプロファイルの設定ライブラリ、キーボード送信用 
BleKeyboard bleKeyboard("M5StickC StopWatch 001"); //Bluetoothペアリング時の表示名

#include <M5StickC.h>

uint32_t tm;
uint32_t startTime;
uint32_t LastTime;

bool stopF = false;
uint32_t stopTime;
uint32_t stopTimeSum;

uint16_t count;
uint16_t datasize = 500;
uint32_t lapArray[500];
uint32_t timeArray[500];

uint16_t BtnF;
uint32_t i;
bool flag = true;

void m5lcd() {
  M5.Lcd.fillScreen(BLACK);  //背景色
  M5.Lcd.setTextColor(WHITE); //テキストカラー
  M5.Lcd.setTextSize(1);     //文字サイズ

  M5.Lcd.setCursor(5, 0); M5.Lcd.println("StopWatch:001");
  M5.Lcd.setCursor(95, 0);

  if (BtnF == 1) {
    if(flag == true){
      M5.Lcd.setTextColor(GREEN);
    }else{
      M5.Lcd.setTextColor(WHITE);
    }
    flag = !flag;
    M5.Lcd.print("Start");
    M5.Lcd.setTextColor(GREEN);
  } else if (BtnF == 2) {
    M5.Lcd.print("Stop");
    M5.Lcd.setTextColor(RED);
  } else if (BtnF == 3) {
    M5.Lcd.print("Reset");
    M5.Lcd.setTextColor(YELLOW);
    M5.Lcd.setCursor(0, 10);
    M5.Lcd.printf("Battery\n");
    M5.Lcd.printf("  Warn :%6d\n"  , M5.Axp.GetWarningLevel());  // バッテリー残量警告 0:残あり, 1:残なし
    M5.Lcd.printf("  Temp :%6.1f\n", M5.Axp.GetTempInAXP192());  // AXP192の内部温度
    M5.Lcd.printf("  V(V) :%6.3f\n", M5.Axp.GetBatVoltage());    // バッテリー電圧(3.0V-4.2V程度)
    M5.Lcd.printf("  I(mA):%6.1f\n", M5.Axp.GetBatCurrent());    // バッテリー電流(プラスが充電、マイナスが放電)
    M5.Lcd.printf("VBus(USB)\n");
    M5.Lcd.printf("  V(V) :%6.3f\n", M5.Axp.GetVBusVoltage());   // USB電源からの電圧
    M5.Lcd.printf("  I(mA):%6.1f\n", M5.Axp.GetVBusCurrent());   // USB電源からの電流
  } else if (BtnF == 4) {
    M5.Lcd.setTextColor(RED);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(5, 10); M5.Lcd.print("Start writing data");
  } else if (BtnF == 5) {
    M5.Lcd.setTextColor(BLUE);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(5, 10); M5.Lcd.print("Data writing");
    M5.Lcd.setCursor(5, 25); M5.Lcd.print("completed");
  } else if (BtnF == 6) {
    M5.Lcd.setTextColor(BLUE);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(5, 10); M5.Lcd.print("Count limit");
    M5.Lcd.setCursor(5, 25); M5.Lcd.print("Please reset");
  } else {
    //起動時
    M5.Lcd.setTextColor(YELLOW);
  }

  if (BtnF < 4) {
    if (count > 0) {
      M5.Lcd.setTextSize(3);
      M5.Lcd.setCursor(5, 15); M5.Lcd.printf("Cnt:%d",count);
      M5.Lcd.setTextColor(WHITE);
      M5.Lcd.setTextSize(2);
      M5.Lcd.setCursor(5, 42); M5.Lcd.printf("Lap :%.2f", (float)lapArray[count] / 1000.0);
      M5.Lcd.setCursor(5, 62); M5.Lcd.printf("Time:%.2f", (float)timeArray[count] / 1000.0);
    }
  }
}
void setup() {
  //  Serial.begin(115200);  //通信速度bps
  delay(30);
  bleKeyboard.begin();   //BLE接続開始

  M5.begin();

  M5.Axp.ScreenBreath(10); //バックライトの輝度(デフォルト12)
  setCpuFrequencyMhz(80); //CPU周波数を下げる(デフォルト240)
  M5.Lcd.setRotation(1);
  m5lcd();

  //  Serial.println("M5Stack StopWatch");
}


void loop() {
  M5.update();
  int axpButton = M5.Axp.GetBtnPress();

  //Aボタンでタイマー開始、ラップタイム記録
  if (M5.BtnA.wasPressed()) {
    if (startTime == 0) {
      startTime = millis();
      LastTime = startTime;

      //      Serial.println("RAPCOUNT,RAP,TIME");

      BtnF = 1;
      m5lcd();

      //Bluetooth HIDプロファイルで送信
      bleKeyboard.print("RAPCOUNT"); bleKeyboard.write(KEY_TAB); //TAB(0xB3)をBluetooth送信
      bleKeyboard.print("LAP"); bleKeyboard.write(KEY_TAB);  //TAB(0xB3)をBluetooth送信
      bleKeyboard.print("TIME"); bleKeyboard.write(KEY_TAB); //TAB(0xB3)をBluetooth送信
      bleKeyboard.write(KEY_RETURN); //改行(0xB0)をBluetooth送信

      //ストップ後の再開
    } else if (stopF == true) {
      tm = millis();

      stopTime = tm - stopTime;
      stopTimeSum += stopTime;

      stopF = false;
      //Serial.println("Timer Restart");

      //LCD表示
      BtnF = 1;
      m5lcd();

      //ラップタイム
    } else {
      tm = millis();
      count += 1;

      //配列に入力
      lapArray[count] = tm - LastTime - stopTime;
      timeArray[count] = tm - startTime - stopTimeSum;

      LastTime = tm;
      stopTime = 0;

      //serial出力
      //      Serial.printf("%d,%.2f,%.2f\r\n", count, (float)lapArray[count] / 1000.0, (float)timeArray[count] / 1000.0);

      //LCD表示
      if (count <= datasize) {
        BtnF = 1;
        m5lcd();

        //Bluetooth HIDプロファイルで送信
        bleKeyboard.print(count);    bleKeyboard.write(KEY_TAB); //TAB(0xB3)キー
        bleKeyboard.print((float)lapArray[count] / 1000.0, 2); bleKeyboard.write(KEY_TAB); //TAB(0xB3)キー
        bleKeyboard.print((float)timeArray[count] / 1000.0, 2); bleKeyboard.write(KEY_TAB); //TAB(0xB3)キー
        bleKeyboard.write(KEY_RETURN); //改行(0xB0)キー

      } else {
        BtnF = 6;
        m5lcd();
      }
    }


  } else if (M5.BtnB.wasPressed()) {  //Bボタンでタイマーリセット
    count = 0;
    startTime = 0; LastTime = 0;
    stopF = false; stopTime = 0; stopTimeSum = 0;

    //    Serial.println("Timer Reset");

    BtnF = 3;
    m5lcd();


  } else if (axpButton == 2 && stopF == true) {  //Bボタン2回押しデータの一括送信
    //    Serial.println("Data write");
    BtnF = 4;
    m5lcd();

    bleKeyboard.print("RAPCOUNT"); bleKeyboard.write(KEY_TAB); //TAB(0xB3)をBluetooth送信
    bleKeyboard.print("LAP"); bleKeyboard.write(KEY_TAB);      //TAB(0xB3)をBluetooth送信
    bleKeyboard.print("TIME"); bleKeyboard.write(KEY_TAB);     //TAB(0xB3)をBluetooth送信
    bleKeyboard.write(KEY_RETURN); //改行(0xB0)をBluetooth送信


    for (int j = 1; j <= count; j++) {
      delay(350);

      bleKeyboard.print(j);    bleKeyboard.write(KEY_TAB); //TAB(0xB3)キー
      bleKeyboard.print((float)lapArray[j] / 1000.0, 2); bleKeyboard.write(KEY_TAB); //TAB(0xB3)キー
      bleKeyboard.print((float)timeArray[j] / 1000.0, 2); bleKeyboard.write(KEY_TAB); //TAB(0xB3)キー
      bleKeyboard.write(KEY_RETURN); //改行(0xB0)キー
    }
    delay(500);

    BtnF = 5;
    m5lcd();

  } else if (axpButton == 2 && stopF == false && startTime > 0) {//電源ボタンでタイマーストップ
    stopTime = millis();
    stopF = true;

    //    Serial.println("Timer Stop");

    BtnF = 2;
    m5lcd();
    
  }
  
  if (i % 50 == 0 && BtnF == 1) {
    m5lcd();
  }
  
  //PCロックが掛からないように一定時間にShiftキーを押す
  if (i % 6000 == 0) {
    //    Serial.println("Shift");
    setCpuFrequencyMhz(240);
    M5.Axp.ScreenBreath(12); //バックライトの輝度(デフォルト12)
    bleKeyboard.write(0x81); //KEY_LEFT_SHIFT
    M5.Axp.ScreenBreath(10); //バックライトの輝度(デフォルト12)
    setCpuFrequencyMhz(80);
    i = 0;
  }
  i += 1;

  delay(10);
}

【困ったこと】

2次元配列でラップタイムと累計時間入れてみたんですが、累計時間の値がおかしくてどうにもこうにもならんかったので、一次元配列でラップタイムと分けました。
配列の使い方調べてたらポインタとか出てきてややこしくて泣きそうです。
わかりそうな気になったんですが、わかりませんでした。

【次やりたいこと】

①時間と②要素作業の手書き→PCへ手打ちの作業で①時間の手作業は減らせそうですが、②要素作業の手書きはどうしたもんかです。
指定のフォーマットの紙を印刷してそれに書いた後、スキャナーで取り込んでOCRで文字認識とかしたいなぁPythonでもありそうなんですが、使えるかなぁ。
ちょっと挑戦してみます。

M5StakCでBluetoothストップウォッチ作ってみた

タイトルの通りBluetooth HID入力できるストップウォッチ作りました。

まずは作ろうと思ったきっかけです。
私が所属している部署の機能の1つに図面に対して標準時間を設定する業務がありまして、その根拠となる時間研究をしています。

紙に経過時間と要素作業名を書いてExcelに手入力して、関数で計算してect...
とにかく時間と手間がかかるわ、入力ミスの懸念もあるわと大変なんです。

そこの担当者がデシマルストップウォッチ故障して買い替えたいと1.2万のものを数個個買いたいと課長に予算の承認依頼したときに
「ストップウォッチの情報って無線で直接送れるやつとか売ってないの?」
「IoTとか言ってる時代なんだから世の中あるんじゃないの?」
と言われてたので私も調べてみたらフェリカとか使って通信しているものを見つけました。

しかし、まぁ高いし、ドライバーインストールして、かざしてCSVで保存して、コピペして、時分秒を秒に換算して...ぁあ~もう




....なんか面白くない!値段高いしあんまり変わらんやんけ。
ピッて押して、Excelのフォーマットに直接バット入ってうぇ~い!!
ってなって欲しいのですよ。


私の知る限りまだ世の中にないので作ります。
よかったらこのアイディアで商品開発したいくらいです。

時間研究とは
工場や事務所,倉庫などにおける作業を基本動作に分解して,各動作の時間を測定し,これを分析して各作業の標準時間を決定する技法。科学的管理法の始祖 F.W.テーラーが提唱したもので,現実の方法としては,ストップウォッチを用いて測定する方法,作業動作を要素に分解して記号化していくワーク・ファクター法,作業の基本動作に標準時間値テーブルを用いるMTMなどがある。作業方法の標準化,作業計画,作業量の決定などに利用される。
出典 ブリタニカ国際大百科事典 小項目事典ブリタニカ国際大百科事典 小項目事典について

【作業の流れ】

①現場で作業とストップウォッチをみながら紙に経過時間と要素作業名を書く。
②事務所に戻って時間と要素作業をExcelにて入力。
③時間を秒に換算ためにExcel関数を入力して計算。
④要素ごとの時間をExcel関数を入力して計算。
⑤項目毎に集計したり、なんやかんやして標準時間を設定。

今回やりたいことは①~④の時間の入力と計算の手数を極限まで減らしたいのです。
やりましょう。


【開発環境】
・Windows10 2H1
Arduino IDE 1.8.15
Arduino IDEに手動で入れたBluetooth HIDキーボードライブラリ
 github.com
Arduino IDEのボードマネージャーでインストール:M5Stack by M5Stack official 1.0.7

【使用機器】

・M5StickC M5StickC - スイッチサイエンス

【機能】

 Aボタン :スタート/ラップタイム/再開
 Bボタン :ストップ
 電源ボタン:リセット

f:id:kesokonpatata:20210623003857j:plain

【コード】

/*
  キーボードライブラリをインストール
  https://github.com/asterics/esp32_mouse_keyboard
  ・最大約49日まで
  ・件数は65535まで
*/
//Bluetooth HIDプロファイルの設定ライブラリ、キーボード送信用
#include <BleKeyboard.h> 
//Bluetoothペアリング時の表示名
BleKeyboard bleKeyboard("M5StickC StopWatch 002"); 

#include <M5StickC.h>

uint16_t count;
float tm;
float startTime;
float LastTime;

float Lap1;
float Lap2;
float Lap3;
float Time1;
float Time2;
float Time3;

bool stopF = false;
float stopTime;
float stopTimeSum;
uint16_t i;
uint16_t BtnF;

void m5lcd() {
  M5.Lcd.fillScreen(BLACK);  //背景色
  M5.Lcd.setTextColor(WHITE); //テキストカラー
  M5.Lcd.setTextSize(1);   //文字サイズ

  M5.Lcd.setCursor(0, 0); M5.Lcd.println("StopWatch 002");
  M5.Lcd.setCursor(90, 0);

  if (BtnF == 1) {
    M5.Lcd.print("Start");
    M5.Lcd.setTextColor(GREEN);
  } else if (BtnF == 2) {
    M5.Lcd.print("Stop");
    M5.Lcd.setTextColor(RED);
  } else if (BtnF == 3) {
    M5.Lcd.print("Reset");
    M5.Lcd.setTextColor(YELLOW);
  } else {
    M5.Lcd.setTextColor(YELLOW);
  }

  M5.Lcd.setTextSize(2);
  if (count > 0) {
    M5.Lcd.setCursor(0, 10); M5.Lcd.printf("Cnt :%d", count);
    M5.Lcd.setCursor(0, 25); M5.Lcd.printf("lap :%.2f", Lap1);
    M5.Lcd.setCursor(0, 40); M5.Lcd.printf("Time:%.2f", Time1);

    M5.Lcd.setTextSize(1);     //文字サイズ
    M5.Lcd.setTextColor(WHITE);
    if (count > 1) {
      M5.Lcd.setCursor(0, 60); M5.Lcd.printf("lap2:%.2f Time:%.2f", Lap2, Time2);
      if (count > 2) {
        M5.Lcd.setCursor(0, 70); M5.Lcd.printf("lap3:%.2f Time:%.2f",  Lap3, Time3);
      }
    }
  }
}
void setup() {
  Serial.begin(115200);  //通信速度bps
  delay(30);
  bleKeyboard.begin();   //BLE接続開始

  M5.begin();

  M5.Axp.ScreenBreath(9); //バックライトの輝度(デフォルト12)
  setCpuFrequencyMhz(160); //CPU周波数を下げる(デフォルト240)
  M5.Lcd.setRotation(1);
  
  m5lcd();

  Serial.println("M5Stack StopWatch");
}

void loop() {
  M5.update();
  int axpButton = M5.Axp.GetBtnPress();

  //Aボタンでタイマー開始、ラップタイム記録
  if (M5.BtnA.wasPressed()) {
    if (startTime == 0.0) {
      startTime = (float)millis() / 1000.0;
      LastTime = startTime;

      Serial.println("RAPCOUNT,RAP,TIME");

      BtnF = 1;
      m5lcd();

      //Bluetooth HIDプロファイルで送信
      bleKeyboard.print("RAPCOUNT"); 
      bleKeyboard.write(KEY_TAB);  //TAB(0xB3)
      bleKeyboard.print("LAP"); 
      bleKeyboard.write(KEY_TAB);  //TAB(0xB3)
      bleKeyboard.print("TIME"); 
      bleKeyboard.write(KEY_TAB);  //TAB(0xB3)
      bleKeyboard.write(KEY_RETURN); //改行(0xB0)

      //ストップ後の再開
    } else if (stopF == true) {
      tm = (float)millis() / 1000.0;

      stopTime = tm - stopTime;
      stopTimeSum += stopTime;

      stopF = false;

      //LCD表示
      BtnF = 1;
      m5lcd();

      //ラップタイム
    } else {
      tm = (float)millis() / 1000.0;
      count += 1;

      Time3 = Time2;
      Time2 = Time1;
      Time1 = tm - startTime - stopTimeSum;

      Lap3 = Lap2;
      Lap2 = Lap1;
      Lap1 = tm - LastTime - stopTime;

      LastTime = tm;
      stopTime = 0.0;

      //serial出力
      Serial.printf("%d,%.2f,%.2f\r\n", count, Lap1, Time1);

      //LCD表示
      BtnF = 1;
      m5lcd();

      //Bluetooth HIDプロファイルで送信
      bleKeyboard.print(count);
      bleKeyboard.write(KEY_TAB); //TAB(0xB3)
      bleKeyboard.print(Lap1, 2);
      bleKeyboard.write(KEY_TAB);  //TAB(0xB3)
      bleKeyboard.print(Time1, 2); 
      bleKeyboard.write(KEY_TAB);  //TAB(0xB3)
      bleKeyboard.write(KEY_RETURN); //改行(0xB0)
    }

  //Bボタンでタイマーリセット
  } else if (M5.BtnB.wasPressed()) {  
    count = 0;
    startTime = 0.0; LastTime = 0.0;
    Time1 = 0.0; Time2 = 0.0; Time3 = 0.0;
    Lap1 = 0.0; Lap2 = 0.0; Lap3 = 0.0;
    stopF = false; stopTime = 0.0; stopTimeSum = 0.0;

    Serial.println("Timer Reset");

    BtnF = 3;
    m5lcd();

  //電源ボタンでタイマーストップ
  } else if (axpButton == 2 && stopF == false && startTime > 0.0) {
    stopTime = (float)millis() / 1000.0;
    stopF = true;

    Serial.println("Timer Stop");

    BtnF = 2;
    m5lcd();
  }

  //一定時間毎にPCロックがかからないようにShiftキーを押す。
  if (i >= 24000) {
    Serial.println("Shift");
    bleKeyboard.write(0x81); //KEY_LEFT_SHIFT
    i = 0;
  }
  i += 1;

  delay(10);
}

【その他】

モバイルバッテリー繋いで使ってみたんですがすぐ切れちゃうんですが、消費電力低いせいですかね。
輝度下げたり、CPUのクロック下げたりとか知らずにやって省電力化を試みたけど気づかなかった~
逆に負荷抵抗とか入れて消費電力増やしたいんですが、なんかいい方法ないかなぁ。

写真だと見えないんですが、ボールペンと一体にしたくて、アルミでブラケットを作ってもらったんですが、フィット感抜群でした(笑)協力してもらった方には感謝です。

とりあえず課長にも担当者にも見せたら結構ウケたので業務に採用してもらえそうです。
1個あたり既製品:12,000円→今回のおもちゃ:3000円で節約できました。開発工数考慮するとあれですが、自宅で趣味としてやってたからまぁ...あれですね。

おしまい

ESP32でブレーカーの電流値を測定

電流計のIoTに挑戦です。
少しは使い方とかプログラミングとかできるようになったんで、そろそろ会社でのアウトプットとして何か作りたいです。


ESP32→WiFiルーター→インターネット→Ambientのサーバー
グラフを見るときはAmbientにアクセスして見れます。

AmbientはIoTデータの可視化サービスです。とりあえずやってみたい人にはおすすめです。面白いです。

f:id:kesokonpatata:20210520073709j:plain
f:id:kesokonpatata:20210520073716j:plain

ESP32はADCの精度が悪いってネット調べてたら色々出てきました。
直接電圧値を返してくれる関数ないかなと思って調べてたら最近のバージョンで出てきました。

どうやらanalogRead()ではなくESP32のanalogReadMilliVolts()を使うようです。。
これはGPIOを引数で渡すと電圧をmVで返してくれる関数みたいです。
あんまり情報がなかったのですが、ESP32の電圧3.3V/分解能4056をanalogRead()にかける手間減りますね。

以下のプログラムはタイマー割り込みで200μs毎に100回、10周期分で1000回のデータを取得しています。

以下の部分はご自分の情報を入れてください。
#define WIFISSID "************" // ルーターSSID
#define WIFIPASS "************" // ルーターのpassword
const uint32_t channelId = ******; // AmbientのチャネルID Ambientに登録してゲットしてください!
const char* writeKey = "***************"; // ライトキー


素人のプログラミングなので変数名とか書き方のお作法などあれですが、まぁ許してください。

プログラム

/**************************************
  電流のセンサ情報を取得してAmbientにデータを送る   
 **************************************/
#include <WiFi.h>
#include "Ambient.h"

volatile float coefficient;     //係数
const float OFFSET_CH0 = 0.00;  //CH0の補正値
const float OFFSET_CH1 = 0.00;  //CH1の補正値

#define CH0_APIN 34  //センサ0のアナログピン番号:A6:34
#define CH1_APIN 35  //センサ1のアナログピン番号:A7:35
#define CH2_APIN 32  //アナログピン番号中間電位 :A4:32

volatile uint16_t value_CN2;
volatile uint32_t SquareAdd_CN0;
volatile uint32_t SquareAdd_CN1;

const uint16_t SAMPLE = 1000;     //ポーリング回数:200μsごとに100サンプルで1周期(50Hz)ぶん取得する
volatile uint16_t isrCounter;     //電流値のポーリング回数のカウント変数

#define WIFISSID "AAAA 1234" // ルーターのSSID
#define WIFIPASS "1234567890"       // ルーターのpassword

//Ambient
WiFiClient client;
Ambient ambient;
const uint32_t channelId = 35402;           // AmbientのチャネルID
const char* writeKey = "950ad7287d7569d5";  // ライトキー

// タイマー処理用タスク
hw_timer_t* tm0 = NULL;          //timer 初期化
volatile SemaphoreHandle_t timerSemaphore;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
volatile boolean t0flag = true;  // 割り込み待ち変数

const uint32_t DELAYTIME = 15000000;  //送信頻度μsで指定
volatile uint32_t startTime;


void IRAM_ATTR WiFi_ON() {
  //WiFiの接続
  WiFi.begin(WIFISSID, WIFIPASS);
  Serial.println(WIFISSID);

  int waiting = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
    waiting = waiting + 1;
    if (waiting >= 200) {  //タイムアウト
      Serial.println(F("WiFi Err ESP Reset"));
      esp_restart();
    }
  }  
  Serial.println(F("----WiFi ON"));
  // 本機のIPアドレスをシリアル出力
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}


/*********************************************
  タイマ割り込みによるポーリングでアナログ電圧値取得       
*********************************************/

void IRAM_ATTR Task0() {  //電流データをサンプル回数分ポーリングする
  uint16_t  loopTime = micros();

  portENTER_CRITICAL_ISR(&timerMux);  // 排他制御で下記を実行
  isrCounter += 1;
  //電流値値取得
  uint32_t AnlogValue0 = analogReadMilliVolts(CH0_APIN) - value_CN2;  //中間電位とCH1の差分を取る
  uint32_t AnlogValue1 = analogReadMilliVolts(CH1_APIN) - value_CN2;  //中間電位とCH2の差分を取る

  //2乗した値を加算していく
  if (AnlogValue0 > 0) {
    SquareAdd_CN0 += +AnlogValue0 * AnlogValue0;
  }
  if (AnlogValue1 > 0) {
    SquareAdd_CN1 += AnlogValue1 * AnlogValue1;
  }

  portEXIT_CRITICAL_ISR(&timerMux);  // 排他制御終了

  if (isrCounter >= SAMPLE) {
    loopTime = micros() - loopTime;
    Serial.printf("Task0 Time: %d μs\n",loopTime);  //実測だと104μs
    t0flag = true;
    timerEnd(tm0);
    tm0 = NULL;
    xSemaphoreGiveFromISR(timerSemaphore, NULL);  // セマフォを開放
  }
}

/*********************************************
              セットアップ関数
*********************************************/
void setup() {
  //CTL-10-CLS 超小型クランプ式交流センサ(Φ10/80Arms)
  const uint16_t RL = 100;  //センサ抵抗値
  const float K = 0.98;     //結合係数
  const uint16_t N = 3000;  //巻数比

  //1mVあたりの電流Io(A)
  coefficient = N / RL / K / 1000;  //0.030612448
  Serial.begin(115200);

  ambient.begin(channelId, writeKey, &client);  // チャネルIDとライトキーを指定してAmbientの初期化

  delay(1000);
  
  Serial.print("coefficient: ");  Serial.println(coefficient, 5);
  Serial.printf("CH0:analogRead()=%d\n", (int)analogRead(CH0_APIN));
  Serial.printf("CH1:analogRead()=%d\n", (int)analogRead(CH1_APIN));
  Serial.printf("CH2:analogRead()=%d\n", (int)analogRead(CH2_APIN));
  Serial.printf("CH0:analogReadMilliVolts()=%d mV\n", (int)analogReadMilliVolts(CH0_APIN));
  Serial.printf("CH1:analogReadMilliVolts()=%d mV\n", (int)analogReadMilliVolts(CH1_APIN));
  Serial.printf("CH2:analogReadMilliVolts()=%d mV\n", (int)analogReadMilliVolts(CH2_APIN));

  Serial.println(F("Loop start"));
}


void loop() {
  while (t0flag == true) {
    startTime = micros();  // 開始時間保存
    t0flag = false;
    isrCounter = 0;
    SquareAdd_CN0 = 0.00;
    SquareAdd_CN1 = 0.00;
    value_CN2 = 0;

    for (int i = 0; i < 10; i++) {
      value_CN2 += analogReadMilliVolts(CH2_APIN);  //中間電位を取得
    }
    value_CN2 = value_CN2 / 10; //平均値を利用する。
    Serial.printf("CH2: %d mV\n", value_CN2);

    // WDT(ウォッチドックタイマ)設定
    timerSemaphore = xSemaphoreCreateBinary();              //バイナリセマフォを作成
    tm0 = timerBegin(0, getApbFrequency() / 1000000, true); //タイマ番号0-3まで利用可,ペリフェラル周波数:timer=1us,
    timerAttachInterrupt(tm0, &Task0, true);                //タイマ割り込みが入ったときにタスク実行
    timerAlarmWrite(tm0, 200, true);                        //アラーム、引数2はμ秒で指定:200μs
    timerAlarmEnable(tm0);                                  //タイマー有効化
  }

  delay(110); //電圧の取得が終わるまで待つ

  if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE) {
    //2乗平均平方根で電流実効値を計算する。
    float rms_CN0 = 0.00;
    if (1.0 < SquareAdd_CN0) {
      rms_CN0 = sqrt(SquareAdd_CN0 / SAMPLE);
      rms_CN0 = rms_CN0 * coefficient + OFFSET_CH0;
    }

    float rms_CN1 = 0.00;
    if (1.0 < SquareAdd_CN1) {
      rms_CN1 = sqrt(SquareAdd_CN1 / SAMPLE);
      rms_CN1 = rms_CN1 * coefficient + OFFSET_CH1;
    }
    float sumRms = rms_CN0 + rms_CN1;

    Serial.print(F("Ch0: "));  Serial.print(rms_CN0); Serial.println(F(" A"));
    Serial.print(F("Ch1: "));  Serial.print(rms_CN1); Serial.println(F(" A"));
    Serial.print(F("ChSum: ")); Serial.print(sumRms); Serial.println(F(" A"));

    WiFi_ON();

    // 温度、湿度の値をAmbientに送信する
    ambient.set(1, String(sumRms).c_str());
    if (ambient.send()) {
      Serial.println(F("ambient Send OK!"));
    } else {
      Serial.println(F("ambient Send NG"));
    }

    delay(200);
    WiFi.mode(WIFI_OFF);  //WiFiをOFFにして省電力
    Serial.println(F("----WiFi OFF"));

    //ループの設定時間から処理時間分を引いてdylayする。
    uint32_t delaytaTime = micros() - startTime;
    delaytaTime = (DELAYTIME - delaytaTime) / 1000;
    Serial.printf("LOOPTIME: %d ms\n", (int)(DELAYTIME / 1000));
    Serial.printf("DelayTime: %d ms\n", delaytaTime);
    delay(delaytaTime);
  }
}

【何も繋がない時の出力結果】
22:42:00.014 -> coefficient: 0.03061
22:42:00.014 -> CH0:analogRead()=1841
22:42:00.014 -> CH1:analogRead()=1840
22:42:00.014 -> CH2:analogRead()=1840
22:42:00.014 -> CH0:analogReadMilliVolts()=1672 mV
22:42:00.014 -> CH1:analogReadMilliVolts()=1672 mV
22:42:00.014 -> CH2:analogReadMilliVolts()=1672 mV
22:42:00.014 -> Loop start
22:42:00.014 -> CH2: 1669 mV
22:42:00.201 -> Task0 Time: 104 μs
22:42:00.248 -> Ch0: 0.05 A
22:42:00.248 -> Ch1: 0.04 A
22:42:00.248 -> ChSum: 0.10 A
22:42:00.342 -> Buffalo-G-3CAA


【Ambientに送信されるデータ】
CH0: 0.1


割り込みでは104μsかかっているので、サンプルの取得間隔は200μsで良かったです。

【大変だったこと】
延長コードを割いてクランプセンサーを1CH挟んで測定したときにドライヤーつけて電圧図ろうとしたらコンセントの端子に触れて強い光と共にタップが黒くなった(笑)
テスターや工具による感電には気をつけましょう。

おわり

Micro:bitでブレーカーの電流速定2その後

前回マイクロビットとクランプ電流センサーで電流速定して、もう一個のマイクロビットに無線で電流値送って現在の電流値をとれるようにしました。

カロリー○イトの箱に収めたのはウケたんで、とりあえず良かったということにしましょう!うん。


実際に使ってもらった感想聞いたら以下のような会話ががありました。
・面白いし、いい感じだった。
・以前arduinoで挫折したけど、Micro:bitはブロックのプログラミングで面白そうだから、自分でも買って子供とプログラミングの勉強やってみた。

・最近あたたかくなってブレーカー落ちないから、そんなに必要じゃなくなってきたなぁ〜←んっ?
・次はどのくらい使ってるか推移が見たくなるなぁ〜←おっ?

ケン:見れたらなにをしたいですか?
ブレーカーが落ちる前に電流値の閾値超えたらブレーカー落とすとか?
課長:それ余計に落ちやすくなるから(笑)
課長:特に決まってないけど、傾向見れたらなんかできないかなぁ
ケン:じゃやってみますか!
(あっ、これ会社のIoT化とか話が出た時だったら時間とお金かけて失敗するパターンだ。)

次回はブレーカーの電流値を飛ばすところから、外でもスマホで見れるようにしてみましょう。



話は大きく変わりますが、
IoTとか会社でやるのにあんまり上手く行ってる例が少ないと思ってるので、自分で開発して運用できるものであるかを検証してみます。

ちなみに以下のパターンは私が見た、まぁ良くある失敗のパターンです。

①今の時代はDX、IoT入れよう。この機器入れるんやぁ!
ビッグデータ必要だよね。色々なデータを蓄積しよう。
③やっぱりIoTだからリアルタイムに見える化だよね。
(更新間隔は短くして沢山取らないとね!)

〜〜導入に4ヶ月経過〜〜

⑤ネットワークの設定とかわからんなぁ。
IP?サブネットマスク?良くわからん!
LANケーブルこの穴に挿したら動くの?助けて。

〜〜設定後〜〜

見える化して何しようか、うーん

⑦思いつかないからとりあえず考えよう。

〜〜1年後〜〜

⑧そういえばアレどうなってるんだっけ?
見える化出来てる?なんか使えるんじゃない?

ect...


いつまでカイゼンせずに数字見とるんかい!
"見とる化"か〜ぃ!!

よくわからずに横文字並んだツール入れて、
入れたら良くなると思ってる人って多いんですよね。

そもそも導入してなにをどうしたいか、目的とか目標とかわからないと何が良いのか悪いのかわからないですね。

時間は有限だし、新しいことをやるためには今やってる何かをやめる必要がありそうです。
やめるってことは業務フローを変えないとですね。

前提知識としてトヨタ生産方式とかの知識もないと見える化で終わっちゃうので気をつけよう...

おわり

Micro:bitで電流測定

事の発端は課長からの一言
最近ドライヤー使った時にブレーカー落ちるからなんとかしたいんだけどどうにかできない??

私:炊飯器やポット使ってる時にドライヤー使わなければ大丈夫ですねーもしくは電力契約見直して増やすとかどうでしょう!
課長:契約変えるのはなんか負けた気がするし、ドライヤー使う前にブレーカー落ちないか見える化したいんだ。
私:電力量を見たいならスマートメータに変えてBルート契約したらNature Rumo Eとか、自作でやるならESP32とかでデータ引っこ抜けるのでは?
課長:スマートメータじゃないんだよね~Bルート契約とか面倒だなぁクランプメータとかからデータ引っこ抜けないの?
私:ほほう!面白そうですね。やってみますか!(自分の家でやると無駄遣いって怒られるからこれは楽しみだなぁ)


そこで今回はマイコンとクランプセンサー繋いで交流の電流値測定してまずは見える化してみましょうってことがきっかけです。電力だと電圧とか力率とか面倒だから電流のみをを測ります。


せっかく遊びでやるからオモチャっぽい作りにしてみましょう!(笑)

ケースは今回も菓子の箱、カ○リーメイト中身は美味しくいただきました!
そして使うはマイクロビットでブロック使ってプログラミング、子供のおもちゃ感出して行きましょう!

電流測定側の完成品の写真ははこちらです。
f:id:kesokonpatata:20210308064202j:plain
f:id:kesokonpatata:20210308064155j:plain
f:id:kesokonpatata:20210308064158j:plain
f:id:kesokonpatata:20211206213429j:plain

ちなみにもう一個のマイクロビットで電流値を受信してみました。


【材料】大体8700円くらいでした
Micro:bit v2 2個
・クランプ式交流電流センサ(CTL-10-CLS) 2個
Micro:bitプロトタイプ基板 1枚
https://www.amazon.co.jp/gp/aw/d/B07TQJJ4YT/ref=yo_ii_img?ie=UTF8&psc=1

JST コネクタ 2P JST-SM オスメス
https://www.amazon.co.jp/gp/aw/d/B01M63SOL2?psc=1&ref=ppx_pop_mob_b_asin_image

・抵抗(100Ω) 2個
・抵抗(4.7kΩ) 2個
・セラミックコンデンサ(105) 2個※見様見真似で定数も適切かわからないです。リップルとかノイズとかオシロで見たいんだがほしいなぁ
・お菓子の箱(カロリーメイトは幅がぴったりでした。)


【予備知識】
クランプセンサーの電流から交流電流の実行値を算出する。

超小型クランプ式交流電流センサ(φ10/80Arms)CTL-10-CLS
www.u-rd.com

①データシートより
Eo=K×Io×RL÷n(VDC)

②①より変換
電流Io=電圧Eo×変流比N÷負荷抵抗RL÷結合係数K
Io=Eo × 3000 ÷ 100 ÷ 0.99
 = 30.612244898 × Eo(A)

マイコンにかかる電圧Eo = マイコンの電圧 × マイコンの分解能 × analog Readの読み取り値
Eo=3.3 ÷ 1024 × analog Readの読み取り値 
 =0.00322265625 × analog Readの読み取り値(V)

②に③代入により
マイコンAD変換の値1当たりの電流値
Io=3.3 ÷ 1024 × 3000 ÷ 100 ÷ 0.99 × analog Readの読み取り値
= 0.09765625 × analog Readの読み取り値(A)

⑤交流の場合のanalog Readの読み取り値は測定電圧の実効値を求めます。
電流波形はマイナスもあるのでそのまま足すと値がゼロになるので、読み取った値を2乗してn回合計し、平均して平方根で求めます。
AnalogReedの値を2乗して最低1周期何度もポーリングして合計する。
1/fより
50Hz→1/50=20(ms)
60Hz→1/60=16.666...(ms)

じゃ1周期分を何回取得するんだいってところが結構悩みました。
ブロックだとタイマ割り込みできないじゃない(私の知識不足?)、Ardoino IDEで書くのは子供のおもちゃっぽく?という謎の意地でやらないとして、とりあえず測定周期分ひたすら読み取って合計してみます。
結論としてはブロックだとfor文で50Hzで1周期(20ms)で2chで100回程度とれてました。
タイマ割り込みでやってないからかやはりビミョーに100回の測定時間が1周期分とずれるのが気持ち悪いです。

500ms分取得したら50Hzだと25期分になるので測定時間が少しずれてもまあ誤差で済むと思います。
60Hzでもちょうど30周期取れるのでどちらにも対応できるってことにしよう。うん

てか1回の測定が100μsって結構遅いんだなぁ~
micro:bit V2はSoCがNordic nRF52833だったので、Nordicのサイトを知り合いに聞きながら調べたら内部抵抗10Ωだと読み込みに5μsだっけ?(うろ覚え)
v1.5:16MHz→v2:64MHzと高速化されてますので、旧バージョン(v1.5)だとサンプリングはもっと少ないと思われます。


【要件や仕様】
・測定範囲は1A~40Aくらい
・測定間隔は5sに1回更新する。
・ブレーカーにクランプして測定する(L1とL2の2ch)
・測定側と表示側は無線で送信する。(マイクロビットの無線を使おう)

・表示はドライヤーの近くで2ch文の合計を表示する。
・表示側は数字でも見たいし、2AごとにLEDで埋めてくれる機能も入れよう。
・値によって制御とかは今回はしない。表示だけ。


【回路図】
3.3VとGNDを分圧して1.65Vの基準電位を作ってその差を読み取って行きます。
これで交流波形のマイナスも測定できるようになります。
調べてたらレールスプリッタっていう回路だそうです。(間違ってたらすみません)
とりあえず汚い手書き画像載せときます。
f:id:kesokonpatata:20210309124846j:plain

【電流測定側のプログラム】

makecode.microbit.org


【表示側のプログラム】

makecode.microbit.org


余談ですが
将来的に設備の稼働率や稼働状況のモニターとか作って組み合わせとかスケジューリングのための分析とかできそうだなぁ〜

機会があったらESP32とルーターとラズパイで複数の設備の電流値を取ってみたいですね。

今まではなんか高いIoT機器を買って稼働率見えるようにしてるみたいで、満足して改善までいきつかないか、分析できるスキルがないかで終わってたような気がします。

安い早いでこれならたくさん検証とフィードバックを繰り返して次への学びに繋げられそうですね。

おわり

【Seeeduino XIAO】RS232cから受信した信号をUSBHIDで送信してみる。2

先日RS232cをHID出力に入力する機器を作ったんですが、1件づつ間隔を開けて送信する場合はいい感じでした。

しかし...

連続で40~60件分をまとめて送信すると、PCで開いたメモ帳やExcelに入力して描画する
スピードが合わないからか文字が70行目くらいでぐちゃぐちゃになりました。orz
バッファ溢れかな??


今回は最大100件だったので、
試しに最大60文字を100件送るプログラムを作って調査してみたんですが、
送信間隔350ms以上で正常に入力されるようになりました。
Excelだと500ms以上であれば描画が追いついてないですが最後まで正常に入力されてました。
描画がボトルネックなのかな??

そこで前回のHID仕様を改良してみました。
変更点はマイコンにバッファをもたせて、送信間隔を900ms程度に遅延させてみました。
色々調整しながら試してみましたが以下でうまくいきました。


100件分のRS232c信号をUSB HIDでPCに送れるようにしています。

/*
Seeedino XINO
UART(HardwareSerial)
UART1(RX:GPIO7 TX:GPIO6)※受信(RX)のみ使用
ボーレート:9600
ビット長:8ビット、パリティ:なし、ストップビット:なし(SERIAL_8N1)
接続
送信側→本機
3.3V-3.3V
GND -GND
TX -GPIO7(RX)

/

#include <Keyboard.h>

#define BPS0 9600                 // bps UART0(USB)
#define BPS1 9600                 // bps UART1
#define BAFFSIZE 300              // 連続で受信する場合の文字列のバッファ数
#define DATASIZE 70               // 一度に送る文字列のバイト数

char baffArr[BAFFSIZE][DATASIZE]; // バッファ
int ReadX = 0;
int ReadY = 0;
int WriteX = 0;
int WriteY = 0;

#define INTERVAL 900   // 最小送信間隔ms(アプリの入力速度に合わせて変更する)
int count = 0;
String strDATA = "";   // 送信する文字列

/***************************************************************
   初期化
 **************************************************************/
void setup() {
  strDATA.reserve(DATASIZE); // strDATAの文字列の格納領域をbyte数確保
  Keyboard.begin();          // USB HID接続開始
  Serial.begin(BPS0);        // UART0
  Serial1.begin(BPS1);       // UART1
  delay(1000);
  //Keyboard.println("USB HID Start!!");
}

/***************************************************************
  1バイトづつSerialで読み込み、baffArrに一旦格納する。strDATAに結合してUSB HIDで送信
   *************************************************************/
void loop() {
  //読み込み
  while (Serial1.available() > 0) {     // 受信したデータバッファが1バイト以上存在する場合
    char inChar = (char)Serial1.read(); // Serial1からデータ読み込み
    if (inChar == '\n') {               // 改行コード(LF)がある場合の処理
      baffArr[ReadX][ReadY] = '\0';
      ReadX += 1;
      ReadX = ReadX % BAFFSIZE;
      ReadY = 0;
    } else if (inChar != '\r') {        // CRの改行コードの場合は結合しない
      baffArr[ReadX][ReadY] = inChar;   // 読み込んだデータを結合
      ReadY += 1;
    }
  }

  //書き出し
  if (INTERVAL <= count) {
    if (ReadX != WriteX) {             //データが保存されている場合に実行
      for (WriteY = 0; WriteY < DATASIZE; WriteY++) {
        if (baffArr[WriteX][WriteY] != '\0') {
          strDATA += baffArr[WriteX][WriteY];
          baffArr[WriteX][WriteY] = '\0';
        } else {
          break;
        }
      }
      if (strDATA.length() > 1) {
        Keyboard.println(strDATA);     // データ送信
      }
      strDATA = "";                    // データ初期化
      WriteX += 1;
      WriteX = WriteX % BAFFSIZE;
    }
  }
  
  count = count % INTERVAL;
  count += 1;
  delay(1);
}