Go Down

Topic: Libreria TWI accorgimenti (Read 6917 times) previous topic - next topic

leo72

Può andare.

Potresti ottimizzare il test: a te serve sapere quando t è arrivato a zero, quindi deve essere falso per t>0.
Code: [Select]
while (!(TWCR & (1 << TWINT) && !t--) _delay_ms(1);

RobertoBochet

#16
Oct 13, 2014, 01:21 pm Last Edit: Oct 13, 2014, 01:40 pm by RobertoBochet Reason: 1
Perfetto, implementato, cosi dovremmo esserci. Grazie :)
Ci sono altre modifiche che farebbero bene alle funzionalità del codice? O ci siamo?
In ogni caso sto lavorando sulla storia del millis, è più complicata del previsto il grande problema è eseguire l'incremento nell'interrupt di overflow prima che se ne verifichi un altro, ma per questo direi che è il caso di aprire un altro topic.

Grazie mille Leo :)

Edit:
Ho notato che l'attesa dell'AVR è vicina al microsecondo quindi direi che l'attesa di di 15ms è un esagerazione pazzesca
Code: [Select]
bool TWIWait()
{
uint8_t t = 5;
while (!(TWCR & (1 << TWINT)) && !t--) _delay_us(1);
UARTTx8(t);
return TWCR & (1 << TWINT);
}

leo72

Quote
Ho notato che l'attesa dell'AVR è vicina al microsecondo quindi direi che l'attesa di di 15ms è un esagerazione pazzesca

dipende dalle esigenze, 5 o 10 ms non mi pare siano un grosso problema, stiamo infatti parlando di un timeout, cioè di un'uscita di emergenza che deve essere utilizzata solo nel caso che il dispositivo non risponda, se risponde non ti poni neanche il problema: avrai i dati in pochissimo tempo. ;)

RobertoBochet

Certo, è solo che il delay_ms di 1 mi sembrava esagerato tra un tentativo ed un altro, il timeout per quanto mi riguarda può essere anche di 1/10 di secondo. Ora sto lottando contro un altro problema, quelle che porta la dimensione del buffer fissa a 32 e che non possa essere modificato con un #define prima dell'inclusione come accade per la libreria delay.h

leo72

Perché non puoi modificarlo?
Puoi fare una catena di #define all'inizio del file e regolare il buffer in base al chip, così consumi la memoria proporzionalmente a quanta ne hai disponibile oppure, se vuoi un metodo dinamico, che possa cioè essere modificato in corso d'opera direttamente dallo sketch, devi obbligatoriamente farlo in un solo modo: includendo sia il file .h che il file .c in un unico file header. Le #define che da sketch modificano i parametri delle librerie funzionano solo con i file header.

RobertoBochet

Adesso ti spiego tutto, essendo una libreria mi trovo davanti a due progetti il primo la libreria TWI e il secondo il progetto dove viene usata la libreria(ovviamente ci sono altri progetti con librerie annesse). Quindi compilo la libreria TWI ma visto che richiamo una costante all'interno del file c questa viene sostituita con il valore della stessa, nota nel file c la funzione TWIInit che richiama BufferInit. Per tanto anche se nel progetto finale appongo un define su quella costante questa verra ignorata obbligando un buffer di dimensione fissa di 32. Capito? Ho cercato di essere il piu chiaro possibile ma se non si capisce chiedi pure.

Maurotec

Il problema e che @RobertoBochet precompila le librerie come faccio io e ho affrontato lo stesso problema senza mai risolverlo definitivamente. Non l'ho risolto perché alle volte la soluzione complica la scrittura del codice o meglio diventa fondamentale la documentazione.

Soluzioni possibili:
Crea una macro la quale crea un array di byte e poi passa il puntatore alla TWI, serve anche specificare la dimensione del buffer in quanto TWI è già compilata e sizeof funziona a tempo di compilazione.
Servirà quindi una funzione di inizializzazione TWI a cui passerai il puntatore buffer che hai creato globalmente e la dimensione in byte di questo.

Altra soluzione potrebbe essere l'uso di extern in TWI. extern è un collegamento non risolto in fase di compilazione ma in fase di link. Quindi dichiari il buffer della TWI extern e nell'header fornisci una macro che crea il buffer e chiama la funzione di inizializzazione.

Altra soluzione avanzata è quella di creare un oggetto c che gestisce vari tipi di buffer, durante l'inizializzazione della TWI specifichi il gestore e il tipo di buffer, o per semplicità il gestore si limita a gestire un ringbuffer o quello che serve. Questa soluzione anche se raffinata non lo trovo molto efficiente, la escluderei.

