Dove iniziare per programmare SAM3U

quello che intendevo è che gli interrupt su tutti i pin sono raggruppati in 3 gruppi da 8, mentre quei 2 pin hano dei registri (e penso delle funzionalità) dedicate. In un altra discussione c'è un PDF che mette a confronto i tempi dell'uso del "gestore generico" dell'interrupt CHANGE su tutti i pin con quello "specializzato" dei due pin, e si dimostrava (con tanto di esempi di codice da testare) che il gestore "specializzato" è quasi il doppio più veloce

lesto:
quello che intendevo è che gli interrupt su tutti i pin sono raggruppati in 3 gruppi da 8, mentre quei 2 pin hano dei registri (e penso delle funzionalità) dedicate. In un altra discussione c'è un PDF che mette a confronto i tempi dell'uso del "gestore generico" dell'interrupt CHANGE su tutti i pin con quello "specializzato" dei due pin, e si dimostrava (con tanto di esempi di codice da testare) che il gestore "specializzato" è quasi il doppio più veloce

Non ti devi però dimenticare che gli INT esterni hanno una priorità maggiore rispetto agli INT interni (i PCINT) quindi anche l'MCU li esegue con una solerzia maggiore. Poi va vista la circuiteria interna, tieni conto che alcuni interrupt esterni come ad esempio l'INT0 restano attivi anche in diverse modalità di risparmio energetico, probabilmente a causa di una divisione modulare interna differente.

ah ecco, nei miei post precedenti sostituite "gestore generico" con PCINT e "gestore dedicato" con INT.
cmq il link al PDF, per completezza: http://arduino-pinchangeint.googlecode.com/files/PinChangeInt%20Speed%20Test-1.3.pdf

lesto:
quello che intendevo è che gli interrupt su tutti i pin sono raggruppati in 3 gruppi da 8, mentre quei 2 pin hano dei registri (e penso delle funzionalità) dedicate. In un altra discussione c'è un PDF che mette a confronto i tempi dell'uso del "gestore generico" dell'interrupt CHANGE su tutti i pin con quello "specializzato" dei due pin

Non esiste un gestore generico e uno specializzato degli interrupt, c'è solo una lista di priorità in base al tipo di evento con valenza maggiore per il valore minore nel vettore di interrupt, INT0 e INT1 hanno valenza maggiore di PCINT0-2 e vengono eseguiti prima in caso di eventi simultanei, se arrivano singolarmente non cambia nulla.
La differenza la può fare la ISR dove nel caso di pin generici potresti avere la necessità di capire da quale pin, degli otto possibili, è arrivato l'interrupt, questo richiede qualche ciclo macchina in più rispetto agli interrupt INT0 e INT1 che possiedono vettori distinti e univoci.

leo72:
quindi anche l'MCU li esegue con una solerzia maggiore.

No, il tempo di risposta è identico per tutti gli interrupt, da 3 a 4 tre cicli macchina per saltare alla ISR, quello che cambia è solo la precedenza.
Se arrivano in simultanea INT0 e PCINT0 prima viene eseguita la ISR di INT0 e poi la ISR di PCINT0, se PCINT0 arriva da solo il tempo di salto alla ISR è identico a quello della INT0.
La gestione della ISR richiede un certo tempo dato dalla somma di quello necessario per salvare lo stato macchina nello stack, eseguire la ISR, ripristinare lo stato macchina recuperandolo dallo stack.
Mediamente la fase di entrata/uscita dalla ISR e salvataggio/ripristino dello stato macchina richiede circa 1.2 us su Arduino, poi va aggiunto il tempo richiesto dal codice contenuto nella ISR.

quindi stai dicendo che probabilmente quelle differenze misurate dal tipo del PDF sono semplicemente l'interferenza degli altri interrupt esterni (vedi Serial)? oppure la INT ha priorità anche su interrupt interni? immagino ci sia tutto sul datasheet ma sono al lavoro e mi interessa

Per "solerzia" non intendevo la celerità delle istruzioni ma proprio ciò che avevo premesso, ossia la priorità. Il concetto di "precedenza". E' come nel traffico quando arriva un'ambulanza a sirene spiegate, che vien fatta passare dal vigile prima delle altre auto.

Quindi, ricapitolando, va bene anche se si usa attachInterrupt, no? Io ho usato gli ingressi collegati agli INT...

