Lezione 17 di 18

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

⏱️ Durata stimata: 75–90 min 📊 Livello: Avanzato (V anno) 🔧 Strumenti: Simulatore web · Editor TP · più programmi aperti contemporaneamente 📋 Prerequisiti: Lezioni 06–16

🔧 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).

! === MAIN ===
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)
Stack di chiamata FANUC — profondità massima
MAIN → CALL PRESA → CALL CHECK_IO → (profondità 3)
↑ ok                      ↑ ok             ↑ max consigliato
Il R-30iB Mate supporta fino a 5 livelli di annidamento CALL. In pratica: non superare 3 livelli — il programma diventa difficile da seguire con lo STEP dell’iPendant. Se serve più profondità, riorganizzare l’architettura.

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.

! Avvia il sotto-programma di log asincrono mentre il robot continua a muoversi
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
CALL — usa quando:
  • 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
RUN — usa quando:
  • 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).

Convenzione interfaccia sotto-programmi
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)
Documenta sempre l’interfaccia di ogni sotto-programma con un commento nelle prime righe: quali R[ ] si aspetta in ingresso, quali scrive in uscita. Senza documentazione, la convenzione è invisibile e il programma diventa impossibile da mantenere.
! === MAIN — chiama DEPOSITA con destinazione parametrizzata ===
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.

Architettura a 3 livelli per cella FANUC
Livello 1 — Orchestrazione
MAIN · CELL_CTRL
Loop principale, gestione start/stop, sequenza ciclo, decisioni SELECT, check fine lotto
↓ CALL
Livello 2 — Processo
PRESA
DEPOSITA
GRIGLIA_NEXT
Livello 2 — Processo
APPROCCIO
RISALITA
TRANSITO_SAFE
Livello 2 — Servizio
CHECK_IO
AGGIORNA_CNT
INIT_CELLA
↓ CALL (opzionale — max 3 livelli totali)
Livello 3 — Utilità
APRI_PINZA · CHIUDI_PINZA
Livello 3 — Utilità
WAIT_DI · SET_DO_SAFE

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

  1. 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
  2. Nel programma principale PICK_PLACE_IO, sostituisci le righe di presa con:
    CALL PRESA
    IF R[56]=1, JMP LBL[99]
  3. 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.
  4. 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

  1. 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]).
  2. 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]
  3. 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.
  4. 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

  1. 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.
  2. Nel programma principale, dopo la risalita dal deposito, aggiungi:
    RUN AGG_CNT
    Il robot continua subito verso home mentre AGG_CNT aggiorna i registri in parallelo.
  3. Osserva nel pannello DATA che R[1] si aggiorna mentre il robot è già in movimento verso home. Questo è il comportamento atteso di RUN.
  4. ⚠️ 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:

1: J P[1:HOME] 100% FINE
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 è:

SORT_MAIN ← orchestrazione (max 25 righe)
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:

! GRIGLIA_NEXT
! 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

Cosa hai imparato oggi:
  • CALL nomeprog — sincrono, aspetta il completamento · usa sempre quando il sotto-programma muove il robot
  • RUN 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