Tutte le soluzioni comunque necessitano di mantenere la documentazione, in mancanza della quale la tua TWI con buffer predefinito funziona sempre e senza precauzioni, quella flessibile no.

Per curiosità che ide usi?

Ciao.

RobertoBochet


Il problema e che @RobertoBochet precompila le librerie

Azzeccato in pieno.

Senza nessuna modifica in questo momento mi trovo con questa situazione
-Header
Code: [Select]

#ifndef TWI_H
#define TWI_H

#ifndef TWI_RX_BUFFER_SIZE//Se la dimensione del buffer di ricezione non è stata definita
#define TWI_RX_BUFFER_SIZE 32//Dimensione del buffer software di ricezione del TWI
#endif

uint8_t _TWIRxBufferArray[TWI_RX_BUFFER_SIZE];//Array per il buffer di ricezione

Buffer TWIRxBuffer;//Buffer di ricezione

void TWIInit(uint8_t, TWIPrescaler);//(TWBR, Prescaler)//Iniziazlizza la connessione TWI
#endif

-C file
Code: [Select]
void TWIInit(uint8_t twbr, TWIPrescaler p)
{
TWIError = 0;//Imposto la variabile di segnalazione errori a 0

BufferInit(&TWIRxBuffer, _TWIRxBufferArray, TWI_RX_BUFFER_SIZE);//Inizializzo il buffer di ricezione

TWBR = twbr;//Imposto il valore per la generazione dell'onda di clock su SCL
TWSR = p;//Imposto il prescaler
}

Ovviamente si capisce come questo sistema non permetta la modifica della dimensione. Solo che non riesco a capire i tuoi sistemi.

Io avevo provato a definire una variabile
Code: [Select]
_TWIRxBufferSize = TWI_RX_BUFFER_SIZE;
nell'header file ma non mi veniva accettata l'inizializzazione... se mi potete aiutare con una soluzione abbastanza ortodossa mi fareste un favore immenso visto che questo problema non è limitato a questa sola libreria

Maurotec

Buffer TWIRxBuffer;
Il tipo Buffer cosa è?
Classe, typedef di char, uint ecc.

Se Buffer è una classe allora puoi creare istanza come variabile globale nel programma eseguibile, e passi il puntatore alla classe TWI che sa come si usa il tipo Buffer.

Se vuoi invece fare una prova con extern, scrivi:
extern uint8_t _TWIRxBufferArray[];
extern uint8_t _TWIRxBufferSize;

La libreria TWI per compilata correttamente ma per poter funzionare nella fase di link devi fornire i nomi non risolti, quindi dovrai:

Code: [Select]

uint8_t  _TWIRxBufferSize = 64;
uint8_t _TWIRxBufferArray[_TWIRxBufferSize];

int main() {

}


Se ci si dimentica di dichiarare quei nomi di variabile la TWI non potrà funzionare e si verificherà un errore nella fase del link. Per evitare di dovere dichiarare il nome e il tipo nell'header della TWI puoi creare una macro tipo:
Code: [Select]

#define TWI_CREATE_RXBUFFER(size) uint8_t  _TWIRxBufferSize = size;\
     uint8_t _TWIRxBufferArray[size]


Quindi prima del main() invochi la macro:
TWI_CREATE_RXBUFFER(64);
la quale si espande in:
uint8_t  _TWIRxBufferSize = 64;
uint8_t _TWIRxBufferArray[64];

Quote
se mi potete aiutare con una soluzione abbastanza ortodossa mi fareste un favore immenso visto che questo problema non è limitato a questa sola libreria

Stai affrontando un problema comune che si presenterà sempre specie con microcontroller di ridotte risorse, diversamente si usa creare tutto con malloc che invoca l'allocazione dinamica della memoria.

C'è in giro un libreria per la gestione di stack, ringbuffer, linked list, ecc specificamente pensata per microcontroller, il nome non posso ricordarmelo (troppo tempo è passato), se salta fuori ti posto il link, comunque ricordo che non era immediato usarla e mi ero riproposta di studiarla nel dettaglio, ma poi ....

Ciao.


RobertoBochet

#24
Oct 14, 2014, 09:10 pm Last Edit: Oct 14, 2014, 09:16 pm by RobertoBochet Reason: 1
Direi che conviene inziare facendvi vedere la libreria che gestisce il buffer
Code: [Select]
#ifndef BOOL
#define BOOL
typedef enum { false, true } bool;
#endif

