FUNCTION_BLOCK "Modbus0.4"
TITLE =Modbus CP SERVER/SLAVE
//********************************************************
//**** Funktionsbaustein Für MODBUS/TCP SERVER(Slave) **** 
//********************************************************
//unterstützt die Modbus Funktionscodes:
//
//01h,02h --> Read n Bits
//03h,04h --> Read n Words
//05h     --> Write a Bit
//06h     --> Write a Word
//10h     --> Write n Words
//0fh     --> Write n Bits
//
//Stand 19.03.09 L.Weiß
VERSION : 0.4


VAR_INPUT
  VERB_ID : INT ;	//Verbindung-ID NetPro
  LADDR : WORD ;	//HW Ladeadresse
  REGISTER_DB : BLOCK_DB ;	//Datenbaustein Modbus Register
END_VAR
VAR
  KOMTEL : STRUCT 	
   TIH : BYTE ;	
   TIL : BYTE ;	
   PIH : BYTE ;	
   PIL : BYTE ;	
   LFH : BYTE ;	
   LFL : BYTE ;	
   SLAVE : BYTE ;	
   FUNCCODE : BYTE ;	
   ADRESS : WORD ;	
   ANZAHL : WORD ;	
   ANZAHL_BYTES : BYTE ;	
   Daten0 : BYTE ;	
   DATEN : ARRAY  [1 .. 124 ] OF WORD ;	
  END_STRUCT ;	
  KOMM_NDR : BOOL ;	
  KOMM_STATUS : WORD ;	
  KOMM_LEN : INT ;	
  KOMM_ERROR : BOOL ;	
  ANTTEL_3 : STRUCT 	
   TIH : BYTE ;	
   TIL : BYTE ;	
   PIH : BYTE ;	
   PIL : BYTE ;	
   LFH : BYTE ;	
   LFL : BYTE ;	
   SLAVE : BYTE ;	
   FUNCCODE : BYTE ;	
   ANZAHL : BYTE ;	
   Daten_1 : BYTE ;	
   Daten : ARRAY  [1 .. 255 ] OF BYTE ;	
  END_STRUCT ;	
  ANTTEL_6 : STRUCT 	
   TIH : BYTE ;	
   TIL : BYTE ;	
   PIH : BYTE ;	
   PIL : BYTE ;	
   LFH : BYTE ;	
   LFL : BYTE ;	
   SLAVE : BYTE ;	
   FUNCCODE : BYTE ;	
   Adresse : WORD ;	
   Wert : WORD ;	
  END_STRUCT ;	
  SEND_ANTW_3 : BOOL ;	
  SEND_3_NDR : BOOL ;	
  SEND_3_STATUS : WORD ;	
  SEND_3_LEN : INT ;	
  SEND_3_ERROR : BOOL ;	
  SEND_3_DONE : BOOL ;	
  Zeiger_1 : DINT ;	
  Zeiger_2 : DINT ;	
  RECV_2 : BOOL ;	
  NDR2 : BOOL ;	
  ENTLEEREN : BOOL ;	
  SEND_3_AKTIV : BOOL ;	
  Test : BOOL ;	
  SEND_6_AKTIV : BOOL ;	
  SEND_ANTW_6 : BOOL ;	
  SEND_6_LEN : INT ;	
  SEND_6_DONE : BOOL ;	
  SEND_6_ERROR : BOOL ;	
  SEND_6_STATUS : WORD ;	
  SEND_ANTW_10 : BOOL ;	
  SEND_10_AKTIV : BOOL ;	
  SEND_10_LEN : INT ;	
  SEND_10_DONE : BOOL ;	
  SEND_10_ERROR : BOOL ;	
  SEND_10_STATUS : WORD ;	
  SEND_ANTW_5 : BOOL ;	
  SEND_5_AKTIV : BOOL ;	
  SEND_5_LEN : INT ;	
  SEND_5_DONE : BOOL ;	
  SEND_5_ERROR : BOOL ;	
  SEND_5_STATUS : WORD ;	
  SEND_ANTW_1 : BOOL ;	
  SEND_1_AKTIV : BOOL ;	
  SEND_1_AKTIV_1 : BOOL ;	
  SEND_1_DONE : BOOL ;	
  SEND_1_ERROR : BOOL ;	
  Zeiger_3 : INT ;	
  SEND_1_STATUS : WORD ;	
  SEND_1_LEN : INT ;	
  SEND_0f_LEN : INT ;	
  SEND_ANTW_0f : BOOL ;	
  SEND_0f_DONE : BOOL ;	
  SEND_0f_ERROR : BOOL ;	
  SEND_0f_STATUS : WORD ;	
  SEND_0f_AKTIV : BOOL ;	
  JOB_ACTIVE : BOOL ;	
  JOB_Ueberwachung : "TON";	
  JOB_TIME_OVERFLOW : BOOL ;	
