Home / C# / How to write a Siemens S7 plc driver with C# and Sharp7

How to write a Siemens S7 plc driver with C# and Sharp7

Sharp7 is a new C# driver to communicate to Siemens S7 Plc. The driver has been written by Davide Nardella, the author of Snap7 library, and it’s retro-compatible with the C# wrapper of Snap7. So if you were using Snap7 and the C# wrapper, you can easily replace both with Sharp7 library.

One of the biggest advantages of having the full library in C# is during debugging. You can place a breakpoint in Visual Studio and debug the messages sent and received from the TCP socket without having to leave Visual Studio and without having to use network sniffers or other external software.

Also with Sharp7 it’s easier to read/write multiple variables in a single request than with Snap7. Now ReadMultiVars and WriteMultiVars don’t require pointers and memory management.

Watch on Youtube

Download

The official website of Sharp7 is http://snap7.sourceforge.net/

Sharp7 can be downloaded from Sourceforge download page: https://sourceforge.net/projects/snap7/files/Sharp7/

sharp7-folder

The downloaded archive contains 3 folders:

  • Src: contains the sources of the library. In case of Sharp7 this is the library itself, as there are no binaries to be included
  • Doc: contains the user manual in pdf
  • Examples: contains 4 examples, one for Windows Forms, two for Console application and one for UWP

Documentation

The library comes itself with a complete user manual and examples for Windows Forms, Universal Windows Platform and a simple Console application.

As for the manual, there are some sections that are really important:

  • Compatibility (page 9): this section shows the compatibility of the library functions with the plc that you are using. S7300-S7400 supports every function, while newer plcs can only read and write data.
  • Connection (page 10-11-12): this section shows how to setup and configure the CPU and DB to grant the access to external software.
  • Connection parameters (page 22-26): this one explains how to setup IP, rack and slot of the plc, in order to be able to communicate.
  • Read/Write function description (27-34): contains the definition of the functions that you will use the most, to read and write memory area.
  • Helpers and example (57- 61): contains the definition of conversion function from .Net to S7 variables and some examples.

Configuration for S7-1200

To test the library we will write a simple console program. It will show how to connect/disconnect, read and write block of DB and multiple variables.

In this example we will connect to a S7-1200 plc. This unit has to be configured properly (see page 12 of the user manual).

So let’s uncheck optimized block access for every DB (under program blocks) and configure the CPU protection.

s7-1200-config-1

Creation of the sample project

When importing Sharp7, the best approach is to create a C# Class library project and place Sharp7 sources inside that project.

sharp7-create-new-proj

sharp7-new-project

sharp7-example-solution

Then we will reference the Sharp7 project in all the project that we need.

screenshot-2016-10-20-01-03-16

add-reference

Connection to the plc

Driver creation, connection and disconnection are really easy to do.

// Create and connect the client
var client = new S7Client();
int result = client.ConnectTo("127.0.0.1", 0, 1);
if(result == 0)
{
    Console.WriteLine("Connected to 127.0.0.1");
}
else
{
    Console.WriteLine(client.ErrorText(result));
}

// Disconnect the client
client.Disconnect();

Read from DB

Reading from a single db is also simple, but you must be comfortable with using buffers, converters and the snap7 functions.

To read for example 18 bytes, we need to create an array of 18 bytes. Then pass it to the Read function to populate it with the data from the cpu, then convert all the data from S7 format to C# format.

db1-read

For example this is the code to read this DB:

Console.WriteLine("\n---- Read DB 1");

byte[] db1Buffer = new byte[18];
result = client.DBRead(1, 0, 18, db1Buffer);
if(result != 0)
{
    Console.WriteLine("Error: " + client.ErrorText(result));
}
int db1dbw2= S7.GetIntAt(db1Buffer, 2);
Console.WriteLine("DB1.DBW2: " + db1dbw2);

double db1ddd4 = S7.GetRealAt(db1Buffer, 4);
Console.WriteLine("DB1.DBD4: " + db1ddd4);

double db1dbd8 = S7.GetDIntAt(db1Buffer, 8);
Console.WriteLine("DB1.DBD8: " + db1dbd8);

double db1dbd12 = S7.GetDWordAt(db1Buffer, 12);
Console.WriteLine("DB1.DBD12: " + db1dbd12);

double db1dbw16 = S7.GetWordAt(db1Buffer, 16);
Console.WriteLine("DB1.DBD16: " + db1dbw16); 

Write to DB

The Write function works in a way which is really similar to the Read function. We have to create a buffer, populate it with the data (converted in S7 format) that we need to write, and then call the Write function.
In this case we want to write these values:

db1-write

