RI Lezione 14
🤖 Robotica Industriale · IV Anno · Lezione 14 — Progetto
Cella Pick & Sort 🏭
Progetto integrato di fine IV anno · Presa · Sorting a 3 vie · Loop di produzione · Contatori · Gestione errori
🏭 Questo è il progetto finale del IV anno. Non ci sono nuove istruzioni da imparare — si tratta di mettere insieme tutto: movimenti J/L, teaching dei punti, I/O digitali, registri, strutture di controllo e loop. Il risultato è un programma TP completo e funzionante per una cella di sorting industriale a 3 destinazioni. È il tipo di programma che si scrive davvero in un impianto reale.
🎯 Obiettivi del progetto
- Progettare autonomamente la struttura di un programma TP complesso a partire dalle specifiche di cella
- Integrare movimenti, I/O, registri e strutture di controllo in un unico programma coerente
- Insegnare tutti i punti P[ ] necessari con la strategia approccio/lavoro sviluppata nelle Lezioni 07–08
- Verificare il programma con la procedura STEP → RUN 10% → RUN 100%
- Documentare il programma con commenti e nomenclatura standard
📐 1. Specifiche della cella
1.1 — Layout fisico
Approccio: Z+150 sopra il punto di presa
Orientamento pinza: W=180°, P=0°, R=0°
Sensore pezzo presente: DI[2]
Config robot: N U T, 0, 0, 0
DO[1] = OFF → apri pinza
DI[1] = ON → conferma chiusura (reed)
DI[1] = OFF → conferma apertura (reed)
Tempo corsa atteso: ≤ 0,5 s
Approccio: Z+150
Config robot: N U T, 0, 0, 0
Approccio: Z+150
Config robot: N U T, 0, 0, 0
Approccio: Z+150
Config robot: N U T, 0, 0, 0
Approccio: Z+150
Config robot: N U T, 0, 0, 0
1.2 — Segnali I/O completi
| DI | Funzione |
|---|---|
| DI[1] | Conferma stato pinza (reed) |
| DI[2] | Pezzo presente sul nastro |
| DI[3] | Start ciclo da PLC |
| DI[4] | Stop ciclo da PLC |
| DI[5] | Reset allarme (dopo errore) |
| DO | Funzione |
|---|---|
| DO[1] | Pinza (ON=chiusa) |
| DO[2] | Robot pronto / in attesa start |
| DO[3] | Ciclo attivo |
| DO[4] | Fine lotto |
| DO[5] | Allarme generico |
| DO[6] | Allarme pinza (timeout) |
1.3 — Registri R[ ] del programma
| Registro | Nome | Funzione | Valore iniziale |
|---|---|---|---|
| R[1] | CONT_TOT | Pezzi totali processati | 0 |
| R[2] | CONT_A | Pezzi depositati in zona A | 0 |
| R[3] | CONT_B | Pezzi depositati in zona B | 0 |
| R[4] | CONT_C | Pezzi depositati in zona C | 0 |
| R[5] | TIPO_PEZZO | Tipo pezzo da sistema visione (1/2/3/0) | — |
| R[6] | CONT_SC | Pezzi andati in zona scarti | 0 |
| R[10] | LIMITE | Dimensione lotto | 30 |
1.4 — Punti P[ ] da insegnare
| P[ ] | Nome | Descrizione | Derivazione |
|---|---|---|---|
| P[1] | HOME | Posizione di home sicura | Teach diretto |
| P[10] | APPR_PICK | Approccio presa (Z+150 su PICK) | Derivato da P[11] |
| P[11] | PICK | Punto di presa sul nastro | Teach diretto |
| P[20] | APPR_A | Approccio zona A (Z+150 su PLACE_A) | Derivato da P[21] |
| P[21] | PLACE_A | Punto deposito zona A | Teach diretto |
| P[30] | APPR_B | Approccio zona B | Derivato da P[31] |
| P[31] | PLACE_B | Punto deposito zona B | Teach diretto |
| P[40] | APPR_C | Approccio zona C | Derivato da P[41] |
| P[41] | PLACE_C | Punto deposito zona C | Teach diretto |
| P[50] | APPR_SC | Approccio zona scarti | Derivato da P[51] |
| P[51] | PLACE_SC | Punto deposito scarti | Teach diretto |
🗂️ 2. Struttura del programma
Prima di scrivere una sola riga di codice, si pianifica su carta la struttura del programma: sezioni, flusso principale, condizioni di uscita. Questo è il passo che distingue il programmatore esperto dall’inesperto.
💾 3. Programma completo: PICK_SORT_3
! PROG: PICK_SORT_3
! Cella pick & sort a 3 destinazioni + zona scarti
! R[5]=tipo pezzo (1/2/3/0=scarto) da sistema visione
! ============================================================
! === SEZIONE 0: INIZIALIZZAZIONE ===
1: R[1:CONT_TOT]=0
2: R[2:CONT_A ]=0
3: R[3:CONT_B ]=0
4: R[4:CONT_C ]=0
5: R[6:CONT_SC ]=0
6: R[10:LIMITE ]=30
7: DO[1]=OFF ! pinza aperta
8: DO[3]=OFF
9: DO[4]=OFF
10: DO[5]=OFF
11: DO[6]=OFF
12: J P[1:HOME] 100% FINE
! === SEZIONE 1: ATTESA START ===
13: LBL[5]
14: DO[2]=ON ! robot pronto
15: WAIT DI[3]=ON ! aspetta start da PLC
16: DO[2]=OFF
17: DO[3]=ON ! ciclo attivo
! === SEZIONE 2: INIZIO CICLO ===
18: LBL[1]
19: IF DI[4]=ON, JMP LBL[9] ! stop richiesto?
20: IF R[1]>=R[10], JMP LBL[8] ! lotto completato?
! === SEZIONE 3: PRESA ===
21: WAIT DI[2]=ON ! aspetta pezzo sul nastro
22: J P[10:APPR_PICK] 80% CNT100
23: L P[11:PICK ] 150mm/sec FINE
24: DO[1]=ON ! chiudi pinza
25: WAIT DI[1]=ON TIMEOUT, LBL[99] ! conferma chiusura
26: L P[10:APPR_PICK] 150mm/sec FINE ! risalita
! === SEZIONE 4: SMISTAMENTO ===
27: SELECT R[5]=1, JMP LBL[10] ! tipo 1 → zona A
=2, JMP LBL[20] ! tipo 2 → zona B
=3, JMP LBL[30] ! tipo 3 → zona C
ELSE, JMP LBL[40] ! tipo 0/altro → scarti
! === SEZIONE 5A: DEPOSITO ZONA A ===
28: LBL[10]
29: J P[20:APPR_A ] 80% CNT100
30: L P[21:PLACE_A ] 150mm/sec FINE
31: DO[1]=OFF
32: WAIT DI[1]=OFF TIMEOUT, LBL[98]
33: L P[20:APPR_A ] 150mm/sec FINE
34: R[2:CONT_A]=R[2]+1
35: JMP LBL[49]
! === SEZIONE 5B: DEPOSITO ZONA B ===
36: LBL[20]
37: J P[30:APPR_B ] 80% CNT100
38: L P[31:PLACE_B ] 150mm/sec FINE
39: DO[1]=OFF
40: WAIT DI[1]=OFF TIMEOUT, LBL[98]
41: L P[30:APPR_B ] 150mm/sec FINE
42: R[3:CONT_B]=R[3]+1
43: JMP LBL[49]
! === SEZIONE 5C: DEPOSITO ZONA C ===
44: LBL[30]
45: J P[40:APPR_C ] 80% CNT100
46: L P[41:PLACE_C ] 150mm/sec FINE
47: DO[1]=OFF
48: WAIT DI[1]=OFF TIMEOUT, LBL[98]
49: L P[40:APPR_C ] 150mm/sec FINE
50: R[4:CONT_C]=R[4]+1
51: JMP LBL[49]
! === SEZIONE 5S: DEPOSITO SCARTI ===
52: LBL[40]
53: J P[50:APPR_SC ] 80% CNT100
54: L P[51:PLACE_SC] 150mm/sec FINE
55: DO[1]=OFF
56: WAIT DI[1]=OFF TIMEOUT, LBL[98]
57: L P[50:APPR_SC ] 150mm/sec FINE
58: R[6:CONT_SC]=R[6]+1
59: JMP LBL[49]
! === SEZIONE 6: CONVERGENZA E LOOP ===
60: LBL[49]
61: R[1:CONT_TOT]=R[1]+1 ! conta pezzo processato
62: JMP LBL[1]
! === SEZIONE 7: STOP PULITO (DI[4]=ON) ===
63: LBL[9]
64: J P[1:HOME] 100% FINE
65: DO[3]=OFF
66: JMP LBL[5] ! torna ad aspettare start
! === SEZIONE 8: FINE LOTTO ===
67: LBL[8]
68: J P[1:HOME] 100% FINE
69: DO[3]=OFF
70: DO[4]=ON ! segnala fine lotto al PLC
71: JMP LBL[5] ! attende nuovo start
! === SEZIONE 9: ERRORE TIMEOUT PINZA (apertura) ===
72: LBL[98]
73: J P[1:HOME] 100% FINE
74: DO[3]=OFF
75: DO[6]=ON ! allarme pinza
! [non torna automaticamente — attende intervento tecnico]
! === SEZIONE 10: ERRORE TIMEOUT PINZA (chiusura) ===
76: LBL[99]
77: DO[1]=OFF ! apri pinza per sicurezza
78: J P[1:HOME] 100% FINE
79: DO[3]=OFF
80: DO[5]=ON ! allarme generico
! [non torna automaticamente]
🔬 4. Procedura di collaudo nel simulatore
Un programma non verificato non va in produzione. La procedura di collaudo va seguita in quest’ordine — saltare fasi aumenta il rischio di danni al robot o all’impianto.
Teach
STEP 10%
RUN 30%
Test I/O
RUN 100%
🏋️ 5. Varianti e modifiche guidate
Dopo aver verificato il programma base, prova a implementare queste varianti — ciascuna aggiunge una funzione reale che si trova nei programmi industriali.
🟢 Variante 1 — Lotto configurabile da pannello (Facile)
Attualmente R[10]=30 è hardcoded nell’inizializzazione. Modifica il programma in modo che R[10] non venga azzerato all’avvio se ha già un valore diverso da 0 — così l’operatore può impostarlo dal pannello DATA prima di avviare il ciclo e il programma lo rispetta. Usa un IF: IF R[10]<>0, JMP LBL[4] per saltare la riga di inizializzazione di R[10]. Cosa succede alla prima accensione quando R[10]=0?
🔵 Variante 2 — Contatore scarti con allarme soglia (Medio)
Aggiungi una logica che blocca il ciclo se il numero di scarti supera il 20% dei pezzi totali processati. Dopo ogni deposito in zona scarti, calcola: R[7]=R[6]*100 / R[1] (percentuale scarti). Se R[7]>20: esegui stop di qualità (LBL[97]): vai in home, DO[3]=OFF, accendi DO[7]=ON (allarme qualità), attendi DI[5]=ON (reset), poi azzera R[7] e torna a LBL[5].
Attenzione alla divisione per zero alla prima iterazione (R[1]=0). Come la gestisci?
🟠 Variante 3 — Multi-lotto con statistiche cumulative (Medio)
Attualmente ogni nuovo start (LBL[5] → DI[3]=ON) riparte con i registri azzerati dall’init. Modifica la struttura in modo che:
- L’init (azzeramento R[1..6]) avvenga solo alla prima accensione (R[8:INIZIALIZZATO]=0)
- Ad ogni start successivo i contatori continuano dall’ultimo valore — le statistiche sono cumulative per turno
- L’azzeramento avviene solo quando DI[6]=ON (reset turno esplicito) prima del start
- R[8] viene impostato a 1 dopo il primo init e non viene mai più toccato dall’init
🟣 Variante 4 — Progettazione da zero: cella a zona doppia (Difficile)
Progetta da zero (specifiche → struttura → codice → collaudo) una variante della cella in cui ogni zona A, B, C può ricevere al massimo 10 pezzi. Quando una zona è piena (R[2]=10, R[3]=10 o R[4]=10), i pezzi di quel tipo vengono reindirizzati automaticamente alla zona scarti finché l’operatore non svuota la zona e preme il pulsante di reset zona (DI[7]=reset A, DI[8]=reset B, DI[9]=reset C).
Requsiti: gestione dei flag zona_piena (R[11], R[12], R[13]), logica di reindirizzamento nel SELECT, reset zone dal pannello I/O, contatore separato per “scarti da zona piena” vs “scarti da tipo 0”.
❓ 6. Quiz di verifica
1. Perché R[1] (contatore totale) viene incrementato in LBL[49] (convergenza) e non dentro ciascun ramo di deposito?
Mostra risposta
Perché il contatore totale deve incrementarsi per ogni pezzo processato indipendentemente dalla destinazione — zona A, B, C o scarti. Se fosse in ogni ramo, andrebbe scritto 4 volte (duplicazione) e basterebbe dimenticarlo in uno dei rami per avere un conteggio errato. Posizionarlo nel punto di convergenza LBL[49] garantisce che venga eseguito esattamente una volta per ogni ciclo, qualunque sia stato il percorso. I contatori parziali (R[2], R[3], R[4], R[6]) invece devono stare dentro i rami specifici perché contano solo la destinazione di quel ramo.
2. Il programma ha due etichette di errore: LBL[98] e LBL[99]. Qual è la differenza tra i due casi e perché LBL[99] include DO[1]=OFF mentre LBL[98] no?
Mostra risposta
LBL[99] è il timeout sulla chiusura della pinza (WAIT DI[1]=ON): la pinza ha ricevuto il comando di chiudersi (DO[1]=ON) ma il sensore reed non conferma la chiusura. La pinza potrebbe essere parzialmente chiusa, inceppata, o il pezzo non correttamente agganciato. In questo stato DO[1] è ancora ON — bisogna spegnerlo (DO[1]=OFF) per mettere in sicurezza l’elettrovalvola e tentare l’apertura. LBL[98] invece è il timeout sulla apertura della pinza (WAIT DI[1]=OFF): DO[1] è già OFF (il comando di apertura è già stato dato), il problema è che il sensore non conferma l’apertura completa. DO[1]=OFF è già stato eseguito alla riga precedente al WAIT, quindi non serve ripeterlo.
3. A fine lotto (LBL[8]), il programma esegue JMP LBL[5] senza azzerare R[1]. È un bug o un comportamento voluto?
Mostra risposta
È un comportamento voluto. Se il programma tornasse a LBL[5] con R[1] ancora uguale a R[10] (30), al prossimo start la riga 20 (IF R[1]>=R[10]) sarebbe vera fin dal primo ciclo e il programma salterebbe immediatamente a LBL[8] senza processare nessun pezzo — loop istantaneo fine lotto. Per evitarlo, all’inizio di ogni nuovo lotto R[1] deve essere azzerato. Questo azzeramento avviene all’inizio dell’init (riga 1), che viene eseguito solo all’avvio del programma. In questo progetto base l’operatore deve ri-avviare il programma tra un lotto e l’altro. Nelle varianti più avanzate (Variante 3) si gestisce questo con flag di stato e reset selettivo.
4. Perché la verifica del lotto (IF R[1]>=R[10]) usa >= invece di =?
Mostra risposta
Perché con = il check funziona solo se R[1] raggiunge esattamente R[10]. Se per qualunque motivo R[1] saltasse direttamente da 29 a 31 (scenario improbabile ma possibile in caso di doppio incremento per bug), la condizione R[1]=30 non sarebbe mai vera e il ciclo non si fermerebbe mai. Con >= il lotto si completa correttamente anche se R[1] supera R[10]. È una pratica difensiva generale: usare >= o <= per le condizioni di uscita dai loop invece di = puntuale, a meno che l'uguaglianza esatta non sia semanticamente necessaria.
5. Se il sistema di visione non scrive R[5] prima che il robot arrivi alla riga SELECT, cosa succede al programma?
Mostra risposta
Il SELECT legge il valore attuale di R[5] — che è quello dell’ultima scrittura del sistema di visione, o il valore residuo della precedente iterazione se la visione non ha aggiornato. Se il valore di R[5] è 1, 2 o 3, il pezzo andrà nella zona sbagliata (quella del ciclo precedente). Se R[5] ha un valore non previsto (es. 0 o ancora non inizializzato), la clausola ELSE manda il pezzo agli scarti. In un impianto reale questo problema si risolve sincronizzando la lettura: il robot si posiziona sul nastro, poi WAIT che la visione abbia finito (DI di “visione OK”), poi legge R[5]. Senza quella sincronizzazione il programma può processare pezzi con il tipo del ciclo precedente.
📚 Riepilogo del progetto
- Lettura di specifiche → progettazione su carta → codice → collaudo: metodo completo
- Movimenti J/L con approccio/lavoro e gestione configurazione robot
- I/O integrati: DO comando, DI conferma, WAIT TIMEOUT, etichette errore separate per tipo
- Registri: contatori multipli per destinazione, limite lotto, flag stato
- Strutture di controllo: SELECT a 4 vie con ELSE, IF di uscita loop, convergenza LBL[49]
- Loop: ciclo infinito con start/stop da PLC, uscita fine lotto, statistiche R[2]+R[3]+R[4]+R[6]=R[1]
- Invariante di controllo: R[2]+R[3]+R[4]+R[6] deve sempre uguale R[1] — se non è così c’è un bug
🎓 Fine IV Anno — Inizio V Anno
Hai completato il percorso di IV anno: sai programmare una cella robotizzata completa con movimenti, I/O, logica e ciclo produttivo. Nel V anno si passa al livello avanzato: posizionamento dinamico con PR[ ] e offset, ottimizzazione delle traiettorie, programmazione multi-programma con CALL, fino al progetto finale di cella robotizzata completa.
Lezione 15 — Registri di posizione PR[ ] e offset
Spostare il TCP in modo dinamico durante il ciclo.
Applicazione pratica: deposito su griglia senza insegnare ogni singolo punto.
© Prof. D. Bertolino · Robotica Industriale FANUC ER-4iA · Lezione 14 di 18