END_VAR
VAR_TEMP
  loop_tmp : INT ;	
  BYTE_TMP : BYTE ;	
  error : BOOL ;	
  status : WORD ;	
  len : INT ;	
  ndr : BOOL ;	
  ENTLPUFFER : ARRAY  [1 .. 255 ] OF BYTE ;	
  POINTER_VAR : ANY ;	
  LOOPCNT : INT ;	
  BYTES : INT ;	
  BITS : INT ;	
  BITTMP : BOOL ;	
  AR1_TMP : DWORD ;	
END_VAR
BEGIN
NETWORK
TITLE =Sprung

      TAR1  #AR1_TMP; // AR1 retten

      AUF   #REGISTER_DB; 

      U     #ENTLEEREN; // Notentleerung
      SPB   leer; 

      U     #SEND_1_AKTIV; // Lesen n Bits
      SPB   sen1; 

      U     #SEND_3_AKTIV; // Lesen n Wörter
      SPB   sen3; 

      U     #SEND_5_AKTIV; // Schreiben 1 Bit
      SPB   sen5; 

      U     #SEND_6_AKTIV; // Schreiben 1 Wort
      SPB   sen6; 

      U     #SEND_10_AKTIV; // Schreiben n Wörter
      SPB   se16; 

      U     #SEND_0f_AKTIV; // Schreiben n Bits
      SPB   se0f; 

NETWORK
TITLE =Kommandotelegramm empfangen

// Kommandotelegramm
      U     #RECV_2; 
      SPB   res2; 
