Subroutine interrota e precauzioni per interrupt

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.

La calloc la vedo piuttosto necessaria, se no come dici tu torno al C, e cosi perderei la dinamicità dell'array.

Volendo puoi anche fare una classe che si limita a fornire un'interfaccia ma non crea niente al suo interno. Dichiari l'array del buffer all'esterno globale così lo usi all'interno della classe evitandoti la calloc().

Per interfaccia intendi una classe a cui si possa passare una matrice e questa fornisca i metodi per gestirla? scusa ma derivo dal C# dove il significato di interfaccia è tutta un altra cosa.
Si potrebbe essere una buona idea :slight_smile: Mi dispiace però rinunciare all'heap sugli AVR, non ho ancora capito bene come mai l'heap va a pesare cosi tanto rispetto allo stack, mi potresti illuminare yoshi, mi faresti un enorme favore.

Grazie

PS ho modificato il codice della coda per riadattarlo per bene alla funzione di buffer migliorando il codice, ora lo aggiorno

L'allocazione dinamica della memoria sui microcontrollori è sconsigliata perché ti rende l'eseguibile più grosso e quindi in grossi progetti hai meno linee di codice disponibili ma soprattutto perché ti fa perdere il controllo della ram occupata se non gestita bene. Se hai un programma che alloca dinamicamente oggetti potrebbe succedere per una tua svista l'heap cresca troppo andando a collidere contro lo stack. La differenza tra stack ed heap è che il primo ha un'implementazione hardware cioè ha un registro che contiene gli indirizzi di ritorno delle chiamate a subroutine mentre heap no.
Tutto ciò ovviamente è condizionato da come il compilatore gestisce le risorse, lo stack ad esempio può essere implementato via software andando quindi in ram e causando possibili collisioni con l'heap.
Tutto ciò ovviamente non ti vieta di usare malloc,new ecc. se vuoi e se il compilatore lo consente solo che in futuro potrebbe essere necessario passare a qualche trucco statico per evitare problemi o salvare un po' di spazio quindi chi ben comincia è già a metà dell'opera.

P.S. A differenza del C# il C++ non è completamente a classi quindi non devi necessariamente creare una classe per qualsiasi cosa (infatti il main è una funzione e non un metodo).

P.P.S. Con interfaccia intendo l'insieme di metodi pubblici che ha una classe. Il solo file header definisce un'interfaccia.

Se parliamo di Atmel, lo stack è gestito tutto in RAM, come l'heap. Non ha una implementazione hardware.
Quando serve, il codice vi riversa il contenuto dei registri e da lì dentro li recupera. Sta all'utente rispettare la struttura LIFO dello stack per cui se metti nello stack A,B,C devi poi tirar fuori C,B,A, pena l'incasinamento di tutto. Il salvataggio e recupero del PC, Program Counter, cioè l'indirizzo della prossima istruzione che deve essere useguita dalla CPU, viene invece gestitoin automatico dal compilatore.

Quello che dice Yoshi è vero, cioè che con l'allocazione dinamica della memoria si rischia una bella collisione fra heap e stack: l'heap è posto dopo lo spazio riservato alle variabili statiche inizializzate ed a quelle non inizializzate e cresce verso l'alto mentre lo stack è posto a partire dall'ultima locazione della RAM e cresce verso il basso. Se non si sta attenti, si rischia che l'heap o lo stack o entrambi crescano più del previsto e si accavallino con risultati disastrosi per il codice.

Ok ok ho capito, quindi non ho vere e proprie limitazioni, semplicemente devo scendere a compromessi per una questione di spazio, ok questa è una buona notizia.
Ti ho chiesto dell'interfaccia, perché dal tuo post sembrava che intendessi per il temine interfaccia una classe completa che avesse l'unica differenza di non allocare variabili dinamiche al suo interno.
Non mi sento obbligato ad usare classi in C++ ma ho scelto di spostarmi ad esso, dal C puro, esclusivamente per avere il supporto di queste, come dicevi tu rende il codice più di classe :wink: anche se la classe da quello che sto capendo sui microcontrollori perde MOLTA importanza.

Edit: Ma il micro non rileva queste "collisioni"?

No, non le rileva. E' una gestione lasciata completamente all'utente.
Ecco perché si sconsiglia sempre di fare allocazione dinamica sulle MCU, sia per il sovraccarico sul codice che il compilatore e/o il programmatore deve inserire per gestire questa cosa sia per i ristretti quantitativi di RAM che possono portare a incidenti di questo tipo.

leo72:
Se parliamo di Atmel, lo stack è gestito tutto in RAM, come l'heap. Non ha una implementazione hardware.

@leo72
Niente stack hardware per atmel? Grazie dell'informazione :).

Sì, le classi se ci sono le usi per lo più come layout del codice e non per molto altro, ovviamente parlo sempre di MCU ad 8/16 bit.

yoshi93:
@leo72
Niente stack hardware per atmel? Grazie dell'informazione :).

Per lo meno per gli AVR8.
Tant'è che ad esempio l'Atmega2560, che è uno di quei pochi chip Atmel che può gestire anche RAM esterna, può spostare lo stack sulla memoria addizionale.