#ifndef BUFFER_H
#define BUFFER_H

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

typedef struct
{
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
} Buffer;

bool BufferInit(Buffer*, uint8_t*, uint8_t);//Inizializza il buffer e restituisce true se l'operazione è riuscita

bool BufferIsEmpty(Buffer*);//Verifica che il buffer sia vuoto
bool BufferIsFull(Buffer*);//Verfica che il buffer sia pieno
uint8_t BufferCount(Buffer*);//Restituisce il numero di elementi presenti nel buffer

bool BufferPush(Buffer*, uint8_t);//Inserisce un byte nel buffer
uint8_t BufferPull(Buffer*);//Preleva e restituisce il primo byte inserito (FIFO)
uint8_t BufferPop(Buffer*);//preleva e restituisce l'ultimo byte inserito (LIFO)

void BufferFlush(Buffer*);//Elimina tutti gli elementi nel buffer

#endif

Code: [Select]
#include "Buffer.h"

uint8_t BufferInit(Buffer* buffer, uint8_t* array, uint8_t size)
{
if(buffer == NULL || array == NULL || size <= 0) return false;//Se buffer o array puntano a NULL oppure la dimensione dell'array è minore o uguale a 0 restituisco false

buffer->size = size;//Imposto la varibile che indica le dimensioni del buffer
buffer->array = buffer->firstSpace = array;//Imposto il puntatore all'array e al primo spazio libero
buffer->firstElement = NULL;//Imposto il puntatore al primo elemento a NULL

return true;//Restituisco true
}

bool BufferIsEmpty(Buffer* buffer)
{
return buffer->firstElement == NULL;//Se il puntatore al primo elemento punta a NULL segno che il buffer è vuoto restituisco true
}
bool BufferIsFull(Buffer* buffer)
{
return buffer->firstElement == buffer->firstSpace;//Se i puntatori del primo elemento e primo spazio libero puntano alla stessa cella segno che il buffer è pieno restituisco true
}
uint8_t BufferCount(Buffer* buffer)
{
if(BufferIsEmpty(buffer)) return 0;//Se il buffer è vuoto restituisco 0
if(BufferIsFull(buffer)) return buffer->size;//Se il buffer è pieno restituisco la dimensione del buffer

if(buffer->firstElement < buffer->firstSpace) return buffer->firstSpace - buffer->firstElement;//Se il puntatore al primo elemento si trova prima del primo spazio libero restituisco la differenza tra i due
else return buffer->size - (buffer->firstElement - buffer->firstSpace);//Se il puntatore al primo elemento di trova dopo il primo spazio libero restituisco la dimensione del buffer meno la differenza tra il puntatore al primo elemento e il primo spazio libero
}

bool BufferPush(Buffer* buffer, uint8_t val)
{
if (BufferIsFull(buffer)) return false;//Se il buffer è pieno restituisco false

if (BufferIsEmpty(buffer)) buffer->firstElement = buffer->firstSpace;//Se il buffer è vuoto imposto il puntatore al primo elemento al primo spazio libero
*buffer->firstSpace = val;//Imposto il valore nel primo spazio disponibile nel buffer
buffer->firstSpace++;//Sposto il puntatore una cella in avanti
if (buffer->array + buffer->size == buffer->firstSpace) buffer->firstSpace = buffer->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
}
uint8_t BufferPull(Buffer* buffer)
{
uint8_t val;
if (BufferIsEmpty(buffer)) return 0;//Se il buffer è vuoto restituisco 0

val = *buffer->firstElement;//Copio il valore nella variabile che verrà restituita
buffer->firstElement++;//Sposto il puntatore una cella in avanti
if (buffer->array + buffer->size == buffer->firstElement) buffer->firstElement = buffer->array;//Se il puntatore sconfina in una zona al di fuori del buffer lo si reimposta alla prima cella allocata
if (buffer->firstElement == buffer->firstSpace) buffer->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 val;//Restituisco il valore
}
uint8_t BufferPop(Buffer* buffer)
{
if (BufferIsEmpty(buffer)) return 0;//Se il buffer è vuoto restituisco 0

if (buffer->firstSpace == buffer->array) buffer->firstSpace += buffer->size - 1;//Se il puntatore al primo spazio libero punta alla prima cella allocata allora lo sposto all'ultima cella allocata
else buffer->firstSpace--;//In caso contrario sposto il puntatore al primo spazio libero indietro di una cella
if (buffer->firstElement == buffer->firstSpace) buffer->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 *buffer->firstSpace;//Restituisco il valore
}

