[ADVANCED] DRO, Prestazioni SPI e Interrupts speed

Dunque... il tema è questo: Costruire un DRO per una macchina a controllo numerico (CNC) che sia abbastanza prestante da seguire gli impulsi forniti al Drive che gestisce i motori passo passo.

  • La macchina è dotata di motori stepper a 200 step per rivoluzione
  • I Drives sono configurati per funzionare a 1/8 od 1/16 di passo
  • La macchina raggiunge i 4000mm/min

Due calcoli spannometrici indicano che gli impulsi forniti dal controller arrivano fino a 45.000 al secondo

Il micro che implementa il DRO deve essere in grado quindi di gestire 45.000 interrupt al secondo. Se poi vogliamo che lo stesso micro si accolli l'onere di gestire la visualizzazione dei 3 gradi di libertà della macchina, arriviamo a 135.000 interrupt al sec (lasciando, per ora, da parte gli accodamenti di tali interrupt)...

Secondo me Arduino gna fa, ma facciamo un pò di cagnara per vedere dove può arrivare (c'ho giusto un fine settimana dove non c'ho un czz da fare..)

Il primo sistema che mi viene in mente è di usare un visualizzatore (graditi suggerimenti) a 8 cifre, già bello pronto, con MAX7219. Il bus è SPI, quindi possiamo andare belli veloci. Almeno in teoria....

'Na roba così:

volatile long variaBBile

void loop()
{
spara "variaBBile" versus MAX7219
delay(qualcosina)
}

INTERRUPT()
{
if direzione = 1
variaBBile++
else
variaBBile--
}

I primi accorgimenti che sono necessari vertono su un pò di programmazione AVR Bare Metal

Leviamo il dinosauro morto della libreria SPI (SPI.begin()) e l'overhead che si porta dietro la funzione attachInterrupt() e utilizziamo la programmazione diretta dei registri SPI più la macro diretta AVR per gli Interrupt.

Spulciando il datasheet ATMEGA328 comincio a scrivere un pò di codice:

SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1)|(0<<SPR0);
SPCR = (1<<SPR1)|(1<<SPR0);
SPSR = (0<<SPI2X);
SPSR |= 0;

...che dovrebbe essere la migliore (e, polarity, clock, ecc, compatibile con il MAX7219).

Per l'interrupt recupero dal file avr\interrupts.h, la macro:

ISR(INT0_vect)
{
........
}

Adesso mi munisco di oscilloscopio e vedo a quanti interrupts/sec VUOTI, riesce a reggere un Arduino, ma non mi aspetto di raggiungere numeri entusiasmanti...

Esseri Advanced spremete le meningi e suggerite qualcosa...

Ammazza, sono tutti al Bar gli Advanced...

Cmq ho tirato su un programma per una MEGA (niente UNO nei cassetti, strano! Mah...)

Dentro il vettore dell'interrupt eseguo una funzione ultra-veloce che semplicemente toggla un pin.

Non esiste sistema più veloce e dal codice più esiguo.

bitSet(DDRH, 7); // Setto l'uscita

ISR(INT0_vect) // Routine dell'INTERRUPT
{
bitSet(PINH, 7); // togglo il pin
}

Mi mancano i puntali per L'Oscilloscopio che ho lasciato al laboratorio in Azienda, quindi fino a domani gniente freq max dell'Interrupt!

Non si fa così, eravamo tutti preoccupati e ognuno costruiva la sua teoria, tipo; sarà stato risucchiato dal Water; sembra un Water ma è un portale per l'altra dimensione, e tante altre teorie fantascientifiche che ti risparmio.

Ho letto sopra sopra, e non ci ho capito nulla, ho solo inteso che serve spremere il micro per fargli fare cose che non sarebbe possibile con la programmazione Aurdiniana.

Avevo un link di uno che mostra come ridurre all'osso il tempo speso per l'esecuzione di una ISR, ora non so proprio dove messo, appena lo trovo posto il link.

Ciao e bentornato.

x iscrizione

x iscrizione :smiley:

BaBBuino:
Il micro che implementa il DRO deve essere in grado quindi di gestire 45.000 interrupt al secondo. Se poi vogliamo che lo stesso micro si accolli l'onere di gestire la visualizzazione dei 3 gradi di libertà della macchina, arriviamo a 135.000 interrupt al sec (lasciando, per ora, da parte gli accodamenti di tali interrupt)...

Secondo me Arduino gna fa, ma facciamo un pò di cagnara per vedere dove può arrivare (c'ho giusto un fine settimana dove non c'ho un czz da fare..)

