Tutorial · Principiante · 30 min

Perché il robot si riavvia all'avvio dei motori

Il riavvio del robot all'avvio dei motori sembra un bug del software, ma è un problema di alimentazione: esamina resistenza interna, corrente e brown-out.

Introduzione

Il robot funziona sul banco. Lo metti sul pavimento, i motori partono e tutto si ferma: il monitor seriale riparte dall’inizio, la sequenza dei LED ricomincia e ciò che il robot stava facendo viene dimenticato.

L’istinto di quasi tutti è cercare nel codice. Nel codice non c’è nulla.

Quello che è successo è che l’avvio dei motori ha richiesto più corrente di quanta la batteria potesse fornire senza far scendere la tensione, l’alimentazione è scesa sotto il livello necessario al microcontrollore e la scheda si è riavviata. Questo è un brown-out, cioè una caduta di tensione, ed è il guasto più comune sul primo robot: questo sito lo afferma nel percorso di controllo dei motori, in quasi ogni pagina di un componente e nell’elenco di risoluzione dei problemi di metà dei progetti. Questa è la pagina che lo spiega.

Innanzitutto, stabilisci se è un riavvio o un crash

Questi eventi richiedono correzioni diverse, ma da lontano sono identici.

Inserisci una riga distintiva all’inizio di setup():

void setup() {
  Serial.begin(115200);
  Serial.println(F("---- BOOT ----"));
}

Ora fai funzionare il robot finché non si comporta male e osserva il monitor:

  • ---- BOOT ---- compare di nuovo → la scheda si è riavviata. È un problema di alimentazione. Continua a leggere.
  • L’uscita si interrompe oppure ripete per sempre la stessa riga → la scheda è bloccata in un ciclo o si è bloccata. È un problema di codice e questa è la pagina sbagliata.
  • L’uscita diventa una sequenza di caratteri privi di senso → l’alimentazione scende, ma non abbastanza da provocare il riavvio. Anche questo è un problema di alimentazione e valgono le stesse correzioni.

Su schede AVR puoi fare un passo in più e chiedere al chip perché si è riavviato. MCUSR contiene il motivo, ma main() viene eseguito molto tempo prima che sia necessario leggerlo, quindi catturalo in una funzione .init3 che viene eseguita per prima:

uint8_t resetFlags __attribute__((section(".noinit")));
void captureReset(void) __attribute__((naked, used, section(".init3")));
void captureReset(void) {
  resetFlags = MCUSR;
  MCUSR = 0;
}

void setup() {
  Serial.begin(115200);
  if (resetFlags & _BV(BORF))  Serial.println(F("cause: BROWN-OUT"));
  if (resetFlags & _BV(EXTRF)) Serial.println(F("cause: reset pin"));
  if (resetFlags & _BV(PORF))  Serial.println(F("cause: power-on"));
  if (resetFlags & _BV(WDRF))  Serial.println(F("cause: watchdog"));
}

Un’onesta avvertenza: un Uno che usa Optiboot ha il bootloader che legge MCUSR e lo azzera prima che il tuo codice lo veda, memorizzando il valore nel registro r2. Se sulla tua scheda tutti i flag sono zero, il motivo è questo: torna al test ---- BOOT ----, che non richiede nulla e non mente mai.

Perché la tensione collassa

Una batteria non è una fonte di tensione ideale. È una fonte di tensione in serie con una resistenza, ed è proprio questa resistenza a determinare se il robot funziona:

V_terminal = V_open_circuit − I × R_internal

R_internal è una proprietà della chimica e delle dimensioni della cella e varia enormemente tra pacchi che si trovano nello stesso cassetto.