So we need to create a buffer of 12 bytes, populate the first 4 with a real number, the 4th-7th with a Dint number, the 8th -12th with a DWord number. Also, since our buffer starts from 4.0, we must set the start index to 4.
One has to be really careful with indexing, so what I usually do when creating the buffer is to use the real offset of the variable and to subtract the start index.
So here is the code to write DB1.DBD4, DB1.DBD8 and DB1.DBD12:

db1Buffer = new byte[12];
const int START_INDEX = 4;
S7.SetRealAt(db1Buffer, 4 - START_INDEX, (float)54.36);
S7.SetDIntAt(db1Buffer, 8 - START_INDEX, 555666);
S7.SetDWordAt(db1Buffer, 12 - START_INDEX, 123456);
result = client.DBWrite(1, START_INDEX, db1Buffer.Length, db1Buffer);
if (result != 0)
{
    Console.WriteLine("Error: " + client.ErrorText(result));
}

ReadMultiVar

Reading multiple variables in a single request is one of the most used features of S7 drivers, especially if one needs to read variables that are scattered around multiple DBs.
Example: let’s suppose, we want to read with a single request the values from DB1 and DB3.

readmultivar

So we have to create 2 buffers, in this case both of the same length, and we create an S7MultiVar variable. The S7MultiVar variables have a method “Read”, which populates all the buffers with the data from the plc.
This is the code to make the read:

// Read multi vars

var s7MultiVar = new S7MultiVar(client);
byte[] db1 = new byte[18];
s7MultiVar.Add(S7Consts.S7AreaDB, S7Consts.S7WLByte, 1, 0, 18, ref db1);
byte[] db3 = new byte[18];
s7MultiVar.Add(S7Consts.S7AreaDB, S7Consts.S7WLByte, 3, 0, 18, ref db3);
result = s7MultiVar.Read();
if (result != 0)
{
    Console.WriteLine("Error on s7MultiVar.Read()");
}

Once we called the read method, we can convert the data with the converters

db1dbw2 = S7.GetIntAt(db1, 2);
Console.WriteLine("DB1.DBW2.0 = {0}", db1dbw2);

db1ddd4 = S7.GetRealAt(db1, 4);
Console.WriteLine("DB1.DBW4.0 = {0}", db1ddd4);

db1dbd8 = S7.GetDIntAt(db1, 8);
Console.WriteLine("DB1.DBW8.0 = {0}", db1dbd8);

db3dbw2 = S7.GetIntAt(db3, 2);
Console.WriteLine("DB3.DBW2.0 = {0}", db3dbw2);

db3dbd4 = S7.GetRealAt(db3, 4);
Console.WriteLine("DB3.DBW4.0 = {0}", db3dbd4);

db3dbd8 = S7.GetDIntAt(db3, 8);
Console.WriteLine("DB3.DBW8.0 = {0}", db3dbd8);

WriteMultiVars

This function is usually used less often than the ReadMultiVars, but just in case you need it, here is how it works.
We have to create a buffer for every variable we want to write, then we populate the buffer with the converted values that we want to write, and then we call the Write method.
In this case we will write the DB1 and DB3 values from offset 2 to offset 8.

writemultivar

Again, here is the code. Pay special attention to start index and offsets.

// Write multi vars
s7MultiVar = new S7MultiVar(client);
const int DB1_START_INDEX = 2;
db1 = new byte[10];
S7.SetIntAt(db1, 2 - DB1_START_INDEX, 50);
S7.SetRealAt(db1, 4 - DB1_START_INDEX, (float)36.5);
S7.SetDIntAt(db1, 8 - DB1_START_INDEX, 123456);
s7MultiVar.Add(S7Consts.S7AreaDB, S7Consts.S7WLByte, 1, DB1_START_INDEX, db1.Length, ref db1);

const int DB3_START_INDEX = 2;
db3 = new byte[10];
S7.SetIntAt(db3, 2 - DB3_START_INDEX, -50);
S7.SetRealAt(db3, 4 - DB3_START_INDEX, (float)-25.36);
S7.SetDIntAt(db3, 8 - DB3_START_INDEX, -123456);
s7MultiVar.Add(S7Consts.S7AreaDB, S7Consts.S7WLByte, 3, DB3_START_INDEX, db3.Length, ref db3);
result = s7MultiVar.Write();
if (result != 0)
{
    Console.WriteLine("Error on s7MultiVar.Read()");
}

Sample code

You can find the sample code and the Plc program on Github: https://github.com/mesta1/Sharp7-example

Share Button

