長岡ものづくりアカデミー

IoTを使った工場のDX化

事前準備

•       USB接続ができるWindowsマシンが必要です。

•       インターネットに接続可能な環境が必要です。

•       ArduinoのIDEをインストールしておいてください。
 https://www.arduino.cc/

•       その他 可能な方はIDEに追加インストールをお願いします。

•       ESP32系のCPUを使います。

•       DHTライブラリーを使います。

•       USBシリアルドライバーにはCH430チップを使います。

 

SmartFactory4.0

旭鉄工株式会社のDX事例を見てみましょう。

「生産管理板を書いてPDCAを回しなさい!」

  1. 設備カウンターの時間がピッタリで読めない
  2. 停止時間を正確に測定する手段がない
  3. 正確なサイクルタイムがわからない

 

人にはもっと付加価値の高い仕事を・・

人にはもっと付加価値の高い仕事を・・

1)大掛かりで高い

2)昭和の機器にはつかない

3)欲しいデータが見えない

 

なにを作った?

1.         稼働率モニター(第一世代)

(ア)  シグナルタワーの読み取り

(イ)  スマートあんどん

2.         サイクルタイムモニター(第二世代)

(ア)  生産数カウンター

個数を読み取る方法

•       スイッチ

•       近接センサー(光)

•       リードスイッチ(磁石)

•       ホールセンサー(磁気)

 

近接センサー

  1. 実験で使う近接センサー
  2. PNP系センサー出力の接続例
  3. 動作原理
  4. カウントする方法
  5. 近接センサーの駆動電流

 

赤外線を出し、その反射光があるかないかを読み取る

 

型名                         E3F-DS10 P1

駆動電圧 5-36V DC

検出距離 10cm以内

入力モード                PNP

検出タイプ              NO

 

 

センサーのNOタイプとNCタイプの違い?NOタイプは検出物体がある時にON。NCタイプはない時にONします。

 

PNPオープンコレクタ出力と接続例

PNP型トランジスタは、ヨーロッパなどを中心に多用。

PNP型トランジスタを用いた回路は、負荷電流が流れだす(ソース)側となるので、回路のGND(0V)とコレクタの間に負荷を接続します。 

PNPオープンコレクタ出力の場合、エミッタが電源の+側と接続されており、0Vと出力の間に負荷を接続することによって動作させることができます。

トランジスタがオンになると、出力端子の電圧は電源の+側と同じになります。

 

 

 

 

 

 

電源を接続すると、主回路から赤外線が出力されます。

 

電流は、トランジスタのエミッター(E端子)からコレクタ(C端子)に流れたいのですが、流れることができません。これは、ベース(B端子)に電流が流れていない状態だからです。

 

光電センサーの前を製品が通過すると反射光が、主回路に入力されます。

 

すると主回路からトランジスタのB端子(ベース)に電流が流れます。

B端子に電流が流れると、端子Eと端子Cの間に電流がながれるようになります。

このとき、負荷�Aには、電流が流れているので回路に流れている電圧と同じ電圧がかかります。

負荷�Aの値をマイコンで読み込むと光電センサーの前にカウントしたい製品があるかどうかがわかります。

 

光電センサーの前には、製品があるときに電圧がHiになる。製品と製品との間では、電圧はLoになる。こういったアナログ電圧の変化をマイコンは読み取ります。マイコンを使って10ms間隔で光電センサーの出力信号を読込み続けます。マイコンは命令された通り、10ms間隔データを読み取ると、センサーの前に物体があるときはHiの信号を出し続けます。また、センサーの前に物体がなくなるとLo信号を出し続けます。これから個数(カウント)へと変換します。

アルゴリズム

 1)前の状態から変化があった。

 2)現在の状態がONである

1)と2)が成り立つときに、カウントを+1する。これから個数(カウント)へと変換します。

 

光電センサー駆動電流の計測

光電センサーを駆動するには電源が必要です。LED等であれば、Arduinoなどのマイコンであればギリギリ駆動できます。Arduino等はマイコンの種類によりますが5mA程度のLEDを駆動するだけで、マイコン的には限界だと思います。これは、最近のマイコンは低電力が求められているためです。5mA程度のLEDを駆動する場合には、1kΩの電流制限用抵抗をいれる必要があります。

今回使うマイコンボードでは、ACアダプターから9Vが入力され、電源回路によって3.3Vと5Vの出力がボード上から得ることができる。光電センサーの駆動電圧は5V―36Vなのでボード上の5V出力端子にそのまま接続しても大丈夫。

 