esatto, se usi ISR forse risparmi qualcosa per via del fatto che non fa il salto alla tua funzione, son pochi cicli ma magari basta. In oltre se postassi il codice si potrebbe vedere di ottimizzare la funzione e farcela stare coi tempi

lesto:
oppure la INT ha priorità anche su interrupt interni? immagino ci sia tutto sul datasheet ma sono al lavoro e mi interessa

La INT0 e INT1 sono i primi due interrupt della lista, quindi hanno maggiore priorità su tutti gli altri, però la questione si pone solo se ci sono interrupt nella stessa finestra temporale di INT0 e INT1, ovvero vengono gestiti solo quando le ISR INTx hanno terminato il loro compito.

@lesto:
A pag. 57, tab. 11.1, c'è l'elenco per priorità dei vettori di interrupt.

ehm... ehm ehm.... smiley-roll-sweat smiley-roll-sweat smiley-roll-sweat smiley-roll-sweat
Si perde tanto in prestazioni usando attachInterrupt invece che utilizzare direttamente le ISR? Io pensavo che attachInterrupt non facesse altro che indirizzare un jump alla funzione richiesta...

Scusa al posto di "core" o scritto "cose", cioè XMega a quale core ti riferisci?

ISR(vect) // e una macro
{
if (qualcosa)
call function pointer
}

Questa è più o meno come si presenta il vettore di interrupt di arduino, si fa uso di attachInterrupt per registrare un puntatore a funzione utente, che come vedi viene chiamata da dentro la isr, quindi scrivendo direttamente il codice nella isr si risparmia una if e una chiamata a funzione che comporta il salvataggio di SREG e la situazione dello stack. Quindi qualcosa si risparmia.

La situazione di janos è tipica ed è questo il momento per ottimizzare quanto più possibile il codice. Certo ci sarebbe da studiare il codice al fine di capire qual'è la porzione di codice che più necessita di ottimizazione.

Il 2560 mi pare che ha 4 interrupt trigerabili da INT0-INT3, se usi encoder in quadratura sono necesari solo due pin, se gli encoder sono due cominciano i problemi o almeno ci sono maggiori probabilità che i due encoder scatenino eventi che non possono essere gestiti in parallelo, ma in questo caso ti accorgeresti che le isr monopolizzano l'uso del mcu e rimane davvero poco tempo per eseguire altro codice.

In genere nella isr c'è solo un contatore chAcount++; e chBcount-- e quindi più rapido di così non si può, se nelle isr fai altro devi vedere se influisce in modo importante.

esatto, se usi ISR forse risparmi qualcosa per via del fatto che non fa il salto alla tua funzione, son pochi cicli ma magari basta. In oltre se postassi il codice si potrebbe vedere di ottimizzare la funzione e farcela stare coi tempi

Si ma quanto costa una chiamata a funzione? si tratta di un tempo costante o è variabile?

Ciao.

postare tutto il codice non posso, sia per segreto aziendale sia perché sono circa 1500 righe... :slight_smile:

L'encoder penso sia la cosa che più ho ottimizzato...

#define encInterrA 0
#define encInterrB 1
#define encA 2
#define encB 3

volatile unsigned long contEnc = 0; //Valore del conteggio dell'encoder

void setup() {
  digitalWrite(encA, HIGH);                           //Attivo i pullup degli ingressi dell'encoder
  digitalWrite(encB, HIGH);                           //Attivo i pullup degli ingressi dell'encoder
  attachInterrupt(encInterrA, encoderA, CHANGE);      //Attivo il gestore di interrupt per gli ingressi dell'encoder
  attachInterrupt(encInterrB, encoderB, CHANGE);      //Attivo il gestore di interrupt per gli ingressi dell'encoder
};

void encoderA() {
  unsigned char i = PINE;                   //Leggo il valore di PINE
  unsigned char a = i & (1 << PE4);         //Maschero gli altri bit per isolare quello corrisp. alla fase A
  unsigned char b = i & (1 << PE5);         //Maschero gli altri bit per isolare quello corrisp. alla fase B
  if (a == (1 << PE4)) {                    //Se A è alto
    if (b == 0) contEnc++;                  //e B è basso incrementa
    else contEnc--;                         //altrimenti decrementa
  }
  else {                                    //Se A è basso
    if (b == (1 << PE5)) contEnc++;         //e B è alto incrementa
    else contEnc--;                         //Altrimenti decrementa
  }
};

