Controllo Closed-Loop DC di due motori con Arduino UNO

Sto continuando a divertirmi col mio Arduino UNO.

Dopo la routine di lettura di tre encoder in quadratura, eccovi la mia ultima creazione.

E' uno skech che utilizzando proprio la lettura dei tre encoder postata in precedenza facendo uso, oltre che della PinChange-interrupt, anche di un Timer1_COMPARE-Interrupt, per richiamare due loop PID molto semplificati ed interrompibili dal Change-Interrupt, esegue il controllo di posizione su due motori DC.

Il controllo corrente non è implementato, si sfrutta la limitazione di corrente offerta dallo Shield 'Cytron MDD10' usato per il pilotaggio dei motori. Non è implementata nemmeno una limitazione di velocità massima. Sarebbe bello farlo a farci stare il tutto.

Poi per provare il tutto mi sono inventato un meccanismo che esegue in successione una serie di posizionamenti su 10 diverse posizioni, quindi attende 3 secondi, dando modo di verificare che la posizione di arresto dei motori sia sempre la stessa e non si sia perso qualche conteggio per strada e poi ricomincia da capo. Durante lo sviluppo avevo anche inserito una Serial.Print() per visualizzare le posizioni sul plotter seriale dell'IDE di Arduino, ma con due motori è praticamente impossibile vedere qualcosa di utile. Siamo al limite ed anche oltre.

Se qualcuno ha idee e gli interessa migliorarlo ed implementare anche altre funzioni, io sono qui, parliamone!

controllo_2x_DC_CL.ino (21.8 KB)

Ora sono a casa e non ho l'HW sottomano, quindi non l'ho ancora testato. Lo pubblico solo come spunto di discussione/lavoro.

Ho modificato lo sketch precedente per funzionare come doppio closed-loop comandabili da segnali STEP/DIR. La cosa è stata ottenuta modificando la routine ASSEMBLY per leggere gli ingressi A0-A3 come encoder in quadratura usati per feedback motore, gli ingressi A4 ed A5, come STEP per gli ingressi di pilotaggio. I segnali DIR, che non occorre leggere in interrupt sono i pin 4 e 7.
La lettura ingressi è fatta dalla ISR pin_change. I PID sono eseguiti ad intervalli di tempo regolari di 1.44mSec. Questo valore si modifica nella riga di assegnazione:

    OCR1A=89; // periodo PID=(OCR1A+1)*256/16000000 secondi

Se non si dovesse vedere nulla sul monitor seriale, oltre una certa velocità (in effetti l'Arduino si Impalla proprio perchè si satura lo Stack) è probabile che per il numero di impulsi/giro encoder e le velocità impiegate, il calcolo del PID non si sia ancora concluso, quando arriva l'interrupt a tempo successivo. In questo caso o si aumenta il periodo di calcolo o si riduce il numero di impulsi/giro. Attenzione che variando il periodo PID occorre correggere in proporzione le costanti KP, KI e KD.

Il loop esegue solo la visualizzazione di posizione richiesta e posizione reale per entrambi i motori.
In una eventuale applicazione reale potrebbe anche rimanere vuoto. Magari Vi si potrebbe gestire una sorta di Enable dei motori. Basterebbe in base allo stato di un ingresso mettere a zero le uscite e disattivare l'interrupt a tempo disattivando con ciò l'esecuzione dei PID.

controllo_2x_closedLoop.ino (17.4 KB)

Dimenticavo!
Dal momento che l'encoder di feedback legge in modalità x4 e l'ingresso STEP/DIR in modalità x1 e dato che il PID lavora comparando i due conteggi, la risoluzione in STEP/giro del sistema sara uguale a quella dell'encoder impiegato x4.

Volendo modificare questa cosa ci sono due strade:

  1. Inserire un moltiplicatore/divisore di uno dei contatori (magari quello della posizione richiesta) e poi far lavorare il PID con il valore calcolato. Sarebbe però molto oneroso in termini di tempo macchina. Il rapporto però lo potro variare a piacere.

  2. Nella rotine in Assembly, far incrementare/decrementare i valori non di uno ma di un valore diverso.

Ad esempio ho un encoder da 100Impulsi/giro e voglio avere una risoluzione di 200Step/giro. Nella routine Assembly dove conteggio valore3 e valore4 incremento/decremento di 2.
Si può fare anche l'opposto. Faccio incrementare valore1 e valore2 di 2 ogni volta. Il sistema appare come un 800 Step/giro, ma la precisione reale sarà pari a+/-2 step, non si potrà mai avere una precisione superiore al quadruplo degli impulsi/giro dell'encoder.

Scusate! l'Altra sera nella foga di postare il tutto ho lasciato qualche strafalcione. Sia nel codice che nei commenti.

Questo dalle prime prove sembra essere OK.

P.s. Vedo che più d'uno lo ha guardato ma nessun commento. Che c'è? Non piace?... E' poco interessante?
Provato e non funziona? (Beh! in effetti questo si... Non funzionava, ma l'avevo detto che non era
testato!)

Ora mi piglio la prima reprimenda dai moderatori. Lo so già! :slight_smile: :slight_smile:

Luca

controllo_2x_closedLoop.ino (18.6 KB)

Oggi ho pensato una cosa relativamente al controllo closed loop che ho postato, Ve la espongo e mi piacerebbe conoscere la vostra opinione.

Nel mio sketch sia per i due encoder di feedback, sia per i contatori di posizione richiesta, gestisco nella ISR in Assembly un totale di 4 contatori a 32 bit, poi nella ISR a tempo eseguo il calcolo del PID con un periodo di 2,24mSec, che difficilmente posso ridurre e che invece mi piacerebbe portare a 1mSec.

L'idea è questa:

Se io eseguissi la routine in Assembly non sul'interrupt PinChange, ma a intervalli di tepo fissi, ad esempio 25000 volte al secondo e ed il calcolo del PID lo eseguissi con una frequenza di 1KHz, lasciandolo sempre interrompibile dalla routine di lettura degli encoder, otterrei due cose:

  1. A prescindere dalla relazione di fase con cui arrivano le transizione dei singoli segnali, io avrei l'aggiornamento di tutti e quattro i contatori almeno 25000 volte al secondo, ovvero questa (o poco meno) sarebbe la massima frequenza teorica per tutti e quattro i conteggi.

  2. Dato che il PID viene eseguito 25000/1000=25 volte meno spesso dell'aggiornamento dei contatori e quindi tra una esecuzione del ciclo PID e la successiva, i contatori potrebbero incrementarsi/decrementarsi al più di 25 unità, 8 bit sono più che sufficienti (con conseguente dimagrimento della ISR) per contenere tale variazione ed il valore a 32 bit potrebbe essere ricostruito semplicemente sommando al counter a 32 bit la differenza tra lettura attuale e quella precedente. Tra l'altro essendo i counter ad 8 bit non dovrei nemmeno farne la lettura in modo atomico.

Ora ipotizzando di riuscire a contenere la ISR con contatori ad 8Bit in circa 250Cicli di clock (ed è un conto abbastanza fattibile, forse ne bastano anche meno), avrei che il consumo di risorse per la lettura degli ingressi sarà pari a 250x25000=6,250,000 cicli.
Quindi con un clock di 16MHz avrò ancora 9,750,000 cicli a disposizione per eseguire 1000 volte in un secondo il PID ovvero 9,750 cicli per ogni singola esecuzione, che ad occhio dovrebbero più che bastare.

Cosa ne pensate?