Assembly su Arduino (inline assembly)

Salve a tutti,
sto cercando di scrivere una funzione che mi gestisca un encoder incrementale in linguaggio assembler per ovvie ragioni di velocità. Ho trovato a giro qualche guida sull'inline assembly ma non c'ho capito granché. Sapreste spiegarmi in maniera chiara la sintassi dell'inline assembly? Grazie mille.

è un pò lunghino l'argomento, ma qui trovi un pò di info:
http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html

Scusami ma non riesco a capire la sintassi (forse perché non conosco benissimo l'inglese).

Potresti farmi un esempio banale? Del tipo, quando viene chiamata la funzione se il pin PE4 (pin 2 dell'arduino) è HIGH incrementa un contatore chiamato contEnc (variabile globale del tipo unsigned long) altrimenti no. Da questo esempio dovrei riuscire a capire la dinamica di funzionamento e poi adattarlo alle mie esigenze...

Ovviamente poi, se arrivo in fondo, posto il risultato... :slight_smile:

Che velocitá di rotazione hai e quanti impulsi fa l'encoder per giro?

Ciao Uwe

Diciamo che, sovradimensionando il tutto, farà 24 giri al secondo ed è un 500 impulsi/giro, quindi se lavoro su 4 fronti sono 48000 impulsi al secondo.

Per il momento ho semplificato al massimo che mi permette il C++, ovvero:

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

void encoderA() {
  unsigned char i = PINE;
  unsigned char a = i & (1 << PE4);
  unsigned char b = i & (1 << PE5);
  if (a == (1 << PE4)) {
    if (b == 0) contEnc++;
    else contEnc--;
  }
  else {
    if (b == (1 << PE5)) contEnc++;
    else contEnc--;
  }
}

void encoderB() {
  unsigned char i = PINE;
  unsigned char a = i & (1 << PE4);
  unsigned char b = i & (1 << PE5);
  if (b == (1 << PE5)) {
    if (a == (1 << PE4)) contEnc++;
    else contEnc--;
  }
  else {
    if (a == 0) contEnc++;
    else contEnc--;
  }
}

void setup() {
  digitalWrite(encA, HIGH);
  digitalWrite(encB, HIGH);
  attachInterrupt(encInterrA, encoderA, CHANGE);
  attachInterrupt(encInterrB, encoderB, CHANGE);
}

E sono abbastanza soddisfatto, ma se riuscissi a farlo in assembler mi piacerebbe di più.

ASSEMBLY, non assembler. L'ASSEMBLY è un linguaggio, l'assembler è il software che trasforma i programmi scritti in assembly in codice macchina.
Non è per essere pignolo ma è per chiamare le cose con il loro nome. E' come quando qualcuno chiama corrente la tensione e viceversa. Non è la stessa cosa.

Detto questo, quando scrivi un programma in assembly devi partire da un presupposto. Non hai NIENTE di pronto. Anche l'impostare un pin come input o output prevede che tu di debba scrivere a mano la relativa funzione. Con il linguaggio di Arduino tu dai un bel pinMode(num_pin, modalità) e sei a posto. Con l'assembly devi fare tutto in maniera molto più complicata.

Ti consiglio intanto di dare un'occhiata al datasheet dell'Atmega. Lì ci sono moltissimi esempi di codice assembly per svolgere le funzioni base di gestione del micro nonché l'elenco dei codici mnemonici del linguaggio.

Scusa, non sapevo la differenza fra assembly e assembler, grazie della delucidazione.
Lo so che in assembly va fatto tutto a mano, ho già programmato in assembly, ma non riesco a capire come integrare un pezzo di codice in assembly dentro un programma scritto in c, andando per di più a scrivere in una variabile che per il resto del programma è globale e non passata come argomento.

P.S. Se guardi la porzione di codice che ho postato sopra vedrai che non ho usato la digitalRead ma sono andato a leggere direttamente dentro la PINE per motivi di efficienza.

Janos:
Scusa, non sapevo la differenza fra assembly e assembler, grazie della delucidazione.

Potresti cambiare il titolo al thread :stuck_out_tongue:

Lo so che in assembly va fatto tutto a mano, ho già programmato in assembly, ma non riesco a capire come integrare un pezzo di codice in assembly dentro un programma scritto in c

Devi scrivere asm("codice assembly") all'interno del tuo codice.

andando per di più a scrivere in una variabile che per il resto del programma è globale e non passata come argomento.

Uhm... qui la cosa si fa complicata, almeno per me. L'assembly non ha il concetto di variabili ma di locazioni di memoria. Dovresti recuperare la locazione di memoria della tua variabile e scriverci dentro. Ma non conosco così bene il C per dirti come fare.

P.S. Se guardi la porzione di codice che ho postato sopra vedrai che non ho usato la digitalRead ma sono andato a leggere direttamente dentro la PINE per motivi di efficienza.

Non ci avevo fatto caso :stuck_out_tongue:

Ciao,

trovi della documentazione qui per l'inline assembly

http://www.nongnu.org/avr-libc/user-manual/inline_asm.html

Ciao,
Marco.

ciao

guarda l'esempio in questo thread su come modifica dall'asm una variabile dichiarata esternamente:
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1207370768/2

Potresti cambiare il titolo al thread

Cambiato... :wink: Non mi ricordavo di aver scritto assembler nel titolo... :smiley:

ciao

guarda l'esempio in questo thread su come modifica dall'asm una variabile dichiarata esternamente:
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1207370768/2

Questo mi potrebbe tornare utile, grazie...

Ciao,

trovi della documentazione qui per l'inline assembly

Inline Assembler Cookbook

Ciao,
Marco.

Mi potresti spiegare gli statement della sintassi

asm(code : output operand list : input operand list [: clobber list]);

per favore? Dalla spiegazione non l'ho capito... Inoltre non ho capito bene cosa fa l'asm volatile e quindi non ho capito se devo usarlo o no dentro un interrupt...

P.S. Grazie mille a tutti... 8)

Un'altra cosa: come si trattano le variabili a 32 bit come gli unsigned long? :roll_eyes:

Inizia a leggere, di materiale da studiare ne hai molto :wink:
Io non posso darti una mano perché l'assembly degli Atmel non lo conosco.

Ok, grazie mille per le dritte. Se riesco nell'impresa pubblicherò il risultato... :slight_smile:

Ciao,

per quanto riguarda volatile e funzioni ISR, beh la spiegazione la trovi qui nelle FAQ (vale per qualsiasi tipo di variabile)
"My program doesn't recognize a variable updated within an interrupt routine"
http://www.nongnu.org/avr-libc/user-manual/FAQ.html

Per quanto riguarda la sintassi

asm(code : output operand list : input operand list [: clobber list]);

gli "output operand list" e "input operand list" servono a definire il legame fra i registri e gli operandi C, definendo questo legame si per quelli in output ed input.

Ciao,
Marco.

Ci sono riuscito ma non ho avuto nessun aumento di prestazioni rispetto alla soluzione che ho pubblicato ieri, comunque a scopo "didattico" pubblico la soluzione, magari a qualcuno nel futuro tornerà utile...

Questo codice utilizza i pin 2 e 3 come ingressi a interrupt per le fasi dell'encoder, ma ripeto che non ho trovato aumenti prestazionali rispetto alla soluzione in C che ho pubblicato ieri, quindi utilizzo quella che è più comprensibile.

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

void setup() {
  digitalWrite(encA, HIGH);  //Necessario se l'encoder ha le uscite NPN open-collector
  digitalWrite(encB, HIGH);  //Necessario se l'encoder ha le uscite NPN open-collector
  attachInterrupt(encInterrA, encoderA, CHANGE);
  attachInterrupt(encInterrB, encoderB, CHANGE);
}

/********************************
Poiché in assembly non c'è un'istruzione per effettuare una somma fra un registro e una costante è
necessario utilizzare le istruzioni subi (subtract with immediate) e sbci ( subtract with immediate with carry)
e se bisogna aggiungere 1 non si fa altro che sottrarre -1;
-1 in complemento a 2 è, su 32 bit, 4294967295 (32 uni) ma scomposto a gruppi di 8 bit vengono 4 gruppi di
8 uni che equivalgono a 255.
Per ulteriori dettagli visitare
http://www.atmel.com/dyn/resources/prod_documents/doc0937.pdf
http://it.wikipedia.org/wiki/Complemento_a_due
*********************************/

void encoderA() {
  asm volatile (
  "sbic %1,4"          "\n\t"
  "rjmp Aalto"         "\n\t"
  "sbic %1,5"          "\n\t"
  "rjmp Aincrementa"   "\n\t"
  "rjmp Adecrementa"   "\n\t"
  
  "Aalto:"             "\n\t"
  "sbis %1,5"          "\n\t"
  "rjmp Aincrementa"   "\n\t"
  
  "Adecrementa:"       "\n\t"
  "subi %A0,1"         "\n\t"
  "sbci %B0,0"         "\n\t"
  "sbci %C0,0"         "\n\t"
  "sbci %D0,0"         "\n\t"
  "rjmp Afine"          "\n\t"
  
  "Aincrementa:"       "\n\t"
  "subi %A0,255"       "\n\t"
  "sbci %B0,255"       "\n\t"
  "sbci %C0,255"       "\n\t"
  "sbci %D0,255"       "\n\t"
  
  "Afine:"
  : "+d" (contEnc)
  : "I" (_SFR_IO_ADDR(PINE))
  );
}

void encoderB() {
  asm volatile (
  "sbic %1,5"          "\n\t"
  "rjmp Balto"         "\n\t"
  "sbic %1,4"          "\n\t"
  "rjmp Bdecrementa"   "\n\t"
  "rjmp Bincrementa"   "\n\t"
  
  "Balto:"             "\n\t"
  "sbis %1,4"          "\n\t"
  "rjmp Bdecrementa"   "\n\t"
  
  "Bincrementa:"       "\n\t"
  "subi %A0,255"       "\n\t"
  "sbci %B0,255"       "\n\t"
  "sbci %C0,255"       "\n\t"
  "sbci %D0,255"       "\n\t"
  "rjmp Bfine"          "\n\t"
  
  "Bdecrementa:"       "\n\t"
  "subi %A0,1"         "\n\t"
  "sbci %B0,0"         "\n\t"
  "sbci %C0,0"         "\n\t"
  "sbci %D0,0"         "\n\t"
  
  "Bfine:"
  : "+d" (contEnc)
  : "I" (_SFR_IO_ADDR(PINE))
  );
}

P.S. Un caloroso GRAZIE a tutti coloro che mi hanno aiutato... :wink:

Ciao,

grazie per aver pubblicato il codice, utile per chi volesse utilizzare l'Assembly vedere una soluzione finita.

Mi ero scordato di inserire questo altro documento sull'utilizzo in parallelo di C and Assembly.

E' un esempio per ATtiny ed utilizza un diverso approccio.

Fa parte dei "Demo projects" della AVR-libc

Pagina generale progetti con descrizione breve:
http://www.nongnu.org/avr-libc/user-manual/group__demos.html

C + Assembly
http://www.nongnu.org/avr-libc/user-manual/group__asmdemo.html

Probabilmente questa soluzione permette un incremento delle prestazioni, ma e' piu' difficile da implementare (almeno per me).

Ciao,
Marco.

considera prove in chi provi anche le varie ottimizzazioni (-Ox)
infine racconta un po', che impressione hai avuto

Magari prova anche a non usare AttachInt... ma le macro predefinite che puntano al vettore di interrupt che intendi usare, ora non ricordo INT0 e INT1 e PCINTxx per i vettori PinChanged.

Se il compilatore non ottimizza dovresti recuperare un pò, ma come dice flameman c'è controllora il lavoro svolto da gcc.

Ciao.