void encoderB() {
  unsigned char i = PINE;                   //Leggo il valore di PINE
  unsigned char a = i & (1 << PE4);         //Maschero gli altri bit per isolare quello corrisp. alla fase A
  unsigned char b = i & (1 << PE5);         //Maschero gli altri bit per isolare quello corrisp. alla fase B
  if (b == (1 << PE5)) {                    //Se B è alto
    if (a == (1 << PE4)) contEnc++;         //e A è alto incrementa
    else contEnc--;                         //altrimenti decrementa
  }
  else {                                    //Se B è basso
    if (a == 0) contEnc++;                  //e A è basso incrementa
    else contEnc--;                         //Altrimenti decrementa
  }
};

la chiamata ha una parte costante, visto che i dati da salvare in stack son sempre quelli, però deve salvare anche le variabili locali (se presenti) ed infine fare il passaggio di parametri
poi considera che finita la funzione chiamanta viene rifatto tutto al contrario.

edit:
@janos: non dichiarare le variabili dentro la funzione, ma falle globali. così risparmi qualche ciclo.

poi (1 << PE5) è una operazione che puoi evitare se PE5 è costante.
per esempio potrebbe diventare da

 if (b == (1 << PE5))

a (se PE5 valesse 16 per esempio)

 if (b == B00010000)

ci sono mille altre vie, il fatto di fare (1 << PE5) vuol dire fondentalmente sposta 1 a sinistra di PE5 bit, ma se PE5 è costante, questo valore te lo puoi calcolare a mano e usare come define

"1 << PE5" non occupa nessun ciclo del processore, è un calcolo che esegue una volta il precompilatore. Attenzione, non prendo una variabile, metto un 1 all'inizio e faccio e ROR (rotate on right) ma è una costante con un 1 nella posizione indicata da PE5. PE5 è una costante che è definita non so dove di preciso ma a partire dal file avr/io.h

La mie isr erano così:

unsigned int npulseA = 1;
unsigned int npulseB = 1;
void encoderChA()
{
    if ((PIND & _BV(2)) ^ (PIND & _BV(3)))
    {
        encoderpos++;
    }
    else
    {
        encoderpos--;
    } 
}

void encoderChB()
{
   
    if ((PIND & _BV(2)) ^ (PIND & _BV(3)))
    {
        encoderpos--;
    }
    else
    {
        encoderpos++;
    }
}

Codice per la 2009.

"1 << PE5" non occupa nessun ciclo del processore, è un calcolo che esegue una volta il precompilatore. Attenzione, non prendo una variabile, metto un 1 all'inizio e faccio e ROR (rotate on right) ma è una costante con un 1 nella posizione indicata da PE5. PE5 è una costante che è definita nel file portpins.h

Si, suppongo PE5 e definito per avere "valore" 5 quindi è uno shift << di 5.

Comunque usa direttamente PINE anziche assegnarlo a i;

Ciao.

MauroTec:
Comunque usa direttamente PINE anziche assegnarlo a i

Effettivamente non è male come idea... =)

Janos:
"1 << PE5" non occupa nessun ciclo del processore, è un calcolo che esegue una volta il precompilatore. Attenzione, non prendo una variabile, metto un 1 all'inizio e faccio e ROR (rotate on right) ma è una costante con un 1 nella posizione indicata da PE5. PE5 è una costante che è definita non so dove di preciso ma a partire dal file avr/io.h

sicuramente è un'operazione perchè puoi usare anche valori non costanti, poi non so se il compilatore se ne accorge che usi costanti e la risolve.. dovresti controllare l'assembly

Secondo quello che c'hanno spiegato all'università quella non è un'operazione perché è un numero... E' un'operazione che esegue il precompilatore.

Janos:
Secondo quello che c'hanno spiegato all'università quella non è un'operazione perché è un numero... E' un'operazione che esegue il precompilatore.

Forse confondi l'operazione di sostituzione di PE5 che il compilatore esegue con il corrispondente valore portato da PE5 con l'operazione di rotazione (o shift), che viene eseguita sul contenuto di una variabile a runtime.