USBドライバーのインストール

•       D1R32ボードはUSB通信用にCH430チップを搭載

•       検索で「install CH430 drivers Windows」と打ってドライバーをインストール先を探します。

•       sparksにあったのでダウンロードしてインストールする。

  https://sparks.gogo.co.nz/ch340.html

•       【注意】USBドライバーが新しすぎると動かないかも・・・

•       2014.8.8日製のUSBドライバーでの動作を確認しました。最新のは動きがおかしかったです。。。

 

ESP32ボードのArduinoIDEサポートのインストール

ArduinoIDEではいろいろなマイコンを開発することができます。しかし、そのためにはボードのサポートライブラリーをインストールする必要があります。

加のボードマネージャーURL

•       ファイルー>環境設定ー>設定のtabから

•       追加のボードマネージャーURLとして以下を追記します。

https://espressif.github.io/arduino-esp32/package_esp32_index.json

 

ボードマネージャー

•       ツールー>ボードー>ボードマネージャーから

•       ESP32 をインストールします。

ボードの設定

•       ツールー>ボード>ESP32 Arduinoから

•       ESP32 Wrover Modulesを選択します。

 

DHTセンサーのライブラリーのインストール

•       ツールー>ライブラリーの管理 から

•       DHTで検索し DHT sensor library を選択します。

 

演習1

void setup() {

  Serial.begin(115200);

}

void loop() {

   Serial.println("Hello World");

}

 

 

演習2

 

#include <DHT.h>

//ボードに接続したDHTセンサーのピン番号IO17 (GPIO)

const int PIN_DHT = 17;

DHT dht(PIN_DHT,DHT11);

// 温度を取得

String getTemperature() {

  float temperature = dht.readTemperature();

  return String(temperature);

}

// 湿度を取得

String getHumidity() {

  float humidity = dht.readHumidity();

  return String(humidity);

}

void setup() {

  Serial.begin(115200);

  // dhtライブラリの初期処理

  dht.begin();

}

void loop() {

  Serial.print("温度:" + getTemperature());

  Serial.println("湿度:" + getHumidity());

  sleep(2);//2秒まつ

}

 

演習3

#include <DHT.h>

//DHTセンサーのピン番号IO17 (GPIO)

const int PIN_DHT = 17;

DHT dht(PIN_DHT,DHT11);

//PESセンサーのピン番号IO16 (GPIO)

const int pesPin = 16;

// 温度を取得

String getTemperature() {

  float temperature = dht.readTemperature();

  return String(temperature);

}

// 湿度を取得

String getHumidity() {

  float humidity = dht.readHumidity();

  return String(humidity);

}

void setup() {

  Serial.begin(115200);

  pinMode(pesPin, INPUT);

  // dhtライブラリの初期処理

  dht.begin();

}

unsigned long count;

int state = 0;

int previous = 0;

void loop() {

  state = digitalRead(pesPin);

  if( state != previous && state == 1 ){

    count++;

    Serial.print("count :");

    Serial.println(count);      

    Serial.print("温度:" + getTemperature());

    Serial.println("湿度:" + getHumidity());

    }

  previous = state;

}

 

演習4

追加インストール 1

ESP32のWi-Fi設定をsoftAPモードに設定し、Webサーバーを動かします。

サーバーの構築には下記のファイルが必要です。

Arduino IDEに、必要なライブラリをインストールしていきます。
最初にWebサーバーを動かすための「ESPAsyncWebServer」をインストールします。

