Lezione 13 di 18

RI Lezione 13

🤖 Robotica Industriale · IV Anno · Lezione 13

Cicli e loop 🔁

Loop a contatore · Loop infinito controllato · Uscita anticipata · Loop annidati · Ciclo produttivo continuo

⏱️ Durata stimata: 60–75 min 📊 Livello: Intermedio (IV anno) 🔧 Strumenti: Simulatore web · Editor TP · Pannello I/O 📋 Prerequisiti: Lezioni 06–12

🔁 Il robot non si ferma mai — a meno che tu non glielo dica. Un robot industriale in produzione non esegue il programma una volta sola: cicla continuamente per ore, turni interi, finché non viene fermato. Questa lezione costruisce le strutture di loop che rendono possibile il ciclo produttivo reale — con controllo preciso su quante volte girare, quando fermarsi, e come uscire in modo sicuro da situazioni anomale.

🎯 Obiettivi della lezione

  • Costruire un loop a contatore (FOR implicito) con LBL, R[ ] e IF
  • Implementare un loop infinito controllato con uscita su condizione esterna (DI o R[ ])
  • Gestire l’uscita anticipata dal loop in caso di errore o segnale di stop
  • Costruire loop annidati per scorrere strutture a griglia
  • Progettare il ciclo produttivo continuo standard con start/stop esterno

📖 1. Teoria

1.1 — Loop a contatore (FOR implicito)

TP non ha un costrutto FOR nativo, ma il pattern LBL + R[ ] + IF produce esattamente lo stesso risultato. La struttura si compone sempre di 4 elementi in quest’ordine:

1
Inizializzazione
R[1]=0
2
Etichetta inizio loop
LBL[1]
3
Corpo del loop
… istruzioni …
4
Incremento + test + salto
R[1]=R[1]+1
IF R[1]<N, JMP LBL[1]
! Loop a contatore — esegue il corpo 10 volte

1: R[1]=0                           ! inizializza contatore
2: R[2]=10                           ! numero iterazioni (in registro → modificabile)

3: LBL[1]                           ! inizio loop

! — corpo del loop —
4: J P[1:HOME] 100% FINE
5: J P[2:POS_A] 80% FINE
6: WAIT 0.50(sec)
! — fine corpo —

7: R[1]=R[1]+1                       ! incrementa
8: IF R[1]<R[2], JMP LBL[1]         ! torna se non finito

! — dopo il loop —
9: J P[1:HOME] 100% FINE
Test al fondo vs test in testa — differenza pratica
Test al fondo (pattern standard TP)
LBL[1]
… corpo …
R[1]=R[1]+1
IF R[1]<N, JMP LBL[1]
Il corpo viene eseguito almeno una volta anche se N=0. Equivalente al do-while.
Test in testa (versione sicura)
LBL[1]
IF R[1]>=N, JMP LBL[9]
… corpo …
R[1]=R[1]+1
JMP LBL[1]
LBL[9]
Il corpo non viene eseguito se N=0. Equivalente al while. Preferire quando N può essere zero.

1.2 — Loop infinito controllato

Il ciclo produttivo standard di un robot industriale è un loop infinito: il robot cicla finché non arriva un segnale esterno di stop (fine turno, cambio lotto, allarme). La struttura usa JMP LBL senza condizione di uscita nel corpo — l’uscita avviene solo tramite IF su DI[ ] o R[ ].

! Loop infinito — esce solo su DI[4]=ON (segnale stop)

1: J P[1:HOME] 100% FINE
2: DO[2]=ON                           ! robot pronto

3: LBL[1]                           ! inizio ciclo

! verifica stop PRIMA di ogni ciclo
4: IF DI[4]=ON, JMP LBL[9]         ! stop richiesto → esci dal loop

! corpo del ciclo produttivo
5: WAIT DI[2]=ON                      ! aspetta pezzo
6: J P[10] 80% CNT100
7: L P[11] 150mm/sec FINE
8: DO[1]=ON
9: WAIT DI[1]=ON TIMEOUT, LBL[99]
10: L P[10] 150mm/sec FINE
11: J P[20] 80% CNT100
12: L P[21] 150mm/sec FINE
13: DO[1]=OFF
14: WAIT DI[1]=OFF TIMEOUT, LBL[99]
15: L P[20] 150mm/sec FINE
16: R[1]=R[1]+1                       ! conta pezzo

17: JMP LBL[1]                         ! torna all’inizio — loop infinito

