RI Lezione 17
🤖 Robotica Industriale · V Anno · Lezione 17
Multi-programma e CALL 🔧
Sotto-programmi riutilizzabili · CALL e RUN · Parametri via R[ ] · Architettura modulare per celle complesse
🔧 Un programma da 200 righe con 4 sequenze di presa quasi identiche è un programma fragile. Se il punto di presa si sposta di 5 mm, bisogna modificarlo in 4 posti — e dimenticarne uno significa un bug silenzioso. La programmazione modulare divide il codice in sotto-programmi specializzati: la logica di presa sta in un posto solo, chiamata da dove serve. Manutenzione, debug e riuso diventano operazioni semplici.
🎯 Obiettivi della lezione
- Capire la differenza tra CALL (sincrono) e RUN (asincrono) e quando usare ciascuno
- Progettare sotto-programmi con interfaccia esplicita tramite R[ ] come parametri in ingresso e uscita
- Strutturare un’architettura multi-programma: programma principale + libreria di sotto-programmi
- Gestire il ritorno da sotto-programma e il passaggio di stato tramite R[ ] flag
- Convertire un programma monolitico in architettura modulare
📖 1. Teoria
1.1 — CALL: esecuzione sincrona di un sotto-programma
CALL nomeprog interrompe il programma chiamante, esegue il sotto-programma fino alla fine, poi riprende il chiamante dalla riga successiva. Il sotto-programma termina con la sua ultima riga (non serve un’istruzione di ritorno esplicita — il controller gestisce lo stack automaticamente).
1: J P[1:HOME] 100% FINE
2: WAIT DI[2]=ON
3: CALL PRESA ! esegue PRESA, aspetta che finisca
4: CALL DEPOSITO_A ! esegue DEPOSITO_A, poi continua
5: CALL AGGIORNA_CONTATORI
6: JMP LBL[1]
! === PRESA (sotto-programma) ===
1: J P[10:APPR_PICK] 80% CNT50
2: L P[11:PICK ] 120mm/sec FINE
3: DO[1]=ON
4: WAIT DI[1]=ON TIMEOUT, LBL[99]
5: L P[10:APPR_PICK] 300mm/sec CNT80
! fine sotto-programma — ritorno automatico al chiamante (riga 4 di MAIN)
↑ ok ↑ ok ↑ max consigliato
1.2 — RUN: esecuzione asincrona (parallela)
RUN nomeprog avvia il sotto-programma su un task secondario e ritorna immediatamente al chiamante — i due programmi girano in parallelo. Il chiamante non aspetta il completamento del sotto-programma.
1: RUN LOG_PRODUZIONE ! parte LOG_PRODUZIONE in parallelo
2: J P[10:APPR_PICK] 80% CNT50 ! MAIN continua senza aspettare
3: L P[11:PICK ] 120mm/sec FINE
- Il risultato del sotto-programma serve prima di continuare
- Il sotto-programma muove il robot (un robot, un movimento alla volta)
- Il sotto-programma scrive R[ ] che il chiamante leggerà subito dopo
- Quasi sempre — è la scelta predefinita
- Il sotto-programma non muove il robot (solo I/O, attese, scrittura registri)
- Operazioni di comunicazione con PLC o sistema visione
- Logging / aggiornamento contatori mentre il robot è già in movimento
- ⚠️ Mai due programmi che muovono il robot in parallelo
1.3 — Interfaccia tra programmi: parametri via R[ ]
Il linguaggio TP non ha parametri formali come le funzioni nei linguaggi ad alto livello. L’interfaccia tra il programma chiamante e il sotto-programma si realizza per convenzione tramite registri R[ ] dedicati — scritti prima del CALL (parametri in ingresso) e letti dopo il CALL (valori in uscita).
| Registro | Ruolo | Chi scrive | Chi legge |
|---|---|---|---|
| R[50] | Parametro in ingresso 1 | Chiamante (prima del CALL) | Sotto-programma |
| R[51] | Parametro in ingresso 2 | Chiamante | Sotto-programma |
| R[55] | Valore di ritorno / codice esito | Sotto-programma | Chiamante (dopo il CALL) |
| R[56] | Flag errore (0=ok, 1=errore) | Sotto-programma | Chiamante (dopo il CALL) |
R[50]=R[5] ! parametro: tipo pezzo (1/2/3) → R[50]
CALL DEPOSITA
IF R[56]=1, JMP LBL[99] ! controlla flag errore al ritorno
! === DEPOSITA — sotto-programma ===
! IN: R[50] = destinazione (1=A, 2=B, 3=C, altro=scarti)
! OUT: R[55] = pezzi depositati nella zona (contatore locale)
! R[56] = flag errore (0=ok, 1=timeout pinza)
1: R[56]=0 ! presuppone successo
2: SELECT R[50]=1, JMP LBL[10]
=2, JMP LBL[20]
=3, JMP LBL[30]
ELSE, JMP LBL[40]
3: LBL[10] ! zona A
4: J P[20:APPR_A ] 80% CNT50
5: L P[21:PLACE_A] 120mm/sec FINE
6: DO[1]=OFF
7: WAIT DI[1]=OFF TIMEOUT, LBL[98]
8: L P[20:APPR_A ] 300mm/sec CNT80
9: JMP LBL[49]
! … (LBL[20], LBL[30], LBL[40] analoghi) …
10: LBL[49] ! convergenza — ritorno ok
11: R[56]=0
! fine sotto-programma
12: LBL[98] ! timeout apertura pinza
13: R[56]=1 ! segnala errore al chiamante
! fine sotto-programma — MAIN gestirà l’errore
1.4 — Architettura modulare: struttura consigliata
Per una cella industriale complessa, la struttura modulare standard prevede tre livelli: un programma principale che orchestra il ciclo, sotto-programmi di processo che incapsulano le operazioni fisiche, e sotto-programmi di servizio che gestiscono I/O e stato.
DEPOSITA
GRIGLIA_NEXT
RISALITA
TRANSITO_SAFE
AGGIORNA_CNT
INIT_CELLA
1.5 — Regole pratiche per la programmazione modulare TP
| Regola | Motivazione |
|---|---|
| Ogni sotto-programma documenta la propria interfaccia nelle prime 3 righe di commento | Senza documentazione l’interfaccia è invisibile — chi modifica dopo non sa cosa passa chi chiama |
| Un sotto-programma fa una cosa sola — nome descrittivo del verbo che esegue | PRESA fa la presa, non la presa + il trasporto + il deposito |
| I sotto-programmi di processo non fanno decisioni di routing | Il SELECT zona A/B/C appartiene al MAIN. DEPOSITA riceve la destinazione come parametro R[50] |
| Ogni sotto-programma scrive R[56]=0 all’inizio e R[56]=1 in caso di errore | Il chiamante controlla R[56] dopo ogni CALL — standard uniforme |
| I LBL[ ] sono locali al programma — ogni programma ha il suo spazio di etichette | LBL[99] in PRESA e LBL[99] in DEPOSITA sono etichette diverse — nessun conflitto |
| Non usare JMP tra programmi diversi | JMP LBL[n] salta solo all’interno del programma corrente — non è possibile saltare a un LBL in un altro programma |
💻 2. Esempi guidati nel simulatore
Esempio A — Il primo CALL: estrarre la presa in sotto-programma
- Crea un nuovo programma chiamato PRESA nel simulatore. Trasferisci in esso le righe di presa del programma PICK_PLACE_IO (dalla J P[10] alla L P[10] di risalita inclusa). Aggiungi le 3 righe di commento interfaccia in cima:
! PRESA — esegue presa pezzo sul nastro
! IN: nessun parametro
! OUT: R[56]=0 ok · R[56]=1 timeout pinza
- Nel programma principale PICK_PLACE_IO, sostituisci le righe di presa con:
CALL PRESA
IF R[56]=1, JMP LBL[99] - Esegui con STEP. Quando il controller raggiunge CALL PRESA, dovrebbe entrare nel sotto-programma. Verifica sul simulatore che lo STEP entri nelle righe di PRESA e poi torni al chiamante.
- Crea un secondo programma DEPOSITO estraendo le righe di deposito. Il programma principale diventa: HOME → CALL PRESA → (check errore) → CALL DEPOSITO → (check errore) → loop. Conta le righe prima e dopo la refactoring.
Esempio B — Sotto-programma parametrico DEPOSITA
- Crea il sotto-programma DEPOSITA con l’interfaccia della sezione 1.3. Il SELECT su R[50] gestisce le 4 destinazioni (1=A, 2=B, 3=C, altro=scarti). Ogni ramo aggiorna anche il contatore di zona corrispondente (R[2], R[3], R[4], R[6]).
- Nel programma principale MAIN, prima del CALL DEPOSITA:
R[50]=R[5] ! passa tipo pezzo come parametro
CALL DEPOSITA
IF R[56]=1, JMP LBL[99] - Testa i 4 rami: imposta R[5]=1, esegui ciclo completo → pezzo va in zona A e R[2] incrementa. Ripeti con R[5]=2, R[5]=3, R[5]=0.
- Verifica che il main non contenga più nessuna logica di routing zona — tutta nel sotto-programma DEPOSITA. Il main deve essere leggibile in meno di 20 righe.
Esempio C — RUN per logging asincrono
- Crea il sotto-programma AGG_CNT che incrementa R[1] e imposta DO[3]=ON se R[1]>=R[10] (fine lotto). Non contiene movimenti robot.
- Nel programma principale, dopo la risalita dal deposito, aggiungi:
RUN AGG_CNTIl robot continua subito verso home mentre AGG_CNT aggiorna i registri in parallelo. - Osserva nel pannello DATA che R[1] si aggiorna mentre il robot è già in movimento verso home. Questo è il comportamento atteso di RUN.
- ⚠️ Verifica che AGG_CNT non contenga alcuna istruzione di movimento — altrimenti due task competerebbero per il controllo degli assi e il controller genererebbe un errore di task conflict.
🏋️ 3. Esercizi
🟢 Esercizio 1 — Identifica la modularizzazione (Facile)
Dato il seguente programma monolitico da 16 righe, identifica quali gruppi di righe estrarre in sotto-programmi e proponi un nome e un’interfaccia (IN/OUT in R[ ]) per ciascuno:
2: WAIT DI[3]=ON
3: DO[2]=OFF · DO[3]=ON
4: LBL[1]
5: IF DI[4]=ON, JMP LBL[9]
6: WAIT DI[2]=ON
7: J P[10:APPR_P] 80% CNT50
8: L P[11:PICK ] 120mm/sec FINE
9: DO[1]=ON
10: WAIT DI[1]=ON TIMEOUT, LBL[99]
11: L P[10:APPR_P] 300mm/sec CNT80
12: J P[20:APPR_D] 70% CNT50
13: L P[21:PLACE ] 120mm/sec FINE
14: DO[1]=OFF
15: WAIT DI[1]=OFF TIMEOUT, LBL[98]
16: L P[20:APPR_D] 300mm/sec CNT80
17: R[1]=R[1]+1
18: JMP LBL[1]
19: LBL[9]: J P[1] 100% FINE · DO[3]=OFF · JMP LBL[5]
Scrivi il programma MAIN riformulato con CALL che sostituisce i blocchi estratti. MAIN deve avere al massimo 10 righe esclusi i commenti.
🔵 Esercizio 2 — Refactoring PICK_SORT_3 (Medio)
Converti il programma PICK_SORT_3 della Lezione 14 in architettura modulare a 2 livelli. La struttura richiesta è:
PRESA ← presa dal nastro
DEPOSITA ← deposito con R[50]=destinazione
INIT_CELLA ← inizializzazione registri e I/O
STOP_PULITO ← sequenza di stop (home + DO)
Requisiti: ogni sotto-programma deve avere il commento interfaccia nelle prime 3 righe. R[56] deve essere il flag errore standard in tutti i sotto-programmi. SORT_MAIN deve contenere solo orchestrazione — zero movimenti diretti, zero logica I/O di pinza. Verifica che il programma si comporti identicamente al monolitico originale.
🟠 Esercizio 3 — Sotto-programma GRIGLIA con PR[ ] (Medio)
Crea un sotto-programma riutilizzabile GRIGLIA_NEXT che calcola e aggiorna l’offset PR[1] per la cella successiva di una griglia e ne aggiorna gli indici. L’interfaccia:
! IN: R[30]=riga corrente · R[31]=colonna corrente
! R[32]=N_righe · R[33]=N_col · R[34]=passo_Y · R[35]=passo_X
! OUT: PR[1] aggiornato con offset cella corrente
! R[30], R[31] avanzati alla cella successiva
! R[56]=0 ok · R[56]=1 griglia completata
Il programma principale che usa GRIGLIA_NEXT diventa: CALL PRESA → imposta R[50]=1 → CALL DEPOSITA_CON_OFFSET (che chiama internamente GRIGLIA_NEXT) → check R[56]: se =1, griglia piena, accendi DO[4] e aspetta reset. Verifica con una griglia 2×3 che le 6 celle siano visitate nell’ordine corretto e che al completamento R[56]=1 sia restituito correttamente.
🟣 Esercizio 4 — Libreria pinza riutilizzabile (Difficile)
Progetta e implementa una libreria di 3 sotto-programmi di utilità per la pinza pneumatica che possano essere riusati in qualsiasi cella:
- CHIUDI_PINZA: DO[1]=ON, WAIT DI[1]=ON con timeout configurabile (R[60]=timeout in secondi), ritorna R[56]=0 ok o R[56]=1 timeout
- APRI_PINZA: DO[1]=OFF, WAIT DI[1]=OFF con timeout R[60], ritorna R[56]=0 o R[56]=1
- CHECK_PINZA: verifica coerenza stato pinza — se DO[1]=ON ma DI[1]=OFF dopo 0,3 s → R[56]=2 (pinza aperta quando dovrebbe essere chiusa); se DO[1]=OFF ma DI[1]=ON → R[56]=3 (pinza chiusa quando dovrebbe essere aperta). R[56]=0 se tutto coerente.
I programmi PRESA e DEPOSITA dell’Esercizio 2 devono chiamare CHIUDI_PINZA e APRI_PINZA invece di gestire direttamente DO[1] e DI[1]. Verifica che il comportamento sia identico e che la gestione degli errori sia più granulare (il chiamante sa se è stato un timeout o una incoerenza di stato).
❓ 4. Quiz di verifica
1. Un programmatore inserisce RUN DEPOSITA invece di CALL DEPOSITA nel programma principale. DEPOSITA contiene movimenti robot. Cosa succede?
Mostra risposta
Il controller tenterà di eseguire MAIN e DEPOSITA in parallelo — due task che entrambi comandano gli assi del robot contemporaneamente. Il controller R-30iB Mate rileva questo conflitto e genera un errore di task conflict (o motion conflict), interrompendo l’esecuzione. In alcuni casi il robot può eseguire movimenti imprevedibili prima che il sistema di sicurezza intervenga. La regola è assoluta: RUN si usa solo per sotto-programmi che non contengono istruzioni di movimento (J, L, C). Se si vuole esecuzione sincrona con movimenti, si usa sempre CALL.
2. Se LBL[99] esiste in MAIN e anche in PRESA (sotto-programma), un JMP LBL[99] dentro PRESA salta a quale delle due?
Mostra risposta
JMP LBL[99] dentro PRESA salta a LBL[99] di PRESA — le etichette sono locali al programma. Il controller mantiene il contesto di esecuzione separato per ogni programma nello stack CALL. Questo è un vantaggio della modularità: ogni sotto-programma può usare LBL[99] come etichetta di errore interna senza interferire con gli altri. Se il programmatore vuole che l’errore venga gestito dal chiamante MAIN, il modo corretto è scrivere R[56]=1 e raggiungere la fine del sotto-programma (ritorno normale) — il chiamante verificherà R[56] dopo il CALL e salterà al suo LBL[99].
3. Il programma principale ha 5 livelli di CALL annidati: MAIN → A → B → C → D → E. È un problema?
Mostra risposta
Tecnicamente il R-30iB Mate supporta fino a 5 livelli di annidamento CALL, quindi non genererà un errore immediato. Ma è un problema di manutenibilità e debug. Con 5 livelli, seguire l’esecuzione con lo STEP dell’iPendant diventa molto difficile: si entra in A, poi in B, poi in C… per una singola presa. Inoltre, un errore in E deve propagarsi attraverso 4 livelli di check R[56] per arrivare a MAIN. La raccomandazione pratica è max 3 livelli. Se serve più profondità, si riorganizza l’architettura: fondere A e B in un unico sotto-programma, o spostare funzioni dal livello 4–5 al livello 2–3.
4. Perché il sotto-programma DEPOSITA non dovrebbe contenere il SELECT per scegliere zona A/B/C/scarti, ma riceverlo come parametro R[50]?
Mostra risposta
Due ragioni. Prima — riutilizzabilità: se DEPOSITA contiene il SELECT, è legato alla logica di questa specifica cella. Se domani la cella cambia (zona D aggiunta, zona B soppressa), bisogna modificare DEPOSITA. Se invece DEPOSITA riceve la destinazione come parametro e si occupa solo del movimento fisico, la logica di routing (chi va dove) resta nel MAIN dove appartiene — l’unica modifica necessaria è nel SELECT del MAIN. Seconda — separazione delle responsabilità: MAIN conosce il tipo di pezzo (R[5]) e decide dove mandarlo; DEPOSITA sa come eseguire un deposito fisico in una destinazione specificata. Mescolare le due responsabilità viola il principio di coesione: ogni modulo deve fare bene una cosa sola.
5. Un programma MAIN usa CALL PRESA in un loop. Dopo la 15ª iterazione PRESA ritorna con R[56]=1 (timeout pinza). Il MAIN salta a LBL[99] (stop di emergenza) e si ferma. Come fa il tecnico a sapere qual era l’iterazione al momento dell’errore e quanti pezzi erano stati processati correttamente?
Mostra risposta
Grazie ai registri persistenti. R[1:CONT_TOT] conta i pezzi processati correttamente — al momento dell’errore contiene 14 (le prime 14 iterazioni andate a buon fine). Il registro non viene azzerato dall’errore né dallo spegnimento. Il tecnico apre il pannello DATA → Registers sull’iPendant e legge R[1]=14. Per una diagnosi più dettagliata si può aggiungere un registro R[90:LAST_ERR] che viene scritto prima del JMP LBL[99] con un codice errore (es. R[90]=1 per timeout presa, R[90]=2 per timeout deposito) — così il tecnico sa anche il tipo di errore dell’ultima iterazione. Questo è il pattern standard per la diagnostica post-errore nei programmi industriali.
📚 Riepilogo della lezione
CALL nomeprog— sincrono, aspetta il completamento · usa sempre quando il sotto-programma muove il robotRUN nomeprog— asincrono, ritorna subito · solo per sotto-programmi senza movimenti robot- Interfaccia via R[ ]: R[50–54] parametri in ingresso · R[55] valore di ritorno · R[56] flag errore
- LBL[ ] sono locali al programma — LBL[99] in PRESA e LBL[99] in MAIN sono etichette diverse e indipendenti
- Architettura a 3 livelli: orchestrazione → processo → utilità · max 3 livelli di annidamento CALL
- Ogni sotto-programma: fa una cosa · documenta la propria interfaccia · scrive R[56] in uscita
🏁 Ultima lezione del percorso →
Hai imparato a strutturare il codice in moduli riutilizzabili. Nella lezione finale integrerai tutto: movimenti ottimizzati, offset su griglia, architettura modulare, I/O professionale e ciclo produttivo completo in un unico progetto di cella robotizzata reale.
Lezione 18 — Progetto finale: Cella robotizzata completa
Integrazione di tutto il percorso V anno in un’unica cella industriale.
Specifiche realistiche · Architettura modulare · Ottimizzazione · Collaudo formale.
© Prof. D. Bertolino · Robotica Industriale FANUC ER-4iA · Lezione 17 di 18