Githubページ(https://github.com/me-no-dev/ESPAsyncWebServer)から

zipファイルをダウンロードします。

Githubページ上の右上にあるCODEをクリック

Download ZIPでzipファイルをダウンロードします。

ESPAsyncWebServerライブラリーをインクルード

ArduinoIDEのメニューから スケッチ ー> ライブラリーをインクルード ー> .ZIP形式のライブラリーをインクルード

を選択し、さきほどダウンロードしたESPAsyncWebServer-master.zipファイルを

選択して「開く」ことでESPAsyncWebServerがArduinoのライブラリーにインクルードされます。

 

追加インストール 2

次に、ESPAsyncWebServerが依存している「AsyncTCP」というライブラリもインストールします。

Githubページ(https://github.com/me-no-dev/AsyncTCP)から

zipファイルをダウンロードします。

Githubページ上の右上にあるCODEをクリック

Download ZIPでzipファイルをダウンロードします。

 

AsyncTCPライブラリーをインクルード

ArduinoIDEのメニューから スケッチ ー> ライブラリーをインクルード ー> .ZIP形式のライブラリーをインクルード

を選択し、さきほどダウンロードしたAsyncTCP-master.zipファイルを

選択して「開く」ことでAsyncTCPがArduinoのライブラリーにインクルードされます。

 

追加インストール 3

サーバーの構築には下記のファイルが必要です。

  index.html  <=アクセスした際に最初に見えるWEBページ HTML言語

  style.ccs <=WEBページのデザイン

これらのファイルは、ESP32のSPIFFS (SPI Flash File System) に保存して

おくと便利です。

ESP32のフラッシュメモリの一部をストレージとして使う方法をSPIFFS

と言います。

SPIFFSを使うためにはArduinoのIDEに追加のアップローダをインストール

して機能を追加する必要があります。

google「ESP32 SPIFFS 」といれて検索するか以下のサイトからもらってきます

https://github.com/me-no-dev/arduino-esp32fs-plugin/releases/tag/1.1

 

展開すると  

  ESP32FS\tool\esp32fs.jar 

となっている。

Arduinoのtoolsに先ほど展開した

   ESP32FS\tool\esp32fs.jar 

をコピーすれば良い。

スケッチがあるフォルダにアップローダ用のフォルダ

を作ります。

名前は 「data」 と必ずしてください。

dataフォルダーにindex.htmlとstyle.cssを配置

します。

Arduinoの「ツール」からSPIFFSに割り当てるサイズを選びます。

 

ESP32は4Mのメモリを持っています。

今回は下記とします。

プログラム 1.2MByte

SPIFFS 1.5MByte

 を割り当てます。

ESP32_NagaokaEDU_ex4.ino

/**********************************************

 ESP32 ながおかアカデミー アクセスポイントバージョン

                           Ver. 20230904

**********************************************/

// Import required libraries

#include "WiFi.h"

#include "ESPAsyncWebServer.h"

#include "FS.h"

#include "SPIFFS.h"

#include <DHT.h>

#include <DHT_U.h>

TaskHandle_t th[2];

 

//ボードに接続したDHTセンサーのピン番号IO17 (GPIO)

const int PIN_DHT = 17;

DHT dht(PIN_DHT,DHT11);

//ボードに接続したPESセンサーのピン番号IO16 (GPIO)

const int pesPin = 16;

String pesState;

//ボード上に実装されているLEDのピン番号IO2 (GPIO)

const int ledPin = 2;

String ledState;

String ledState_old = "OFF";

// アクセスポイントとパスワード

const char* ssid = "ESP32AP";

const char* pass = "12345678";

const IPAddress ip(192,168,4,1);

const IPAddress subnet(255,255,255,0);

AsyncWebServer server(80);

bool myWiFiFirstConnect = true;

// Wifi接続用タスク

void myWiFiTask(void *pvParameters) {

  while (true) {

    IPAddress myIP = WiFi.softAPIP();

    if (!myIP) { 

      Serial.println("Connecting WiFi");

      WiFi.softAP(ssid, pass);                               // SSIDとパスの設定

      delay(100);                                                          // このdelayを入れないと失敗する場合がある

      WiFi.setTxPower(WIFI_POWER_19_5dBm);//電波強度を最大

      WiFi.softAPConfig(ip, ip, subnet);          // IPアドレス、ゲートウェイ、サブネットマスクの設定

      IPAddress myIP = WiFi.softAPIP();      

      // 各種情報を表示

      Serial.print("SSID: ");

      Serial.println(ssid);

      Serial.print("AP IP address: ");

      Serial.println(myIP);

      }

      vTaskDelay (5000); // Check again in about 5s

    }

}

 

// Sensorカウントタスク

unsigned long count;

void mySensorTask(void *pvParameters) {

  count = 0;

  int state = 0;

  int previous = 0;

  while(1){

 

    state = digitalRead(pesPin);

    if( state != previous && state == 1 && ledState.equals("ON") ){

       count++;

       Serial.print("count :");

       Serial.println(count);         

    }

    previous = state;

    vTaskDelay (10);

  }

}

String getState() {

  // LEDの状態を取得

   if(digitalRead(ledPin)){

      ledState = "ON";

    } else {

        ledState = "OFF";

    }

  return String(ledState) ;

}

String getTemperature() {

  // 温度を取得

  float temperature = dht.readTemperature();

  return String(temperature);

}

String getHumidity() {

  // 湿度を取得

   float humidity = dht.readHumidity();

   return String(humidity);

}

String getPESensor() {

   return String(count);

}

 

unsigned long time_data = 0;

unsigned long time_start = 0;

unsigned long second = 0;

String getTimer() {

  // 経過時刻を取得

   time_data = millis();

  if(ledState.equals("ON")){

    second =  time_data/1000 - time_start/1000 ;

  } else {

    second = 0;

  }

  return String(second);

}

String getAverage() {

  // 一秒あたりの平均個数を取得

  float average = 0.0 ;

  if( second && count ){

    average = second / (float)count ;

    //Serial.println(average);

  }

  return String(average,3);

}

// LEDの状態と温湿度の更新

String processor(const String& var){

  if(var == "STATE"){ 

    if(digitalRead(ledPin)){

      ledState = "ON";

      if(!ledState.equals(ledState_old)) {

        count = 0;

      }

    } else {

        ledState = "OFF";

        time_start = millis();

    }

   ledState_old =  ledState;

   return ledState;

  } else if (var == "TEMPERATURE"){

      return getTemperature();

  } else if (var == "HUMIDITY"){

      return getHumidity();

  } else if (var == "PESENSOR"){

      return getPESensor();

  } else if (var == "TIMER"){

      return getTimer();

  }  else if (var == "AVERAGE"){

      return getAverage();

  } 

  return String();

}

#include "nvs_flash.h"

void setup(){

 

  Serial.begin(115200);

  //Serial.setDebugOutput(true);

  Serial.println("NVM ERROR CHECK");

  ESP_ERROR_CHECK(nvs_flash_erase());

  nvs_flash_init();

 

  // dhtライブラリの初期処理

  dht.begin();

  

  pinMode(ledPin, OUTPUT);

  digitalWrite(ledPin, LOW); 

 

  pinMode(pesPin, INPUT);

  // Initialize SPIFFS

  if(!SPIFFS.begin(true)){

    Serial.println("An Error has occurred while mounting SPIFFS");

    return;

  }

 

// タスクの作製 コア0にて動作 サイズ8kB

  xTaskCreatePinnedToCore(myWiFiTask, "myWiFiTask", 8192, NULL, 3, &th[0], 0);

  // タスクの作製 コア1にて動作 サイズ8kB

  xTaskCreatePinnedToCore(mySensorTask, "mySensorTask", 4096, NULL, 5,&th[1], 1);

 

// ルートアクセス [/] web page

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){

    request->send(SPIFFS, "/index.html", String(), false, processor);

  });

 

  // style.css

  server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){

    request->send(SPIFFS, "/style.css", "text/css");

  });

  // GPIOをONにする HIGH

  server.on("/on", HTTP_GET, [](AsyncWebServerRequest *request){

    digitalWrite(ledPin, HIGH);   

    request->send(SPIFFS, "/index.html", String(), false, processor);

  });

 

  // GPIOをOFFにする LOW

  server.on("/off", HTTP_GET, [](AsyncWebServerRequest *request){

    digitalWrite(ledPin, LOW);   

    request->send(SPIFFS, "/index.html", String(), false, processor);

  });

 

