Definizione Interrupt Service Routine

Carissimi, oggi mentre cercavo di digerire la colomba mi sono messo a cercare di capire come funziona la libreria SPI.h.
Pessima idea. :~

Con l'aiuto del datasheet del 328P in realtà credo di aver capito come si comporta, ma mi è venuto un grosso dubbio riguardo alle ISR.
Le funzioni attachInterrupt() e detachInterrupt() della libreria infatti, si limitano ad attivare o disattivare l'interrupt relativo all'SPI (se non vado errato).

La domanda che mi sorge spontanea è allora: come faccio a definire un'ISR?
Ho visto sul datasheet che il il vettore degli handler ha un apposito spazio che dovrebbe contenere (immagino) l'indirizzo della mia funzione:

VectorNo Program Address Source Interrupt Definition
18 0x0022 SPI, STC SPI Serial Transfer Complete

Come faccio però in pratica?

Ho pensato di risolvere il problema leggendo il codice della attachInterrupt() generica, quella relativa agli interrupt esterni, che tra gli altri argomenti prevede anche il passaggio della funzione che faccia da handler. Però qui arriva la seconda domanda: dove lo trovo il codice di questa funzione, così come quello di tutte le altre funzioni "di base", non appartenenti ad alcuna libreria?

Grazie e Buona Pasqua Buona Pasquetta (a questo punto :slight_smile: )

oggi mentre cercavo di digerire la colomba

gran brutta cosa ... consiglio della Coca Cola, una bella passegiata o simile che risolve meglio il problema che studiare Arduino.

In certe funzioni deve disattivare eventuali interrupts perché quello che fa la funzione é molto criticca a livello di tempo. Se per esempio la libreria SPI deve mandare dei dati con una frequenza vicina a quella del Clock (8 o 4MHz) non possono esserci interuzini dati da un'interrupt.

C ha pochissime funzioni che non sono definiti in qualche libreria. Arduino include parecchie librerie in automatico.
Non so se ha senso che Tu Ti faccia questo lavoro (lascio esprimersi altre persone). Per l' uso medio - alto di Arduino non é necessario conoscere il funzionamento nei minimi particolari delle funzioni e librerie.

Ciao Uwe

VanDerTull:
dove lo trovo il codice di questa funzione, così come quello di tutte le altre funzioni "di base", non appartenenti ad alcuna libreria?

In quello che viene chiamato CORE di Arduino, nelle sottocartelle dell'IDE.
--> \arduino-1.0.x\hardware\arduino\cores\arduino

PaoloP:
In quello che viene chiamato CORE di Arduino, nelle sottocartelle dell'IDE.
--> \arduino-1.0.x\hardware\arduino\cores\arduino

Grazie Paolo...domani, per digerire la grigliata, mi metto a leggere :stuck_out_tongue:

uwefed:
In certe funzioni deve disattivare eventuali interrupts perché quello che fa la funzione é molto criticca a livello di tempo. Se per esempio la libreria SPI deve mandare dei dati con una frequenza vicina a quella del Clock (8 o 4MHz) non possono esserci interuzini dati da un'interrupt.

Ok uwe, però, se ho capito bene quello che ho letto sul datasheet, l'SPI lavora "in parallelo" alle altre operazioni del micro, nel senso che una volta caricato il byte da trasmettere nell'apposito registro, la trasmissione avviene in automatico mentre altre operazioni continuano a essere eseguite. Sbaglio?
E dato che in ricezione, quando il secondo byte viene completamente ricevuto, il primo viene perso. Quindi senza l'utilizzo degli interrupt difficilmente riesco a "garantire" una latenza adeguata (soprattutto se lavoro a frequenze alte)..no?

VanDerTull:
La domanda che mi sorge spontanea è allora: come faccio a definire un'ISR?

Nel datasheet esiste una tabella dei vettori di interrupt.
Per creare una ISR devi:

  1. verificare il vettore che vuoi usare (cap. 11.1)
  2. creare il codice relativo. Ad esempio, per il vettore 18, una cosa tipo ISR(SPI_vect) {...}
  3. attivare l'interrupt agendo sul registro. Siccome vuoi usare l'interrupt sul completamento del trasferimento dati SPI devi impostare ad 1 il bit SPIE del registro SPCR (cap. 18.5).

VanDerTull:
Le funzioni attachInterrupt() e detachInterrupt() della libreria infatti, si limitano ad attivare o disattivare l'interrupt relativo all'SPI (se non vado errato).

no, quelle funzioni agiscono sugli interrupt causati da un vettore a parte. Esistono vari meccanismi Hardware indipendenti per gestire i vari interrupt; nel remoto caso di una sovrapposizione, essi hanno una priorità.

