Introduction: ARDUINO™ "HACK" PLC SIEMENS™ THROUGH LAN/WIFI INDUSTRIAL ETHERNET NETWORK

About: PLC, Arduino - Do it yourself project

Today, I’d share with you how to connect Arduino with PLC S7-300 via Ethernet. From Internet, I also studied some libraries that can handle this communication, such as:

I selected “settimino” library to test communication between Arduino & PLC because of its pro & easy to understand. To get this test, you need to have certain knowledge of PLC Siemens and also have to spend quite money of course....

Cautions:

Do not connect Arduino with Ethernet shield to factory industrial Ethernet network & apply this test. It can cause serious consequences.

With project's VIDEO below, NODEMCU + MPU6050 is communicated with PLC via WIFI to control Speed/ Direction of DC motor.

Step 1: ARDUINO LIBRARY & PLC SIEMENS

Dave Nardella - Italian - is the author of two great libraries of interfaces between ARM Linux / MIPS microprocessors and PLC Siemens S7 ™:

  • Snap7: Snap7 is a cross-platform, open source Ethernet communication library for Siemens PLC (LOGO 0BA7 / 0BA8, S7-200 / 300/400 & CPUs 1200/1500) and Raspberry PI (1 and 2), BeagleBone Black.... Link:

http://snap7.sourceforge.net/

  • Settimino: It was rewritten from Snap7 to be compatible with the Arduino platform.

http://settimino.sourceforge.net/

About PLC, you can refer to the Siemens official website:

Step 2: B.O.M

Bill Of Material is as below:

Important note about Ethernet Shield: The resistor value in the red rectangle should be 49R9 or 510 (about 50 ~ 51 ohm), with some Ethernet Shields, this value is 511 (510 ohm). I have a problem with the 511 resistor so I cannot connect to the PLC & finally have to buy another one with R510, then, the connection is successful.

I tested PLC & Arduino communications with 2 options:

  • ARDUINO UNO R3 equipped with ARDUINO Ethernet Shield R3.
  • NodeMCU ESP 12-E V1.0 standalone & connect via wifi router.

Step 3: HARDWARE CONFIGURATION - ETHERNET SHIELD

Hardware configuration - ARDUINO UNO R3 equipped with ARDUINO Ethernet Shield R3

Hardware configuration explanation:

  • With this configuration you have the freedom to modify both PLC program/Data and Arduino Sketch. And Snap7 ClientDemo is optional.
  • In the Arduino IDE, we used ConnectTo () to define the IP_Address, Rack, Slot for the first connection, which set up the internal parameters and connect to the PLC.
  • In the STEP 7 program, the IP_Address of the PLC (CP343-1) must be the address declared in the Arduino program: 192.168.0.71. See details in the image.

Hardware config in SIMATIC MANAGER

Actual hardware demo kit:

With above configuration, two controllers can exchange data with each other easily, for example:

  • PLC side can get the MPU-6050 data, read distance of HC-SR05 or control RC-SERVO.
  • Arduino side can read temperature from PT100/ thermocouples, get status of 24V proximity switches, or turn on / off 220VAC lamps.