Grafico della tensione ai morsetti rispetto alla corrente assorbita per quattro pacchi batteria. Un blocco PP3 da 9 volt parte dalla tensione più alta, 9 volt, ma scende ripidamente, attraversando 5 volt entro 2 ampere. Quattro celle alcaline AA partono da 6 volt e scendono a 4.4 volt. Due celle al litio 18650 partono da 7.4 volt e restano quasi costanti a 7.2 volt. Quattro celle NiMH AA partono dal valore più basso, 4.8 volt, e restano quasi costanti a 4.56 volt. Una linea verticale tratteggiata segna la corrente di picco di 2 ampere dovuta all'avvio di due motori.
Due motori con riduttore TT che partono insieme assorbono circa 2 A. Il blocco da 9 V ha la tensione a circuito aperto più alta sugli scaffali e offre il risultato peggiore sotto carico: perde 4 V per raggiungerlo. Il pacco NiMH parte 4.2 V più basso e arriva 0.6 V più alto. Scarica SVG
Pacco A circuito aperto R_internal Con un picco di 2 A
2×18650 Li-ion 7.4 V ≈ 0.10 Ω 7.20 V
4×AA NiMH 4.8 V ≈ 0.12 Ω 4.56 V
4×AA alkaline 6.0 V ≈ 0.80 Ω 4.40 V
Blocco 9 V PP3 9.0 V ≈ 2.0 Ω 5.00 V, e in diminuzione

Da questi dati seguono tre considerazioni che valgono più dei numeri:

Il blocco da nove volt è la peggiore batteria che tu possa usare in un robot. Ha la tensione più alta stampata sulla scatola e circa 500 mAh di capacità dietro due ohm di resistenza interna. È una batteria per un rilevatore di fumo.

Le celle alcaline non sono rigide. La loro resistenza interna è circa sei volte quella di una cella NiMH e aumenta mentre si scaricano: il robot funziona per venti minuti e poi comincia a riavviarsi, evento che sembra un guasto intermittente ma è soltanto una batteria scarica che si comporta esattamente come previsto.

Le ricaricabili vincono dove conta. NiMH e Li-ion sono le chimiche standard per robot non perché siano ricaricabili, ma perché mantengono la tensione sotto carico.

Calcola il fabbisogno di corrente prima di scegliere il pacco

Suma ciò che il robot assorbe due volte: una per il consumo normale e una per l’istante peggiore.

Componente In funzionamento Caso peggiore
Scheda Arduino Uno 45 mA 45 mA
Motore con riduttore TT ×2 400 mA 2000 mA (entrambi in stallo)
Matrice IR a riflessione 100 mA 100 mA
HC-SR04 15 mA 15 mA
Servomotore SG90 150 mA 700 mA (in stallo)
Bluetooth HC-05 30 mA 40 mA
ESP32 (Wi-Fi) 80 mA 250 mA (picco in trasmissione)

Un semplice seguilinea, con Uno, due motori e una matrice IR, assorbe circa 545 mA in funzionamento e 2.15 A nell’istante peggiore. L’istante peggiore è di solito breve; l’eccezione è un robot che spinge al limite della trazione, che resta alla corrente di stallo per tutta la durata della spinta. Dimensiona il pacco in base al primo valore e il collasso in base al secondo, perché rispondono a domande diverse:

runtime  ≈ capacity / running current      2000 mAh / 545 mA ≈ 3.7 h ideal
sag      = worst-case current × R_internal

Prevedi un’autonomia reale pari a circa due terzi di tale valore. Non scaricherai un pacco fino a zero, i picchi costano più della loro media e le capacità nominali vengono misurate a basse velocità di scarica.

L’intervallo che il rilevatore di brown-out non intercetta

Ogni AVR dispone di un rilevatore di brown-out che mantiene il chip in reset sotto una tensione impostata. Su un Arduino Uno questo fusibile è impostato di fabbrica a 2.7 V.

Un ATmega328P a 16 MHz non è previsto per funzionare a 2.7 V. La classe di velocità nella scheda tecnica va da 4 MHz a 1.8 V fino a 20 MHz a 4.5 V e, interpolando a 16 MHz, il minimo è circa 3.8 V.

