Modbus TCP – ESP32

Spread the love

Biasanya kita dengar PLC digunakan dalam industri untuk mengawal. Pernah terfikir untuk kawal actuator atau sensor industri dengan ESP32? Sepatutnya boleh buat Modbus TCP – ESP32. Tapi saya tak pernah cuba dengan peralatan sebenar. Setakat cuba dengan simulasi di perisian FactoryIO sahaja.

Tetapan sistem

Modbus TCP – ESP32. Bagi membolehkan esp32 berhubung dengan PLC pastikan bahawa esp32 dan plc boleh berhubung melalui rangkaian. Secara ringkas sambungan ditunjukkan oleh rajah berikut:

Modbus TCP - ESP32

IO antara server dan client boleh ditunjukkan seperti rajah berikut:

Dalam factoryIO link adalah seperti berikut:

Sambungan ESP32 dan code arduino

Modbus TCP - ESP32
/*
  Modbus TCP - ESP32
  ------------------

  Fungsi:
  - Fill Button  -> Modbus Coil 0
  - Drain Button -> Modbus Coil 1

  - Fill Valve   -> Input Status 0
  - Fill LED     -> Input Status 1
  - Drain Valve  -> Input Status 2
  - Drain LED    -> Input Status 3

  - Timer        -> Input Register 0

  Timer:
  - FILL  = 5 saat
  - DRAIN = 10 saat

  Library:
  ModbusIP_ESP8266
*/

#ifdef ESP8266
  #include <ESP8266WiFi.h>
#else
  #include <WiFi.h>
#endif

#include <ModbusIP_ESP8266.h>


// ======================================================
// WIFI
// ======================================================

const char* WIFI_SSID     = "yyyyyyyy";
const char* WIFI_PASSWORD = "xxxxxxxxx";


// ======================================================
// MODBUS REGISTER ADDRESS
// ======================================================

// Input Status
const uint16_t FILLVALVE_ISTS  = 0;
const uint16_t FILLLED_ISTS    = 1;
const uint16_t DRAINVALVE_ISTS = 2;
const uint16_t DRAINLED_ISTS   = 3;

// Coil
const uint16_t FILLBTN_COIL  = 0;
const uint16_t DRAINBTN_COIL = 1;

// Input Register
const uint16_t TIMER_IREG = 0;

const int N = 3;   // samakan dengan count dalam Configuration Factory IO


// ======================================================
// GPIO
// ======================================================

const uint8_t FILL_BUTTON_PIN  = 26;
const uint8_t DRAIN_BUTTON_PIN = 25;

const uint8_t FILL_LED_PIN  = 32;
const uint8_t DRAIN_LED_PIN = 33;


// ======================================================
// TIMER
// ======================================================

const unsigned long TIMER_INTERVAL = 1000;

// Fill = 5 saat
const int FILL_TIMER_START = 5;

// Drain = 10 saat
const int DRAIN_TIMER_START = 10;


// ======================================================
// MODBUS OBJECT
// ======================================================

ModbusIP mb;


// ======================================================
// SYSTEM MODE
// ======================================================

enum SystemMode
{
  MODE_NORMAL = 0,
  MODE_FILL   = 1,
  MODE_DRAIN  = 2
};

SystemMode mode = MODE_NORMAL;


// ======================================================
// SYSTEM VARIABLES
// ======================================================

bool fillButton  = false;
bool drainButton = false;

bool fillIO  = false;
bool drainIO = true;

int timer = 0;

unsigned long previousMillis = 0;


// ======================================================
// SETUP MODBUS REGISTERS
// ======================================================

