Go Down

Topic: Subroutine interrota e precauzioni per interrupt (Read 7611 times) previous topic - next topic

RobertoBochet

Salve, sto sviluppando una libreria in C++ per l'utilizzo dell'UART sul Atmega328p. In pratica in trasmissione inserisco in buffer da 32 byte i dati da trasmettere e per non bloccare l'esecuzione aspettando che vengano inviati uno ad uno i dati nel buffer "sbologno" la mansione ad un intterupt con vettore USART_TX_vect con la seguente funzione
Code: [Select]

uint8_t data;//Alloco un byte di memoria per il dato da trasmettere
if(UART::txBufferIndex != 0)//Verifico che nel buffer di trasmissione vi siano dei dati
{
data = UART::rxBuffer[0];//Salvo il dato da trasmettere
for(uint8_t i = 1; i < UART::rxBufferIndex; i++)//Creo un ciclo per spostare di una posizione indietro i dati nel buffer di trasmissione
{
UART::rxBuffer[i-1] = UART::rxBuffer[i];//Sposto il dato corrente indietro di uno nel buffer
}
UART::txBufferIndex--;//Decremento la posizione del primo byte disponibile nel buffer di trasmissione
UDR0 = data;//Inserisco il valore ad 8 bit nel buffer di trasmissione
}

Come potete notare, ogni volta che viene completata la trasmissione di un byte tutti i dati nel buffer vengono spostati di uno verso il primo elemento, (Avrei preferito una lista scritta per la gestione FIFO ma su avr non ho nemmeno idea se si possano usare le malloc, anche se ne dubito) alla fine dello spostamento il primo dato prelevato viene posizionato nel buffer di invio. Fino qui mi sembra di aver scritto un codice che non vada incontro a nessuna problematica in fase di esecuzione.
Passando alla funzione che immette i byte nel buffer mi sorge un dubbio, ma prima di esplicarvelo posto il codice problematico
Code: [Select]

UART::txBuffer[UART::txBufferIndex] = data;
UART::txBufferIndex++;

Qui inserisco il dato che mi viene passato da trasmettere, nell primo posto disponibile del buffer e poi incremento il valore che mi indica quale sia l'ultimo elemento nel buffer; ora mi sorge un dubbio, se per caso l'interrupt si verificasse  tra le due operazioni, il dato immesso nel buffer verrebbe perso, in quanto non verrebbe spostato in avanti secondo la logica FIFO, per tanto mi chiedo se questo effettivamente si potrebbe verificare, in tal caso come posso ovviare se non spostando in avanti tutti e 31 gli elementi del buffer?Esiste per caso una sintassi che dice all'avr di eseguire una piccola porzione di codice senza interruzioni?

Grazie in anticipo

leo72

Lo sai tu che la trasmissione seriale, a partire dalla versione 1.0 dell'IDE, è stata implementata tutta con interrupt per cui l'IDE fa già da 1 anno e più quel che tu stai realizzando ora?
Ma magari non ho capito io perché ti vuoi riscrivere una lib tua per gestire la seriale... stai forse programmando in C++ puro senza usare l'ambiente di Arduino?

Tornando al tuo quesito,
Quote
Esiste per caso una sintassi che dice all'avr di eseguire una piccola porzione di codice senza interruzioni?

Sì certo che esiste.
Basta abilitare/disabilitare il flag nel registro di sistema che attiva/disattiva la gestione degli interrupt a livello globale.
In Arduino puoi usare le macro cli() e sei():

cli sta per clear interrupt, e disattiva il suddetto flag
sei sta per set interrupt e riattiva il flag

Non mi ricordo se sono macro predefinite nell'ambiente Arduino o sono della toolchain Atmel. In ogni caso l'operazione è facilmente replicabile scrivendo nel registro.

SREG &= ~(1<<SREG_I)  --> disattiva il flag (equivale a "cli")
SREG |= (1<<SREG_I) --< attiva il flag (equivale a "sei")

