Alla scoperta dei PLC: la programmazione testuale

PLC - linguaggi testuali

Nella scorsa puntata del corso sui PLC abbiamo analizzato il linguaggio di programmazione forse maggiormente diffuso, vale a dire la logica ladder. Proseguiamo ora la nostra avventura nel campo della programmazione dei PLC andando a scoprire un linguaggio di tipo testuale, probabilmente il più diffuso e il più autorevole nella sua categoria: il testo strutturato (Structured Text).

Introduzione

Come suggerisce il nome stesso (Programmable Logic Controller), un PLC richiede di essere programmato per poter eseguire correttamente il suo compito. Nella scorsa puntata della nostra “avventura” nell’affascinante mondo dei PLC, abbiamo preso in considerazione (con un livello di dettaglio sufficientemente avanzato) la programmazione in logica ladder. Storicamente, tuttavia, sono comparse diverse tecniche di programmazione dei PLC, tutte presentate nel primo post della serie. Questa moltitudine di tecniche programmative se, da un lato, ha contribuito ad aumentare l’interesse e la diffusione dei PLC, ha, dall’altro, creato una certa confusione e incompatabilità tra i vari tipi di dispositivi presenti sul mercato. Si è reso pertanto necessario introdurre una standardizzazione degli strumenti di programmazione, che è avvenuta (come abbiamo già visto in precedenza) con la pubblicazione nel 1993 dello standard IEC 61131-3 da parte dell’International Electrotechnical Commission. Tra i linguaggi contemplati dallo standard rientrano sia la logica ladder che il testo strutturato, oggetto del presente articolo.

IEC 61131-3

La parte 3 dello standard IEC 61131 definisce un’architettura del software PLC chiamata “modello software”, schematicamente riportata nell’immagine seguente:

Come indicato nella figura, al livello più alto il modello software introduce il concetto di “configurazione”, intendendo con ciò tutto il software presente sul PLC. Al suo interno, la configurazione può contenere una o più risorse, dove per “risorsa” si intende tutto l’ambiente richiesto per eseguire il programma contenuto al suo interno. Procedendo verso i livelli più interni, osserviamo poi che ogni programma è composto da una collezione di elementi software chiamati “funzioni” (functions) e blocchi funzionali (functional blocks), il cui scopo è quello di eseguire un determinato insieme di istruzioni (statements). Per funzione si intende infatti una sequenza di istruzioni che, applicate ad una o più variabili di ingresso, è in grado di determinare il valore da assegnare alla variabile di uscita. E’ importante in oltre osservare che una funzione ha sempre un comportamento deterministico (o ripetitivo): con ciò intendiamo dire che dato uno stesso set di variabili in ingresso, una data funzione produrrà sempre lo stesso valore di variabile in uscita. La stessa cosa non avviene, invece, per i blocchi funzionali, i quali possono produrre valori di uscita differenti anche se eseguiti con gli stessi valori di variabili in ingresso. Ciò avviene perchè ogni blocco funzionale contiene, oltre a un set di istruzioni analogo a quello presente in una funzione, anche una struttura dati. I valori contenuti in questa struttura dati vengono mantenuti anche quando il blocco funzionale ha terminato la propria esecuzione (in modo analogo alle variabili globali o statiche del linguaggio C). Il contenuto di questa struttura dati può ovviamente influenzare il comportamento del blocco funzionale, ed è proprio questo il motivo per cui uno stesso blocco funzionale, con lo stesso set di variabili in ingresso, può produrre valori in uscita differenti.

Il testo strutturato

Il testo strutturato (“Structured Text”, o semplicemente ST in inglese), è uno dei cinque linguaggi previsti dallo standard IEC 61131 (come del resto la logica ladder, oggetto del precedente post). Si tratta di un linguaggio di programmazione ad alto livello, con numerose somiglianze con il Pascal, un linguaggio le cui caratteristiche propedeutiche sono universalmente riconosciute. Il linguaggio ST presenta la caratteristica di essere molto flessibile ed intuitivo, e ben si presta ad essere impiegato per scrivere algoritmi di controllo anche di una certa complessità. ST presenta lo stesso livello di efficienza, in termini di performance, della logica ladder, ed utilizza le strutture di controllo classiche di ogni linguaggio di programmazione ad alto livello: istruzioni di salto (condizionale e non), cicli, ecc. Per scrivere un blocco di codice in ST è sufficiente disporre di un qualunque editor ed il codice prodotto presenta il vantaggio di essere ben strutturato e facilmente comprensibile. Completano il quadro la disponibilità di un’ampia selezione di librerie e utility pronte per l’uso, oltre alla caratteristica (fondamentale per l’attività di debugging on-site) di modificare e caricare parti dell’applicazione mentre il PLC sta girando (modifiche “on-line”). Entriamo ora nel dettaglio, analizzando le caratteristiche salienti del linguaggio ST, quali i tipi di dati, le istruzioni (di assegnamento, condizionali, e di salto), e le funzioni.