Per esempio, ogni timer ha degli interrupt indipendeti, esistono 3 sistemi di interrupt di cui ognuno gestisce 8 pin, alcuni pin ne hanno aggiuntivi e dedicati che sono quelli usati dalla attachInterrupt (che quindi è un mondo mooooolto limitato di ciò che puoi avere), etc..

ps. la SPI è bloccate o usa dei buffer riempiti/svuotati da interrupt? esiste una gestione degli errori con timeout o simili?

Il periferico SPI è non bloccante: ti basta caricare il byte da passare nel registro SPDR (SPI Data Register) e lui lo trasmette alla velocità impostata. Alla fine lancia un interrupt se abilitato e setta il bit SPIF (SPI Interrupt Flag).

Tuttavia, per come è implementato il trasferimento nella libreria SPI.h credo che se usi l'apposita funzione sia da considerare bloccante perchè aspetta che il trasferimento sia finito per ritornare. Il vantaggio è che non devi definire alcun ISR per gestire la comunicazione.

byte SPIClass::transfer(byte _data) {
  SPDR = _data;
  while (!(SPSR & _BV(SPIF)))
    ;
  return SPDR;
}

Per quanto riguarda i buffer in uscita non ne hai, hai solo il registro SPDR. In ingresso invece hai un "buffer" a due byte: mentre ricevi un byte hai tempo per copiare il precendente, ma appena anche il secondo viene ricevuto del tutto, il primo va perso.

Per quanto riguarda le funzioni attach e detachInterrupt() esistono sia quelle generiche del core (relative agli interrupt esterni) sia quelle della classe SPI che si "limitano" ad abilitare o disabilitare l'handler (che però non passi come argomento). Tutto questo inizia ad avere senso alla luce di quello che ha scritto leo, ma sto ancora cercando di capire bene come funziona :grin:

Dimenticavo: la gestione degli errori.
Hai un bit che ti segnala se scrivi nel registro SPDR mentre il trasfetimento corrente è ancora in atto, ma non hai niente relativo a timer o cose del genere. Del resto non credo che abbia senso/ che ce ne sia bisogno per come è fatto il protocollo: se vuoi implementare ack o altro devi agire a livello più alto.

che il chip possa funzionare in modlità non bloccante lo immaginavo, ma la libreria è bloccante. in oltre come dicevo quel while non tiene conto degli errori: se per esempio qualcuno stacca il dispositivo SPI, o c'è un errore di trasmissione, ti si impalla tutto il codice...

In realtà non credo che il codice si blocchi, ma questa non è una mancanza della libreria quanto una debolezza intrinseca del protocollo: l'ATmega manderà comunque tutto il dato fuori e setterà la flag di interrupt alla fine sia che ci siano disturbi sulla linea sia che dall'altra parte venga disattivato lo slave. Io non apprezzo il fatto che durante la comnicazione il micro resti fermo ad aspettare, ma comunque prima o poi ritornerà a lavorare: se vuoi controlli sulla vitalità dello slave o controlli sulla correttezza dei dati trasmessi devi implementarli a livello più alto per forza di cose per come è pensato l'SPI.

Almeno questo è quello di cui sono convinto :roll_eyes:

Posso fare una domanda? Ma hai un problema oppure questo thread è una specie di esternazione delle tue idee? XD
Avevi iniziato la discussione facendo intendere che ti serviva capire come si scriveva una ISR e ti avevo dato alcune dritte, però adesso sembra che tu ne sappia più di quel lasciavi intendere e che stia ragionando fra te e te di come risolvere le cose :wink: :smiley:

Secondo me c'è un motivo per cui per la seriale hanno previsto un buffer da elaborare ad interrupt e per la SPI no.

La seriale è mooooooooooooooooolto più lenta per processore (anche a 115200bps ci vogliono circa 1100 cicli di clock per trasmettere 8 bit) mentre la SPI, viaggiando a 4MHz (8MHz al limite) ci mette 32 (16 se a 8MHz) cicli di clock per trasmettere gli stessi 8 bit: in quel poco tempo il micro avrebbe appena il tempo (forse neppure quello) per tornare al codice principale per poi tornare a servire l'interrupt successivo.

lesto:
che il chip possa funzionare in modlità non bloccante lo immaginavo, ma la libreria è bloccante.