13 comments

  1. I don’t thing the Siemens project (the one loaded in the portal) is in the download – at least I cant seem to locate it.

  2. is it work in simatic manager?

    • Yes of course.

      • So.whats different between wincc and C#? Wincc is easy.I can edit dB in wincc (read or write)!why use c#?

        And
        How find component C# for Hmi?

        • WinCC, like all other HMI packages (FactoryTalk, Wonderware, iFix, …), is absolutely fine for the most of the tasks. You can do almost everything that you can do with C#.

          However, I find that more and more OEM that write their own software without those HMI packages.

          There are some advantages of using C# instead of WinCC or other HMI, for example you can use a version control software like Git, you can do countinous integration, unit testing, automation tests, etc. Also software written in C# scales well if you have a lot of programmers working on it.

          Also if you need to integrate external software, dlls, strange stuff from different vendors, custom protocols, custom hardware, I think that these HMIs may have some limits.

          One thing has to be clear, C# is more expensive because the learning curve is more steep.

          For the components there is something online, but the most components for industrial automation are not free. However it’s not that difficult to write such components. And if you need charts, gauges and other controls, there is plenty of them. This is just an example http://wpftoolkit.codeplex.com/

  3. hi! io lkearning how o use this for a Project, now I have followed every step in your tutorial, but at the momento of running my program the buffer on the readresult variable always is 262144, I download it the plc program to my plc I´m using a CPU 1212C with tia portal v14, I configured the DB and the protection of the plc and my program connects with the adress but does not read anything, since I used your program the buffer suze that I used is 38

    • 262144 is Bad Datasize passed to send/recv. It means that you are probably reading more than you can read. I suspect that your DB has less than 38 bytes.

  4. Hello! Thanks for the tutorial. First time I used the S7.Net library, but I was only able to get it working with PLCSIM, not with real PLC. So I found this Sharp7 library and your tutorial. But the problem is that I am a newbie and can’t really figure out how to manage simple tasks like writing/reading the PLC outputs (like set Q0.0 to 1) or memory bits (e.g. read what value is in M2.1). My attempt so far was something like this: client.WriteArea(0x82, 0, 0, 1, 0x01, new byte[] {1}), which was meant to set the state of first output to 1, but it didn’t work. Could you please guide me on the right way?

    • Sharp7 has some helpers that should be used instead of ReadArea: MBRead, MBWrite, EBRead, EBWrite, ABRead, ABWrite. M is the memory area, E probably are inputs, and A are outputs. German abbreviations.
      If you need to write a single bit, without modifying the other bits, I wrote an helper in a recent article, maybe you can take a look at it and get some inspiration.

      /// <summary>
      /// Writes a bit at the specified address. Es.: DB1.DBX10.2 writes the bit in db 1, word 10, 3rd bit
      /// </summary>
      /// <param name="address">Es.: DB1.DBX10.2 writes the bit in db 1, word 10, 3rd bit</param>
      /// <param name="value">true or false</param>
      /// <returns></returns>
      private int WriteBit(string address, bool value)
      {
          var strings = address.Split('.');
          int db = Convert.ToInt32(strings[0].Replace("DB", ""));
          int pos = Convert.ToInt32(strings[1].Replace("DBX", ""));
          int bit = Convert.ToInt32(strings[2]);
          return WriteBit(db, pos, bit, value);
      }
      
      private int WriteBit(int db, int pos, int bit, bool value)
      {
          lock (_locker)
          {
              var buffer = new byte[1];
              S7.SetBitAt(ref buffer, 0, bit, value);
              return _client.WriteArea(S7Consts.S7AreaDB, db, pos + bit, buffer.Length, S7Consts.S7WLBit, buffer);
          }
      }
      
      • Thanks, I tried it and was able to get it working with PLCSIM. With this code I was able to set the Q0.0 to 1: client.ABWrite(0, 1, new byte[] { 1 }); However when I download the same TIA project to my real S71200 plc and try to set the Q0.0 with the same C# code, it connects OK, but the ABWrite method returns error code – ISO : Invalid PDU received (the same error happens when using other methods like EBRead, MBWrite, MBRead, DBWrite,…). I have double checked that in the PLC properties the PUT/GET check box is checked and Full Access is granted. Have you tried the Sharp7 library with a real PLC not only PLCSIM? The same thing happens with S7.Net driver. I have thought that my PLC could be faulty, because when I want to Flash LEDs through TIA Portal an error dialog pops up saying: “The target online device does not support LEDs Flashing or is not reachable.” However everything else works okay, I can Go Online, monitor values, force values throught Force table, PLC program runs ok. Do you have experience with such behaviour or am I missing something when I transfer from PLCSIM to real device? Thanks for your time.

Leave a Reply