I commenti

I commenti giocano un ruolo fondamentale nella scrittura del codice sorgente, indipendentemente dal tipo di linguaggio utilizzato. Un sorgente ben commentato consente infatti di comprendere meglio il codice sorgente e ne semplifica notevolmente la manutenzione, soprattutto quando le modifiche sono apportate da persone diverse dagli sviluppatori originali. In ST abbiamo a disposizione due tipi di commenti:

// prima forma di commento, da utilizzarsi per linee singole
(* seconda forma di commento, adatta per commenti suddivisi
su più linee*)

La prima forma di commento è esattamente uguale al commento per linee singolo del C++ (doppio slash), mentre la seconda forma è simile a quella del C/C++ (con la differenza che si usano le parentesi tonde anzichè gli slash).

 

Tipi di dati

Come ogni linguaggio ad alto livello, anche l’ST dispone di alcuni tipi di dati detti elementari, e altri tipi di dati detti derivati. Vediamo in dettaglio le due diverse categorie.

Tipi di dati elementari

I tipi di dati elementari sono quelli adatti a rappresentare i seguenti dati:

  • numeri interi;
  • numeri in virgola mobile;
  • data;
  • stringhe di caratteri;
  • stringhe di bit.

Interi

Tipo di dati IEC Descrizione Range Esempio
SINT short integer -128..+127 100
INT integer -32768..+32767 10000
DINT double integer -231..+231-1 210
LINT long integer -263..+263-1 240
USINT unsigned short integer 0..255 25
UINT unsigned integer 0..+216-1 1546
UDINT unsigned double integer 0..+232-1 230
ULINT unsigned long integer 0..+264-1 258

Numeri in virgola mobile

Tipo di dati IEC Descrizione Range Esempio
REAL real -1038..1038 -1025
LREAL long real -10308..10308 -10100

Tipi per rappresentare tempo e data

Tipo di dati IEC Descrizione Esempio
TIME la durata del tempo TIME#15d1h13m11s34ms
DATE data del calendario DATE#2013-05-31
TIME_OF_DAY ora del giorno TIME_OF_DAY#22:55:16.90
DATE_AND_TIME data e ora del giorno DATE_AND_TIME#2013-07-04-11:25:16.00

Tipi per rappresentare stringhe di caratteri

Tipo di dati IEC Descrizione Esempio
STRING stringa di caratteri ‘Programma per PLC’

Tipi per rappresentare stringhe di bit (bit string)

Tipo di dati IEC Descrizione Esempio
BOOL stringa di 1 solo bit 1, TRUE (equivale a 1), FALSE (equivale a 0)
BYTE stringa di 8 bit 10100110
WORD stringa di 16 bit 0001001010100110
DWORD stringa di 32 bit 00000001001000110100010101100111
LWORD stringa di 64 bit 0000000100100011010001010110011110001001..

Tipi di dati derivati

Oltre ai tipi di dati basilari appena visti, lo standard IEC 61131-3 fornisce allo sviluppatore la possibilità di definire e introdurre dei propri tipi di dati, adattandoli alle specifiche applicazioni. Per poter definire un tipo di dati “custom”, occorre includere la sua definizione tra le parole chiave TYPE e END_TYPE. I tipi di dati derivati possono poi essere suddivisi in quattro categorie:

  • strutture;
  • enumerati;
  • tipi “sub-range”;
  • array.

Vediamo ora in dettaglio in cosa consistono questi quattro tipi derivati.

Strutture

Una struttura viene definita in ST tramite le parole chiave STRUCT e END_STRUCT. Ciascun campo della struttura viene poi definito includendo il suo nome, seguito da un carattere ‘:’, dal tipo (che può essere base o derivato) e infine dal carattere ‘;’. E’ possibile inoltre annidare le strutture (una struttura può cioè contenere una o più strutture nella sua definizione). Vediamo il tutto con un esempio pratico:

TYPE T-shirt :
   STRUCT
      taglia:        INT;
      colore:        INT;
      disponibilità: BOOL;
      prezzo:        INT;
   END_STRUCT;
END_TYPE

In questo esempio abbiamo voluto definire il tipo maglietta (T-shirt), caratterizzato dai campi (proprietà) elencati. Vedremo in un esempio successivo come espandere la definizione di questo tipo derivato.

Enumerati

Gli enumerati vengono definiti racchiudendo tutti i possibili valori del tipo tra parentesi tonde. Tornando al nostro esempio precedente, supponiamo di voler definire due tipi enumerati per il colore e per la taglia della nostra maglietta. Possiamo ad esempio definire:

TYPE Colore :
   (Bianco, Rosso, Blu, Giallo, Verde, Azzurro, Rosa, Nero);
END_TYPE

TYPE Taglia:
   (XS, S, M, L, XL, XXL);
END_TYPE

A questo punto possiamo migliorare la struttura T-shirt introducendo i due tipi enumerati:

TYPE T-shirt :
   STRUCT
      taglia:        Taglia;
      colore:        Colore;
      disponibilità: BOOL;
      prezzo:        INT;
   END_STRUCT;
END_TYPE

Sub-range

I tipi sub-range, come del resto dice il nome stesso, sono ottenuti limitando il range di un altro tipo (tipicamente un intero). La definizione del sub-range viene eseguita indicando dapprima il tipo che si vuole limitare, seguito dagli estremi inferiore e superiore, racchiusi tra parentesi tonde e separati dai caratteri ‘..’.

TYPE Angolo :
   REAL(-180..+180);
END_TYPE

I tipi sub-range sono molto utili per impedire che a una variabile venga assegnato un valore al di fuori del range prestabilito. Se ciò avviene, infatti, viene generato un run-time error, proteggendo la validità del dato.

Array

In ST gli array sono definiti tramite la parola chiave ARRAY, seguita dagli indici dell’array racchiusi tra parentesi quadre (è possibile definire anche array multidimensionali), dalla parola chiave OF e infine dal tipo di variabile che compone l’array (può trattarsi sia di un tipo base che di uno derivato). Vediamo tutto questo con un paio di esempi:

// array monodimensionale di 100 interi
TYPE Mio_Array :
   ARRAY[1..100] OF INT;
END_TYPE
// array bidimensionale (25 linee per 80 colonne)
TYPE Mio_Array_2D :
   ARRAY[1..25, 1..80] OF USINT;
END_TYPE

Chi arriva da esperienze programmative con il linguaggio C, avrà subito notato come l’indice inferiore dell’array, negli esempi proposti, parta dal valore 1 e non da 0. In realtà, ST permette di utilizzare anche la classica notazione del C, con gli indici degli array che partono da 0. Ad esempio:

// array di 10 elementi con indice che parte da 0
TYPE altro_array :
   ARRAY[0..9] OF INT;
END_TYPE

Dichiarazione di variabili

ST permette di definire variabili con nome, esattamente come avviene nei comuni linguaggi di programmazione ad alto livello. E’ inoltre possibile definire delle variabili che utilizzano l’accesso diretto alla memoria, tipicamente variabili mappate direttamente sugli I/O del PLC, come ad esempio: I:002/01 e 40001. Le parole chiave utilizzate nella dichiarazione di variabili sono le seguenti:

Dichiarazione Descrizione
VAR dichiarazione di variabile interna o locale
VAR_GLOBAL dichiarazione di variabile globale
VAR_INPUT dichiarazione di variabile di input
VAR_OUTPUT dichiarazione di variabile di output
VAR_IN_OUT dichiarazione di variabile di input/output
VAR_ACCESS dichiarazione di variabile ad accesso diretto
VAR_EXTERNAL dichiarazione di variabile esterna
VAR_TEMP dichiarazione di variabile temporanea
AT assegnazione di un indirizzo di memoria ad una variabile
CONSTANT dichiarazione di una costante (variaile non modificabile)
RETAIN indica che la variabile mantiene il valore anche dopo aver rimosso l’alimentazione
END_VAR indica la chiusura della fase dichiarativa delle variabili

Esempio:

VAR
   var1: INT;
   string1: STRING;
END_VAR
VAR_INPUT
   ora: TIME_OF_DAY;
END_VAR
VAR_OUTPUT
   uscita: BOOL;
END_VAR

Le variabili di tipo VAR_INPUT, VAR_OUTPUT, e VAR_IN_OUT vengono utilizzate per definire le variabili passate come argomenti al programma oppure a una sua funzione.

Struttura di un programma

Il codice scritto in ST segue una struttura ben definita. Inizialmente occorre specificare la dichiarazione delle variabili e la dichiarazione del programma, cosa che deve essere fatta all’interno delle parole chiave INTERFACE e END_INTERFACE:

INTERFACE
   VAR_GLOBAL
      // dichiarazione delle variaibli globali
   END_VAR
   PROGRAM NomeProgramma;
END_INTERFACE

Dopo l’interfaccia, occorre definire le funzioni e i blocchi funzionali:

FUNCTION_BLOCK NomeFunzione
   ...
END_FUNCTION_BLOCK

Inine, troviamo il blocco implementation, in cui viene inclusa la definizione del programma:

IMPLEMENTATION
   PROGRAM NomeProgramma
      ...
   END_PROGRAM
END_IMPLEMENTATION

Assegnamento di variabile

In ST, per dichiarare una variabile si usa la notazione ‘:=’, proprio come avviene nel linguaggio Pascal, a cui ST si ispira profondamente. Ad esempio:

var1 := (var2 + 3) MOD 100;
// esempio di utilizzo della funzione aritmetica SIN
VarC := SIN(VarA);

Particolarmente interessanti sono alcune forme di assegnamento che si possono eseguire in ST sugli array. Ad esempio:

Array1 := Array2;

L’esempio di cui sopra permette di eseguire con un’unica istruzione l’assegnamento dell’array Array2 all’array Array1. Affinchè ciò avvenga senza errori, è necessario che i due array siano dello stesso tipo e abbiano la stessa dimensione. Un’altra caratteristica rimarchevole di ST è la possibilità di inizializzare gli array durante l’assegnamento, anche con valori differenti tra loro. Si consideri come esempio il seguente assegnamento/inizializzazione di un array:

Array_ST := 10(1), 20(2), 30(3);

Il significato è il seguente: i primi 10 elementi dell’array sono inizializzati al valore 1, i successivi 20 elementi al valore 2, e i 30 successivi al valore 3. In Pascal, per ottenere lo stesso risultato sarebbe stato necessario utilizzare molte più istruzioni.

 

Le funzioni

Le funzioni rappresentano i “mattoncini” elementari sui quali viene costruito l’intero programma. Ogni funzione è composta da un set di istruzioni, da zero oppure più variabili di ingresso,e da una o più variabili di uscita. Le funzioni permettono di eseguire più volte lo stesso tipo di codice senza doverlo riscriverlo, è infatti sufficiente eseguire più volte una chiamata alla stessa funzione. Come già anticipato precedentemente, le funzioni in ST presentano la caratteristica di produrre sempre lo stesso risultato a fronte della stessa combinazionie di valori in ingresso. Le funzioni (FUNCTION) non hanno quindi variabili statiche, e il contenuto delle variabili locali viene perso una volta che si esce dalla funzione. I FUNCTION_BLOCK, viceversa, mantengono lo stato delle proprie variabili anche quando si esce dagli stessi. In ST è possibile accedere ai parametri di uscita dei FUNCTION_BLOCK da qualunque parte del codice e in qualunque momento, ed è questo uno dei motivi per cui essi sono maggiormenti utilizzati rispetto alle tradizionali FUNCTION. Come esempio, si consideri il seguente FUNCTION_BLOCK che esegue la media aritmetica di tre valori reali forniti in ingresso:

FUNCTION_BLOCK media3real : REAL
   VAR_INPUT
      val1, val2, val3 : REAL;
   END_VAR
   media3real := (var1 + var2 + var3) / 3;
END_FUNCTION_BLOCK

Il codice mostrato sopra può poi essere chiamato come avviene nei comuni linguaggi di programmazione, ad esempio con:

media := media3real(10.0, 20.0, 30.0);

Istruzioni condizionali

Anche qui ST non si discosta molto dai tradizionali linguaggi ad alto livello. La tipica forma delle istruzioni condizionali è la seguente:

IF <condizione> THEN
   <istruzione>
ELSE
   <istruzione>
END_IF;

Come avviene in C, quando il numero di “rami” da esaminare in una sequenza if-then-else diventa troppo elevato, è conveniente ricorrere all’istruzione CASE, che oltretutto migliora notevolmente la leggibilità del codice. La sintassi del costrutto CASE è la seguente:

CASE <espressione> OF
   <selettore Valore 1> : <istruzione 1>
   <selettore Valore 2> : <istruzione 2>
   ...
ELSE
   <seguono altre istruzioni>
END_CASE;

Il selettore utilizzato nell’istruzione CASE può anche essere un tipo enumerato, come mostrato nel seguente caso:

...
VAR
   color : Colore; // vedi sopra
END_VAR
CASE <espressione> OF
   Bianco : <istruzione 1>
   Rosso  : <istruzione 2>
   Blu    : <istruzione 3>
...
ELSE
   <seguono altre istruzioni...>
END_CASE;

I cicli

Anche qui ST non si discosta molto dai comuni linguaggi di programmazione, disponendo delle tre classiche forme di ciclo (o istruzioni di iterazione): ciclo FOR-DO, ciclo DO-WHILE, e ciclo REPEAT-UNTIL. Il ciclo FOR presenta una sintassi di questo tipo:

FOR <valore iniziale> TO <valore finale> BY <incremento> DO
   <istruzioni...>
END_FOR;

Le variabili ammissibili per controllare il ciclo FOR-DO devono necessariamente essere di tipo INT, SINT, o DINT. La parte BY indica il passo (step) di incremento della variabile che controlla il ciclo; questa parte può anche essere omessa, e in questo caso lo step utilizzato di default è 1. Esempio:

FOR idx := 1 TO 10 DO
   Array_Int[idx] := idx;
END_FOR;

Il ciclo WHILE-DO ha una struttura di questo tipo:

WHILE <espressione booleana> DO
   <istruzioni...>
END_WHILE;

Esempio:

VAR
   I : INT;
END_VAR
I := 100;
WHILE (I > 0) DO
   // esegui alcune istruzioni...
   I := I - 1;
END_WHILE;

Il ciclo in questo caso verrà eseguito fintantoche la variabile I è positiva. Poichè essa viene decrementata di 1 unità ad ogni attivazione del ciclo e il suo valore iniziale è pari a 100, il ciclo verrà eseguito 100 volte. Abbiamo infine il ciclo REPEAT-UNTIL. Questo è simile al ciclo WHILE-DO, con la differenza che il controllo viene effettuato dopo che le istruzioni sono state eseguite. La sintassi è la seguente:

REPEAT
   <istruzioni...>
UNTIL <espressione booleana>
END_REPEAT;

EXIT e RETURN

Queste due istruzioni particolari sono del tutto simili alle corrispettive break e return del C. EXIT serve infatti a uscire immediatamente da un ciclo (blocco di codice iterativo), sia esso un FOR-DO, REPEAT-UNTIL, oppure DO-WHILE, mentre RETURN è simile, ma può essere utilizzato solo all’interno di FUNCTION o FUNCTION_BLOCK.

Conclusioni

Abbiamo visto in questo articolo un buon esempio di linguaggio testuale per la programmazione dei PLC. Anche se la trattazione, per ragioni di spazio, è stata limitata ai concetti e ai costrutti fondamentali del linguaggio, le informazioni qui riportate sono più che sufficienti per affrontare senza patemi la scrittura (o la modifica e manutenzione) di codice per PLC scritto in ST. Nel prossimo post vedremo di presentare qualche tool software per la scrittura di codice in ambito PLC, e sarà inoltre presentato qualche esempio applicativo sia in logica ladder che in ST. Arrivederci alla prossima puntata!

 

Quello che hai appena letto è un Articolo Premium reso disponibile affinché potessi valutare la qualità dei nostri contenuti!

 

Gli Articoli Tecnici Premium sono infatti riservati agli abbonati e vengono raccolti mensilmente nella nostra rivista digitale EOS-Book in PDF, ePub e mobi.
volantino eos-book1
Vorresti accedere a tutti gli altri Articoli Premium e fare il download degli EOS-Book? Allora valuta la possibilità di sottoscrivere un abbonamento a partire da € 2,95!
Scopri di più

Leave a Reply