Arduino no se usi il suo IDE perché oltre al tuo codice vengono aggiunte tante cose che non servono per il DRO, ma servono per Arduino, p.e. la gestione di millis/micros, tutte cose che usano gli interrupt, alcune anche in modo atomico, cose che alla fine ti portano sicuramente a perdere dei conteggi.
In pratica è lo stesso problema degli encoder ad alta risoluzione e alta velocità, arriva un numero enorme di interrupt da gestire e le piccole mcu a ott bit fanno molta fatica a stargli dietro, o proprio non ci riescono, e comunque poi si riesce a fare poco altro visto che quasi tutto il tempo macchina viene assorbito dagli interrupt.
Facciamo due conti, 45.000 interrupt al secondo vuol dire un periodo di 22.2 us tra gli eventi, dal punto di vista del micro, parliamo di un AVR 328/2560 con clok a 16 MHz, sono circa 250 istruzioni assembly, rammento che la maggior parte delle istruzioni degli AVR richiede 2 cicli macchina e un discreto numero 3-4 cicli macchina.
Se non mi ricordo male gcc per gestire entrata ed uscita dall'interrupt, ipotizzando di non fare altro che resettare i relativi flag se necessario, richiede circa 2us, oltre alla manciata di cicli macchina richiesti dalla mcu per gestire il salto al relativo vettore di interrupt e ritorno al codice ci sono anche le operazioni che deve fare il C per gestire correttamente la ISR.
Alla fine rimangono solo 20 us scarsi per gestire tutte le operazioni richieste dalla ISR, nel caso del DRO si tratta solo di incrementare/decrementare una variabile, cosa che richiede anche l'acquisizione del DIR dello stepper per conoscere il verso di rotazione, fino a questo punto siamo ampiamente nei 20 us, però se in mezzo ci metto anche l'aggiornamento del display tramite SPI sicuramente non ce la fai.
Una scappatoia che dovrebbe consentirti di gestire i tre assi, a questo punto siamo a solo 7 us disponibili per ogni ISR, è aggiornare il display periodicamente, p.e. ogni decimo di secondo, tanto non serve a nulla vedere le cifre scorrere a velocità pazzesca visto che l'occhio umano è lento, in questo modo non carichi le ISR di un lavoro inutile e l'aggiornamento lo fai nel main dove inserisci un banale ciclo for molto lungo, per creare un delay che non pesa sugli interrupt, tanto anche se l'operazione spi viene interrotta non succede nulla, semplicemente viene completata dopo.
Ovviamente tutto quanto deve essere fatto in C puro tramite AvrStudio, assolutamente no C++ e tanto meno IDE di Arduino visto che stai litigando con i nanosecondi. :slight_smile:

Perché non consideri l'uso di 3 contatori up/down collegati ad Arduino?

X iscrizione :slight_smile:
Manco lo so cosa è DRO :slight_smile:
I segnali li porti al micro in parallelo all'ingresso del drive ?

cyberhs:
Perché non consideri l'uso di 3 contatori up/down collegati ad Arduino?

...e poi torniamo all'età della pietra... ::slight_smile:

Un bel 74C926 del 1982 e siamo a posto! :grinning:

Ma no, dai, piuttosto uso una DUE o vado di PIC32 a ricchi MHz

Cmq, dai tempi che rileva Astro, un 328 ben ottimizzato dovrebbe farcela a fare almeno un asse.

Testato, questo è un DRO:

Ovvero un dispositivo montato bordo macchina su frese (o altra CNC) per visualizzare le distanze percorse dagli assi rispetto ad uno zero. Si, collego l'ingresso Arduinico in parallelo all'ingresso impulsi del driver.

Mentre cercavo la foto per testato, ho visto che un sacco di gente l'ha già fatto un DRO con Arduino. Adesso indago... ::slight_smile:

A proposito, Astro, mi sembra molto improbabile che i DRO commerciali di fascia bassa abbiano MCU particolarmente performanti! Avranno qualche cadavere a 8 bit anche loro.

BaBBuino:
A proposito, Astro, mi sembra molto improbabile che i DRO commerciali di fascia bassa abbiano MCU particolarmente performanti!

Più semplicemente usano una mcu dotato di QEI, p.e. un PIC18F4431 :slight_smile:

OK, ho già usato degli encoder con il modulo QEI di un dsPIC30F4012, ma mi sembra che non si debba arrivare a tanto. E poi mi pare che il contatore interno QEI arrivi solo a 65535.

Senza arrivare ai PIC32 da 100MHz, uso l'interrupt di picchetto da 40MHz (veri) e dovremmo starci dentro bene.

