Tips and tricks =================== Swap variabile DWORD ---------------------- Ecco come utilizzare la funzione **ROL** per eseguire lo swap su variabile **DWORD**. Nell'esempio Il valore **16#12345678** viene trasformato nel valore **16#56781234** e trasferito nella variabile **VarOut**. +-------------------------------------------------------+ | **Definizione variabili** | +-------------------------------------------------------+ | |image0| | +-------------------------------------------------------+ **Esempio LD** |image1| **Esempio IL** .. code-block:: none LD 16#12345678 ROL 16 ST VarOut (* Output variable *) **Esempio ST** .. code-block:: none VarOut:=ROL(16#12345678, 16); (* Output variable *) Swap variabile WORD ----------------------- Ecco come utilizzare la funzione **ROL** per eseguire lo swap su variabile **WORD**. Nell'esempio Il valore **16#1234** viene trasformato nel valore **16#3412** e trasferito nella variabile **VarOut**. +--------------------------------------------------+ | **Definizione variabili** | +--------------------------------------------------+ | |image2| | +--------------------------------------------------+ **Esempio LD** |image3| **Esempio IL** .. code-block:: none LD 16#1234 ROL 8 ST VarOut (* Output variable *) **Esempio ST** .. code-block:: none VarOut:=ROL(16#1234, 8); (* Output variable *) Swap variabile BYTE ----------------------- Ecco come utilizzare la funzione **ROL** per eseguire lo swap su variabile **BYTE**. Nell'esempio Il valore **16#12** viene trasformato nel valore **16#21** e trasferito nella variabile **VarOut**. +------------------------------------------------+ | **Definizione variabili** | +------------------------------------------------+ | |image4| | +------------------------------------------------+ **Esempio LD** |image5| **Esempio IL** .. code-block:: none LD 16#12 ROL 4 ST VarOut (* Output variable *) **Esempio ST** .. code-block:: none VarOut:=ROL(16#12, 4); (* Output variable *) Espandere DWORD in 32 BOOL ------------------------------ Ecco un come utilizzando i blocchi funzione `DoubleToWord <#6.7.7.DoubleToWord, Double to word conversion|outline>`__, `WordToByte <#6.7.6.WordToByte, Word to byte conversion|outline>`__, `ByteToBit <#6.7.4.ByteToBit, Byte to bit conversion|outline>`__ sia possibile espandere una variabile **DWORD** in 32 variabili **BOOL**. +----------------------------------------------+ | **Definizione variabili** | +----------------------------------------------+ | |image6| | +----------------------------------------------+ **Esempio FBD** *(Ptp114a200, FBD_DWExpand)* |image7| **Comprimere 32 BOOL in DWORD** ------------------------------- Ecco come utilizzando i blocchi funzione `BitToByte <#6.7.3.BitToByte, Bit to byte conversion|outline>`__, `ByteToWord <#6.7.5.ByteToWord, Byte to word conversion|outline>`__, `WordToDouble <#6.7.8.WordToDouble, Word to double conversion|outline>`__ sia possibile comprimere 32 variabili **BOOL** in una variabile **DWORD**. +------------------------------------------------+ | **Definizione variabili** | +------------------------------------------------+ | |image8| | +------------------------------------------------+ **Esempio FBD** *(Ptp114a200, FBD_DWCompress)* |image9| Definizione di “Format” nella SysVarsscanf ---------------------------------------------- Come visto la funzione `SysVarsscanf <#FctSysVarsscanf>`__ legge una stringa e ne interpreta il contenuto basandosi sul parametro **Format**. Questo parametro deve essere definito seguendo le indicazioni riportate nella tabella. +-----------------------------------+-----------------------------------+ | **Specifier** | **Meaning** | +-----------------------------------+-----------------------------------+ | %d | An integer in normal decimal | | | (that is, base-10) notation. | +-----------------------------------+-----------------------------------+ | %o | An integer in octal (base-8) | | | notation. | +-----------------------------------+-----------------------------------+ | %x | An integer in hexadecimal | | | (base-16) notation. | +-----------------------------------+-----------------------------------+ | %D | An integer in decimal, or (if it | | | starts with 0) octal, or (if it | | | starts with 0x) hexadecimal | | | notation. Hence, sscanf("12", | | | "%D", i), sscanf("014", "%D", i) | | | and sscanf("0xC", "%D", i) all | | | yield the value 12 in i. | +-----------------------------------+-----------------------------------+ | %f | A floating-point number. | +-----------------------------------+-----------------------------------+ | %c | The character code of a single | | | character. | +-----------------------------------+-----------------------------------+ | %s | A string. If %s is followed by | | | %d, %s will read any | | | non-numerical characters. If | | | followed by %[], %s will read any | | | characters not present in the set | | | in the %[]. If followed by normal | | | text, %s will match all | | | characters up to, but not | | | including, the first occurrence | | | of that text. | +-----------------------------------+-----------------------------------+ | %Ns | As above, but a string of exactly | | | N characters | +-----------------------------------+-----------------------------------+ | %[chars] | A string containing any of the | | | characters in the list | | | characters. | | | | | | A minus sign can be used to give | | | a range of values, so e. g. | | | %[a-d] means a string consisting | | | of any of the characters a, b and | | | c. | | | | | | A ^ sign means "not", so e. g. | | | %[^abc] means any character | | | except a, b and c. | | | | | | They can be combined, so %[a-cf] | | | means a, b, c, and f. | +-----------------------------------+-----------------------------------+ | %{format%} | Repeatedly matches the format | | | specifier format as many times as | | | possible, and gives an array of | | | arrays with the results. Example: | | | %{%d%} matches zero or more | | | integers. | +-----------------------------------+-----------------------------------+ | %% | A single percent (%) character. | +-----------------------------------+-----------------------------------+ Se viene posto un asterisco tra il segno % ed l'operatore (Esempio %*d) la funzione esegue la scansione dell'operatore ma non valorizza la variabile di ritorno. Questo può essere utile per saltare dalla stringa alcuni caratteri. Le definizioni riportate possono essere modificate in accordo alle **Regular expressions**. Di seguito riporto uno screenshot di un programma di esempio. +-----------+ | |image10| | +-----------+ Come si vede nella scansione dei numeri è possibile definire il numero di cifre da acquisire. Ipotizzando una stringa del tipo “124.5” avremo: **SysVarsscanf(ADR(SSource), '%d', USINT_TYPE** Ritornerà il valore USINT **124** fermando la scansione al primo carattere che non è numerico (Il punto decimale). **SysVarsscanf(ADR(SSource), '%2d', USINT_TYPE** Ritornerà il valore USINT **12** fermando la scansione dopo 2 cifre. **SysVarsscanf(ADR(SSource), '%f', REAL_TYPE** Ritornerà il valore REAL **124.5** fermando la scansione al primo carattere che non è numerico (Lo spazio dopo il numero). **SysVarsscanf(ADR(SSource), '%2f', REAL_TYPE** Ritornerà il valore REAL **12** fermando la scansione dopo 2 cifre. **SysVarsscanf(ADR(SSource), '%s', STRING_TYPE** Ritornerà il valore STRING **ab+cd:e** fermando la scansione al terminatore di stringa. **SysVarsscanf(ADR(SSource), '%3s', STRING_TYPE** Ritornerà il valore STRING **ab+** fermando la scansione dopo 3 caratteri. **SysVarsscanf(ADR(SSource), '%3[a-z]', STRING_TYPE** Ritornerà il valore STRING **ab** fermando la scansione al primo carattere che non è compreso nel range da a-z. Nel caso ci fossero stati più caratteri in quel range la scansione si sarebbe fermata al 3 carattere. **SysVarsscanf(ADR(SSource), '%[a-z]', STRING_TYPE** Ritornerà il valore STRING **ab** fermando la scansione al primo carattere che non è compreso nel range da a-z. **SysVarsscanf(ADR(SSource), '%[^+]', STRING_TYPE** Ritornerà il valore STRING **ab** fermando la scansione al primo carattere che non è diverso dal carattere “+”. **SysVarsscanf(ADR(SSource), '%[^:]', STRING_TYPE** Ritornerà il valore STRING **ab+cd** fermando la scansione al primo carattere che non è diverso dal carattere “:”. **Definire caratteri ascii non stampabili** ------------------------------------------- Nella gestione di protocolli di comunicazione e/o per impostare modalità di stampa su stampanti necessita eseguire l'output di caratteri ascii non stampabili, cioè caratteri con codici inferiori al 16#20 o superiori al 16#7F. Per la definizione dei caratteri ascii stampabili basta includere tra apici singoli i caratteri (Esempio **'abcd'**). Per i caratteri non stampabili, occorre anteporre al valore esadecimale del carattere il simbolo $, quindi per il carattere di 16#02 avremo la definizione **'$02'**, per **'$03'** e così di seguito. Ricordo che alcuni caratteri di controllo come il line feed, codice 16#0A, sia possibile definirli sia come **'$0A'** che come **'$l'**. Il carriage return, codice 16#0D, è possibile definirlo sia come **'$0D'** che come **'$r'**. Riporto tabella esplicativa. +--------------+-----------------+-----------------+---------------------------+ | **Sequenza** | **Significato** | **Esadecimale** | **Esempio** | +--------------+-----------------+-----------------+---------------------------+ | $$ | Carattere $ | 16#24 | ’I paid $$5 for this’ | +--------------+-----------------+-----------------+---------------------------+ | $' | Apostrofo | 16#27 | ’Enter $’Y$’ for YES’ | +--------------+-----------------+-----------------+---------------------------+ | $l | Line feed | 16#0A | ’next $l line’ | +--------------+-----------------+-----------------+---------------------------+ | $r | Carriage return | 16#0D | 'Hello$r' | +--------------+-----------------+-----------------+---------------------------+ | $n | New line | 16#0D0A | ’This is a line$n’ | +--------------+-----------------+-----------------+---------------------------+ | $p | New page | 16#0C | ’last line $p first line’ | +--------------+-----------------+-----------------+---------------------------+ | $t | Tabulazione | 16#09 | ’name$tsize$tdate’ | +--------------+-----------------+-----------------+---------------------------+ | $hh | | 16#hh | ’ABCD = $41$42$43$44’ | +--------------+-----------------+-----------------+---------------------------+ Ecco un esempio di utilizzo della funzione **SysVarfprintf** per definire oltre ai caratteri stampabili anche i caratteri non stampabili ed inviarli verso lo stream di uscita. In questo esempio viene inviato verso la porta seriale **COM0** la stringa **[Ciao]** seguita da carriage return e line feed. +---------------------------+ | **Definizione variabili** | +---------------------------+ | |image11| | +---------------------------+ | **Esempio LD** | +---------------------------+ | |image12| | +---------------------------+ **Rx/Tx dati su stream** ------------------------ Come si è visto con la funzione **Sysfopen** è possibile definire un collegamento tra una risorsa di I/O ed un flusso di dati **stream** da cui è possibile gestire la ricezione e/o la trasmissione di dati. Per la ricezione dei dati in ingresso dallo stream si utilizza la funzione per controllo se dati presenti **SysGetIChars** e la funzione per la lettura degli stessi **Sysfgetc**. +---------------------------------------------------+ | **Definizione variabili** | +---------------------------------------------------+ | |image13| | +---------------------------------------------------+ **Esempio ST** .. code-block:: none (* Rx data from stream. *) Ptr:=ADR(RxString); (* String pointer *) WHILE (TO_BOOL(SysGetIChars(File))) DO @Ptr:=TO_USINT(Sysfgetc(File)); (* Rx string *) Ptr:=Ptr+1; (* String pointer *) (* Check if string pointer overflow. *) IF (Ptr > ADR(RxString)+31) THEN EXIT; END_IF; END_WHILE; Per la trasmissione dei dati in uscita dallo **stream** si utilizza la funzione che controlla se spazio disponibile **SysGetOSpace**, e se lo spazio è sufficiente a contenere la stringa, o come nell'esempio, se il buffer di uscita è vuoto è possibile trasferire la stringa sullo stream con la funzione **SysVarfprintf**. +--------------------------------------------------------------------+ | **Definizione variabili** | +--------------------------------------------------------------------+ | |image14| | +--------------------------------------------------------------------+ **Esempio ST** .. code-block:: none (* Tx data to stream. *) IF (TO_UDINT(SysGetOSpace(File)) = SysGetTxBSize(File)) THEN i:=TO_UINT(SysVarfprintf(File, '%s', STRING_TYPE, ADR(TxString))); END_IF; Conversione tipo dati --------------------- La conversione dei dati (Detto anche **casting**) è una pratica necessaria nella programmazione, naturalmente se il tipo dati di destinazione ha un range inferiore del tipo dati di origine viene effettuato un troncamento del valore. Vediamo caso per caso le varie conversioni: **Tipo BOOL**: Le conversioni da qualsiasi tipo verso un **BOOL**, tornano FALSE se il valore del dato da convertire è 0. Tornano TRUE se il valore del dato da convertire è diverso da 0 (anche < 0). **Tipo SINT/USINT:** Le conversioni da qualsiasi tipo verso un **USINT**, tornano il valore del byte meno significativo del valore da convertire espresso in esadecimale. Esempio il valore 4660 (16#1234) tornerà 52 (16#34), lo stesso vale per i REAL esempio 300.0 (16#012C) tornerà 44 (16#2C). Per il tipo **SINT** se il valore esadecimale supera 127 il numero sarà negativo. **Tipo INT/UINT:** Le conversioni da qualsiasi tipo verso un **UINT**, tornano il valore dei due bytes meno significativi del valore da convertire espresso in esadecimale. Esempio il valore 305419896 (16#12345678) tornerà 22136 (16#5678), lo stesso vale per i REAL esempio 90000.0 (16#15F90) tornerà 24464 (16#5F90). Per il tipo **INT** se il valore esadecimale supera 32767 il numero sarà negativo. Nella programmazione IEC con LogicLab sono previste apposite funzioni di conversione di tipo, vediamole. +-----------------+-----------------+-----------------+-----------------+ | **Name** | **Input type** | **Output type** | **Function** | +-----------------+-----------------+-----------------+-----------------+ | TO_BOOL | Any | BOOL | Converts any | | | | | data type into | | | | | a boolean | +-----------------+-----------------+-----------------+-----------------+ | TO_SINT | Any | SINT | Converts any | | | | | data type into | | | | | a short integer | | | | | (8 bits, | | | | | signed) | +-----------------+-----------------+-----------------+-----------------+ | TO_USINT | Any | USINT | Converts any | | | | | data type into | | | | | an unsigned | | | | | short integer | | | | | (8 bits, | | | | | unsigned) | +-----------------+-----------------+-----------------+-----------------+ | TO_INT | Any | INT | Converts any | | | | | data type into | | | | | an integer (16 | | | | | bits, signed) | +-----------------+-----------------+-----------------+-----------------+ | TO_UINT | Any | UINT | Converts any | | | | | data type into | | | | | an unsigned | | | | | integer (16 | | | | | bits, unsigned) | +-----------------+-----------------+-----------------+-----------------+ | TO_DINT | Any | DINT | Converts any | | | | | data type into | | | | | a long integer | | | | | (32 bits, | | | | | signed) | +-----------------+-----------------+-----------------+-----------------+ | TO_UDINT | Any | UDINT | Converts any | | | | | data type into | | | | | an unsigned | | | | | long integer | | | | | (32 bits, | | | | | unsigned) | +-----------------+-----------------+-----------------+-----------------+ | TO_REAL | Any | REAL | Converts any | | | | | data type into | | | | | a floating | | | | | point (32 bits, | | | | | signed) | +-----------------+-----------------+-----------------+-----------------+ **Esempi** Conversione di una variabile di tipo DINT in una variabile di tipo USINT nei diversi linguaggi di programmazione. Naturalmente se il valore della variabile **VarDINT** supera il valore 255 (Limite della variabile **VarUSINT**), verrà ritornato il resto della divisione per il limite. +-----------------------------------------------------+ | **Definizione variabili** | +-----------------------------------------------------+ | |image15| | +-----------------------------------------------------+ **Esempio FBD** |image16| **Esempio LD** |image17| **Esempio IL** .. code-block:: none LD VarDINT (* DINT variable *) TO_USINT ST VarUSINT (* USINT variable *) **Esempio ST** .. code-block:: none VarUSINT:=TO_USINT(VarDINT); (* USINT variable *) **User Informations and Settings** ---------------------------------- E' previsto un interfacciamento tra il programma utente PLC ed il sistema operativo, questo permette di visualizzare ed impostare da sistema operativo variabili il cui valore è disponibile all'interno del programma utente. **SysUInfoA**, **SysUInfoB**, **SysUInfoC**, **SysUInfoD**, 4 variabili stringa da 16 caratteri, che possono essere valorizzate da programma utente e visualizzabili da sistema operativo. **SysUSetA**, **SysUSetB**, **SysUSetC**, **SysUSetD**, 4 variabili stringa da 16 caratteri, che possono essere valorizzate da sistema operativo e utilizzate da programma utente. E' prevista una pagina web (Menù **User**) in cui sono visualizzate le variabili **SysUInfo(x)** ed è possibile impostare le variabili **SysUSet(x)**. |image18| Nell'esempio riporto un semplice programma che acquisisce i valori delle variabili **SysUSet(x)** e li trasferisce nella rispettiva variabile di visualizzazione **SysUInfo(x)**. Nella pagina web il valore impostato nella variabile **SysUSet(x)** su accettazione con il tasto **Write** verrà ritornato nella variabile **SysUInfo(x)** come visibile dallo screenshot riportato sopra. +--------------------------------------------------------------------------+ | **Definizione variabili** | +--------------------------------------------------------------------------+ | |image19| | +--------------------------------------------------------------------------+ **Esempio ST** .. code-block:: none (* Read user settings and write user infos. *) IF (SysUVSet) THEN i:=SysVarsscanf(ADR(SysUSetA), '%d', UDINT_TYPE, ADR(Values[0])); i:=SysVarsnprintf(ADR(SysUInfoA), 16, '%d', UDINT_TYPE, ADR(Values[0])); i:=SysVarsscanf(ADR(SysUSetB), '%d', UDINT_TYPE, ADR(Values[1])); i:=SysVarsnprintf(ADR(SysUInfoB), 16, '%d', UDINT_TYPE, ADR(Values[1])); i:=SysVarsscanf(ADR(SysUSetC), '%d', UDINT_TYPE, ADR(Values[2])); i:=SysVarsnprintf(ADR(SysUInfoC), 16, '%d', UDINT_TYPE, ADR(Values[2])); i:=SysVarsscanf(ADR(SysUSetD), '%d', UDINT_TYPE, ADR(Values[3])); i:=SysVarsnprintf(ADR(SysUInfoD), 16, '%d', UDINT_TYPE, ADR(Values[3])); END_IF; **Aggiungere porte seriali** ---------------------------- Sui dispositivi dotati di connessione Ethernet è possibile aggiungere porte seriali utilizzando dei convertitori Ethernet/Seriale. Ecco lo screenshot di un programma LD che utilizza un convertitore ATC1000 configurato come server per aggiungere una porta seriale da utilizzare per una connessione Modbus. |image20| Se configuri il convertitore come server TCP devi utilizzare il FB **SysTCPClient** per connetterti al convertitore e poi potrai utilizzare il file pointer in uscita al FB per gestire il tuo protocollo di comunicazione. Nel mio esempio ho scelto una configurazione dove il convertitore Ethernet/Seriale agisce da server TCP e lo SlimLine da Client, ma naturalmente puoi invertire i ruoli. Oppure puoi utilizzare una connessione UDP. Se hai più porte seriali da aggiungere dovrai istanziare più FB SysTCPClient, una per ogni porta COM aggiuntiva. Naturalmente il modo di comunicazione RS232/RS485 ed i parametri, baudrate, parità, bits vanno impostati tramite la pagina web di configurazione del convertitore. .. |image0| image:: media/image1.jpg :width: 7.08681in :height: 0.40556in .. |image1| image:: media/image2.jpg :width: 7.08681in :height: 1.12569in .. |image2| image:: media/image3.jpg :width: 7.08681in :height: 0.29931in .. |image3| image:: media/image4.jpg :width: 7.08681in :height: 1.05139in .. |image4| image:: media/image5.jpg :width: 7.08681in :height: 0.35069in .. |image5| image:: media/image6.jpg :width: 7.08681in :height: 1.06319in .. |image6| image:: media/image7.jpg :width: 7.08681in :height: 1.75972in .. |image7| image:: media/image8.jpg :width: 3.95694in :height: 5.90556in .. |image8| image:: media/image9.jpg :width: 7.08681in :height: 1.58681in .. |image9| image:: media/image10.jpg :width: 4.67708in :height: 5.90556in .. |image10| image:: media/image11.jpg :width: 7.36389in :height: 3.26181in .. |image11| image:: media/image12.jpg :width: 7.08681in :height: 0.35069in .. |image12| image:: media/image13.jpg :width: 7.08681in :height: 2.7875in .. |image13| image:: media/image14.jpg :width: 7.08681in :height: 0.70069in .. |image14| image:: media/image15.jpg :width: 7.08681in :height: 0.70069in .. |image15| image:: media/image16.jpg :width: 7.08681in :height: 0.52361in .. |image16| image:: media/image17.jpg :width: 2.54236in :height: 0.45833in .. |image17| image:: media/image18.jpg :width: 7.08681in :height: 0.93681in .. |image18| image:: media/image19.jpg :width: 7.08681in :height: 2.77569in .. |image19| image:: media/image20.jpg :width: 7.08681in :height: 0.52361in .. |image20| image:: media/image21.jpg :width: 7.08681in :height: 5.23611in