Step 4: PROGRAM - ETHERNET SHIELD

    1. SETTIMINO LIBRARY

    You can download the link at: Settimino Library, which includes: library, detailed instructions for using settiminno library and sample programs.

    In the manual, please note the terms "Big-Endian" and "Little-Endian" to describe differences in reading and writing data between ARDUINO ™ (Little -Endian) and PLC Siemens S7 ™ ( Big -Endian).

    • Big –Endian: MSB (left-most bit) --> LSB (right-most bit), for example, DWORD 0x4C21112F is stored in PLC Siemens S7 ™ as follows:

    • Little Endian: LSB (left-most bit) --> MSB (right-most bit), with same DWORD above but ARDUINO ™ will be stored in the opposite way to PLC

    2. PLC PROGRAM

    I used STEP 7 Professional 2010 V5.5 to configure hardware & write program for PLC. If you are an automatic engineer, you will be very knowledgeable about it...

    You can also use the SIMATIC WinCC as an HMI / SCADA system to control Siemens S7 ™ PLCs or ARDUINO ™.

    Settimino can directly access the Siemens PLCs with built-in Ethernet moldule (such as CPU 315-2PN/DP, CPU412-2PN/DP, CPU414-3PN/DP ...) or via a separated Ethernet card (like CP343 for CPU S7-300), or CP443 for CPU S7-400). I was tested in the case of S7-300 CPU + CP343.

    3. ARDUINO PROGRAM

    3.1. Arduino read DB values from PLC - “DBGetDemo”

    • In PLC program, I created DB2 containing 100 bytes with initial values assigned in order from 0 ~ 99.
    • The “DBGetDemo” read 100 bytes values from DB2 and displayed them on the Serial Monitor of Arduino IDE.
    • While Arduino reading DB2 from the PLC, I used "FORCE" function to change value of two bytes DB2.DBB0 & DB2.DBB1 to check whether Arduino is reading correctly or not.

    Detail you can see at:

    3.2. Write to PLC’s Data Block at Security Level 3

    Program Overview:

    • Enable Security Level 3 (Read / Write Protection) in the CPU300 configuration – Then, Compile & Download to Module.
    • In PLC, we created DB1 (Data Block) containing 1,090 bytes with KNOW_HOW_PROTECT (generally speaking, this DB is locked in Siemens terminology).
    • The “WriteDemo” program writes the desired value from Arduino to the DB1.DBB0 & DB1.DBB1 being stored in the PLC.
    • Change DB1 values by Arduino program and check the PLC's DB1 online monitoring.

    Detail you can check at:

    Step 5: HARDWARE CONFIGURATION - NODEMCU ESP 12-E V1.0

    Hardware diagram:

    Actual system picture:

    Wireless router is located inside my home with distance about 15m and it is not shown on the picture.

    NodeMCU intergate MPU6050 as picture:

    Hardware configuration explanation:

    • As picture above, 24VDC PLC outputs are connected to "24V to 5V Converter Board" to change voltage level and then control DC motor through L298N with PWM integrated function inside PLC 314C-2DP. I had to do like that because I didn't have motor DC drive connecting with PLC.
    • NodeMCU + MPU6050 are connected to PLC system by wifi router and it took roll value from MPU6050 to adjust direction & speed of DC motor.
    • This demo is based on integrated PWM function in CPU314C-2DP. To control pulse width modulation via the user program, we use SFB 49 "PULSE". The following operations are available:
      • * Starting/stopping via software gate SW_EN.
      • * Enabling/controlling the output DO.
      • * Retrieving the status bits STS_EN, STS_STRT and STS_DO.
      • * Input of the output value.
      • * Jobs for reading/writing the registers.
    • From beginning of this project, my purpose is making a vibration sensor that can be integrated to industrial network, something like: https://www.dytran.com/Series-7556A-Analog-6D-Sen...It will be very cheap & useful for vibration protect or vibration analysis. For example with high power motor, it can be attached on gearbox and take 6DOF vibration data for analysis or popup alarm to PLC system when vibration is higher than limit. Or we can use it as portable analysis device for prevented maintenance. With this demo, it's just start point & it can be come true with acceptable sampling time.

    Step 6: ARDUINO PROGRAM - NODEMCU ESP 12-E V1.0

    NodeMCU control PLC through Wifi network

    /*----------------------------------------------------------------------
    Thank to Davide Nardella
    ----------------------------------------------------------------------*/
    // Wifi -> #define S7WIFI
    // Cable -> #define S7WIRED
    #define S7WIFI
    #include
    #include
    #ifdef S7WIFI
    #include
    #endif
    #include "Settimino.h"
    #include
    // MPU6050 Slave Device Address
    const uint8_t MPU6050SlaveAddress = 0x68;
    // Select SDA and SCL pins for I2C communication
    const uint8_t scl = D1;
    const uint8_t sda = D2;
    // sensitivity scale factor respective to full scale setting provided in datasheet
    const uint16_t AccelScaleFactor = 16384;
    const uint16_t GyroScaleFactor = 131;
    // MPU6050 few configuration register addresses
    const uint8_t MPU6050_REGISTER_SMPLRT_DIV = 0x19;
    const uint8_t MPU6050_REGISTER_USER_CTRL = 0x6A;
    const uint8_t MPU6050_REGISTER_PWR_MGMT_1 = 0x6B;
    const uint8_t MPU6050_REGISTER_PWR_MGMT_2 = 0x6C;
    const uint8_t MPU6050_REGISTER_CONFIG = 0x1A;
    const uint8_t MPU6050_REGISTER_GYRO_CONFIG = 0x1B;
    const uint8_t MPU6050_REGISTER_ACCEL_CONFIG = 0x1C;
    const uint8_t MPU6050_REGISTER_FIFO_EN = 0x23;
    const uint8_t MPU6050_REGISTER_INT_ENABLE = 0x38;
    const uint8_t MPU6050_REGISTER_ACCEL_XOUT_H = 0x3B;
    const uint8_t MPU6050_REGISTER_SIGNAL_PATH_RESET = 0x68;
    int16_t AccelX, AccelY, AccelZ, Temperature, GyroX, GyroY, GyroZ;
    #define DO_IT_SMALL
    // Enter a MAC address and IP address for your controller below.
    // The IP address will be dependent on your local network:
    byte mac[] = {
    0x90, 0xA2, 0xDA, 0x0F, 0x08, 0xE11 };
    IPAddress Local(192,168,0,70); // Local Address
    IPAddress PLC(192,168,0,71); // PLC Address
    // Following constants are needed if you are connecting via WIFI
    // The ssid is the name of my WIFI network (the password obviously is wrong)
    char ssid[] = "FPT-Telecom"; // Your network SSID (name)
    char pass[] = "12345689"; // Your network password (if any)
    IPAddress Gateway(192, 168, 0, 1);
    IPAddress Subnet(255, 255, 255, 0);
    int DBNum = 2; // This DB must be present in your PLC
    byte Buffer[512];
    #ifdef S7WIFI
    // S7Client will create a WiFiClient as TCP Client
    S7Client Client(_S7WIFI);
    #else
    // S7Client will create an EthernetClient as TCP Client
    S7Client Client(_S7WIRED);
    #endif
    unsigned long Elapsed; // To calc the execution time
    //----------------------------------------------------------------------
    // Setup : Init Ethernet and Serial port
    //----------------------------------------------------------------------
    void setup() {
    // Open serial communications and wait for port to open:
    Wire.begin(sda, scl);
    MPU6050_Init();
    Serial.begin(9600);
    while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
    }
    #ifdef S7WIFI
    //--------------------------------------------- ESP8266 Initialization
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);
    WiFi.begin(ssid, pass);
    WiFi.config(Local, Gateway, Subnet);
    while (WiFi.status() != WL_CONNECTED)
    {
    delay(500);
    Serial.print(".");
    }
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.print("Local IP address : ");
    Serial.println(WiFi.localIP());
    #else
    //--------------------------------Wired Ethernet Shield Initialization
    // Start the Ethernet Library
    Ethernet.begin(mac, Local);
    // Setup Time, someone said me to leave 2000 because some
    // rubbish compatible boards are a bit deaf.
    delay(2000);
    Serial.println("");
    Serial.println("Cable connected");
    Serial.print("Local IP address : ");
    Serial.println(Ethernet.localIP());
    #endif
    }
    //----------------------------------------------------------------------
    // Connects to the PLC
    //----------------------------------------------------------------------
    bool Connect()
    {
    int Result=Client.ConnectTo(PLC,
    0, // Rack (see the doc.)
    2); // Slot (see the doc.)
    Serial.print("Connecting to ");Serial.println(PLC);
    if (Result==0)
    {
    Serial.print("Connected ! PDU Length = ");Serial.println(Client.GetPDULength());
    }
    else
    Serial.println("Connection error");
    return Result==0;
    }
    //----------------------------------------------------------------------
    // Dumps a buffer (a very rough routine)
    //----------------------------------------------------------------------
    void Dump(void *Buffer, int Length)
    {
    int i, cnt=0;
    pbyte buf;
    if (Buffer!=NULL)
    buf = pbyte(Buffer);
    else
    buf = pbyte(&PDU.DATA[0]);
    Serial.print("[ Dumping ");Serial.print(Length);
    Serial.println(" bytes ]==========================");
    for (i=0; i
    {
    cnt++;
    if (buf[i]<0x10)
    Serial.print("0");
    Serial.print(buf[i], HEX);
    Serial.print(" ");
    if (cnt==16)
    {
    cnt=0;
    Serial.println();
    }
    }
    Serial.println("===============================================");
    }
    //----------------------------------------------------------------------
    // Prints the Error number
    //----------------------------------------------------------------------
    void CheckError(int ErrNo)
    {
    Serial.print("Error No. 0x");
    Serial.println(ErrNo, HEX);
    // Checks if it's a Severe Error => we need to disconnect
    if (ErrNo & 0x00FF)
    {
    Serial.println("SEVERE ERROR, disconnecting.");
    Client.Disconnect();
    }
    }
    //----------------------------------------------------------------------
    // Profiling routines
    //----------------------------------------------------------------------
    void MarkTime()
    {
    Elapsed=millis();
    }
    //----------------------------------------------------------------------
    void ShowTime()
    {
    // Calcs the time
    Elapsed=millis()-Elapsed;
    Serial.print("Job time (ms) : ");
    Serial.println(Elapsed);
    }
    //----------------------------------------------------------------------
    // Main Loop
    //----------------------------------------------------------------------
    void loop()
    {
    int Size, Result;
    void *Target;
    double Ax, Ay, Az, T, Gx, Gy, Gz;
    double roll = 0.00, pitch = 0.00; //Roll & Pitch are the angles which rotate by the axis X and y
    Read_RawValue(MPU6050SlaveAddress, MPU6050_REGISTER_ACCEL_XOUT_H);
    //divide each with their sensitivity scale factor
    Ax = (double)AccelX/AccelScaleFactor;
    Ay = (double)AccelY/AccelScaleFactor;
    Az = (double)AccelZ/AccelScaleFactor;
    T = (double)Temperature/340+36.53; //temperature formula
    Gx = (double)GyroX/GyroScaleFactor;
    Gy = (double)GyroY/GyroScaleFactor;
    Gz = (double)GyroZ/GyroScaleFactor;
    roll = atan2(Ay , Az) * 57.3;
    pitch = atan2((- Ax) , sqrt(Ay * Ay + Az * Az)) * 57.3;
    //Serial.print(" Roll: "); Serial.println(roll);
    //Serial.print(" Pitch: "); Serial.println(pitch);
    //Serial.print(" Az: "); Serial.print(Az);
    //Serial.print(" T: "); Serial.print(T);
    //Serial.print(" Gx: "); Serial.print(Gx);
    //Serial.print(" Gy: "); Serial.print(Gy);
    //Serial.print(" Gz: "); Serial.println(Gz);
    delay(100);
    //----------------------------------------------------------------------
    // Programm_for as to variable Int
    //----------------------------------------------------------------------
    int write_data = (int)(map(roll, -180, 180, -1000, 1000)); //// Write roll to PLC
    //Serial.print(" Write data prepare: ");Serial.println(write_data);
    //delay(500);
    //----------------------------------------------------------------------
    // Start connessione DB
    //----------------------------------------------------------------------
    #ifdef DO_IT_SMALL
    Size=2;
    Target = NULL; // Uses the internal Buffer (PDU.DATA[])
    #else
    Size=1024;
    Target = &Buffer; // Uses a larger buffer
    #endif
    // Connection
    while (!Client.Connected)
    {
    if (!Connect())
    delay(500);
    }
    //----------------------------------------------------------------------
    // Little_Endian & Big_Endian convertion
    //----------------------------------------------------------------------
    int write_data_var1 = write_data;
    byte* bytes = (byte*)& write_data_var1;
    //----------------------------------------------------------------------
    // Word/Int Correction
    //----------------------------------------------------------------------
    void Correction(void *bytes);
    {
    byte *pointerbyte;
    byte temp;
    pointerbyte=(byte*)(bytes);
    // Swap byte
    temp=*(pointerbyte+1);
    *(pointerbyte+1) = *pointerbyte;
    *pointerbyte = temp;
    }
    //----------------------------------------------------------------------
    // Serial check
    //----------------------------------------------------------------------
    Serial.print("Write ");Serial.print(Size);Serial.println(" bytes from DB1.DBB0");
    // Put the current tick
    MarkTime();
    Result=Client.WriteArea(S7AreaDB, // We are requesting DB access
    1, // DB Number = 1
    0, // Start from byte N.0
    Size, // We need "Size" bytes
    bytes); // Put them into our target (Buffer or PDU)
    if (Result==0)
    {
    ShowTime();
    Dump(Target, Size);
    }
    else
    CheckError(Result);
    delay(500);
    }
    void I2C_Write(uint8_t deviceAddress, uint8_t regAddress, uint8_t data){
    Wire.beginTransmission(deviceAddress);
    Wire.write(regAddress);
    Wire.write(data);
    Wire.endTransmission();
    }
    // read all 14 register
    void Read_RawValue(uint8_t deviceAddress, uint8_t regAddress){
    Wire.beginTransmission(deviceAddress);
    Wire.write(regAddress);
    Wire.endTransmission();
    Wire.requestFrom(deviceAddress, (uint8_t)14);
    AccelX = (((int16_t)Wire.read()<<8) | Wire.read());
    AccelY = (((int16_t)Wire.read()<<8) | Wire.read());
    AccelZ = (((int16_t)Wire.read()<<8) | Wire.read());
    Temperature = (((int16_t)Wire.read()<<8) | Wire.read());
    GyroX = (((int16_t)Wire.read()<<8) | Wire.read());
    GyroY = (((int16_t)Wire.read()<<8) | Wire.read());
    GyroZ = (((int16_t)Wire.read()<<8) | Wire.read());
    }
    //configure MPU6050
    void MPU6050_Init(){
    delay(150);
    I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_SMPLRT_DIV, 0x07);
    I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_PWR_MGMT_1, 0x01);
    I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_PWR_MGMT_2, 0x00);
    I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_CONFIG, 0x00);
    I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_GYRO_CONFIG, 0x00);//set +/-250 degree/second full scale
    I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_ACCEL_CONFIG, 0x00);// set +/- 2g full scale
    I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_FIFO_EN, 0x00);
    I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_INT_ENABLE, 0x01);
    I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_SIGNAL_PATH_RESET, 0x00);
    I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_USER_CTRL, 0x00);
    }

    Step 7: PLC PROGRAM - NODEMCU ESP 12-E V1.0

    PLC program control DC motor:

    • Motor direction (CW or CCW) is according to MPU6050 - ROLL VALUE (Positive or Negative). Q124.4.& Q124.5 connected to "Converter board" and L298N at pin IN1, IN2 to set motor direction.
    • And motor speed - DB1.DBW2 - is ABS(ROLL VALUE). PLC PWM output Q124.0 is connected to "Coverter board" & L298N at pin ENA to control PWM of DC motor.
    • SFB49 was used in ladder program below to generate PWM at output channel 0 - Q124.0.

    Step 8: 24V TO 5V CONVERTER BOARD

    I used ULN2803 to convert the voltage level. Each ULN2803 contain eight darling-ton transistors, it means we can convert 8 signals by using one ULN2803. Circuit diagram as follow:

    Picture of Converter Board

    Step 9: MORE PICTURES & VIDEOS

    About this project, you can check at my channel address:

    And more videos for PLC project at:

    Step 10: SUMMARY

    • With S7-300 PLC in this test, Settimino can almost read / write to the PLC regardless of the CPU Protection Level. This security issue caused certainly a great deal of difficulty for automation engineers who work with Siemens ™ CPUs.
    • Arduino is not compatible with industrial environments such as dust, humidity, temperature, and cannot meet reliability requirements as PLC. But more or less, this is not bad way for us to make IoT / Smart Home using new generation CPUs Siemens with built-in Ethernet port, affordable, in conjunction with the Arduino.
    • Special thank to Dave Nardella for "settimino" library....

    PLEASE VOTE FOR ME ... so that I have more motivation to do more useful projects!!!