Grafico della frequenza massima di clock rispetto alla tensione di alimentazione per ATmega328P. Una linea sale da 4 megahertz a 1.8 volt a 20 megahertz a 4.5 volt e poi si appiattisce, con l'area sottostante evidenziata come regione sicura. Una linea orizzontale tratteggiata segna i 16 megahertz a cui opera un Arduino Uno e incontra il limite sicuro a 3.8 volt. Una fascia rossa tra 2.7 volt e 3.8 volt è annotata come fuori specifica a 16 megahertz ma sopra la soglia di brown-out.
Tra 2.7 V e 3.8 V il chip opera più rapidamente di quanto consentano la tensione di alimentazione e il rilevatore di brown-out, impostato a 2.7 V, non interverrà. In quella fascia di 1.1 V si manifestano i guasti robot più strani. Scarica SVG

Questo spiega il sintomo che le persone trovano più difficile da credere: un robot che non esegue un riavvio pulito, ma semplicemente si comporta in modo strano quando i motori sono in funzione. Variabili corrotte, un servomotore che salta, un sensore I²C che restituisce valori privi di senso, una macchina a stati in uno stato da cui non esiste alcuna transizione in ingresso. In quella fascia il chip esegue istruzioni fuori dalle proprie condizioni operative garantite, ed è proprio la parola «garantite» a fare la differenza.

Non trattare quindi «non si è riavviato» come prova che l’alimentazione sia regolare.

Un pacco, due linee di alimentazione

La soluzione non è usare due batterie, ma alimentare due percorsi da un solo pacco.

  • La corrente dei motori va dal pacco direttamente al morsetto di alimentazione del driver, come +12V di un L298N o VM di un TB6612FNG. Non passa mai dal microcontrollore.
  • La corrente logica proviene da 5 V regolati: il regolatore integrato nel driver, un modulo buck separato oppure USB mentre sviluppi.
  • Le masse devono essere collegate insieme. Ogni segnale tra la scheda e il driver è una tensione misurata rispetto alla massa; senza un riferimento comune, i pin di direzione non hanno significato. Questo è il secondo guasto di cablaggio più comune dopo quello iniziale.

Non alimentare mai i motori tramite il pin 5 V di un Arduino o il regolatore del suo jack cilindrico. Il regolatore dell’Uno è un piccolo componente lineare che brucia la differenza tra ingresso e uscita sotto forma di calore: con 7.4 V in ingresso, 5 V in uscita e 500 mA, si tratta di (7.4 − 5) × 0.5 = 1.2 W in un componente senza dissipatore. Si scalda, si spegne per protezione termica e il robot si riavvia per un motivo che non ha nulla a che fare con la batteria.

Che cosa risolve davvero un condensatore

Il consiglio standard è mettere un condensatore elettrolitico di grandi dimensioni in parallelo all’alimentazione dei motori. Il consiglio è corretto, ma la ragione solitamente addotta è sbagliata, e l’aritmetica lo dimostra:

ΔV = I × Δt / C

Un condensatore da 1000 µF cui si chiede di sostenere un picco di avvio di 2 A per 10 ms subirebbe una caduta di 2 × 0.010 / 0.001 = 20 V. Non può svolgere questo compito. Un condensatore non sostiene il picco di avvio del motore: solo una batteria più rigida può farlo.

Quello che sostiene è invece il transiente rapido. A una frequenza di commutazione PWM di 8 kHz il periodo è 125 µs, quindi lo stesso condensatore vede un’ondulazione di 2 × 0.000125 / 0.001 = 0.25 V, un compito che svolge senza problemi. Assorbe anche i picchi di commutazione che i motori a spazzole restituiscono all’alimentazione.

Quindi montane uno, da 470 a 1000 µF in parallelo all’alimentazione dei motori, più un condensatore ceramico da 0.1 µF vicino al driver, e aspettati che riduca rumore e malfunzionamenti dei sensori. Non aspettarti che risolva un riavvio. Se un condensatore ha risolto il riavvio, il pacco era al limite e hai acquistato pochissimo margine.