// LED状態表示

  server.on("/state", HTTP_GET, [](AsyncWebServerRequest *request){

    request->send_P(200, "text/plain", getState().c_str());

  });

  // 温度表示

  server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){

    request->send_P(200, "text/plain", getTemperature().c_str());

  });

  // 湿度表示

  server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){

    request->send_P(200, "text/plain", getHumidity().c_str());

  });

  // 光電センサー表示

  server.on("/pesensor", HTTP_GET, [](AsyncWebServerRequest *request){

    request->send_P(200, "text/plain", getPESensor().c_str());

  }); 

  // 経過時間表示

  server.on("/timer", HTTP_GET, [](AsyncWebServerRequest *request){

    request->send_P(200, "text/plain", getTimer().c_str());

  });

  // 一個あたりの平均経過時間

  server.on("/average", HTTP_GET, [](AsyncWebServerRequest *request){

    request->send_P(200, "text/plain", getAverage().c_str());

  });

  server.begin();

}

void loop(){

 

}

 

index.html dataフォルダに配置 (ファイル名は 小文字で付ける )

 

<!DOCTYPE html>

<!--

 Shouhei Yano

 National Institute of Technology College, in Nagaoka .

-->

<html lang="ja">

<head>

  <meta charSet="utf-8"/>

  <title>NagaokaアカデミーWEBサーバ</title>

  <meta name="viewport" content="width=device-width, initial-scale=1">

  <link rel="icon" href="data:,">

  <link rel="stylesheet" type="text/css" href="style.css">

