You are on page 1of 14

/* Siemens Simatic S5 PLC transfer of a dataword from an Arduino Mega equipped with an Adafruit 2*16 LCD display with

5 integrated buttons.
* The transfer protocol used is based on the document as511protocol_description by Luca Gallina dated March 17, 2004.
* Also, by modifying the "standard" TTY/RS232 converter found by our friend Google, a third computer can monitor the serial communications
* between the programming unit (I.E the computer running S5) and the PLC AS511 PG port by using for example an excellent freeware called
* Realterm, to confirm proper communications to/from the PLC.
* All information shared within, as is and with no strings attached whatsoever, is already available for download elsewhere on the WWW.
* Standard disclaimer applies, I.E use at your own risk. If TSHTF = Don't come crying to me.
*
*/

#include <Wire.h> // Standard Adafruit libraries etc as found on the interwebs.


#include <Adafruit_RGBLCDShield.h> // I'm sure there's variables etc that which aren't used at all
#include <utility/Adafruit_MCP23017.h> // in this sketch, but I'm too lazy to clean it up.
#include <EEPROM.h> // Also, WYSIWYG. Done in a haste with no structure whatsoever,
#include <SPI.h> // littered with makeshift variables, loops, etc etc.
#include <SD.h> // I'm sure hackers would have a field day boiling down this code
#include <LiquidCrystal.h> // to a few intricate loops using arrays and such, good for them.
// Besides, if it ain't broken, don't try to fix it, right....? ;)
Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();

#define WHITE 0x7

int back = 0;
int old = 0;
int uld = 0;
int cur = 0;
int inhib = 0;
int buff[255];
int x = 0;
int y = 0;
int lsb = EEPROM.read(50);
int msb = EEPROM.read(55);
int outp = 0;
int setp = 0;
int contr[19] = {16, 6, 2, 22, 16, 3, 16, 6, 2, 9, 16, 16, 6, 16, 6, 2, 18, 16, 3};
int xsum = 0;
long timeout = 0;
bool brejk = false;
bool AS511_tx_rx = false;
bool up = false;
bool down = false;
bool left = false;
bool right = false;
bool kok = false;

void MAX(){
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Max value =");
lcd.setCursor(0, 1);
lcd.print("32767");
lcd.noCursor(); }

void basic0(){
lcd.setCursor(0, 0);
lcd.print("Setpoint");}

void basic1(){
lcd.setCursor(0, 1);
lcd.print("Input");}

void plcnofun(){
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("PLC not");
lcd.setCursor(0, 1);
lcd.print("responding");
delay(250);
lcd.clear();
delay(150); }

void again(){
lcd.clear();
basic0();
if (outp <=9){ cur = 15; }
if (outp <=99 && outp >= 10){ cur = 14; }
if (outp <=999 && outp >= 100){ cur = 13; }
if (outp <=9999 && outp >= 1000){ cur = 12; }
if (outp <=32767 && outp >= 10000){ cur = 11; }
lcd.setCursor(cur,0);
lcd.print(outp);
basic1();
if (setp <=9){ cur = 15; }
if (setp <=99 && setp >= 10){ cur = 14; }
if (setp <=999 && setp >= 100){ cur = 13; }
if (setp <=9999 && setp >= 1000){ cur = 12; }
if (setp <=32767 && setp >= 10000){ cur = 11; }
lcd.setCursor(cur,1);
lcd.print(setp);
}