Misura invece di indovinare

Dieci minuti con un multimetro sono migliori di un pomeriggio di teorie.

  1. Imposta il multimetro in volt in CC e collegalo ai morsetti della batteria, non all’Arduino.
  2. Annota la tensione a riposo.
  3. Avvia i motori, idealmente sotto un carico reale con il robot sul pavimento.
  4. Osserva il valore.

Una caduta superiore a poche decine di millivolt è la risposta. Per ricavare R_internal, dividi: un pacco da 6.0 V che scende a 4.4 V assorbendo circa 2 A dà (6.0 − 4.4) / 2 = 0.8 Ω.

Ripeti quindi la misurazione sulla linea da 5 V mentre i motori si avviano. È il valore che il microcontrollore sperimenta davvero e, se scende verso 3.8 V, hai trovato il guasto senza bisogno di un oscilloscopio.

I multimetri economici calcolano la media troppo lentamente per rilevare il punto più basso di un calo di pochi millisecondi, quindi considera ottimistico il valore letto. Il calo è almeno così grave.

Correzioni, dalla più economica

  1. Smetti di alimentare i motori tramite la scheda. È gratuito e risolve direttamente una grande parte dei casi.
  2. Cambia pacco. Alcalino o 9 V → NiMH o 18650. Spesso è tutta la soluzione.
  3. Collega insieme le masse. È gratuito e la sua assenza causa guasti più strani di un brown-out.
  4. Aggiungi il condensatore di massa. Per rumore e anomalie dei sensori, non per i riavvii.
  5. Fai una rampa sui motori anziché applicare gradini. Un profilo di movimento distribuisce la corrente di avvio nel tempo invece di richiederla tutta in un solo istante: è lo stesso codice che impedisce alle ruote di slittare.
  6. Dimensiona correttamente i motori. Se a un motore viene richiesta più coppia di quanta debba, assorbe più corrente del dovuto; il calcolo per dimensionare un motore con riduttore sta a monte dell’intero problema.
  7. Separa il regolatore. Un piccolo convertitore buck per la linea logica la disaccoppia completamente dal collasso dei motori.

Una volta sistemata la linea, misurarla via software è il passo successivo. Leggere correttamente una tensione è più difficile di quanto sembri, perché il riferimento predefinito dell’ADC è la linea di alimentazione.

Quando qualcosa va storto

Sintomo Di solito significa
L’uscita seriale riparte dall’inizio all’avvio dei motori Brown-out; controlla il pacco prima del codice
Funziona con USB, fallisce con la batteria Il pacco non può fornire il picco, USB sì
Funziona per venti minuti, poi si riavvia ripetutamente La resistenza interna delle celle alcaline aumenta durante la scarica
Comportamento anomalo senza un riavvio pulito Alimentazione nella fascia 2.7–3.8 V: fuori specifica, ma sotto nessuna soglia
I sensori restituiscono valori privi di senso solo durante la guida Rumore di alimentazione; condensatore di massa e linee separate
I pin di direzione sembrano non fare nulla Nessuna massa in comune tra scheda e driver
Il regolatore è troppo caldo da toccare La logica preleva corrente attraverso un regolatore lineare con troppa tensione in ingresso
Un condensatore lo ha risolto, poi il problema è tornato Il pacco era al limite; il condensatore ha comprato millisecondi, non ampere

L’alimentazione è lo strato sottostante a ogni altro strato del robot, ed è per questo che un guasto qui imita un guasto ovunque. Quando accade qualcosa di inexplicable nell’istante in cui i motori si avviano, misura prima la batteria: costa dieci minuti e ha ragione più spesso di qualsiasi altra ipotesi.

Esplora il grafo

Fa parte di queste costruzioni

Progetti e percorsi di apprendimento che includono questo tutorial.

Per approfondire

Fonti