</head>

<body>

  <h1>Nagaokaアカデミー</h1>

 

  <p>

    <span class="sensor-labels">温度</span>

    <span id="temperature">%TEMPERATURE%</span>

    <sup class="units">&deg;C</sup>

  </p>

 

  <p>

    <span class="sensor-labels">湿度</span>

    <span id="humidity">%HUMIDITY%</span>

    <sup class="units">&#37;</sup>

  </p>

 

  <p>

    <span class="sensor-labels">現在の個数</span>

    <span id="pesensor">%PESENSOR%</span>

    <sup class="units">個</sup>

  </p>

 

 <p>

   <span class="sensor-labels">経過時間 </spin>

   <span id="timer">%TIMER%</span>

   <sup class="units">秒</sup>

 </p>

 

 <p>

   <span class="sensor-labels">一個当たりの時間 </spin>

   <span id="average">%AVERAGE%</span>

   <sup class="units">秒/個</sup>

 </p>

 

 <p>カウント<strong><span id="state">%STATE%</span></strong> </p>

 

  <p>

    <a href="/on"><button class="button">ON</button></a>

    <a href="/off"><button class="button button2">OFF</button></a>

  </p>

 

 

</body>

<script>

  setInterval(function ( ) {

    var xhttp = new XMLHttpRequest();

    xhttp.onreadystatechange = function() {

      if (this.readyState == 4 && this.status == 200) {

        document.getElementById("state").innerHTML = this.responseText;

      }

    };

    xhttp.open("GET", "/state", true);

    xhttp.send();

  }, 10000 ) ;

 

  setInterval(function ( ) {

    var xhttp = new XMLHttpRequest();

    xhttp.onreadystatechange = function() {

      if (this.readyState == 4 && this.status == 200) {

        document.getElementById("temperature").innerHTML = this.responseText;

      }

    };

    xhttp.open("GET", "/temperature", true);

    xhttp.send();

  }, 10000 ) ;

 

  setInterval(function ( ) {

    var xhttp = new XMLHttpRequest();

    xhttp.onreadystatechange = function() {

      if (this.readyState == 4 && this.status == 200) {

        document.getElementById("humidity").innerHTML = this.responseText;

      }

    };

    xhttp.open("GET", "/humidity", true);

    xhttp.send();

  }, 10000 ) ;

 

  setInterval(function ( ) {

    var xhttp = new XMLHttpRequest();

    xhttp.onreadystatechange = function() {

      if (this.readyState == 4 && this.status == 200) {

        document.getElementById("pesensor").innerHTML = this.responseText;

      }

    };

    xhttp.open("GET", "/pesensor", true);

    xhttp.send();

  }, 1000 ) ;

 

  setInterval(function ( ) {

    var xhttp = new XMLHttpRequest();

    xhttp.onreadystatechange = function() {

      if (this.readyState == 4 && this.status == 200) {

        document.getElementById("timer").innerHTML = this.responseText;

      }

    };

    xhttp.open("GET", "/timer", true);

    xhttp.send();

  }, 1000 ) ;

 

  setInterval(function ( ) {

    var xhttp = new XMLHttpRequest();

    xhttp.onreadystatechange = function() {

      if (this.readyState == 4 && this.status == 200) {

        document.getElementById("average").innerHTML = this.responseText;

      }

    };

    xhttp.open("GET", "/average", true);

    xhttp.send();

  }, 1000 ) ;

 

 </script>

</html>

 

style.css Dataフォルダーに配置 ファイル名は小文字とすること

 

/***

   Rui Santos

   Complete project details at https://RandomNerdTutorials.com

***/

 

html {

  font-family: Arial;

  display: inline-block;

  margin: 0px auto;

  text-align: center;

}

h1 {

  color: #0F3376;

  padding: 2vh;

}

p {

  font-size: 1.5rem;

}

.button {

  display: inline-block;

  background-color: #008CBA;

  border: none;

  border-radius: 4px;

  color: white;

  padding: 16px 40px;

  text-decoration: none;

  font-size: 30px;

  margin: 2px;

  cursor: pointer;

}

.button2 {

  background-color: #f44336;

}

.units {

  font-size: 1.2rem;

 }

.sensor-labels {

  font-size: 1.5rem;

  vertical-align:middle;

  padding-bottom: 15px;

}