Quando disattivi il flag SRGE_I dici al microcontrollore di ignorare tutti gli interrupt.
Per "tutti" intendo tutti, quindi non solo quelli della seriale  ma anche quelli dei timer.

Se invece non vuoi gestire solo quelli della seriale, basta agire sui flag della seriale, i bit del registro UCSR0B. Datasheet pag 195.

Ah, tutto questo discorso vale se le 2 istruzioni che hai riportato:
Code: [Select]
UART::txBuffer[UART::txBufferIndex] = data;
UART::txBufferIndex++;

sono all'esterno di una ISR. Se sei all'interno, non ti devi neanche porre il problema perché di default tutte le ISR sono atomiche, ossia disattivano gli interrupt ad inizio ISR e poi li riattivano a fine ISR, in modo che nessuna ISR possa essere interrotta da un'altra ISR.

RobertoBochet

Grazie mille leo, risposta precisa e completa :)
Io lavoro con Atmel studio 6.2, due arduino UNO(uno come isp programmar e l'altro senza bootloader da programmare) e scrivo codice in C++ (sono arrivato dal C per poter usufruire del paradigma OOP) quindi cerco di ripiegare il meno possibile su librerie di terze parti, poi in questo modo sono sicuro di capire il funzionamento dell'HW(Il processing unito alle decine di librerie rendono il processo di sviluppo estremamente semplice, ma un po' poco trasparente per quello che cerco io).
La scelta di disattivare gli interrupt globali mi era venuta in mente ma non volevo ostacolare le altre periferiche o perdermi dati nel buffer di ricezione (anche quest ultimo lo gestisco via interrupt) quindi l'avevo esclusa. Mhh optero decisamente per la disabilitazione del singolo interrupt.
Grazie ancora :)

RobertoBochet

Vorrei porre un altra domanda, dettata da un po di incomprensione nel funzionamento del buffer fisico.
Nel mio codice intercetto gli interrupt  sui vettori USART_TX_vect e USART_RX_vect ma guardando sul datasheet noto che la trasmissione viene scandita da UDRE, Quale è meglio usare? che effettive differenze ho nei due utilizzi?

leo72

Il flag UDRE0 viene settato quando la trasmissione è terminata, mentre l'interrupt viene sollevato quando viene terminato l'invio dei dati. Dipende dalle necessità e come implementi la cosa.
Leggo sul datasheet che viene anche usato il settaggio manuale di UDRE0 per sollevare un interrupt, quindi l'uso è polivalente.

yoshi93

Comunque a parte il discorso su interrupt vari io ti consiglierei di implementare un buffer circolare così non perdi tempo a spostare tutti gli elementi di un posto.

RobertoBochet

#6
Sep 17, 2014, 07:56 pm Last Edit: Sep 17, 2014, 08:01 pm by RobertoBochet Reason: 1
Guarda più che un buffer circolare avrei implementato un buffer FIFO ma entrambe le soluzioni richiedono piu byte di un semplice array monodimensionale
-Buffer circolare => n*2+2 (ogni elemento occupa 1 byte per il dato e 1 byte per il puntatore all'elemento successivo piu i due puntatori al primo ed ultimo elemento)
-Buffer FIFO => n*2+1 (ogni elemento occupa 1 byte per il dato e 1 byte per il puntatore all'elemento successivo piu un puntatore al primo elemento)
-Array => N (il numero di elementi definiti in partenza per il valore di un byte ciascuno)

Poi come dicevo in precedenza non ho esperienza di allocazione della memoria dinamica negli AVR.

Edit:
Scusate ma cosa succede se mentre sto inviando un dato ne viene ricevuto uno, visto che il registro è solo uno

yoshi93

#7
Sep 17, 2014, 08:05 pm Last Edit: Sep 17, 2014, 08:08 pm by yoshi93 Reason: 1
http://en.wikipedia.org/wiki/Circular_buffer

Guarda la parte del codice, non mi sembra che usi 2n+2 elementi per l'implementazione, al massimo n+2 elementi e un po' di condizioni per verificare se è pieno/ha posto.

P.S. Ovviamente nel tuo caso non devi usare calloc ma usare un array fisso di 32 elementi.

RobertoBochet

è un implementazione in effetti che utilizza solo 5+n+1 byte si potrebbe anche adoperare.

leo72

Il buffer circolare usa 2 buffer di n elementi. Nel 1° ci stanno le posizioni , nel 2° i dati.
Il buffer che tiene le posizioni è di tipo incrementale, per cui non ti serve avere un'ulteriore cella che conserva l'ultima posizione perché ti basta 1 variabile da tenere in RAM, che carichi all'inizio con il valore dell'ultima cella usata nel buffer dei dati. Questo valore si trova facilmente scorrendo il buffer delle posizioni: nel momento in cui la cella n+1 contiene un valore inferiore alla cella n hai trovato dove il buffer si sovrascrive, ossia la posizione dell'ultima cella dei dati salvata.

Esempio:
buffer dei dati
23
9
1234
4
..
..

buffer delle posizioni
0
1
2
3  <---
0
0
0
....

Quando arrivi a confrontare la 4a cella, che contiene 3, con la 5a cella, che contiene zero, vedi che n è maggiore di n+1 per cui hai trovato la posizione dell'ultimo valore scritto nel buffer dei dati.

Però l'uso di un buffer circolare per un buffer seriale mi sembra uno spreco di risorse. Si usa per memorizzare dati in memorie non volatili come le EEPROM e per ricaricarli al riavvio in caso di blackout o cose del genere. Su un circuito che sta in ricezione sulla seriale è uno spreco di risorse, devi tenere il doppio della RAM occupata.

yoshi93

Usare due array di n elementi è un po' esagerato, ne basta uno e poi un po' di variabili di stato. La seriale di arduino usa un buffer circolare mi sembra, la usai come esempio di utilizzo di tale metodo per la mia libreria seriale per pic.

RobertoBochet

Io lo farei con un puntatore alla testa, il numero di elementi presenti e ogni elemento composto da valore e puntatore all'elemento successivo. Ognuno ha il suo metodo XD
Per quello (lo ripeto) avrei optato per un buffer FIFO.

leo72


La seriale di arduino usa un buffer circolare mi sembra, la usai come esempio di utilizzo di tale metodo per la mia libreria seriale per pic.


Hai ragione tu, il ring buffer o buffer circolare prevede l'uso di un unico buffer. Io mi confondevo con il doppio buffer circolare usato ad esempio per il salvataggio dei dati in EEPROM per risparmiare le scritture. Sorry  :smiley-sweat: :smiley-sweat:

RobertoBochet

Ottimo quindi vada per buffer circolare. Ma scusate, è un problema che non mi sono mai posto, ma gli AVR hanno un heap? O pure tutti i dati vanno sullo stack?(Credo di avere già una risposta parlando di C++ ma vorrei una conferma)

yoshi93

#14
Sep 19, 2014, 10:38 pm Last Edit: Sep 19, 2014, 10:40 pm by yoshi93 Reason: 1
Dipende dal compilatore e quale AVR usi cioè se quelli che hanno fatto il compilatore hanno implementato delle routine di allocazione dei registri. In genere no perché sarebbe uno spreco di risorse ma volendo si può fare. Alla fine tu hai delle pagine di memoria su cui puoi farci di tutto e di più e in genere uno stack hardware di n livelli (sui pic della serie 18 mi sembra che fossero trentadue anche se adesso potrei confondermi mentre sui pic16 era di 8) quindi se hai voglia di occupare tutta la ram del micro con una routine che ti permetta di usare un heap sei liberissimo di farlo  :P. Il linguaggio di programmazione non c'entra nulla, dipende se hai oppure no un sistema software che controlla la cosa (su un pc è il sistema operativo).

Go Up