Non si blocca nulla, la SPI, lato master, non ha alcun modo, hardware, per sapere se è andata a buon fine la trasmissione, semplicemente emette tutti i bit e poi setta il relativo flag, l'attesa serve solo se devi inviare più byte di seguito, fino a che non hai finito quello in corso non puoi inviarne.
Eventualmente si può prevedere un risposta di consenso (se possibile farlo), basta un singolo byte, da parte dello slave dopo che ha ricevuto un pacchetto dati, se questo byte non arriva, o arriva con un valore non atteso, vuol dire che c'è stato un problema nella trasmissione del pacchetto dati.
Diverso è il discorso lato slave dove il device si aspetta di ricevere un certo numero di impulsi di clock, quanti dipende dalla dimensione della word SPI (può essere anche di 16-32 bit), se non arrivano tutti rimane in attesa, senza settare il flag di ricezione avvenuta, in eterno o, peggio, ne prende alcuni dalla trasmissione seguente con effetti disastrosi sul dato, in questo caso è necessario prevedere un time out.

leo72:
Posso fare una domanda? Ma hai un problema oppure questo thread è una specie di esternazione delle tue idee? XD
Avevi iniziato la discussione facendo intendere che ti serviva capire come si scriveva una ISR e ti avevo dato alcune dritte, però adesso sembra che tu ne sappia più di quel lasciavi intendere e che stia ragionando fra te e te di come risolvere le cose :wink: :smiley:

:stuck_out_tongue_closed_eyes:
In realtà i problemi che avevo credo che tu e PaoloP li abbiate risolti. Tuttavia non ho ancora avuto tempo per mettermi al lavoro e verificare se effettivamente riesco a scrivere la ISR come mi hai indicato (anche se non vedo come potrebbe non funzionare).

Il resto del topic è nato dalla domanda di lesto e ho riletto varie volte il datasheet da quando ho postato a oggi^^

E intanto sto anche ragionando di come risolvermi le cose..ma questa è un'altra esternazione di quello che penso :stuck_out_tongue:

Alla fine sono riuscito a scrivere qualcosa: per quanto riguarda l'interrupt handler la sintassi è quella che ha detto leo, ISR(SPI_STC_vect){...}.

Ho provato a buttare giù una funzione che invii una sequenza di byte che sia non bloccante. Credo che funzioni ma per ora ho fatto un testing mooolto parziale. Per farlo ho semplicemente aggiunto nella libreria SPI questo:

volatile int SPI_len;
char *SPI_str;

void SPIClass::sendString(char* string, byte length)
{
	//make sure that the SPI interrupt is enabled
	SPI.attachInterrupt();
	//save length and str pointer in global variable
	SPI_len = length;
	SPI_str = string;
	//move first char in the SPDR register
	SPDR = *string;
}

int SPIClass::isBusy(){
	return(SPI_len);
}

ISR(SPI_STC_vect){
	*SPI_str = SPDR;
	SPI_str++;
	if (--SPI_len) SPDR = *SPI_str;
}

La funzione SPI.isBusy() si può chiamare per evitare di scrivere mentre il precedente trasferimento è ancora in corso.

Grazie ancora a tutti per l'aiuto :slight_smile:
Ciao!

cìè confusione con i puntatori.
In oltre devi assicurarti che mentre sei busy la sendString non funzioni... io farei che ritorna false, oppure un numero: 0 = tutto ok, altrimenti un numero che corrisponde all'errore riscontrato

edit: dimenticavo, manca anche la DISATTIVAZIONE dell'interrupt quando arrivi a 0

per i puntatori:

SPI_str = string;

brutta roba, se l'utente cambia la stringa passata come parametro, a te cambia la SPI_str, e la len può essere errata... fai una copia in un array locale.

la Serial ad esempio usa un array (buffer) di 64char "circolare": ovvero mette i dati nell'array, quando l'array è pieno (o la stringa da scrivere troppo grande) ti tiene bloccato nella Write, mette i pezzi di stringa che ci stanno nell'array buffer ficnhè non li ha messi tutti nel buffer. poi "ti lascia andare"

Si, non ho fatto nessun controllo di errori..è ancora nello stato di bozza :cold_sweat:

Per quanto riguarda la stringa/buffer di ingresso sto cercando di capire cosa sia meglio, ad esempio se usare due buffer per i dati in uscita e in ingresso o uno solo. Il motivo per cui l'avevo scritta così (oltre al fatto che è la prima cosa che mi è venuta in mente :grin:) è che si hanno i dati in ingresso direttamente disponibili senza dover fare la copia di una stringa dall'apposito buffer. Però, oltre al problema che dici tu, si perde anche la stringa inviata.

Credo che poi la scelta sia da fare in base all'applicazione che ci si vuole costruire sopra... io ad esempio dovrei attaccarmi a dei modulini wireless che trasmettono (fino) a 2Mbps e hanno un buffer interno con solo 3 "spazi", quindi non vorrei perdere tempo tra le letture per evitare di perdermi dati. Non sono comunque affatto sicuro che quello che ho scritto possa funzionare per quello ^^