! — uscita pulita dal loop —
18: LBL[9]
19: J P[1:HOME] 100% FINE
20: DO[2]=OFF                           ! robot non più pronto
21: DO[5]=ON                           ! segnala fine ciclo al PLC

! — errore timeout —
LBL[99]
22: J P[1:HOME] 100% FINE
23: DO[6]=ON                           ! allarme

📌 Dove controllare lo stop: il check IF DI[4]=ON, JMP LBL[9] va posto all’inizio di ogni iterazione, non a metà ciclo. Così il robot completa sempre il pezzo corrente (presa + deposito) prima di fermarsi — non abbandona il pezzo a metà aria. Questa è la pratica industriale standard: arresto al termine del ciclo, non a ciclo aperto.

1.3 — Uscita anticipata dal loop

A volte è necessario uscire dal loop non al suo completamento naturale, ma a seguito di una condizione anomala rilevata nel mezzo del corpo. Il pattern usa un JMP verso LBL[99] (errore) o LBL[9] (stop pulito) da qualsiasi punto del corpo.

! Uscita anticipata su più condizioni

LBL[1]
! check stop all’inizio
IF DI[4]=ON, JMP LBL[9]             ! stop operatore

! check condizione prerequisito
IF DI[6]=OFF, JMP LBL[95]            ! pressione OK? se no → errore pressione

… corpo ciclo …

! check contatore errori a metà corpo
IF R[4]>=3, JMP LBL[96]              ! troppi NG consecutivi → stop

… resto corpo …

JMP LBL[1]                             ! iterazione normale

LBL[9]                                 ! uscita pulita su stop
J P[1:HOME] 100% FINE
DO[5]=ON

LBL[95]                                ! errore pressione
J P[1:HOME] 100% FINE
DO[7]=ON

LBL[96]                                ! troppi NG consecutivi
J P[1:HOME] 100% FINE
DO[8]=ON

1.4 — Loop annidati

Due loop uno dentro l’altro permettono di scorrere strutture a griglia (righe × colonne). La regola fondamentale: il loop interno deve usare etichette LBL diverse dal loop esterno, e il contatore interno deve essere azzerato a ogni nuova iterazione del loop esterno.

! Loop annidato — griglia 3 righe × 4 colonne = 12 celle

1: R[30]=0                           ! indice riga (0..2)
2: R[31]=0                           ! indice colonna (0..3)
3: R[32]=3                           ! numero righe
4: R[33]=4                           ! numero colonne
5: R[34]=40                           ! passo riga (mm)
6: R[35]=50                           ! passo colonna (mm)

7: LBL[2]                           ! loop esterno: righe
8: R[31]=0                          ! azzera colonna a ogni nuova riga

9: LBL[3]                          ! loop interno: colonne

! calcolo offset cella corrente
10: R[36]=R[30]*R[34]                ! offset Y = riga × passo_riga
11: R[37]=R[31]*R[35]                ! offset X = col × passo_col
! … usa R[36],R[37] per posizionarsi sulla cella …
12: WAIT 0.20(sec)                  ! (simulato: al posto del movimento)

13: R[31]=R[31]+1                     ! colonna successiva
14: IF R[31]<R[33], JMP LBL[3]      ! fine loop interno

15: R[30]=R[30]+1                       ! riga successiva
16: IF R[30]<R[32], JMP LBL[2]         ! fine loop esterno

! tutte le 12 celle visitate — R[36],R[37] contengono offset ultima cella

⚠️ Errore classico nei loop annidati: dimenticare R[31]=0 alla riga 8 (reset colonna all’inizio di ogni riga). Senza quel reset, la seconda riga parte da colonna=4 (già esaurita dal loop precedente) e non esegue nessuna iterazione. Il risultato: solo la prima riga viene visitata, le altre saltate silenziosamente.

1.5 — Ciclo produttivo completo: start/stop dal PLC

In un impianto reale il ciclo viene avviato e fermato dal PLC di supervisione tramite segnali DI. Il pattern standard prevede: attesa del segnale di start → ciclo continuo → uscita su stop → ritorno in attesa di nuovo start.

! ============================================
! Ciclo produttivo con start/stop da PLC
! DI[4]=start · DI[5]=stop · DO[2]=robot_pronto · DO[5]=ciclo_attivo
! ============================================

! — INIT —
1: R[1]=0                           ! azzera contatore pezzi
2: DO[1]=OFF                           ! pinza aperta
3: J P[1:HOME] 100% FINE