// 6 Byte HEADER
      CALL "AG_RECV" (
           ID                       := #VERB_ID,
           LADDR                    := #LADDR,
           RECV                     := P#DIX 6.0 BYTE 6,
           NDR                      := #KOMM_NDR,
           ERROR                    := #KOMM_ERROR,
           STATUS                   := #KOMM_STATUS,
           LEN                      := #KOMM_LEN);

      UN    #KOMM_NDR; // keine Daten weiter
      ZV    Z      1; // Zählen
      SPB   faus; 
      SET   ; 
      S     #RECV_2; // neue Daten einlesen

// ANY-Pointer zusammensetzen

res2: LAR1  P##POINTER_VAR; 
      L     B#16#10; // Syntax-ID
      T     LB [AR1,P#0.0]; 
      L     B#16#2; // Datentyp Byte
      T     LB [AR1,P#1.0]; 
      L     DIB   11; // Anzahl Bytes
      T     LW [AR1,P#2.0]; 
      L     DINO; // Instanz-DB
      T     LW [AR1,P#4.0]; 
      L     P#DBX 12.0; // Byte Offset
      T     LD [AR1,P#6.0]; 

// Daten empfangen

      CALL "AG_RECV" (
           ID                       := #VERB_ID,
           LADDR                    := #LADDR,
           RECV                     := #POINTER_VAR,
           NDR                      := #NDR2,
           ERROR                    := #error,
           STATUS                   := #status,
           LEN                      := #KOMM_LEN);

      U     #NDR2; // Daten empfangen
      O     #error; // Fehler
      R     #RECV_2; // Freigabe Headerempfang
      ZV    Z      2; // Zählen

      UN    #NDR2; 
      SPB   faus; 
NETWORK
TITLE =Funktionscode 3 READ n WORDS

// Transaction Identifier zurücksenden
      L     DIW    6; 
      T     DIW  276; 
      T     DIW  542; 
// Funktionscode 3,4 Read n Words
// Wenn Funktionscode 3 oder 4 und NDR --> Antwort senden
      U(    ; 
      L     DIB   13; 
      L     3; 
      ==I   ; 
      )     ; 
      O(    ; 
      L     DIB   13; 
      L     4; 
      ==I   ; 
      )     ; 
      U     #NDR2; // Datensatz empfangen
      =     #SEND_ANTW_3; 
      S     #SEND_3_AKTIV; 
      SPBN  m001; 
// Funktionscode
      L     DIB   13; 
      T     DIB  283; // Funktionscode
// Slaveadresse
      L     DIB   12; 
      T     DIB  282; 
// Anzahl der gelesenen Bytes
      L     DIW   16; 
      L     2; 
      *I    ; 
      T     DIB  284; // Anzahl
      L     3; 
      +I    ; 
      T     DIB  281; // LFL
// Sendelänge berechnen 9 + n bytes
      L     DIW   16; 
      L     2; 
      *I    ; 
      L     9; 
      +I    ; 
      T     #SEND_3_LEN; 
// Daten rangieren
      L     DIW   14; // Offset
      L     2; 
      *I    ; 
      T     #Zeiger_1; 

      L     0; // Nullen
      T     #Zeiger_2; 

      L     DIW   16; // Anzahl
      L     2; 
      *I    ; 
      T     #loop_tmp; 
loop: L     #Zeiger_1; 
      SLW   3; // 3Bit links 
      LAR1  ; // in Adressregister 1 schreiben
      L     DBB [AR1,P#0.0]; // Aus Register-DB
      T     #BYTE_TMP; 
      L     #Zeiger_2; 
      SLW   3; 
      LAR1  ; 
      L     #BYTE_TMP; 
      T     DIB [AR1,P#285.0]; // In Sendepuffer
      L     #Zeiger_1; // Zeiger 1 inc
      +     1; 
      T     #Zeiger_1; 
      L     #Zeiger_2; // Zeiger 2 inc
      +     1; 
      T     #Zeiger_2; 
      L     #Zeiger_2; // wenn alles übertragen raus
      L     #loop_tmp; 
      <I    ; 
      SPB   loop; 

sen3: CALL "AG_SEND" (// senden
           ACT                      := #SEND_ANTW_3,
           ID                       := #VERB_ID,
           LADDR                    := #LADDR,
           SEND                     := P#DIX 276.0 BYTE 264,
           LEN                      := #SEND_3_LEN,
           DONE                     := #SEND_3_DONE,
           ERROR                    := #SEND_3_ERROR,
           STATUS                   := #SEND_3_STATUS);
      U     #SEND_3_DONE; // Datensatz empfangen
      O     #SEND_3_ERROR; // Fehler
      R     #SEND_3_AKTIV; 
      ZV    Z      3; // Zählen

NETWORK
TITLE =Write a Word

// Funktionscode 6 Write a Word
// Wenn Funktionscode 6 und NDR --> Antwort senden
m001: L     DIB   13; 
      L     6; 
      ==I   ; 
      U     #NDR2; 
      =     #SEND_ANTW_6; 
      S     #SEND_6_AKTIV; 
      SPBN  m002; 
// Wert in Register-DB übernehmen
      L     DIW   14; // Adresse 
      L     2; // *2 da Byte
      *I    ; 
      SLW   3; 
      LAR1  ; 
      L     DIW   16; // Wert
      T     DBW [AR1,P#0.0]; 
// *********   Antwort ********************************
// Funktionscode
      L     6; 
      T     DIB  283; // Funktionscode
// Slaveadresse
      L     DIB   12; 
      T     DIB  282; 
// Register Adresse
      L     DIW   14; 
      T     DIW  284; 
// Register Wert
      L     DIW   16; 
      T     DIW  286; 
// Länge
      L     6; 
      T     DIB  281; 
      L     12; // LFL
      T     #SEND_6_LEN; // FC5

sen6: CALL "AG_SEND" (// senden
           ACT                      := #SEND_ANTW_6,
           ID                       := #VERB_ID,
           LADDR                    := #LADDR,
           SEND                     := P#DIX 276.0 BYTE 264,
           LEN                      := #SEND_6_LEN,
           DONE                     := #SEND_6_DONE,
           ERROR                    := #SEND_6_ERROR,
           STATUS                   := #SEND_6_STATUS);
      U     #SEND_6_DONE; // Datensatz empfangen
      O     #SEND_6_ERROR; // Fehler
      R     #SEND_6_AKTIV; 
      ZV    Z      4; // Zählen

NETWORK
TITLE =Write n Words 10h

// Funktionscode 10 Write n Word
// Wenn Funktionscode 10 und NDR --> Antwort senden
m002: L     DIB   13; 
      L     16; 
      ==I   ; 
      U     #NDR2; 
      =     #SEND_ANTW_10; 
      S     #SEND_10_AKTIV; 
      SPBN  m003; 
// Werte in Register übernehmen
// Adresse im Register DB 1tes Wort
      L     DIW   14; 
      L     2; 
      *I    ; 
      T     #Zeiger_2; // Ziel in Bytes
// Anzahl der Register / Worte
      L     DIW   16; 
      L     2; 
      *I    ; 
      T     #loop_tmp; // Anzahl zu übertr. Bytes
// Offset erstes Register
      L     19; 
      T     #Zeiger_1; // Quelle
// Schleifenzähler
      L     0; 
      T     #LOOPCNT; // Schleifenzähler
lop2: L     #Zeiger_1; 
      SLW   3; // 3Bit links 
      LAR1  ; // in Adressregister 1 schreiben
      L     DIB [AR1,P#0.0]; // Quelle
      T     #BYTE_TMP; 
      L     #Zeiger_2; 
      SLW   3; 
      LAR1  ; 
      L     #BYTE_TMP; 
      T     DBB [AR1,P#0.0]; // Ziel
      L     #Zeiger_1; // Zeiger 1 inc
      +     1; 
      T     #Zeiger_1; 
      L     #Zeiger_2; // Zeiger 2 inc
      +     1; 
      T     #Zeiger_2; 
      L     #LOOPCNT; 
      +     1; 
      T     #LOOPCNT; // wenn alles übertragen raus
      L     #loop_tmp; 
      <I    ; 
      SPB   lop2; 
// *********   Antwort ********************************
// Funktionscode
      L     16; 
      T     DIB  283; // Funktionscode
// Slaveadresse
      L     DIB   12; 
      T     DIB  282; 
// Adresse 1 Wort
      L     DIW   14; 
      T     DIW  284; 
// Anzahl der Worte
      L     DIW   16; 
      T     DIW  286; 
// Länge
      L     6; 
      T     DIB  281; 
      L     12; // LFL
      T     #SEND_10_LEN; // FC5

se16: CALL "AG_SEND" (// senden
           ACT                      := #SEND_ANTW_10,
           ID                       := #VERB_ID,
           LADDR                    := #LADDR,
           SEND                     := P#DIX 276.0 BYTE 264,
           LEN                      := #SEND_10_LEN,
           DONE                     := #SEND_10_DONE,
           ERROR                    := #SEND_10_ERROR,
           STATUS                   := #SEND_10_STATUS);
      U     #SEND_10_DONE; // Datensatz empfangen
      O     #SEND_10_ERROR; // Fehler
      R     #SEND_10_AKTIV; 
      ZV    Z      5; // Zählen

NETWORK
TITLE =Write a Bit 5h

// Wenn Funktionscode 5 und NDR --> Antwort senden
m003: L     DIB   13; 
      L     5; 
      ==I   ; 
      U     #NDR2; 
      =     #SEND_ANTW_5; 
      S     #SEND_5_AKTIV; 
      SPBN  m004; 
// Bit setzen
      L     DIW   14; // Bit Nummer
      LAR1  ; // in Adressregister 1
// Zustand des Bits 0000h -> 0 ff00h -> 1
      L     DIW   16; // Zustand
      L     W#16#FF00; // Setzen
      ==I   ; 
      SPB   setb; 
      CLR   ; // ansonsten
      =     DBX [AR1,P#0.0]; // rücksetze Bit 
      SPA   weit; 
setb: SET   ; 
      =     DBX [AR1,P#0.0]; // setze Bit 
// *********   Antwort ********************************
// Funktionscode
weit: L     5; 
      T     DIB  283; // Funktionscode
// Slaveadresse
      L     DIB   12; 
      T     DIB  282; 
// Adresse 1 Wort
      L     DIW   14; 
      T     DIW  284; 
// Zustand des Bits 0000h -> 0 ff00h -> 1
      L     DIW   16; 
      T     DIW  286; 
// Länge
      L     6; 
      T     DIB  281; 
      L     12; // LFL
      T     #SEND_5_LEN; // FC5

sen5: CALL "AG_SEND" (// senden
           ACT                      := #SEND_ANTW_5,
           ID                       := #VERB_ID,
           LADDR                    := #LADDR,
           SEND                     := P#DIX 276.0 BYTE 264,
           LEN                      := #SEND_5_LEN,
           DONE                     := #SEND_5_DONE,
           ERROR                    := #SEND_5_ERROR,
           STATUS                   := #SEND_5_STATUS);
      U     #SEND_5_DONE; // Datensatz empfangen
      O     #SEND_5_ERROR; // Fehler
      R     #SEND_5_AKTIV; 
      ZV    Z      6; // Zählen


NETWORK
TITLE =Funktionscode 1h, n Bit lesen

// Wenn Funktionscode 1,2 und NDR --> Antwort senden
m004: U(    ; 
      L     DIB   13; 
      L     1; 
      ==I   ; 
      )     ; 
      O(    ; 
      L     DIB   13; 
      L     2; 
      ==I   ; 
      )     ; 
      U     #NDR2; 
      =     #SEND_ANTW_1; 
      S     #SEND_1_AKTIV; 
      SPBN  m005; 

// *********   Antwort ********************************
// Funktionscode
      L     DIB   13; 
      T     DIB  283; // Funktionscode
// Slaveadresse
      L     DIB   12; 
      T     DIB  282; 
// Adresse 1.Bit
      L     DIW   14; 
      T     #Zeiger_1; 
// Andresse Anzahl der Bits
      L     DIW   16; // Anzahl Bits
      T     #Zeiger_2; 
      ITD   ; 
      DTR   ; 
      L     8.000000e+000; 
      /R    ; 
      RND+  ; 
      T     DIB  284; // Anzahl in Bytes
// Zielschleife
      L     0; 
      T     #Zeiger_3; 
// Kopierschleife
llp1: L     #Zeiger_1; 
      LAR1  ; 
      U     DBX [AR1,P#0.0]; // Aus Register-DB
      =     #BITTMP; 
      L     #Zeiger_3; 
      LAR1  ; 
      U     #BITTMP; 
      =     DIX [AR1,P#285.0]; // In Sendepuffer
      L     #Zeiger_1; // Zeiger 1 inc
      +     1; 
      T     #Zeiger_1; 
      L     #Zeiger_3; // Zeiger 3 inc
      +     1; 
      T     #Zeiger_3; 
      L     #Zeiger_2; 
      <=I   ; 
      SPB   llp1; 
// Länge
      L     DIB  284; // Anzahl der Bytes mit Daten
      L     9; // + Header
      +I    ; 
      T     #SEND_1_LEN; // FC5 
      L     6; 
      -I    ; 
      T     DIB  281; // LFL

sen1: CALL "AG_SEND" (// senden
           ACT                      := #SEND_ANTW_1,
           ID                       := #VERB_ID,
           LADDR                    := #LADDR,
           SEND                     := P#DIX 276.0 BYTE 264,
           LEN                      := #SEND_1_LEN,
           DONE                     := #SEND_1_DONE,
           ERROR                    := #SEND_1_ERROR,
           STATUS                   := #SEND_1_STATUS);
      U     #SEND_1_DONE; // Datensatz empfangen
      O     #SEND_1_ERROR; // Fehler
      R     #SEND_1_AKTIV; 
      ZV    Z      7; // Zählen
NETWORK
TITLE =Funktionscode 0fh, Write n Bits

// Wenn Funktionscode 0f und NDR --> Antwort senden
m005: L     DIB   13; 
      L     W#16#F; 
      ==I   ; 
      U     #NDR2; 
      =     #SEND_ANTW_0f; 
      S     #SEND_0f_AKTIV; 
      SPBN  faus; 
// Werte in Register übernehmen
// Adresse im Register DB 1tes Bit
      L     DIW   14; 
      T     #Zeiger_2; // Ziel 
// Anzahl der Bits
      L     DIW   16; 
      T     #loop_tmp; // Anzahl zu Bits
// Offset erstes Register
      L     19; // ab DIB 19
      SLW   3; 
      T     #Zeiger_1; // Quelle
// Schleifenzähler
      L     0; 
      T     #Zeiger_3; // Schleifenzähler
// Kopierschleife
llp2: L     #Zeiger_1; 
      LAR1  ; 
      U     DIX [AR1,P#0.0]; // Aus Empfangspuffer
      =     #BITTMP; 
      L     #Zeiger_2; 
      LAR1  ; 
      U     #BITTMP; 
      =     DBX [AR1,P#0.0]; // In Register DB
      L     #Zeiger_1; // Zeiger 1 inc
      +     1; 
      T     #Zeiger_1; 
      L     #Zeiger_2; // Zeiger 2 inc
      +     1; 
      T     #Zeiger_2; 
      L     #Zeiger_3; // Zeiger 3 inc
      +     1; 
      T     #Zeiger_3; 
      L     #loop_tmp; 
      <=I   ; 
      SPB   llp2; 

// *********   Antwort ********************************
// Funktionscode
      L     W#16#F; 
      T     DIB  283; // Funktionscode
// Slaveadresse
      L     DIB   12; 
      T     DIB  282; 
// Adresse 1 Bit
      L     DIW   14; 
      T     DIW  284; 
// Anzahl der Bits
      L     DIW   16; 
      T     DIW  286; 
// Länge
      L     6; 
      T     DIB  281; 
      L     12; // LFL
      T     #SEND_0f_LEN; // FC5

se0f: CALL "AG_SEND" (// senden
           ACT                      := #SEND_ANTW_0f,
           ID                       := #VERB_ID,
           LADDR                    := #LADDR,
           SEND                     := P#DIX 276.0 BYTE 264,
           LEN                      := #SEND_0f_LEN,
           DONE                     := #SEND_0f_DONE,
           ERROR                    := #SEND_0f_ERROR,
           STATUS                   := #SEND_0f_STATUS);
      U     #SEND_0f_DONE; // Datensatz empfangen
      O     #SEND_0f_ERROR; // Fehler
      R     #SEND_0f_AKTIV; 
      ZV    Z      8; // Zählen
NETWORK
TITLE =Buffer leeren

faus: L     #KOMM_STATUS; 
      L     W#16#80B1; 
      ==I   ; 
      O     #RECV_2; 
      O     #SEND_1_AKTIV; 
      O     #SEND_3_AKTIV; 
      O     #SEND_5_AKTIV; 
      O     #SEND_6_AKTIV; 
      O     #SEND_10_AKTIV; 
      O     #SEND_0f_AKTIV; 
      =     #JOB_ACTIVE; 

      CALL #JOB_Ueberwachung (
           IN                       := #JOB_ACTIVE,
           PT                       := T#5S,
           Q                        := #JOB_TIME_OVERFLOW);

      U     #JOB_TIME_OVERFLOW; 
      R     #RECV_2; 
      R     #SEND_3_AKTIV; 
      R     #SEND_1_AKTIV; 
      R     #SEND_6_AKTIV; 
      R     #SEND_10_AKTIV; 
      R     #SEND_5_AKTIV; 
      R     #SEND_0f_AKTIV; 
      S     #ENTLEEREN; 
      SPB   leer; 

      SPA   ende; 

leer: CALL "AG_RECV" (
           ID                       := #VERB_ID,
           LADDR                    := #LADDR,
           RECV                     := P#DIX 20.0 BYTE 200,
           NDR                      := #ndr,
           ERROR                    := #error,
           STATUS                   := #status,
           LEN                      := #len);

      SET   ; 
      R     #ENTLEEREN; 
NETWORK
TITLE =Baustein ENDE

ende: LAR1  #AR1_TMP; // AR1 wieder herstellen

END_FUNCTION_BLOCK