Ma prima voglio fare qualche prova con Arduino. E' mai possibile che c'è un sacco di gente che l'ha fatto?? Sempre che funzioni realmente, si intende...

Lunedì oscilloscopio bono + gen funzioni e torturiamo il poveraccio... :grinning:

BaBBuino:
OK, ho già usato degli encoder con il modulo QEI di un dsPIC30F4012, ma mi sembra che non si debba arrivare a tanto. E poi mi pare che il contatore interno QEI arrivi solo a 65535.

Proprio non ci arrivi ? :slight_smile:
Il QEI accumula i conteggi, sia avanti che indietro, mentre il micro fa altre cose, ovvero non lo massacri di interrupt.
Periodicamente vai a leggere il QEI e usi il valore contenuto per aggiornare il contatore del DRO tramite somma algebrica, diciamo che ti basta leggerlo almeno 10 volte al secondo, max +/- 4500 count alla massima velocità, esattamente come si fa per determinare la distanza percorsa tramite encoder, che poi è la stessa cosa del DRO.

astrobeed:
Proprio non ci arrivi ? :slight_smile:
Il QEI accumula i conteggi, sia avanti che indietro, mentre il micro fa altre cose, ovvero non lo massacri di interrupt.
Periodicamente vai a leggere il QEI e usi il valore contenuto per aggiornare il contatore del DRO tramite somma algebrica, diciamo che ti basta leggerlo almeno 10 volte al secondo, max +/- 4500 count alla massima velocità, esattamente come si fa per determinare la distanza percorsa tramite encoder, che poi è la stessa cosa del DRO.

Non è che non ci arrivo, ma non volevo "arrivare" a tanto! Soprattutto avendo arduini nel cassetto (cinesi da due lire) con i dsPIC30 che misteriosamente costano ancora un sacco di soldi (più sviluppo PCB)

Ho una UNO32 con PIC32 a 80MHz, ma anche quella costicchia, e volevo fare lo Scozzese-Ebreo di Genova! :grin: :stuck_out_tongue:

QEI immagino sia una periferica nata per gestire encoder in quadratura ?

volevo fare lo Scozzese-Ebreo di Genova!

Uè ... occhio che ti vedo!!!
Che hai contro i genovesi? :frowning:

Mi sembre che BaBBuino sin dal primo messaggio desideri contare gli impulsi dagli impulsi STEP/DIR e non dall'encoder che probabilmente nemmeno c'è'...quindi.....
Puoi fare questa cosa, se dai un occhiata a questi ultimi 2 DRO che ho realizzato mediante un ATmega8
CNC5 , il progamma occupa circa 4k sia per la versione LCD che la versione LED ed stata scritta interamente in C e compilata mediante AVR studio, è stato usato solo 1 interrupt esterno in quanto il controller CNC non genera mai gli impulsti di step X-Y-Z-A-B-C contemporaneamente ma distanziati di almeno 10uSEc, se nella tua applicazione avvengono contemporaneamente puoi usare varie soluzioni, quella più facile e di sicuro successo è usare 3 IRQ esterni, purtroppo Atmega328 ne ha solo 2 (INT0 e INT1) quindi dovrai usare come terzo iRQ un Pin Change Interrupt , se sei abbastanza in gamba dovresti farcela, altrimenti usa l'ATmega2560 che ha 8 INT esterni

icio:
Mi sembre che BaBBuino sin dal primo messaggio desideri contare gli impulsi dagli impulsi STEP/DIR e non dall'encoder che probabilmente nemmeno c'è'...quindi.....

Il modulo QEI è utilizzabile con varie modalità, tra cui anche quella in stile stepper.

ma volendo si puo' anche montare un encoder sull'asse e lavorare su di esso, senza toccare l'attuale elettrocnica ?

Visto che interessa e non ho altro da fare in questa Domenica di freddo... 'Na roba fatta un po' di tempo fa con un dsPIC.

Dal datasheet:


Nella prima immagine vediamo la struttura di un Encoder in quadratura, dotato di tre uscite: A, B e Z (INDEX).
Gli impulsi generati tramite un sistema fotodiodo/disco ottico sono due, e sfasati uno rispetto all'altro di 90° ovvero in quadro (da qui "in quadratura"). Verificando la fase di A rispetto a B, riusciamo, oltre che a contare gli impulsi, a rilevare la direzione della rotazione in avanti o indietro.
La terza uscita Z è riferita ad un solo impulso generato da una singola feritoia in concomitanza del passaggio di questa di fronte al fotodiodo, passaggio che avviene UNA sola volta per giro, permettendo così di avere un riferimento/giro preciso, da cui ZERO.