void setupModbus()
{
  // --------------------------------------------------
  // Input Status
  // --------------------------------------------------

  mb.addIsts(FILLVALVE_ISTS);
  mb.addIsts(FILLLED_ISTS);

  mb.addIsts(DRAINVALVE_ISTS);
  mb.addIsts(DRAINLED_ISTS);


  // --------------------------------------------------
  // Coils
  // --------------------------------------------------

  mb.addCoil(FILLBTN_COIL, false, N);
  mb.addCoil(DRAINBTN_COIL, true, N);


  // --------------------------------------------------
  // Input Register
  // --------------------------------------------------

  mb.addIreg(TIMER_IREG);
}


// ======================================================
// RESET SYSTEM
// ======================================================

void resetSystem()
{
  mode = MODE_NORMAL;

  timer = 0;

  // Reset timer register
  mb.Ireg(TIMER_IREG, 0);


  // --------------------------------------------------
  // Fill
  // --------------------------------------------------

  mb.Ists(FILLVALVE_ISTS, 0);
  mb.Ists(FILLLED_ISTS, 0);


  // --------------------------------------------------
  // Drain
  // --------------------------------------------------

  mb.Ists(DRAINVALVE_ISTS, 0);
  mb.Ists(DRAINLED_ISTS, 0);
}


// ======================================================
// START FILL
// ======================================================

void startFill()
{
  mode = MODE_FILL;

  // Fill timer = 5 saat
  timer = FILL_TIMER_START;

  mb.Ireg(TIMER_IREG, timer);


  // --------------------------------------------------
  // Fill ON
  // --------------------------------------------------

  mb.Ists(FILLVALVE_ISTS, 1);
  mb.Ists(FILLLED_ISTS, 1);


  // --------------------------------------------------
  // Drain OFF
  // --------------------------------------------------

  mb.Ists(DRAINVALVE_ISTS, 0);
  mb.Ists(DRAINLED_ISTS, 0);


  // Reset timer
  previousMillis = millis();


  Serial.println();
  Serial.println("====================");
  Serial.println("MODE : FILL");
  Serial.println("TIMER: 5 seconds");
  Serial.println("====================");
}


// ======================================================
// START DRAIN
// ======================================================

void startDrain()
{
  mode = MODE_DRAIN;

  // Drain timer = 10 saat
  timer = DRAIN_TIMER_START;

  mb.Ireg(TIMER_IREG, timer);


  // --------------------------------------------------
  // Fill OFF
  // --------------------------------------------------

  mb.Ists(FILLVALVE_ISTS, 0);
  mb.Ists(FILLLED_ISTS, 0);


  // --------------------------------------------------
  // Drain ON
  // --------------------------------------------------

  mb.Ists(DRAINVALVE_ISTS, 1);
  mb.Ists(DRAINLED_ISTS, 1);


  // Reset timer
  previousMillis = millis();


  Serial.println();
  Serial.println("====================");
  Serial.println("MODE : DRAIN");
  Serial.println("TIMER: 10 seconds");
  Serial.println("====================");
}


// ======================================================
// READ PHYSICAL BUTTON
// ======================================================

void updateButtons()
{
  /*
    INPUT_PULLUP:
    Button tidak ditekan = HIGH
    Button ditekan       = LOW

    Oleh itu:

    !digitalRead()
    akan menghasilkan:

    Tidak ditekan = false
    Ditekan       = true
  */

  fillButton  = digitalRead(FILL_BUTTON_PIN);
  drainButton = digitalRead(DRAIN_BUTTON_PIN);

  fillIO = mb.Coil(FILLBTN_COIL);
  drainIO = mb.Coil(DRAINBTN_COIL);
}


// ======================================================
// PROCESS COMMAND
// ======================================================

void processCommand()
{
  /*
    Hanya boleh memulakan proses baru
    apabila timer sudah tamat.
  */

  if (timer > 0)
  {
    return;
  }


  // --------------------------------------------------
  // FILL
  // Fill button  = ON
  // Drain button = ON
  // --------------------------------------------------

  if ((!fillButton && drainButton) || (fillIO && drainIO))
  {
    startFill();
  }


  // --------------------------------------------------
  // DRAIN
  // Fill button  = OFF
  // Drain button = OFF
  // --------------------------------------------------

  else if ((fillButton && !drainButton) || (!fillIO && !drainIO))
  {
    startDrain();
  }
}


