Questo è il primo articolo che scrivo che tratta argomenti inerenti al mio lavoro, proverò a fare un riassunto delle tipologie di linguaggi disponibili per la programmazione dei PLC nel mondo Siemens (famiglia S7), portando qualche semplice esempio concreto.
Le "nuove" generazioni di PLC S7-1200 e S7-1500 uscite ormai già da qualche anno, unite all'ambiente di sviluppo TIA Portal che ha soppiantato il Simatic Manager, hanno portato ad una modifica di rotta rendendo di fatto obsoleto un certo modo di programmare.
Indice
|
1. Premessa
Lo standard IEC 61131-3, nato ancora nel 1993 e voluto dall'organizzazione PLCopen, definisce gli elementi base per la programmazione nell'automazione industriale con PLC, quali linguaggi, tipi di dati ed unità organizzative del software.
I linguaggi previsti sono quattro, due di tipo grafico:
- diagramma a contatti - LD (Ladder Diagram)
- diagramma a blocchi funzione - FBD (Function Block Diagram)
e due di tipo testuale:
- lista istruzioni - IL (Instruction List) - dichiarato deprecato dalla terza revisione dello standard (2012)
- testo strutturato - ST (Structured Text)
A questi si aggiunge il diagramma funzionale sequenziale - SFC (Sequential Function Chart), derivato dal Grafcet e che permette di descrivere graficamente le sequenze di un sistema di automazione.
La standardizzazione è molto importante, sia per ridurre i costi/tempi di formazione, sia per facilitare la portabilità del codice o almeno di alcuni moduli, anche se è innegabile che i PLC commerciali presentano spesso delle peculiarità legate al costruttore, come l'esatta sintassi di un particolare linguaggio o le modalità di dichiarazione delle variabili.
Io francamente avrei inserito nella lista anche un linguaggio standard come il C, ma è un mio punta di vista personale.
Per quanto riguarda Siemens, i corrispondenti linguaggi sono:
- diagramma a contatti: KOP (Kontactplan)
- diagramma a blocchi funzione: FUP (Functionsplan)
- lista istruzioni: AWL (Anweisungsliste) / STL (Statement List)
- testo strutturato: SCL (Structured Control Language)
- diagramma funzionale sequenziale: GRAPH
Per le CPU S7-1200 non sono disponibili i linguaggi AWL e GRAPH.
2. Introduzione
In ogni CPU è già presente un sistema operativo che organizza le varie funzioni, ad esempio:
- aggiornamento dell'immagine di processo degli ingressi e delle uscite
- richiamo del programma utente
- gestione degli errori
- identificazione di allarmi e richiamo dei rispettivi OB
Il programma utente elabora il compito specifico di automazione e viene caricato nella CPU.
A meno che non si tratti di poche operazioni e davvero molto semplici, è sconsigliato implementare tutto il codice del programma utente in un unico blocco, per fare un paragone con altri ambiti, sarebbe come scrivere un intero programma C nella funzione Main.
Per questo motivo è previsto che il codice venga organizzato in blocchi (programmazione strutturata), in questo modo possono essere resi più o meno generici e riutilizzabili alcuni moduli, il tutto diventa più leggibile, sezionabile per il debug, modificabile da altri.
La profondità massima di annidamento varia a seconda della famiglia di CPU, ad esempio per S7-300 e S7-1200 si può arrivare ad una profondità di 16 blocchi per classe di priorità, per S7-400 e S7-1500 la profondità massima è di 24 blocchi.
Quando viene richiamato un blocco, ne vengono elaborate le istruzioni fino al termine, di seguito l'elaborazione viene ripresa dall'istruzione successiva del blocco richiamante:
2.1 Ambiente di sviluppo
L'ambiente di sviluppo attuale è TIA (Totally Integrated Automation) Portal, una piattaforma modulare pensata per tutti i compiti di automazione, che consente di installare i vari moduli in modo da avere poi tutto in una unica applicazione, il modulo per lo sviluppo del PLC è STEP 7, la versione basic consente di programmare solo la famiglia S7-1200, quella professional anche S7-300/400/1500.
Il pacchetto PLCSIM consente la simulazione del programma PLC e potrebbe essere molto utile.
In precedenza vi erano più applicazioni separate: Simatic Step 7 per i PLC, Starter per gli azionamenti, WinCC per i pannelli, ecc.
Questo articolo fa riferimento a TIA Portal V16.
2.2 Tipi di blocchi
Le tipologia di blocchi disponibili sono:
- blocchi organizzativi - OB
- funzioni - FC
- blocchi funzionali - FB
- blocchi dati - DB
Per ciascun blocco di codice (OB-FC-FB) può essere specificato il linguaggio di programmazione, in modo da utilizzare quello più adatto a seconda dell'operazione da eseguire.
I blocchi in linguaggio AWL-KOP-FUP sono ulteriormente divisi in segmenti, ciascuno con un rispettivo titolo e commento.
Si può sempre commutare il linguaggio di programmazione tra KOP e FUP, cambierà anche la rappresentazione grafica di quanto già scritto. Si può inoltre avere una programmazione mista anche all'interno del blocco:
- con le CPU S7-300 e S7-400 nei blocchi KOP e FUP possono essere inseriti segmenti AWL
- con le CPU S7-1200 e S7-1500,nei blocchi KOP e FUP possono essere inseriti segmenti SCL
E' bene inoltre abituarsi ad usare le proprietà dei blocchi per memorizzare almeno la versione e l'autore, magari con un commento esteso.
2.2.1 Blocchi organizzativi [OB]
I blocchi organizzativi sono l'interfaccia tra sistema operativo e programma utente, vengono richiamati dal sistema, a seconda della CPU impiegata possono essere disponibili blocchi organizzativi diversi.
OB1 è l'unico blocco presente alla creazione di un nuovo progetto, gli altri possono essere inseriti e programmati per determinare il funzionamento della CPU a seconda dell'evento. Nelle ultime versioni di TIA Portal è possibile modificare anche il numero di questi blocchi organizzativi.
I principali OB sono:
- OB1 (Program Cycle), viene elaborato ciclicamente e rappresenta il programma principale (Main), il tempo ciclo è variabile e dipende dalle operazioni svolte
- OB100 (Startup), viene elaborato una volta sola, all'avvio, nel momento in cui la CPU passa dallo stato STOP a RUN
- OB30-38 (Cyclic Interrupt), vengono elaborati ad intervalli regolari (schedulazione orologio) interrompendo OB1, quindi in maniera indipendente dal tempo di scansione, se si imposta un intervallo troppo basso che non si riesce a rispettare, viene avviato un OB di allarme
- OB40-48 (Hardware Interrupt), vengono richiamati in corrispondenza di uno specifico evento HW assegnato, ad esempio l'attivazione di un ingresso, interrompendo anche in questo caso OB1
- OB80 (Time Error Interrupt), viene richiamato nel caso di superamento del tempo ciclo massimo impostato (Watch Dog)
- OB82 (Diagnostic Error Interrupt), viene richiamato in caso di rilevamento di un errore da parte di un'unità con funzioni di diagnostica
- OB83 (Pull or Plug of Modules), viene richiamato in caso di estrazione/inserimento di moduli progettati
- OB86 (Rack or Station Failure), viene richiamato in caso di guasto di un telaio di montaggio o in periferia decentrata (bus di campo)
- OB121 (Programming Error), viene richiamato se si verifica un errore durante l'elaborazione del programma
Per gli OB sono selezionabili i linguaggi KOP-FUP-SCL-AWL (quest'ultimo non con S7-1200).
2.2.2 Funzioni [FC]
Le funzioni sono blocchi di codice senza memoria, possono essere richiamate più volte ed in punti diversi, direttamente dal programma principale o da altre FC/FB. Possono ricevere e restituire uno o più parametri al chiamante e possono accedere alle variabili globali (DB o merker), ma non hanno una memoria interna statica.
L'editor dei blocchi di codice è diviso in due parti, nella parte superiore è visibile l'interfaccia del blocco, in quella inferiore il codice:
Nell'interfaccia possono essere definiti le seguenti tipologie di parametri:
- Input: parametri in ingresso letti dal blocco
- Output: parametri in uscita scritti dal blocco
- InOut: parametri di transito, letti dal blocco al richiamo e modificabili dallo stesso
- Temp: definizione di variabili locali temporanee, permettono il salvataggio di risultati intermedi, prima di utilizzare questi parametri è buona norma inizializzarli (a seconda del tipo di accesso e del tipo di dato potrebbero assumere dei valori casuali all'entrata nel blocco)
- Constant: definizione di costanti locali
- Return: valore restituito al blocco chiamante, è paragonabile ad un parametro Output, ma può risultare comodo con il linguaggio SCL per scrivere espressioni matematiche o valutazioni logiche su una riga come in altri linguaggi
L'assegnazione dei parametri Input/Output/InOut al richiamo del blocco avviene come copia (call by value) nel caso di dati semplici, mentre avviene tramite puntatore (call by reference) nel caso di dati strutturati.
Per gli FC sono selezionabili i linguaggi KOP-FUP-SCL-AWL (quest'ultimo non con S7-1200).
2.2.3 Blocchi funzionali [FB]
I blocchi funzionali hanno una caratteristica importante rispetto alle funzioni: vengono richiamati con un blocco dati di istanza, infatti vengono anche definiti "blocchi con memoria".
Rispetto alle FC:
- i parametri in ingresso, uscita e transito vengono memorizzati in modo permanente nel blocco dati di istanza, se ne potrà disporre anche dopo l'elaborazione, per le variabili di ingresso si può impostare anche un valore predefinito del parametro (quindi ad esempio è possibile non passare tutti i parametri)
- nell'interfaccia del blocco sarà disponibile una sezione Static, dove è possibile definire variabili che manterranno il valore tra una chiamata e l'altra, anche per queste variabili è possibile impostare un valore iniziale
- nell'interfaccia del blocco non sarà disponibile il parametro Return, specifico delle FC
- tutte le variabili dell'interfaccia del blocco, escluso le Temp e Constant, saranno disponibili nel blocco dati di istanza
Come per le FC, i blocchi funzionali possono essere richiamate più volte ed in punti diversi, direttamente dal programma principale o da altre FC/FB e possono accedere alle variabili globali (DB o merker).
Il blocco funzionale può salvare i propri dati specifici dell'istanza in un proprio DB o nel DB di istanza del FB richiamante (multi-istanza), per fare questo si dichiara il blocco FB da richiamare tra i dati Static.
I blocchi funzionali permettono di lavorare a moduli, facendo attenzione è possibile impostare anche sul PLC una programmazione orientata agli oggetti, anche se rudimentale, consideriamo un FBx come una classe:
- si può impostare l'interfaccia del blocco verso l'esterno
- nei dati Static abbiamo la memorizzazione dei membri della classe
- con la multi-istanza possiamo avere delle sotto-classi all'interno del blocco
- un blocco dati di istanza è come un oggetto, ovvero una istanza della classe FBx
per lavorare così non si dovrà far riferimento a variabili globali all'interno del blocco e non si dovrà accedere al DB di istanza da altre funzioni.
L'assegnazione dei parametri Input/Output/InOut al richiamo del blocco avviene sempre come copia (call by value), tranne nel caso di dati strutturati di transito InOut, che avviene tramite puntatore (call by reference), a meno di casi particolari.
Ne consegue che, a livello di efficienza, è meglio dichiarare nella sezione InOut parametri come strutture dati, in modo da evitare un'inutile copia ed accedervi mediante puntatore.
Per gli FB sono selezionabili i linguaggi KOP-FUP-SCL-AWL-GRAPH (gli ultimi due non con S7-1200).
2.2.4 Blocchi dati [DB]
I blocchi dati sono usati per memorizzare i dati del programma utente. Possono essere:
- DB globali: strutture definite a piacere con variabili visibili da tutti gli altri blocchi
- DB di istanza: sono usati per gestire i dati delle singole istanze degli FB
- DB array: disponibili solo con S7-1500 per utilizzi particolari
I blocchi dati per S7-1200 o S7-1500 possono avere accesso ottimizzato o standard (quelli per S7-300 o S7-400 solo standard), l'impostazione è modificabile nelle Proprietà->Attributi del blocco e di default l'accesso ottimizzato è attivo:
I blocchi con accesso ottimizzato non dispongono di una struttura definita, gli elementi contengono solo il nome simbolico e non l'indirizzo, mentre in quelli con accesso standard, oltre al nome simbolico è visibile la colonna offset e diventa possibile accedere alla variabile anche attraverso il suo indirizzo assoluto in memoria.
L'accesso non ottimizzato è stato mantenuto per consentire l'interfacciamento con terze parti che necessitano di conoscere gli indirizzi delle variabili, altrimenti l'uso del simbolico è fortemente consigliato:
- l'accesso ai dati è più veloce in quanto gestito dal sistema
- è possibile definire la ritentività per le singole variabili, in caso contrario si può impostare la ritentività solo per l'intero DB
- l'indirizzamento assoluto è sempre meno necessario, oltre che più scomodo, protocolli come OPC-UA lavorano in simbolico e da codice è possibile comunque accedere (accessi slice) a singoli bit , byte o word di una variabile, ad esempio #<nome-var>.%X3 per accedere al bit 0.3 di una bitmask
L'accesso ottimizzato lavora in maniera differente tra S7-1200 e S7-1500.
Nel primo caso viene ottimizzata l'occupazione in memoria, salvando le variabili più grandi (come larghezza) all'inizio del blocco e quelle più piccole alla fine:
Nel secondo caso viene privilegiata la performance ed i singoli bit occupano un byte per evitare il mascheramento nelle operazioni booleane a bit:
L'accesso ottimizzato è una impostazione disponibile anche per altri blocchi, per gli FB determina come viene creato il DB di istanza, per gli FC l'indirizzamento delle variabili locali.
2.3 Variabili
Le principali aree degli operandi sono:
- immagine di processo degli ingressi - %I
- immagine di processo delle uscite - %Q
- merker - %M - utilizzo deprecato
- contatori - %C - utilizzo deprecato
- temporizzatori - %T - utilizzo deprecato
- blocchi dati - %DB
- dati locali - %L
Sono tutte aree globali, tranne i dati locali (Temp) dei blocchi.
Le aree di memoria sono organizzate in byte, la notazione con % rappresenta un indirizzamento assoluto:
- %I3.7 - ingresso byte 3, bit 7
- %M10.0 - merker byte 10, bit 0
- %Q5.8 - uscita con indirizzo errato, l'accesso a bit deve essere compreso tra x.0 e x.7
- %DB2.DBB3 - blocco dati 2, byte 3
- %MW50 - merker word 50 (byte 50 e 51)
- %ID4 - ingressi double-word 4 (byte 4-5-6-7)
Mentre con le CPU S7/300-400 l'indirizzamento assoluto era molto utilizzato anche per questioni di performance, con le S7-1200/1500 Siemems consiglia fortemente l'indirizzamento simbolico e l'utilizzo dell'accesso ottimizzato ai blocchi, sconsigliando l'utilizzo di variabili globali come merker, temporizzatori e contatori, da sostituire con variabili di blocco, timer e counter IEC (utilizzabili con i propri DB di istanza o in multi-istanza) in modo da favorire lo sviluppo di moduli generici e riutilizzabili.
Con le tabelle delle variabili è possibile gestire i nomi simbolici delle variabili globali, che vanno richiamati tra apici "nome-var", mentre nell'interfaccia del blocco si gestiscono le variabili di blocco viste in precedenza (Input/Output/InOut/Static/Temp), che vanno richiamate con la notazione #nome-var.
Gli accessi slice (solo per S7-1200/1500) accennati in precedenza consentono un indirizzamento mirato all'interno delle variabili dichiarate:
- #nome-var.%X<numero bit>
- #nome-var.%B<numero byte>
- #nome-var.%W<numero word>
- #nome-var.%D<numero double-word>
2.3.1 Tipi di dati
Per i tipi di dati bisognerebbe scrivere un articolo a parte, potete avere un'infarinata leggendo questo vecchio articolo, che è ancora valido.
Qui vi riporto una tabella riassuntiva con le disponibilità a seconda della CPU:
Numeri binari | S7-300/400 | S7-1200 | S7-1500 |
---|---|---|---|
BOOL | X | X | X |
Bitmask | S7-300/400 | S7-1200 | S7-1500 |
BYTE (8 bit) | X | X | X |
WORD (2 byte) | X | X | X |
DWORD - Double WORD (4 byte) | X | X | X |
LWORD - Long WORD (8 byte) | - | - | X |
Numeri interi | S7-300/400 | S7-1200 | S7-1500 |
SINT - Short INT (1 byte) | - | X | X |
INT (2 byte) | X | X | X |
DINT - Double INT (4 byte) | X | X | X |
USINT - Unsigned Short INT (1 byte) | - | X | X |
UINT - Unsigned INT (2 byte) | - | X | X |
UDINT - Unsigned Double INT (4 byte) | - | X | X |
LINT - Long INT (8 byte) | - | - | X |
ULINT - Unsigned Long INT (8 byte) | - | - | X |
Numeri in virgola mobile | S7-300/400 | S7-1200 | S7-1500 |
REAL (4 byte) | X | X | X |
LREAL - Long REAL (8 byte) | - | X | X |
Tempo | S7-300/400 | S7-1200 | S7-1500 |
S5TIME - formato BCD (2 byte) - deprecato | X | - | X |
TIME (4 byte) [ms] | X | X | X |
LTIME - Long TIME (8 byte) [ns] | - | - | X |
Data e ora | S7-300/400 | S7-1200 | S7-1500 |
DATE (2 byte) [days] | X | X | X |
TOD - Time of day (4 byte) [ms] | X | X | X |
LTOD - Long time of day (8 byte) [ns] | - | - | X |
DT - Date and time (8 byte) | x | - | X |
LDT - Date and long time (8 byte) | - | - | X |
DTL | - | X | X |
Stringhe | S7-300/400 | S7-1200 | S7-1500 |
CHAR (1 byte) | X | X | X |
WCHAR - Wide CHAR (2 byte) | - | X | X |
STRING (len+2 byte) | X | X | X |
WSTRING - Wide CHAR STRING (len+2 word) | - | X | X |
Tipo di dati definiti dall'utente (UDT) | S7-300/400 | S7-1200 | S7-1500 |
UDT | X | X | X |
Strutture dati anonime | S7-300/400 | S7-1200 | S7-1500 |
STRUCT | X | X | X |
Array | S7-300/400 | S7-1200 | S7-1500 |
ARRAY [] of <data type> | X | X | X |
Puntatori | S7-300/400 | S7-1200 | S7-1500 |
REF_TO <data type> | - | - | X |
VARIANT | - | X | X |
POINTER (6 byte) | X | - | X |
ANY (10 byte) | X | - | X |
2.3.2 Temporizzatori e contatori
Pe evitare di usare i timer e counter globali e rendere riutilizzabili i blocchi di codice, abbiamo a disposizione degli FB di sistema (SFB) che implementano le tipologie più utilizzate (qualche informazione sui timer la trovate anche in questo mio articolo) e che necessitano di un DB di istanza:
- Timer IEC
- TON - temporizzatore ritardato all'inserzione
- TOF - temporizzatore ritardato alla disinserzione
- TP - temporizzatore ad impulso (genera un impulso di durata impostata)
- TONR - temporizzatore ritardato all'inserzione ritentivo (con memoria) ovvero quando l'ingresso va a FALSE il conteggio non viene resettato
l'interfaccia di questi blocchi prevede i seguenti parametri di IN/OUT:
- IN (Input) - condizioni per l’avvio del timer [BOOL]
- PT (Input) - tempo di conteggio [TIME,LTIME]
- Q (Output) - stato del timer [BOOL]
- ET (Output) - valore attuale del conteggio [TIME,LTIME]
- R (Input) - resetta il timer [BOOL] - solo con TONR
- Counter IEC
- CTU - conteggio in avanti
- CTD - conteggio all'indietro
- CTUD - conteggio in avanti ed all'indietro
I contatori incrementano o decrementano ll conteggio in corrispondenza di un fronte sull'ingresso, a seconda della tipologia lo stato commuta con il raggiungimento di una soglia impostata.
3. Linguaggi
Facciamo una breve carrellata sui linguaggi di programmazione, per poi tornarci con degli esempi.
3.1 KOP (Ladder Diagram)
Il diagramma a contatti è uno dei primi linguaggi utilizzati sui PLC, molto amato dai manutentori e dal service per l'immediatezza nel seguire le combinazioni logiche, si può in prima battuta immaginare di ruotare in orizzontale i vari rami di uno schema funzionale, dando la vaga impressione di una "scala a pioli" (ladder), la rappresentazione è quindi basata sui circuiti elettromeccanici, i rami sono anche detti rung.
3.2 FUP (Function Block Diagram)
Il diagramma a blocchi funzione è un altro linguaggio grafico, la rappresentazione è basata sui sistemi circuitali elettronici. Ogni segmento contiene i vari percorsi logici e le interrogazioni dei segnali vengono collegate tra di loro mediante dei blocchetti.
Alcuni lo preferiscono al KOP quando si hanno molti rami, io li trovo equivalenti.
3.3 AWL (Instruction List)
AWL è la versione di Siemens della lista istruzioni, definito "hardware oriented", è un linguaggio di basso livello, il più vicino al linguaggio macchina tra quelli visti in precedenza, ricorda un po' l'assembly e lavora direttamente sui registri della CPU.
E' il linguaggio dove ho nettamente più esperienza: quando iniziai a lavorare, nel '95/96, Step 7 era una novità, la "vecchia guardia" era abituata con AWL dello Step 5 e la stessa Siemens definiva questo linguaggio come il più performante e con il set di istruzioni più ampio: se era sempre possibile passare da KOP/FUP ad AWL, non lo era il contrario. Certo può sembrare un po' ostico, ma quando poi ci si abitua ci si rende anche conto delle potenzialità, si imparano a dominare puntatori e registri di indirizzo, e con l'esperienza si mettono in atto varie accortezze.
Con le CPU S7-1200/1500 abbiamo una inversione di rotta:
- le prestazioni superiori nell'usare questo linguaggio non sono più aprezzabili (i registri della CPU da quanto ho capito sono simulati ed i processori molto più veloci)
- il linguaggio ad alto livello SCL non è più opzionale ma integrato nello Step 7 di TIA Portal e consente operazioni complesse con una semplicità non comparabile con AWL
- AWL non è disponibile per le CPU S7-1200, chiaro segnale anche dalla stessa Siemens
- la terza revisione dello standard IEC 61131-3 lo dichiara deprecato: una cattiva programmazione può facilmente causare loop infiniti, puntamento a zone errate di memoria, errori aritmetici, insomma se questo linguaggio può essere intuitivo per un programmatore, senza una formazione specifica è molto difficile analizzarne il codice
3.4 SCL (Structured Text)
Il testo strutturato è un linguaggio simile al Pascal, molto comodo per la manipolazione di dati o per svolgere algoritmi complessi, troviamo infatti i vari costrutti (IF, CASE, FOR, WHILE, ecc.) presenti nei linguaggi ad alto livello.
Con TIA Portal è divenuto uno dei linguaggi nativi e viene compilato direttamente in linguaggio macchina, mentre in passato era opzionale e veniva compilato in codice AWL poco efficiente.
3.5 GRAPH (Sequential Function Chart)
Il diagramma funzionale sequenziale è utilizzato per definire e strutturare l'organizzazione interna dei programmi e dei blocchi, più che un linguaggio è un mezzo grafico per partizionare il codice e visualizzare visivamente lo stato o la modalità della macchina, mettendone in evidenza il comportamento sequenziale.
Molti automatismi possono essere scomposti in una serie di fasi (o stati o passi), con GRAPH è possibile descrivere come un sistema, al verificarsi di determinati eventi, passi da una stato all'altro.
Un diagramma può essere anche molto articolato, con rami mutuamente esclusivi (divergenza e successiva convergenza) o con esecuzione simultanea (parallelismo e successiva sincronizzazione).
4 Esempi di utilizzo
Dopo tante chiacchiere, il miglior modo per capire qualcosa ed evidenziare alcune delle differenze tra i vari linguaggi, è vedere degli esempi di utilizzo.
4.1 Esercizio di logica combinatoria e di temporizzazione
Come esempio di questo tipo ho scelto uno schema che avevo già pronto, relativo ad un compito molto semplice, l'avviamento stella-triangolo di un motore asincrono:
Per realizzarlo tramite PLC, innanzitutto identifichiamo ed assegniamo ingressi ed uscite:
- Ingressi
- I0.0 - F2 Protezione termica, contatto NC
- I0.1 - S1 Pulsante di arresto, contatto NC
- I0.2 - S2 Pulsante di avvio, contatto NA
- Uscite
- Q0.0 - KM1 Teleruttore comando motore
- Q0.1 - KM2 Teleruttore avviamento a stella
- Q0.2 - KM3 Teleruttore connessione a triangolo
Il funzionamento prevede:
- allo start (pressione di S1) devono attivarsi contemporaneamente KM1 e KM2, consentendo l'avvio del motore con collegamento a stella quindi a tensione ridotta
- dopo il tempo di accelerazione (nello schema gestito da KT1) il motore raggiunge la velocità nominale, si disattiva KM2
- in questa breve fase il motore non risulta alimentato e gira per inerzia
- dopo il tempo di transizione T2 (nello schema dovuto alla commutazione dei contatti) si attiva KM3 collegando il motore a triangolo e quindi alla tensione di rete (avviamento concluso)
- in qualunque momento alla pressione del pulsante di stop S1 o con l'intervento della termica F2 il motore deve arrestarsi
Visto che non costa nulla (la logica è programmabile e non cablata) prevediamo di gestire anche il breve ritardo di transizione T2 tra la disattivazione di KM2 e l'attivazione di KM3.
Il diagramma temporale quindi sarebbe:
Proveremo ad impostare il lavoro in modo da ottenere un blocco riutilizzabile tutte le volte che avremo la necessità di usare un avviamento stella-triangolo di un motore asincrono. Io userò un FB, quindi con memoria statica, che consentirà di:
- utilizzare i timer IEC in multi-istanza senza assegnare ogni volta dei DB di istanza specifici
- esporre all'esterno i ritardi T1 e T2, assegnandogli dei valori predefiniti (T1 = 5 s e T2 = 50 ms), in modo che siano comunque modificabili ed adattabili ad ogni motore, ma se vanno bene i valori di default allora possiamo anche non passarli
Possiamo quindi già implementare l'interfaccia del blocco:
dove abbiamo come parametri di input al blocco i tre ingressi ed i due ritardi, in più ho previsto un segnale di abilitazione generica, da utilizzare per condizionare il funzionamento del motore ad esempio a seconda del modo operativo della macchina o con arresto di emergenza non attivo. Come segnali di output avremo le tre uscite dei teleruttori, mentre nei dati Static ho inserito le istanze dei timer IEC, un flag di comando attivo ed una variabile statica necessaria per interrogare il fronte di salita del pulsante di start (non strettamente necessario, ma già che c'eravamo).
L'interrogazione del fronte di salita o di discesa di un ingresso o di una determinata condizione, genera un segnale che rimane alto per un solo ciclo di scansione al verificarsi della commutazione dell'ingresso:
In questo caso, usando il fronte, si evita l'avvio del motore in casi anomali in quanto quando si preme il pulsante di start le abilitazioni devono esserci tutte, ad esempio se vengono premuti sia S1 che S2 simultaneamente e poi si rilascia S1, il motore non parte: è necessario rilasciare anche S2 e ripremerlo.
4.1.1 KOP
Nella logica cablata alcune scelte sono derivate dalla necessità di utilizzare il minor numero di contatti o, come nello schema funzionale di esempio, per evitare di mantenere alimentata la bobina del temporizzatore durante il normale funzionamento del motore.
Nell'implementazione su PLC tutte queste problematiche non ci sono, ogni bobina non ha limite sul numero di contatti, come non c'è nessun problema ad utilizzare delle memorie intermedie, per cui non è necessario seguire "alla lettera" lo schema di partenza.
Il compito lo ho svolto così:
il simbolo ┫P┣ è usato per interrogare il fronte positivo del segnale di #Start, inoltre ho preferito utilizzare l'istruzione flip-flop SR per attivare il comando (R1-reset ha priorità rispetto a S-set), ma sarebbe equivalente ad impostare una bobina con auto-ritenuta:
Ho poi simulato il funzionamento con PLCSIM, nel collegamento online la grafica è molto utile:
Riporto per completezza il trace rilevato, sempre in simulazione:
Il richiamo del blocco funzionale, in questo caso da OB1 è visualizzato così:
dove notiamo l'assegnazione degli ingressi (a sinistra) e delle uscite (a destra), mente i ritardi sono stati lasciati ai valori di default.
4.1.2 FUP
L'implementazione è analoga a quanto visto in KOP, la rappresentazione è la seguente:
notate che gli operandi negati sono indicato con il "pallino", i box con il simbolo "&" rappresentano l'operazione AND, quelli con simbolo ">=1" l'operazione OR.
Anche qui la grafica viene in aiuto nell'analisi con collegamento online:
E qui vediamo il richiamo in OB1 con le stesse modalità del KOP:
4.1.3 AWL
Ecco l'esercizio svolto in AWL, l'editor dà la possibilità di inserire commenti ogni riga:
al contrario di altri PLC, in AWL per iniziare un ramo non c'è l'istruzione LD (Load), ma in genere si parte direttamente con un A (And), ho usato lo mnemonico internazionale, nelle impostazioni è possibile settare in alternativa quello tedesco (che era predefinito nel vecchio Simatic Manager).
L'istruzione FP serve per interrogare il fronte positivo, AN e ON sono le operazioni AND ed OR con operando negato.
Ecco come si presenta il collegamento online, è possibile visualizzare sia il risultato logico di ogni sequenza che lo stato dei vari operandi:
Così compare il richiamo del blocco da OB1:
4.1.4 SCL
Nel caso del SCL, l'interfaccia è stata leggermente modificata per utilizzare il blocco funzionale di sistema R_TRIG, per interrogare il fronte di salita del pulsante start, quindi la memoria BOOL di appoggio Static, diventa l'istanza appunto di R_TRIG ed inoltre è stata aggiunta una variabile intermedia temporanea per l'utilizzo del fronte:
in alternativa avremmo potuto interrogare in autonomia il fronte memorizzando lo stato precedente dell'ingresso su una variabile BOOL e verificando l'evento di commutazione.
Ecco il compito svolto in SCL, anche qui è possibile commentare ogni riga, il tutto diventa molto compatto:
Così compare nel collegamento online:
in caso di espressioni la colonna di destra mostra il risultato dell'elaborazione, ma è possibile con la freccetta espandere la visualizzazione per avere lo stato dei vari operandi.
Il richiamo del blocco da OB1 si presenta così:
i parametri di ingresso e transito sono assegnati con ":=", quelli di uscita con "=>".
4.2 Esercizio di calcolo
Un esempio di calcolo potrebbe essere la conversione di un segnale analogico 4-20mA proveniente da un trasduttore lineare di posizione.
Va sempre verificato sul datasheet del modulo utilizzato, ma in genere la rappresentazione di un valore analogico è di questo tipo:
Immaginiamo che la posizione del dispositivo assuma i valori -500 e 500 rispettivamente agli estremi del range nominale del trasduttore.
In questo caso utilizziamo un FC, non ci serve memoria statica, in Input prevediamo di passare il valore analogico di ingresso, assieme alle posizioni minima/massima corrispondenti ai valori nominali, mentre come ritorno della funzione il valore di posizione calcolato.
La diagnostica di overflow/underflow/rottura conduttore è impostabile anche nella configurazione dell'hardware, ma in questo caso, a scopo didattico, prevediamo di segnalare la condizione di rottura conduttore come parametro di uscita del blocco, valutando un valore analogico inferiore al limite under-range (1.185 mA). L'interfaccia del blocco diventa:
4.2.1 KOP
In questo esercizio ho utilizzato le istruzioni NORM_X e SCALE_X concatenate, la prima restituisce in output un valore Real compreso tra 0 e 1, corrispondente al valore in ingresso rispetto al range nominale, la seconda riporta in scala il valore rispetto alle posizioni limite, se il valore analogico è inferiore al limite under-range, la posizione non viene calcolata e si attiva il segnale #CableCut:
in alternativa è possibile convertire i valori Int in Real ed usare l'operazione CALCULATE in un unico passo:
I connettori EN/ENO, che compaiono anche in FUP, rappresentano rispettivamente l'abilitazione in ingresso ed in uscita: le istruzioni con questo meccanismo vengono eseguite se l'ingresso di abilitazione EN è TRUE, se l'elaborazione del box è regolare allora l'uscita ENO sarà anch'essa a TRUE, se si verifica un errore nel corso dell'elaborazione, ENO viene resettata. Se EN non è interconnessa, l'istruzione viene sempre eseguita.
4.2.2 FUP
La rappresentazione nel diagramma a blocchi funzione anche in questo caso è molto simile al KOP:
4.2.3 AWL
In AWL l'esercizio di calcolo può sembrare un po' complesso.
Per condizionare operazioni in AWL normalmente si usano i salti anche se poco eleganti (in questo caso il salto condizionato JC), mentre per i calcoli bisogna porre attenzione al tipo di dati dei vari operandi, l'istruzione L x sposta il contenuto dell'accumulatore 1 all'accumulatore 2 e carica il valore x nell'accumulatore 1, mentre -R,+R,*R,/R eseguono le operazioni elementari tra accumulatori con dati di tipo Real :
4.2.4 SCL
Con SCL l'esercizio di calcolo diventa quasi banale, in poche righe è tutto implementato (sono andato a capo nell'ultima operazione solo per rendere più leggibile):
4.3 Esempio di selezione di un valore
Per questo compito immaginiamo di dover selezionare da un array di valori di tipo INT, quello corrispondente ad un indice che viene passato come parametro di ingresso, anche in questo caso useremo un FC in quanto non serve memoria statica.
Se l'indice non è compreso nell'intervallo prevediamo di restituire -999.
Come interfaccia del blocco, passiamo in ingresso l'array indefinito (che verrà passato per riferimento) e l'indice per la selezione del valore. La funzione ritornerà il valore selezionato:
se si trattasse di un FB, converrebbe mettere l'array su un parametro di transito InOut, in modo che venga passato tramite puntatore per riferimento (si evita una inutile copia dei dati).
Questo esercizio sembrerebbe banale per chi è abituato con linguaggi ad alto livello, ma sul PLC è stato reso semplice con l'accesso simbolico agli elementi dell'array, in precedenza era possibile con AWL e memorizzazione assoluta, puntando all'indirizzo in memoria dell'elemento.
4.3.1 KOP
L'esercizio è stato svolto usando le istruzioni LOWER/UPPER_BOUND per determinare i limiti dell'array (con DIM si specifica il numero di dimensioni), mentre con IN_RANGE si verifica la correttezza dell'indice passato:
4.3.2 FUP
Anche in questo caso cambia la rappresentazione grafica, ma non il senso rispetto a quanto visto in KOP:
4.3.3 AWL
Ecco l'esercizio svolto in AWL, anche qui abbiamo innanzitutto determinato i limiti inferiore e superiore dell'array ed abbiamo dovuto usare i salti (condizionati JC ed incondizionati JU):
l'istruzione POP ripristina #Index (in DInt) nell'accumulatore 1.
4.3.4 SCL
Anche in questo caso, come nell'esercizio precedente, in questo tipo di operazioni SCL dimostra l'immediatezza e la semplicità di un linguaggio ad alto livello:
4.4 Esercizio di iterazione
Come esempio di iterazione, prevediamo di ritornare l'indice del primo valore di un array di interi, che supera una determinata soglia. Anche in questo caso useremo un FC.
Se nessun valore soddisfa questa condizione, ritorniamo -1.
Impostiamo ora l'interfaccia del blocco, come nel caso precedente passiamo in ingresso l'array indefinito, con anche la soglia per individuare l'indice. La funzione ritornerà l'indice che soddisfa la condizione impostata:
4.4.1 KOP
Con il diagramma a contatti questo tipo di esercizio diventa macchinoso, si devono utilizzare i salti, in ogni segmento può esservi una sola istruzione di salto, e cosi via, io l'ho implementato così:
la prima operazione è quella di individuare i limiti dell'array ed inizializzare l'indice di appoggio, di seguito si effettua la verifica del superamento della soglia per ogni elemento fino al primo che soddisfa la condizione, uscendo dal blocco con ENO=1.
4.4.2 FUP
Vale lo stesso discorso fatto per l'esercizio in KOP:
4.4.3 AWL
Con AWL per eseguire le iterazioni abbiamo l'istruzione specifica LOOP, che però è alquanto macchinosa, decrementa il contenuto dell'accumulatore e salta all'etichetta se il valore è diverso da zero, altrimenti elabora l'istruzione successiva, quindi si deve gestire il contatore di iterazione e l'indice corrispondente:
4.4.4 SCL
Nell'esercizio di ricerca di un elemento, la superiorità del testo strutturato rispetto agli altri linguaggi emerge ancora più che nei due esempi precedenti:
l'istruzione RETURN esce dal blocco.
4.5 Esercizio di organizzazione di una sequenza con GRAPH
Per un'introduzione o un esempio concreto sul diagramma funzionale sequenziale servirebbero uno o più articoli dedicati ed è una materia in cui non sono esperto, provo a riportare un esempio ridotto e volutamente semplicistico (tralasciando modi operativi, arresto di emergenza, protezioni, timeout dei comandi, verifica coerenza segnali):
immaginiamo un portone basculante, mosso da un motore nelle due direzioni di apertura e chiusura, con corrispondenti finecorsa di raggiungimento della posizione; l'apertura viene comandata da un pulsante mentre la chiusura avviene automaticamente dopo un minuto. Una fotocellula, se rileva ostacoli durante la fase di chiusura, interrompe il comando riaprendo automaticamente il portone.
Come al solito mettiamo giù l'elenco degli ingressi e delle uscite:
- Ingressi
- I0.0 - S1 Pulsante di apertura, contatto NA
- I0.1 - S2 Fotocellula OK, contatto NA (non oscurata = 1)
- I0.4 - SQ1 Portone aperto, contatto NA
- I0.5 - SQ2 Portone chiuso, contatto NA
- Uscite
- Q0.0 - KM1 Teleruttore comando apertura portone
- Q0.1 - KM2 Teleruttore comando chiusura portone
Studiamo ora i possibili stati che può assumere il sistema e gli eventi che determinano un cambio di stato:
Ecco il diagramma che ho ottenuto in GRAPH, dove si notano chiaramente gli stati o passi (Sx) e le transizioni (Tx), Siemens la chiama "vista della catena":
se osserviamo la transizione T3, viene verificata la variabile <nome-stato>.T, ovvero il tempo di attivazione dello stato S3 (aperto) per eseguire l'attesa prima della chiusura automatica, mentre le azioni che ho usato sono di tipo "N" (normal not stored), ovvero eseguite continuamente finché lo stato è attivo, quindi non serve verificare il raggiungimento della posizione: quando cambia lo stato le azioni di comando del motore si resettano.
Vi sono vari tipi di azioni, legate anche a possibili eventi come l'attivazione o la disattivazione del passo, quelle di tipo "N" permettono anche il richiamo diretto di blocchi con relativi parametri, ma bisogna porre attenzione: il blocco verrà richiamato solo finché il relativo stato o passo rimane attivo.
In questo caso semplice le azioni sono gestite interamente nel blocco, ma nella pratica vengono gestite con la necessaria flessibilità in FC e FB esterni: le azioni del diagramma si occupano di pubblicare su delle variabili booleane globali qual è lo stato attivo <nome-stato>.X.
Questo è quello che si vede nel collegamento online, lo stato attivo viene evidenziato in verde:
Ho voluto interbloccare i due teleruttori del motore, in modo da evitare in particolare l'inversione di direzione immediata nel caso di oscuramento della fotocellula della transizione T5, per fare questo ho aggiunto due timer alla disinserzione nella sezione "istruzioni permanenti precedenti", ovvero che vengono eseguite continuamente prima della catena:
per poi usare l'interblocco "C" nelle rispettive azioni, che si può modificare nella "vista passo singolo".
In alternativa, per interbloccare l'apertura automatica con oscuramento fotocellula, si potrebbe usare l'azione "TF", che genera un ritardo alla disinserzione legato allo stato attivo:
ed usare questo timer per interbloccare il comando di apertura:
5. Conclusione
Gli esempi che abbiamo visto, anche se molto semplici, mostrano le peculiarità dei vari linguaggi, poi ognuno può avere preferenze personali:
- KOP e FUP sono piuttosto equivalenti, il loro punto di forza è la grafica, che rende semplice la comprensione e diagnosi di espressioni logiche anche a personale non formato, per contro diventano macchinosi nell'implementazione di algoritmi neanche troppo complessi, con il vecchio Simatic Manager molte cose non erano realizzabili con questi linguaggi
- AWL è il linguaggio storico di Siemens, di basso livello, flessibile, efficiente (se scritto bene), ma difficile da interpretare senza adeguata formazione, si possono commettere errori gravi senza il minimo avviso, il debug può portar via molto tempo anche a personale con esperienza, ne viene sconsigliato l'uso e attualmente non è più necessario: si può far tutto con gli altri linguaggi - non è disponibile su S7-1200
- SCL è il linguaggio di alto livello, viene in soccorso dove KOP e FUP sono più carenti, consentendo, unitamente alla notazione simbolica, l'implementazione in maniera confortevole di algoritmi ed operazioni di manipolazione dati sempre più richieste dall'Industry 4.0, risulta inoltre di facile comprensione a qualunque programmatore e probabilmente è quello il cui codice è più facilmente migrabile su altri PLC o piattaforme, per contro può risultare scomodo nell'analizzare espressioni logiche lunghe ed articolate, ma è possibile scriverle su più righe
- GRAPH può aiutare ad organizzare in maniera ordinata sequenze di automazione, grazie alla grafica sono immediatamente chiari stati e transizioni, l'implementazione delle azioni meglio gestirla in blocchi esterni con gli altri linguaggi - non è disponibile su S7-1200
Il documento si è un po' allungato (anche se contiene molte immagini): spero di non avervi annoiato.
6. Riferimenti
PLCopen - Introduzione a IEC 61131-3
PLCopen - Status IEC 61131-3 standard
MotionControlTips - Why is IL language for PLCs falling out of favor
Siemens Forum - TIA Portal vs Step7 SCL compiler
Siemens Forum - Should STL (AWL) be retired?