Nella seconda immagine possiamo osservare la struttura del modulo QEI.
Integra in Hardware tutto il necessario per gestire un Encoder Incrementale, così da sollevare la CPU interna da questi calcoli, ed evitare i LAG che con un Arduino inevitabilmente si avrebbero.
Inoltre i dsPIC hanno - insieme alla ALU normale - un core DSP, quindi esegue tutte le operazioni per calcolare le posizioni e le velocità angolari (con un sacco di pesanti funzioni di seno e coseno) alla velocità della luce (rispetto ad Arduino)

Osserviamo il modulo. Troviamo subito dei filtri digitali su ogni ingresso, così si puliscono i segnali da glitches vari, nonchè riflessioni parassite (avvengono sui bordi delle feritoie ad alta velocita, in cui il fotodiodo legge degli impulsi in più causati dalle riflessioni sulle pareti interne delle feritoie)

Bene, anzi, benissimo, non dobbiamo mettere dei filtri esterni!

Dopo abbiamo la logica della quadratura che legge le fasi tra i due segnali A e B e capisce se stai andando AVANTI o INDIETRO, e quindi fornisce un segnale di UP o DOWN sull'apposito piedino (a cui - per esempio - potremmo collegare due LED a freccia).

Dopo di che c'è un contatore (indicato con POSCNT, che è un registro) avanti/indietro a 16 bit (quindi conta da 0 a +32.767, oppure da 0 a a -32.768, oltre questi valori va in overflow, ovvero si azzera).

Poi c'è un comparatore che lavora insieme al registro MAXCNT.

Come funziona? Si inserisce un numero (il numero degli impulsi/giro che fornisce l'Encoder) dentro il registro MAXCNT e il comparatore lo confronta in tempo reale con quello dentro il contatore di posizione POSCNT, quando i due sono uguali significa che il contatore POSCNT è a tappo, ovvero il Motore-Encoder ha fatto un giro e allora il comparatore azzera il contatore POSCNT, che riparte da 0, dalla posizione zero.

Una seconda modalità - ancora più precisa ed affidabile - consiste nell'azzerare il contatore di POSIZIONE, non arbitrariamente contando gli impulsi per giro, ma utilizzando l'impulso dell'uscita Z (INDEX) quale avviene una sola volta per giro, e per una unica, specifica, posizione dell'Encoder.

Ora due righe sul codice:

Questo codice inizializza il modulo QEI. Per vedere tutti i parametri bisogna pupparsi tutta la sezione che lo riguarda sul datasheet, ma prendendo solo le funzioni che ci interessano (ce ne sono tantissime) che sono anche le più semplici, possiamo semplificare un pò:

void InitQEI(void)
{
ADPCFG |= 0x0038; // Configura i PIN del QEI come INGRESSI DIGITALI
QEICONbits.QEIM = 0; // Disabilito il QEI Module (un modulo si spegne sempre prima di configurarlo, poi lo si riaccende alla fine
QEICONbits.CNTERR = 0; // Azzera il contatore degli errori (si, c'è anche un rilevatore di errori e li conta!)
QEICONbits.QEISIDL = 0; // Continua le operazioni di conteggio anche se la CPU va in sleep
QEICONbits.SWPAB = 0; // Qua puoi scambiare QEA e QEB nel caso hai invertito i fili
QEICONbits.PCDOUT = 0; // Normali operazioni di I/O
QEICONbits.POSRES = 1; // Quando arriva un impulso dal pin INDEX, si resetta il POSCNT
DFLTCONbits.CEID = 1; // Contatore degli errori DISABILITATO
DFLTCONbits.QEOUT = 1; // Filtro Digitale sugli ingressi Qa e QB ABILITATO
DFLTCONbits.QECK = 5; // Frequenza di taglio del Filtro Digitale (bisogna fare il calcolo)
DFLTCONbits.INDOUT = 1; // Filtro Digitale sull' ingresso INDEX, ABILITATO
DFLTCONbits.INDCK = 5; // Frequenza di taglio del Filtro Digitale (bisogna fare il calcolo)
POSCNT = 0; // Reset contatore di posizione (si può settare anche "a mano")
QEICONbits.QEIM = 6; // X4 mode = magheggio per trasformare un encoder da 100 imp a 400 imp.
return;
}

La suite di sviluppo è la versione 8.9 di MPLAB IDE. Non uso l'ultimissima perchè lavoro da anni con questa e mi trovo meglio.