! — ATTESA START —
4: LBL[5]                           ! loop attesa start
5: DO[2]=ON                           ! robot pronto → segnala al PLC
6: WAIT DI[4]=ON                      ! aspetta start da PLC
7: DO[2]=OFF
8: DO[5]=ON                           ! ciclo attivo

! — CICLO PRODUTTIVO —
9: LBL[1]
10: IF DI[5]=ON, JMP LBL[9]         ! stop richiesto?
11: WAIT DI[2]=ON                      ! aspetta pezzo
! … pick & place completo …
12: R[1]=R[1]+1
13: JMP LBL[1]

! — STOP PULITO —
14: LBL[9]
15: J P[1:HOME] 100% FINE
16: DO[5]=OFF                           ! ciclo non più attivo
17: JMP LBL[5]                         ! torna ad aspettare start

! — ERRORE —
LBL[99]
18: J P[1:HOME] 100% FINE
19: DO[5]=OFF
20: DO[6]=ON                           ! allarme — non torna al loop automaticamente

💻 2. Esempi guidati nel simulatore

Esempio A — Loop a contatore con movimento reale

  1. Insegna P[1]=home e P[2]=posizione lavoro (es. J1=+45°, J2=–30°).
  2. Scrivi il programma:
    1: R[1]=0
    2: R[2]=5
    3: J P[1] 100% FINE
    4: LBL[1]
    5: J P[2] 60% FINE
    6: WAIT 0.30(sec)
    7: J P[1] 60% FINE
    8: R[1]=R[1]+1
    9: IF R[1]<R[2], JMP LBL[1]
    10: DO[5]=ON
  3. Esegui. Il robot oscilla 5 volte tra home e P[2], poi accende DO[5].
  4. Verifica nel pannello DATA: R[1]=5 al termine.
  5. Modifica R[2]=3 prima di eseguire: ora oscilla 3 volte. Modifica R[2]=0: cosa succede? (Il corpo viene eseguito una volta — test al fondo.)

Esempio B — Loop infinito con stop su DI[4]

  1. Scrivi la versione semplificata del loop infinito (solo logica, senza movimenti):
    1: R[1]=0
    2: DO[5]=ON
    3: LBL[1]
    4: IF DI[4]=ON, JMP LBL[9]
    5: WAIT 0.40(sec)
    6: R[1]=R[1]+1
    7: JMP LBL[1]
    8: LBL[9]
    9: DO[5]=OFF
    10: DO[6]=ON
  2. Esegui. Il programma gira indefinitamente, R[1] sale senza fermarsi.
  3. Forza DI[4]=ON dal pannello I/O. Il programma esce dal loop (al prossimo ciclo), spegne DO[5] e accende DO[6].
  4. Nota: R[1] al momento dell’uscita mostra quante iterazioni sono state completate.

Esempio C — Loop annidato 2×3: verifica offset nel pannello DATA

  1. Scrivi il loop annidato della sezione 1.4 semplificato a griglia 2×3 (passo riga=40, passo col=50).
  2. Esegui con STEP: dopo ogni WAIT 0.20s, annota i valori di R[30], R[31], R[36], R[37] nel pannello DATA.
  3. Compila la tabella attesa prima di eseguire:
    Iter 1: R[30]=0, R[31]=0, R[36]=0, R[37]=0
    Iter 2: R[30]=0, R[31]=1, R[36]=0, R[37]=50
    Iter 3: R[30]=0, R[31]=2, R[36]=0, R[37]=100
    Iter 4: R[30]=1, R[31]=0, R[36]=40, R[37]=0
    Iter 5: R[30]=1, R[31]=1, R[36]=40, R[37]=50
    Iter 6: R[30]=1, R[31]=2, R[36]=40, R[37]=100
  4. Verifica che i valori nel simulatore coincidano con la tabella.
  5. Rimuovi la riga R[31]=0 all’inizio del loop esterno e riesegui: osserva l’errore classico descritto in 1.4.

🏋️ 3. Esercizi

🟢 Esercizio 1 — Traccia loop a contatore (Facile)

Senza eseguire nel simulatore, determina quante volte viene eseguito il corpo del loop e quale valore ha R[1] alla fine, per ciascuno dei seguenti casi:

R[1]=0 · R[2]=N
LBL[1]
WAIT 0.10(sec)
R[1]=R[1]+1
IF R[1]<R[2], JMP LBL[1]

