Subroutine interrota e precauzioni per interrupt

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

	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

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

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,

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:

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.

Grazie mille leo, risposta precisa e completa :slight_smile:
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 :slight_smile:

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?

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.

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.

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 => n2+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

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.

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

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.

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.

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.

yoshi93:
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 :sweat_smile: :sweat_smile:

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)

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).

Quindi mi devo scordare lo new? o quest'ultimo posizionerà i dati sullo stack?

Il compilatore avr-gcc costruisce un heap e ci memorizza tutte le variabili dinamiche.
http://www.nongnu.org/avr-libc/user-manual/malloc.html
http://www.nongnu.org/avr-libc/user-manual/mem_sections.html

leo72:
Il compilatore avr-gcc costruisce un heap e ci memorizza tutte le variabili dinamiche.

Aggiungendo un po' di overhead al tuo programma quindi in genere,se puoi farne a meno,è meglio.

Quindi un allocazione dinamica dei dati del buffer non è consigliabile?

Edit:
Intanto che attendo risposta ho provato a cimentarmi nella creazione del buffer, mi sono discostato leggermente dalla composizione segnalata da wikipedia
Header

#ifndef Buffer_h
#define Buffer_h

class Buffer
{
protected:
	uint8_t size;//Dimensione in byte del buffer
	uint8_t* array;//Puntatore alla prima cella di memoria allocata
	uint8_t* firstElement;//Puntatore al primo elemento inserito
	uint8_t* firstSpace;//Puntatore al primo spazio libero
	
public:
	Buffer(uint8_t size);
	~Buffer();

	bool IsEmpty();//Verifica che il buffer sia vuoto
	bool IsFull();//Verfica che il buffer sia pieno

	bool Push(uint8_t value);//Inserisce un byte nel buffer
	uint8_t Pull();//Preleva e restituisce il primo byte inserito
	uint8_t Pop();//preleva e restituisce l'ultimo byte inserito
};

#endif

C++

#include <avr/io.h>
#include <stdlib.h>
#include "Buffer.h"

Buffer::Buffer(uint8_t size)
{
	this->size = size;//Imposto la varibile protetta che indica le dimensioni del buffer
	this->array = this->firstElement = this->firstSpace = NULL;//Inizializzo i puntatori a NULL
	if(this->size > 0)//Verifico che la dimensione sia maggiore di 0
	{
		this->array = (uint8_t*) calloc(this->size, sizeof(uint8_t));//Alloco ed inizializzo le celle necessarie ad archiviare i dati
		if(this->array != NULL)//Verifico che l'allocazione sia riuscita
		{
			this->firstSpace = this->array;//Imposto il puntatore al primo spazio libero alla prima cella alllocata
		}
	}
}

Buffer::~Buffer()
{
	free(this->array);//Libero la memoria allocata per contenere i dati del buffer
}

bool Buffer::IsEmpty()
{
	return (this->firstElement == NULL);//Se il puntatore al primo elemento punta a NULL segno che il buffer è vuoto restituisco true
}

bool Buffer::IsFull()
{
	return (this->firstElement == this->firstSpace);//Se i puntatori del primo elemento e primo spazio libero puntano alla stessa cella segno che il buffer è pieno restituisco true
}

bool Buffer::Push(uint8_t value)
{
	if(!this->IsFull())//Controllo che il buffer non sia pieno
	{
		*this->firstSpace = value;//Imposto il valore nel primo spazio disponibile nel buffer
		this->firstSpace++;//Sposto il puntatore una cella in avanti
		if(this->array + this->size == this->firstSpace) this->firstSpace = this->array;//Se il puntatore sconfina in una zona al di fuori del buffer lo si reimposta alla prima cella allocata
		return true;//Restituisco true se il valore è stato inserito correttamente
	}
	else return false;//Restituisco false se lo spazio nel buffer è finito
}

uint8_t Buffer::Pull()
{
	uint8_t value;
	if(!this->IsEmpty())//Crontollo che il buffer non sia vuoto
	{
		value = *this->firstElement;//Copio il valore nella variabile che verrà restituita
		this->firstElement++;//Sposto il puntatore una cella in avanti
		if(this->array + this->size == this->firstElement) this->firstElement = this->array;//Se il puntatore sconfina in una zona al di fuori del buffer lo si reimposta alla prima cella allocata
		if(this->firstElement == this->firstSpace) this->firstElement = NULL;//Se i due puntatori finisco a puntare alla stessa cella come se il buffer fosse pieno reimposto il puntatore al primo elemento a null
		return value;//Restituisco il valore
	}
	else return 0;//Restituisco 0 nel caso il buffer sia vuoto
}

uint8_t Buffer::Pop()
{
	uint8_t value;
	if(!this->IsEmpty())//Crontollo che il buffer non sia vuoto
	{
		if(this->firstSpace == this->array) this->firstSpace += this->size-1;//Se il puntatore al primo spazio libero punta alla prima cella allocata allora lo sposto all'ultima cella allocata
		else this->firstSpace--;//In caso contrario sposto il puntatore al primo spazio libero indietro di una cella
		if(this->firstElement == this->firstSpace) this->firstElement = NULL;//Se i due puntatori finisco a puntare alla stessa cella come se il buffer fosse pieno reimposto il puntatore al primo elemento a null
		return *this->firstSpace;//Restituisco il valore
	}
	else return 0;//Restituisco 0 nel caso il buffer sia vuoto
	
}

Fatta al volo ma se non mi bocciate il calloc dovrebbe andare.

Ps Il compilatore mi ha dato particolarmente noie sull'utilizzo di nullptr

Personalmente sui pic avevo implementato il tutto nella vecchia maniera stile C (niente classi,niente malloc/free e solo una funzione da richiamare all'interno dell' ISR). Con le classi viene fuori una cosa bella pesantuccia però è molto più di 'classe' (capita la battuta?). Ormai che hai iniziato continua così, magari ti tornerà utile dopo per implementare una seriale software e per ridurre la complessità riscrivendola in C c'è sempre tempo.
Il nullptr è una cosa piuttosto recente perché a differenza di NULL è proprio un tipo di dato quindi alcuni compilatori magari non lo riconoscono.