工場改善 IoT Arduino開発

現場の悩みに解決手段を

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

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


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

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


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 "*****" // ルーターのSSID
#define WIFIPASS "********"       // ルーターのpassword

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

// タイマー処理用タスク
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挟んで測定したときにドライヤーつけて電圧図ろうとしたらコンセントの端子に触れて強い光と共にタップが黒くなった(笑)
テスターや工具による感電には気をつけましょう。

おわり