Caso A: R[2]=5 → iterazioni: ?, R[1] finale: ?
Caso B: R[2]=1 → iterazioni: ?, R[1] finale: ?
Caso C: R[2]=0 → iterazioni: ?, R[1] finale: ?
Caso D: R[2]=–1 → iterazioni: ?, R[1] finale: ?

I casi C e D mostrano il comportamento del test al fondo — commenta cosa succederebbe se si usasse il test in testa per C e D.

🔵 Esercizio 2 — Pick & place con loop a lotto e stop (Medio)

Costruisci il programma completo per una cella di produzione con le seguenti specifiche:

— Lotto configurabile: R[10] = numero pezzi (inizializzato a 15)
— Contatore R[1]: si azzera all’avvio, si incrementa dopo ogni deposito
— Segnale stop DI[4]: se attivo all’inizio di un ciclo, esce dal loop e va in home
— Al termine del lotto (R[1]=R[10]): va in home, accende DO[5] (fine lotto)
— Se stop arriva prima del lotto: va in home, accende DO[6] (stop anticipato)

Il corpo del ciclo può essere semplificato (J P[2], WAIT 0.5s, J P[1]) — l’importante è la struttura del loop e le due condizioni di uscita. Verifica nel simulatore entrambi i casi: completamento lotto e stop anticipato.

🟠 Esercizio 3 — Deposito su griglia 2×4 (Medio)

Una cella deposita pezzi su un vassoio organizzato in griglia 2 righe × 4 colonne (8 celle totali). Il punto di riferimento del vassoio è P[50] (angolo in alto a sinistra). Le celle avanzano di 60 mm in X (colonne) e 50 mm in Y (righe).

Scrivi il programma che:
— Usa loop annidato per scorrere tutte le 8 celle in ordine riga per riga
— Calcola R[36]=offset_X e R[37]=offset_Y per ogni cella
— Per ogni cella: simula il deposito con WAIT 0.3s (il movimento reale con offset arriverà in Lezione 15)
— Conta i pezzi depositati in R[1]
— Al completamento delle 8 celle: accende DO[5] (vassoio pieno)

Verifica che l’ordine degli offset X,Y sia corretto: (0,0)→(60,0)→(120,0)→(180,0)→(0,50)→(60,50)→(120,50)→(180,50).

🟣 Esercizio 4 — Ciclo produttivo con start/stop, lotto e watchdog (Difficile)

Implementa il ciclo produttivo completo della sezione 1.5 con queste aggiunte:

— Watchdog pezzo mancante: R[5] conta i cicli consecutivi in cui DI[2] (pezzo presente) non arriva entro un certo numero di tentativi. Se R[5]=5 (cinque cicli a vuoto consecutivi) → uscita di allarme su LBL[97] + DO[7]=ON “nastro vuoto”. R[5] si azzera ogni volta che arriva un pezzo.
— Statistiche di turno: R[1]=pezzi totali, R[2]=pezzi OK (DI[3]=ON), R[3]=pezzi NG
— Restart dopo stop pulito: dopo LBL[9], il programma torna a LBL[5] (attesa start) senza azzerare R[1], R[2], R[3] — le statistiche di turno sono cumulative. Solo la riga di init (R[1..3]=0) all’avvio azzera tutto.

Verifica nel simulatore i tre scenari: completamento lotto, stop operatore, watchdog nastro vuoto.


❓ 4. Quiz di verifica

1. Qual è la differenza pratica tra mettere IF DI[4]=ON, JMP LBL[9] all’inizio del loop vs a metà del corpo?

Mostra risposta

Se il check è all’inizio del loop (prima del WAIT DI[2] e del ciclo pick&place), il robot completa sempre integralmente il ciclo corrente — presa e deposito — prima di verificare lo stop. Quando DI[4] diventa ON, il robot finisce il pezzo in corso e poi esce pulito in home. Se il check fosse a metà corpo (ad esempio tra la presa e il deposito), il robot potrebbe fermarsi con il pezzo già afferrato ma non ancora depositato — la pinza è chiusa, il pezzo è in aria, il robot è in home: situazione pericolosa che richiede intervento manuale. La pratica industriale prescrive il check dello stop sempre all’inizio del ciclo, non dentro il corpo.


2. Nel loop annidato della sezione 1.4, perché R[31] (indice colonna) deve essere azzerato all’inizio di ogni iterazione del loop esterno?