// ======================================================
// UPDATE TIMER
// ======================================================

void updateTimer()
{
  // Tiada proses berjalan
  if (timer <= 0)
  {
    return;
  }


  unsigned long currentMillis = millis();


  // Tunggu 1 saat
  if (currentMillis - previousMillis >= TIMER_INTERVAL)
  {
    previousMillis = currentMillis;

    // Countdown
    timer--;


    // Update Modbus Input Register
    mb.Ireg(TIMER_IREG, timer);


    // Papar pada Serial Monitor
    Serial.print("Mode: ");

    if (mode == MODE_FILL)
    {
      Serial.print("FILL");
    }
    else if (mode == MODE_DRAIN)
    {
      Serial.print("DRAIN");
    }
    else
    {
      Serial.print("NORMAL");
    }

    Serial.print(" | Timer: ");
    Serial.println(timer);


    // ------------------------------------------------
    // Timer tamat
    // ------------------------------------------------

    if (timer == 0)
    {
      resetSystem();

      Serial.println("Timer tamat");
      Serial.println("MODE: NORMAL");
    }
  }
}


// ======================================================
// UPDATE LED
// ======================================================

void updateLED()
{
  // Fill LED
  digitalWrite(
    FILL_LED_PIN,
    mb.Ists(FILLLED_ISTS)
  );


  // Drain LED
  digitalWrite(
    DRAIN_LED_PIN,
    mb.Ists(DRAINLED_ISTS)
  );
}


// ======================================================
// SETUP
// ======================================================

void setup()
{
  // --------------------------------------------------
  // Serial
  // --------------------------------------------------

  Serial.begin(115200);

  delay(100);


  // --------------------------------------------------
  // GPIO
  // --------------------------------------------------

  pinMode(
    FILL_BUTTON_PIN,
    INPUT_PULLUP
  );

  pinMode(
    DRAIN_BUTTON_PIN,
    INPUT_PULLUP
  );


  pinMode(
    FILL_LED_PIN,
    OUTPUT
  );

  pinMode(
    DRAIN_LED_PIN,
    OUTPUT
  );


  // Pastikan LED OFF
  digitalWrite(
    FILL_LED_PIN,
    LOW
  );

  digitalWrite(
    DRAIN_LED_PIN,
    LOW
  );


  // --------------------------------------------------
  // WiFi
  // --------------------------------------------------

  Serial.println();
  Serial.println("==============================");
  Serial.println("Connecting to WiFi...");
  Serial.println("==============================");


  WiFi.begin(
    WIFI_SSID,
    WIFI_PASSWORD
  );


  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);

    Serial.print(".");
  }


  Serial.println();
  Serial.println("WiFi connected");


  Serial.print("IP Address: ");
  Serial.println(
    WiFi.localIP()
  );


  // --------------------------------------------------
  // Modbus TCP Server
  // --------------------------------------------------

  mb.server();


  setupModbus();


  // --------------------------------------------------
  // Initial State
  // --------------------------------------------------

  resetSystem();


  Serial.println();
  Serial.println("==============================");
  Serial.println("Modbus TCP Server Started");
  Serial.println("==============================");
}


// ======================================================
// LOOP
// ======================================================

void loop()
{
  // --------------------------------------------------
  // Process Modbus TCP
  // --------------------------------------------------

  mb.task();

  // --------------------------------------------------
  // Physical Button
  // -> Modbus Coil
  // --------------------------------------------------

  updateButtons();

  // --------------------------------------------------
  // Process Fill / Drain
  // --------------------------------------------------

  processCommand();

  // --------------------------------------------------
  // Timer
  // --------------------------------------------------

  updateTimer();


  // --------------------------------------------------
  // LED
  // --------------------------------------------------

  updateLED();
}
C++