void setup() {

Serial.begin(9600,SERIAL_8E1); // Serial protocol for AS511 = 9600 Baud, 8 databits,


Serial3.begin(9600,SERIAL_8E1); // 1 stopbit, even parity.
lcd.begin(16, 2);
int time = millis();
lcd.setBacklight(WHITE); // Power on startup delay, just to make sure the PLC
lcd.setCursor(0, 0); // is fit for fight. This of course indicates that the
lcd.print("Some company ad"); // Arduino and the PLC are powered up simultaneously.
lcd.setCursor(0, 1); // Also, for any practical measures the startup delay
lcd.print("or whatever...."); // could just as well be 10 seconds long, or whatever
delay(3000); // fits the bill.
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Additional");
lcd.setCursor(0, 1);
lcd.print("information");
delay(3000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Short tutorial");
lcd.setCursor(0, 1);
lcd.print("perhaps...");
lcd.noCursor();
delay(3000);
lcd.clear();
basic0();
if (msb == 255){ msb = 0; lsb=1; setp = 1; outp = 1; } // Sets minimum setpoint to 1 in case the onboard (Mega)
outp = 256 * msb + lsb; // EEPROM is empty, which most probably likely is the case
setp = outp; // whenever you use a brand new Mega.
if (outp <=9){ cur = 15; }
if (outp <=99 && outp >= 10){ cur = 14; }
if (outp <=999 && outp >= 100){ cur = 13; }
if (outp <=9999 && outp >= 1000){ cur = 12; }
if (outp <=32767 && outp >= 10000){ cur = 11; }
lcd.setCursor(cur,0);
lcd.print(outp);
basic1();
lcd.setCursor(cur,1);
lcd.print(setp);
cur = 15;

uint8_t i=0;

void loop() {

uint8_t buttons = lcd.readButtons();

if (buttons && !AS511_tx_rx) { // LCD button reading and data preparing/manipulating


// of the setpoint, which is adapted to fit the range
if (buttons & BUTTON_UP) { // from 0 to 32767, which in turn is the range the
// application in the PLC is working with (in this case).
lcd.noCursor();
up = true;
if (inhib < 1) {
if (cur == 15) { if ((setp + 1) <= 32767) { setp = setp + 1; }}
if (cur == 14) { if ((setp + 10) <= 32767) { setp = setp + 10; }}
if (cur == 13) { if ((setp + 100) <= 32767) { setp = setp + 100; }}
if (cur == 12) { if ((setp + 1000) <= 32767) { setp = setp + 1000; }}
if (cur == 11) { if ((setp + 10000) <= 32767) { setp = setp + 10000; }}
if (setp >=32767) { setp = 32767; }
if (setp <0) { setp = 32767; }
if (setp >=0 and setp <=9) { lcd.setCursor(11,1); lcd.print(" "); lcd.setCursor(15,1); }
if (setp >=10 and setp <=99) { lcd.setCursor(11,1); lcd.print(" "); lcd.setCursor(14,1); }
if (setp >=100 and setp <=999) { lcd.setCursor(11,1); lcd.print(" "); lcd.setCursor(13,1); }
if (setp >=1000 and setp <=9999) { lcd.setCursor(11,1); lcd.print(" "); lcd.setCursor(12,1); }
if (setp >=10000 and setp <=32767) { lcd.setCursor(11,1); }
lcd.print(setp);
inhib = 1; }
}
if (buttons & BUTTON_DOWN) {

lcd.noCursor();
down = true;
if (inhib < 1) {
if (cur == 15) { if ((setp - 1) > 0) { setp = setp - 1; }}
if (cur == 14) { if ((setp - 10) > 0) {setp = setp - 10; }}
if (cur == 13) { if ((setp - 100) > 0) {setp = setp - 100; }}
if (cur == 12) { if ((setp - 1000) > 0) {setp = setp - 1000; }}
if (cur == 11) { if ((setp - 10000) > 0) {setp = setp - 10000; }}
if (setp >=0 and setp <=9) { lcd.setCursor(11,1); lcd.print(" "); lcd.setCursor(15,1); }
if (setp >=10 and setp <=99) { lcd.setCursor(11,1); lcd.print(" "); lcd.setCursor(14,1); }
if (setp >=100 and setp <=999) { lcd.setCursor(11,1); lcd.print(" "); lcd.setCursor(13,1); }
if (setp >=1000 and setp <=9999) { lcd.setCursor(11,1); lcd.print(" "); lcd.setCursor(12,1); }
if (setp >=10000 and setp <=32767) { lcd.setCursor(11,1); }
if (setp <=0) { setp = 1; lcd.setCursor (15,1); lcd.print(setp); }
if (setp > 0) { lcd.print(setp); }
inhib = 1; }
}

if (buttons & BUTTON_LEFT) {

lcd.noCursor();
left = true;
if (inhib < 1) {
if (cur > 11) {
cur = cur - 1; }
else { MAX();
delay(2000);
again();
cur = 11;
lcd.cursor();
lcd.setCursor(cur,1);
}
inhib = 1; }
}

if (buttons & BUTTON_RIGHT) {

lcd.noCursor();
right = true;
if (inhib < 1) {
if (cur < 15) {
cur = cur + 1; }
inhib = 1; }
}

if (buttons & BUTTON_SELECT & (up || down || left || right || kok)) {

lcd.noCursor(); // Pressing Select initiates transfer of the setpoint to the


PLC.
lcd.setCursor(cur,1);
left = false;
right = false;
up = false;
down = false;
kok = false;
back = outp;
uld = setp;
EEPROM.write(50,lsb); // The setpoint is stored in the onboard EEPROM from which
EEPROM.write(55,msb); // it's also fetched during powerup.
AS511_tx_rx = true;
inhib = 1;

}
}

else { inhib = 0;
if (up || down || left || right) { lcd.cursor(); lcd.setCursor(cur,1); }
}

if (AS511_tx_rx) { // As the data to the PLC is sent using serial port 3 we can
// use the native serial monitor to check both what we send
// and what we receive to/from the PLC.

if (x == 0){
Serial3.write(2); // Send #02 (STX) to the PLC. This is a general AS511 code used
Serial.println("PG: 02"); // to prepare the PLC for what we're about to do, which can be to
x=x+1; } // either read or write some data to/from it, or to initiate some
// command or what have you.
// In this particular application we want to use the LCD's
// buttons to change the value of a setpoint and then send
// it to the PLC.
// The setpoint is written to DB48/DW0 in the PLC.

if (x == 1){
if (Serial3.available() >=2 ) { // Get confirmation from the PLC, we're expecting to receive
for (int y=0; y<2; y++) { // #10 (DLE) and #06 (ACK) as a token of "message received and
buff[y] = Serial3.read(); } // understood" from the PLC, basically indicating that the PLC is
Serial.print("AG: "); // ready to do what we ask of it.
for (int y=0; y<2; y++) {
if (buff[y]<=9){ Serial.print("0");}
Serial.print(buff[y], HEX);
Serial.print(" ");}
Serial.println();
x=x+1; timeout = 0;} // All the receive sequences (data coming from the PLC to the
// Arduino) have a timeout safeguard preventing the Arduino from
// freezing.
else { timeout=timeout + 1;
if (timeout >= 50){ brejk=true; x=17; }
}
}

if (x == 2){
Serial3.write(8); // Send #08 (B_OWRITE) to the PLC. This tells the PLC that
Serial.println("PG: 08"); // we're about to overwrite, in this case, DB48/DW0, which
x=x+1; } // is where the PLC in this application reads it's data from
// with regards to whatever changes in operation we want the
// PLC to execute.

if (x == 3){
if (Serial3.available() >=1 ) { // Expecting to receive #02 (STX) from the PLC.
buff[2] = Serial3.read();
Serial.print("AG: ");
if (buff[2]<=9){ Serial.print("0");}
Serial.print(buff[2], HEX);
Serial.println();
x=x+1; timeout = 0;}

else { timeout=timeout + 1;
if (timeout >= 50){ brejk=true; x=17; }
}
}
if (x == 4){
Serial3.write(16); // Send #10 (DLE) & #06 (ACK) to the PLC, all according
Serial3.write(6); // to the AS511 protocol.
Serial.println("PG: 10 06");
x=x+1; }

if (x == 5){
if (Serial3.available() >=3 ) { // Expecting #16, #10 and #03 from the PLC.
for (int y=3; y<6; y++) {
buff[y] = Serial3.read(); }
Serial.print("AG: ");
for (int y=3; y<6; y++) {
if (buff[y]<=9){ Serial.print("0"); }
Serial.print(buff[y], HEX);
Serial.print(" ");}
Serial.println();
x=x+1; timeout = 0;}

else { timeout=timeout + 1;
if (timeout >= 50){ brejk=true; x=17; }
}
}

if (x == 6){
if (brejk){ x=17; }
Serial3.write(16); // Preparing the PLC for overwriting the data in DB48/DW0.
Serial3.write(6);
Serial3.write(1);
Serial3.write(48); // Pointing at DB48.
Serial3.write(0);
Serial3.write(6);
Serial3.write(16);
Serial3.write(3);
x=x+1;
Serial.println("PG: 10 06 01 30 00 06 10 03"); }

if (x == 7){
if (Serial3.available() >=3 ) { // Expecting #10, #06 and #02 from the PLC.
for (int y=6; y<9; y++) {
buff[y] = Serial3.read(); }
Serial.print("AG: ");
for (int y=6; y<9; y++) {
if (buff[y]<=9){ Serial.print("0"); }
Serial.print(buff[y], HEX);
Serial.print(" ");}
Serial.println();
x=x+1; timeout = 0;}

else { timeout=timeout + 1;
if (timeout >= 50){ brejk=true; x=17; }
}
}

if (x == 8){
Serial3.write(16); // Another #10 & #06 to the PLC.
Serial3.write(6);
Serial.println("PG: 10 06");
x=x+1; }

if (x == 9){
if (Serial3.available() >=3 ) { // Expecting #09, #10 & #03 from the PLC.
for (int y=9; y<12; y++) {
buff[y] = Serial3.read(); }
Serial.print("AG: ");
for (int y=9; y<11; y++) {
if (buff[y]<=9){ Serial.print("0"); }
Serial.print(buff[y], HEX);
Serial.print(" ");}
Serial.println();
x=x+1; timeout = 0;}

else { timeout=timeout + 1;
if (timeout >= 50){ brejk=true; x=17; }
}
}

if (x == 10){
Serial3.write(16); // Send #10, #06 & #02 to the PLC.
Serial3.write(6);
Serial3.write(2);
Serial.println("PG: 10 06 02");
x=x+1; }

if (x == 11){
if (Serial3.available() >=2 ) { // Expecting #10 & #06 from the PLC.
for (int y=11; y<13; y++) {
buff[y] = Serial3.read(); }
Serial.print("AG: ");
for (int y=11; y<13; y++) {
if (buff[y]<=9){ Serial.print("0"); }
Serial.print(buff[y], HEX);
Serial.print(" ");}
Serial.println();
x=x+1; timeout = 0;}

else { timeout=timeout + 1;
if (timeout >= 50){ brejk=true; x=17; }
}
}

if (x == 12){

Serial3.write(0); // Here's the actual data writing sequence of DB48/DW0.


Serial3.write(112); // As DB48 only consists of 1 dataword (I.E DW0) the total
Serial3.write(112); // length of DB48 is none the less 6 bytes long.
Serial3.write(1);
Serial3.write(48); // Pointing at DB48 again.
Serial3.write(128);
Serial3.write(0);
Serial3.write(0);
Serial3.write(0);
Serial3.write(0);
Serial3.write(6);
msb = int(setp/256);
lsb = int(setp - msb*256);
Serial3.write(msb); // This is the new DL0 written to DB48......
if (msb == 16){ Serial3.write(msb); }
Serial3.write(lsb); // ......and here's DR0, together they form DW0.
if (lsb == 16){ Serial3.write(lsb); } // In case the value of lsb and/or msb is equal to
Serial3.write(16); // #10 (decimal 16) we have to sent 2 consecutive
Serial3.write(4); // values, I.E we have to send #10 twice, as that's
x=x+1; // part of how the AS511 protocol handles the specific
// value of #10 (decimal 16). Go figure.

Serial.print("PG: 00 70 70 01 30 80 00 00 00 00 06 ");
if (msb <= 15){ Serial.print("0"); }
Serial.print(msb, HEX);
Serial.print(" ");
if (msb == 16){
Serial.print(msb, HEX);
Serial.print(" ");}
if (lsb <= 15){ Serial.print("0");}
Serial.print(lsb, HEX);
Serial.print(" ");
if (lsb == 16){
Serial.print(lsb, HEX);
Serial.print(" "); }
Serial.println("10 04"); }

if (x == 13){
if (Serial3.available() >=3 ) { // Expecting #10, #06 & #02 from the PLC.
for (int y=13; y<16; y++) {
buff[y] = Serial3.read(); }
Serial.print("AG: ");
for (int y=13; y<16; y++) {
if (buff[y]<=9){ Serial.print("0"); }
Serial.print(buff[y], HEX);
Serial.print(" ");}
Serial.println();
x=x+1; timeout = 0;}

else { timeout=timeout + 1;
if (timeout >= 50){ brejk=true; x=17; }
}
}

if (x == 14){
Serial3.write(16); // Another #10 & #06 to the PLC.
Serial3.write(6);
Serial.println("PG: 10 06");
x=x+1; }

if (x == 15){
if (Serial3.available() >=3 ) { // Expecting #12, #10 & #03 from the PLC.
for (int y=16; y<19; y++) {
buff[y] = Serial3.read(); }
Serial.print("AG: ");
for (int y=16; y<19; y++) {
if (buff[y]<=9){ Serial.print("0"); }
Serial.print(buff[y], HEX);
Serial.print(" ");}
Serial.println();
x=x+1; timeout = 0;

xsum = 0;
for (int y=0; y<19; y++){ // XOR checking what was reveived from the PLC versus
xsum = xsum + (buff[y] ^ contr[y]);} // what is expected to be received from the PLC during
// a successful B_OWRITE sequence.
if (xsum > 0){ brejk = true; }}

else { timeout=timeout + 1;
if (timeout >= 50){ brejk=true; x=17; }
}
}

if (x == 16){
Serial3.write(16); // Send the last #10 & #06 to the PLC, this ends the TX/RX
Serial3.write(6); // sequence for B_OWRITE for this time.
Serial.println("PG: 10 06"); // As the B_OWRITE sequence was successfull then we print
x=x+1; // the new setpoint on the LCD display, let's the user know
outp = setp; // that the PLC now has a new setpoint to work with.
lcd.noCursor();
if (setp >=0 and setp <=9) { lcd.setCursor(11,0); lcd.print(" "); lcd.setCursor(15,0); }
if (setp >=10 and setp <=99) { lcd.setCursor(11,0); lcd.print(" "); lcd.setCursor(14,0); }
if (setp >=100 and setp <=999) { lcd.setCursor(11,0); lcd.print(" "); lcd.setCursor(13,0); }
if (setp >=1000 and setp <=9999) { lcd.setCursor(11,0); lcd.print(" "); lcd.setCursor(12,0); }
if (setp >=10000 and setp <=32767) { lcd.setCursor(11,0); }
if (setp <=0) { lcd.setCursor (11,0); lcd.print(" 0"); }
if (setp > 0) { lcd.print(setp); }}
msb = int(outp/256);
lsb = int(outp - msb*256);
EEPROM.write(50,lsb);
EEPROM.write(55,msb);

if ((x == 17) && brejk){


x=0; // If, for whatever reason, the B_OWRITE sequence failed to
old = cur; // write new data to DB48/DW0, let the user know this by
AS511_tx_rx = false; // printing an error message on the LCD display.
for(int y=0; y<3; y++) { // Also, revert the setpoint to the old value that which it
plcnofun(); } // had prior to pressing the Select button, that way the user
outp = back; // not only sees the error message but also sees that the new
setp = uld; // setpoint hasn't actually made it to DB48/DW0 and that the
msb = int(outp/256); // old setpoint is still in use in the PLC, I.E DB48/DW0 hasn't
lsb = int(outp - msb*256); // changed any at all.
EEPROM.write(50,lsb);
EEPROM.write(55,msb);
lcd.clear();
basic0();
if (outp <=9){ cur = 15; }
if (outp <=99 && outp >= 10){ cur = 14; }
if (outp <=999 && outp >= 100){ cur = 13; }
if (outp <=9999 && outp >= 1000){ cur = 12; }
if (outp <=32767 && outp >= 10000){ cur = 11; }
lcd.setCursor(cur,0);
lcd.print(outp);
basic1();
if (setp <=9){ cur = 15; }
if (setp <=99 && setp >= 10){ cur = 14; }
if (setp <=999 && setp >= 100){ cur = 13; }
if (setp <=9999 && setp >= 1000){ cur = 12; }
if (setp <=32767 && setp >= 10000){ cur = 11; }
lcd.setCursor(cur,1);
lcd.print(setp);
cur = old;
lcd.cursor();
lcd.setCursor(cur,1);
brejk = false;
kok = true;
timeout = 0; }

if (x == 17){
x=0;
AS511_tx_rx = false;
timeout = 0;
delay(50);
}
}
}

You might also like