Mostra risposta

Perché R[31] viene incrementato dal loop interno fino a raggiungere R[33] (numero colonne) prima di uscire. Alla fine della prima riga, R[31]=4 (valore di uscita del loop interno). Se non viene azzerato prima della seconda riga, il loop interno trova R[31]=4 già uguale a R[33]=4: la condizione IF R[31]<R[33] è falsa fin dalla prima valutazione, quindi non esegue nessuna iterazione per la seconda riga. Azzerare R[31]=0 all’inizio di ogni iterazione del loop esterno “ricarica” l’indice di colonna per ogni nuova riga.


3. Un loop ha test al fondo con R[2]=0 come limite. Quante volte viene eseguito il corpo?

Mostra risposta

Una volta. Il pattern test al fondo è: LBL → corpo → R[1]=R[1]+1 → IF R[1]<R[2], JMP LBL. Con R[2]=0: il corpo viene eseguito, poi R[1] diventa 1, poi IF 1<0 è falso → non salta → il loop termina. Il corpo è stato eseguito esattamente una volta, anche se il “numero di iterazioni richiesto” era zero. Questo comportamento (almeno un’esecuzione garantita) è identico al do-while dei linguaggi C-like. Se si vuole zero esecuzioni con R[2]=0, occorre usare il test in testa: IF R[1]>=R[2], JMP LBL[fine] come prima istruzione del loop.


4. Perché dopo LBL[99] (errore timeout) il programma NON torna automaticamente al loop, mentre dopo LBL[9] (stop pulito) SÌ torna ad aspettare start?

Mostra risposta

Perché le due situazioni hanno natura diversa. LBL[9] è uno stop volontario e controllato: l’operatore ha premuto stop, il robot ha completato il ciclo corrente, la cella è in uno stato noto e sicuro. Si può ripartire automaticamente appena arriva il nuovo start dal PLC — nessun intervento fisico richiesto. LBL[99] è un guasto: la pinza non ha confermato la chiusura entro il timeout — il sensore potrebbe essere rotto, la pressione mancante, il pezzo posizionato male. Ripartire automaticamente significherebbe ripetere l’operazione con la stessa condizione anomala, moltiplicando il danno. Dopo un errore il robot deve fermarsi, segnalare l’allarme, e aspettare che un tecnico valuti e risolva il problema prima di riautorizzare il ciclo.


5. In un loop infinito con JMP LBL[1] alla fine, come fa il programma a terminare normalmente alla fine del turno?

Mostra risposta

Non termina da solo — ha bisogno di un segnale esterno. Il pattern corretto prevede un check all’inizio del loop: IF DI[stop]=ON, JMP LBL[9]. L’operatore o il PLC di supervisione attiva DI[stop] alla fine del turno; il robot completa il ciclo corrente, arriva al check all’inizio del ciclo successivo, rileva lo stop e salta a LBL[9] dove esegue la procedura di chiusura (home, segnali, ecc.). In alternativa si può usare il segnale HOLD del controller (UOP) che mette in pausa il programma a ciclo chiuso, ma il pattern DI[stop] + IF è quello che si programma esplicitamente nel codice TP.


📚 Riepilogo della lezione

Cosa hai imparato oggi:
  • Loop a contatore: R[1]=0 → LBL[1] → corpo → R[1]=R[1]+1 → IF R[1]<N, JMP LBL[1] — test al fondo esegue almeno 1 volta
  • Loop infinito: JMP LBL[1] senza condizione — uscita solo tramite IF su DI[ ] o R[ ] all’inizio del ciclo
  • Check stop all’inizio del ciclo, non a metà — il robot completa sempre il pezzo corrente prima di fermarsi
  • Loop annidati: LBL diversi, azzeramento contatore interno a ogni nuova iterazione del loop esterno
  • Ciclo produttivo industriale: LBL[5] attesa start → LBL[1] ciclo → LBL[9] stop pulito → JMP LBL[5] — errori a LBL[99] senza ritorno automatico

Prossima lezione →

Lezione 14 — Progetto: Cella Pick & Sort
Progetto integrato di fine IV anno: cella completa con presa, sorting a 3 vie, loop di produzione, contatori e gestione errori. Tutto quello che hai imparato dalla Lezione 06 alla 13 in un unico programma.

© Prof. D. Bertolino · Robotica Industriale FANUC ER-4iA · Lezione 13 di 18