void BufferFlush(Buffer* buffer)
{
buffer->firstElement = NULL;//Reimposto il puntatore al primo elemento a NULL
buffer->firstSpace = buffer->array;//Reimposto il puntatore al primo spazio libero al primo elemento dell'array
}

Tutto puro C niente C++ o allocazione dinamica della memoria, da quanto mi è stato detto sul forum l'allocazione dinamica va fatta con consapevolezza e controlli su AVR.

Adattare la libreria in modo che sia possibile una procedura stile delay.h ti sembra fattibile? Obbligare ad utilizzare metodi fuori dalla libreria TWI mi pare molto piu complicato di quello che vorrei creare... Fare in modo che la funzione che inizializza il buffer sia contenuta nell'header e non nel file C? Possibile?
Non vorrei creare il buffer fuori dalla libreria TWI.

leo72


Tutto puro C niente C++ o allocazione dinamica della memoria, da quanto mi è stato detto sul forum l'allocazione dinamica va fatta con consapevolezza e controlli su AVR.

Diciamo che se riesci a fare tutto in C è meglio, sia per quanto riguarda la compattezza del codice sia per quanto riguarda il consumo di risorse. Il C++ non è "cacca" ma se ti basta il C... tutto di guadagnato  ;)

RobertoBochet

Il C++ non è "cacca" ma se ti basta il C... tutto di guadagnato  ;)

Assolutamente, se C++ mi portasse a una pari efficienza di codice al C lo sceglierei indubbiamente, mi pare che sia stato discusso in abbondanza il fatto che C sia da preferire al C++ in ambiente AVR.

Maurotec

Quote
Adattare la libreria in modo che sia possibile una procedura stile delay.h ti sembra fattibile?

Dedica un poco del tuo tempo a ragionare su come funziona il compilatore C.
La dichiarazione di un array riserva spazio in ram a tempo di compilazione, per questo motivo la dimensione deve essere conosciuta a tempo di compilazione. Quindi non è fattibile qualcosa che funzioni simile al delay.

L'unico modo è creare il buffer della dimensione adatta in base al programma che stai sviluppando, non c'è obbligo di dichiarare il buffer dentro il file che contiene la funzione main(), quindi può benissimo essere dichiarata dentro un modulo del programma.  Ti puoi inventare delle soluzioni che facciano sembrare la cosa meno strana o magari nascondendola come fa la macro, ma non puoi pretendere la staticità e dinamicità contemporaneamente.

Se usassi malloc creeresti il buffer di dimensioni preferite da dentro una funzione della libreria TWI.

Se preferisci ti puoi inventare una libreria separata dove crei un buffer di dimensioni stabilite con define come argomento del compilatore, di questa libreria ne compili 3 o più versioni per ogni versione cambi la define, comunque dovrai sempre ricorrere a extern. 

Però eviti di dover creare il buffer come variabile globale nel corpo del programma, ma ti complichi la vita con le librerie. Sviluppare per microcontroller comporta spesso a cedere a pratiche non considerate come buona programmazione, insomma spesso devi scegliere quello che ti fa schifo se questo è l'unico modo possibile, magari ti sforzerai di renderlo meno schifoso, più comprensibile (curando la documentazione), più semplice da usare (fornendo delle macro pronte che ti evitano di andare a vedere il tipo richiesto). In questo caso non c'è soluzione, la dimensione del buffer creato in una libreria precompilata non può essere modificata in nessun modo, quindi usa quello che ti fa schifo e cerca di renderlo digeribile.

Ciao.


RobertoBochet

Capisco. Quindi a questo punto derei che conviene non compilare la libreria e ogni volta fare copia incolla e ridefinire la costante, direi che non é una soluzione cosi terribile, ma come dici tu non si puo avere dinamicità con staticità, grazie mille per tutte le delucidazioni, siete stati entrambi molto utili
:-)

Maurotec

Esattamente, più precisamente puoi fornire al compilatore un argomento che equivale ad una #define.
Quindi userai #ifndef BUFFER_SIZE #define ecc, se durante la compilazione passi -DBUFFER_SIZE=64 compili con buffer di 64 byte e la ifndef nel codice viene ignorata.

Arduino usa questo espediente per definire F_CPU.
Se sceglie questa soluzione non ti dimenticare di documentarla a lettere cubitali, perché dopo mesi potresti scordarti il nome della define.

PS: predico bene e razzolo male.

Ciao.

Go Up