Go Down

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

RobertoBochet

Buonasera a tutti voi, tempo fa avevo scritto qualche post in cui accennavo allo sviluppo di una libreria TWI ve lo ricordate? Beh ovvio che si XD In ogni caso sono arrivato ad una versione funzionante ma vorrei chiedere a voi se vi sono accorgimenti che potrei prendere per migliorare l'affidabilità e versatilità della libreria, intano inizio postando il codice.
Code: [Select]
#ifndef BOOL
#define BOOL
typedef enum { false, true } bool;
#endif

#ifndef TWI_H
#define TWI_H

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

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

//Error//
#define TWI_ERROR_RX_BUFFER_NO_SPACE 1
#define TWI_ERROR_NO_START_SEND 2
#define TWI_ERROR_NO_SLAW_SEND_ACK_RECEIVE 3
#define TWI_ERROR_NO_DATA_SEND_ACK_RECEIVE 4
#define TWI_ERROR_NO_RESTART_SEND 5
#define TWI_ERROR_NO_SLAR_SEND_ACK_RECEIVE 6
#define TWI_ERROR_NO_DATA_RECEIVE_ACK_SEND 7
#define TWI_ERROR_NO_DATA_RECEIVE_NACK_SEND 8

typedef enum { p1, p4, p16, p64 } TWIPrescaler;//Valori di prescaler per la generazione del onda SCL
typedef enum { write, read } TWIOperation;//Operazioni per di trasmissione

uint8_t TWIRxBufferArray[TWIRxBufferSize];//Array per il buffer di ricezione

Buffer TWIRxBuffer;//Buffer di ricezione

uint8_t TWIError;//Variabile di segnalazione errori

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

bool TWIStart();//Invia il sergnale di start
bool TWIRestart();//Invia il segnale di restart
bool TWIContactSlave(uint8_t, TWIOperation);//(Indirizzo slave, Operazione)//Contatta lo slave con la modalità stabilita
bool TWIContactSlaveForWrite(uint8_t);//(Indirizzo slave)//Contatta lo slave in scrittura
bool TWIContactSlaveForRead(uint8_t);//(Indirizzo slave)//Contatta lo slave in lettura
bool TWISend(uint8_t);//(Dato da inviare)//Invia un byte
void TWIStop();//Invia il segnale di stop
void TWIWait();//Attende che il bus sia pronto

bool TWIRead(uint8_t, uint8_t, uint8_t);//(Indirizzo slave, Indirizzo registro, Numero celle da leggere)//Legge n byte da uno slave
bool TWIWrite(uint8_t, uint8_t, uint8_t);//(Indirizzo slave, Indirizzo registro, Valore)//Scrive un valore su un registro di uno slave

#endif

Ammetto che i commenti vadano riscritti da 0 per spiegare meglio il funzionamento. Pensavo di aggiungere un controllo di stato sulla TWI che impedisca per esempio di inviare una scelta di slave senza che sia stato eseguito uno start, puo servire? O sarebbe una verifica inutile trattandosi di linguaggio di basso livello? Se potete suggerirmi qualche aggiunta o modifica ve ne sarei grato.
Grazie e buona serata :)

PS se per l'improbabile caso vi mettiate ad analizzare il codice in dettaglio, non ho problemi a postare anche la buffer.h/c.

RobertoBochet

#1
Oct 08, 2014, 11:15 pm Last Edit: Oct 13, 2014, 01:52 am by RobertoBochet Reason: 1
Code: [Select]
#include "TWI.h"
#include <avr/io.h>
#include <util/delay.h>

void TWIInit(uint8_t twbr, TWIPrescaler p)
{
TWIError = 0;//Imposto la variabile di segnalazione errori a 0
BufferInit(&TWIRxBuffer, TWIRxBufferArray, TWIRxBufferSize);//Inizializzo il buffer di ricezione

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

bool TWIStart()
{
TWIError = 0;//Resetto la variabile di segnalazione errori
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);//Abilito il bus, resetto il bit l'interrupt e invio il segnale di start
if(!TWIWait()) return false;//Attendo che l'operazione sia stata completata
if((TWSR & 0xF8) != 0x08)//Verifico che l'operazione sia andata a buon fine
{
TWIStop();//Invio il segnale di stop sul bus
TWIError = TWI_ERROR_NO_START_SEND;//Modifico la variabile di segnalazione errori con il valore di errore per start non riuscito
return false;//Restituisco false per segnalare l'operazione non riuscita
}
return true;//Restituisco true per segnalare l'operazione riuscita
}
bool TWIRestart()
{
TWIError = 0;//Resetto la variabile di segnalazione errori
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);//Abilito il bus, resetto il bit l'interrupt e invio il segnale di restart
if(!TWIWait()) return false;//Attendo che l'operazione sia stata completata
if((TWSR & 0xF8) != 0x10)//Verifico che l'operazione sia andata a buon fine
{
TWIStop();//Invio il segnale di stop sul bus
TWIError = TWI_ERROR_NO_RESTART_SEND;//Modifico la variabile di segnalazione errori con il valore di errore per restart non riuscito
return false;//Restituisco false per segnalare l'operazione non riuscita
}
return true;//Restituisco true per segnalare l'operazione riuscita
}
bool TWIContactSlave(uint8_t slaveAddress, TWIOperation operation)
{
TWIError = 0;//Resetto la variabile di segnalazione errori
TWDR = (slaveAddress << 1) | operation;//Sposto l'indirizzo del device un bit a sinistra e aggiungo il bit di modalità
TWCR = (1 << TWINT) | (1 << TWEN);//Abilito il bus, e resetto il bit di interrupt
if(!TWIWait()) return false;//Attendo che l'operazione sia stata completata
if((TWSR & 0xF8) != (operation?0x40:0x18))//Verifico che l'operazione sia andata a buon fine
{
TWIStop();//Invio il segnale di stop sul bus
TWIError = operation?TWI_ERROR_NO_SLAR_SEND_ACK_RECEIVE:TWI_ERROR_NO_SLAW_SEND_ACK_RECEIVE;//Modifico la variabile di segnalazione errrori con il valore di errore di contatto device R/W
return false;//Restituisco false per segnalare l'operazione non riuscita
}
return true;//Restituisco true per segnalare l'operazione completata
}
bool TWIContactSlaveForWrite(uint8_t slaveAddress)
{
return TWIContactSlave(slaveAddress, write);//Contatto il device in scrittura
}
bool TWIContactSlaveForRead(uint8_t slaveAddress)
{
return TWIContactSlave(slaveAddress, read);//Contatto il device in lettura
}
bool TWISend(uint8_t data)
{
TWIError = 0;//Resetto la varibaile di segnalazione errori
TWDR = data;//Posiziono il dato da inviare nel registro dati del TWI
TWCR = (1 << TWINT) | (1 << TWEN);//Resetto il bit di interrupt e avvio il bus
if(!TWIWait()) return false;//Attendo che l'operazione sia stata completata
if((TWSR & 0xF8) != 0x28)//Verifico che l'operazione sia andata a buon fine
{
TWIStop();//Invio il segnale di stop sul bus
TWIError = TWI_ERROR_NO_DATA_SEND_ACK_RECEIVE;//Modifico la variabile di segnalazione errrori con il valore di errore di dato in invio
return false;//Restituisco false per segnalare l'operazione non riuscita
}
return true;//Restituisco true per segnalare l'operazione riuscita
}
bool TWIReceive(bool ack)
{
TWIError = 0;//Resetto la varibaile di segnalazione errori
TWCR = (1 << TWINT) | (1 << TWEN) | (ack?(1 << TWEA):0);//Resetto il bit di interrupt, abilito il bus e in caso invio l'ACK
if(!TWIWait()) return false;//Attendo che l'operazione sia stata completata
if((TWSR & 0xF8) != (ack?0x50:0x58))//Verifico che l'operazione sia andata a buon fine
{
TWIStop();//Invio il segnale di stop sul bus
TWIError = ack?TWI_ERROR_NO_DATA_RECEIVE_ACK_SEND:TWI_ERROR_NO_DATA_RECEIVE_NACK_SEND;//Modifico la variabile di segnalazione errrori con il valore di errore di dato in ricezione
return false;//Restituisco false per segnalare l'operazione non riuscita
}
BufferPush(&TWIRxBuffer, TWDR);//Inserisco il byte ricevuto nel buffer di ricezione
return true;//Restituisco false per segnalare l'operazione riuscita
}
void TWIStop()
{
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);//Reseto il bit di interrupt, abilito il bus e invio il segnale di stop
}
bool TWIWait()
{
uint8_t t = 15;
while (!(TWCR & (1 << TWINT)) && t-- != 0) _delay_ms(1);
return TWCR & (1 << TWINT);
}

bool TWIRead(uint8_t slaveAddress, uint8_t addressRegister, uint8_t number)
{
TWIError = 0;//Resetto la variabile di segnalazione errori
if(TWIRxBuffer.size - BufferCount(&TWIRxBuffer) < number)//Verifico che lo spazio disponibile nel buffer di ricezione sia insufficiente contenere i dati in arrivo
{
TWIError = TWI_ERROR_RX_BUFFER_NO_SPACE;//Modifico la variabile di segnalazione errori con il valore di errore per spazio insufficiente nel buffer di ricezione
return false;//Restituisco false per segnalare l'operazione fallita
}

if(!TWIStart()) return false;//Ivio il segnale di start
if(!TWIContactSlaveForWrite(slaveAddress)) return false;//Invio l'indirizzo dello slave con modalità scrittura
if(!TWISend(addressRegister)) return false;//Invio l'indirizzo del registro da cui iniziare la lettura
if(!TWIRestart()) return false;//Invio il segnale di restart
if(!TWIContactSlaveForRead(slaveAddress)) return false;//Invio l'indirizzo dello slave con modalità lettura
while(--number != 0) if(!TWIReceive(true)) return false;//Ricevo tutti i byte tranne l'ultimo e li posiziono nel buffer di ricezione
if(!TWIReceive(false)) return false;//Ricevo l'ultimo byte da leggere e lo posiziono nel buffer di ricezione
TWIStop();//Invio il segnale di stop

return true;//Restituisco true per segnalare la completa e corretta ricezione dei dati
}
bool TWIWrite(uint8_t slaveAddress, uint8_t addressRegister, uint8_t data)
{
if(!TWIStart()) return false;//Invio il segnale di start
if(!TWIContactSlaveForWrite(slaveAddress)) return false;//Invio l'indirizzo dello slave con modalità scrittura
if(!TWISend(addressRegister)) return false;//Invio l'indirizzo del registro in cui voglio scrivere
if(!TWISend(data)) return false;//Invio il byte da scrivere nel registro
TWIStop();//Invio il segnale di stop

return true;//Restituisco true per segnalare la completa e corretta scrittura del dato
}

leo72

Metti un timeout nei while in modo che se per qualche motivo la trasmissione/ricezione si interrompe il codice non rimanga bloccato.

RobertoBochet

Grazie leo, ti voglio chiedere però come implementarlo, a getto il primo timeout che penso si possa utilizzare sia dettato dal watchdog impostato ad interrupt ma non vorrei fare l'ovveride di impostazioni dettate dall'utente su un componente cosi importante.
Un uint8_t che si incrementa fino ad un tot nel ciclo while potrebbe andare?

leo72

Dato che nella maggior parte dei casi si tratta di leggere dei flag da dei registri per verificare che l'operazione in corso sia conclusa, basta un controllo basato sul tempo che scorre.
In questo caso puoi usare la millis e contare un certo lasso di tempo, trascorso il quale esci dal while. Usa una variabile di stato per controllare se sei uscito per timeout o regolarmente.

RobertoBochet

Lavoro fuori dal supporto Arduino (se non erro millis() è proprio una funzione legata ad Arduino) quindi dovrei crearmi la suddetta funzione. Vorrei una conferma, a ogni overflow di uno dei due timer a 8bit aumento un contatore di millisecondi tenendo conto del prescaler giusto? Basta un operazione cosi semplice? O bisogna tenere conto di casi particolari?

leo72

Dipende se vuoi usare quel timer anche per il segnale PWM oppure no (sull'Arduino il timer 0 serve sia millis che il PWM, e per questo motivo hanno dovuto adottare una soluzione un pò complicata per poter avere entrambe le cose).

RobertoBochet

Cioè andare ad intercettare usi che vadano a modificare la frequenza PWM? Come la CTC, PWM Phase
Correct e fast PWM con TOP variabile? (per intendersi modalità da datasheet 2, 5, 7)

leo72

Un timer puoi usarlo sia per generare interrupt che per generare segnali quadri sui pin.
se ti serve un timer solo come contamillisecondi, sei avvantaggiato perché la strada è più semplice.

Se apri i file del core di Arduino e vai a vedere cosa c'è dietro la gestione del timer 0 capisci a cosa mi riferisco  ;)
Lì hanno impostato il timer in modalità Fast PWM con un prescaler a 64 che genera un overflow ogni 1/976 secondi. Per "addirizzare" il tempo, hanno utilizzato un altro contatotre che conta 24 overflow in più, in modo che millis sia incrementato appunto 1000 volte al secondo. Poi c'è tutta la parte che riguarda l'aggancio e lo stacco dei pin quando si vuole usare anche il PWM. Insomma, tanto lavoro di codice generico perché l'Arduino deve affrontare tutti i casi. Su codici scritti da zero per le proprie necessità è ovvio che si tende ad ottimizzare e semplificare, quindi imposta il timer per le tue esigenze e lascia stare questi casi  ;)

RobertoBochet

Gia, ma visto che parliamo di una libreria vorrei evitare di pregiudicare delle funzioni, per quello in partenza parlavo di WatchDog, ora vedrò di ragionarci un po sopra, sto vedendo di creare un gruppo di funzioni che si servano del timer per giusto il tempo necessario e di riadattare l'incremento a tutte le modalità PWM, vediamo se si riesce a combinare qualcosa

leo72

Col watchdog non scendi sotto a 16/1000 di secondo. Già fatto  ;)
Il mio scheduler leOS2 usa proprio il timer del watchdog ma anche usando il massimo prescaler il minimo intervallo è 16 ms.

Quote
Gia, ma visto che parliamo di una libreria vorrei evitare di pregiudicare delle funzioni,

A questo punto mi sorge una domanda: ma se stai replicando le funzioni del core di Arduino, perché allora non usare quello?  :smiley-sweat:
Mi spiego, qual'è il vantaggio nello stare a rovinarsi il fegato con queste implementazioni quando hai già tutto pronto? La scelta di non affidarsi all'ambiente di Arduino di solito la si fa per ottimizzare le risorse ed avere prestazioni maggiori. Così non stai riappesantendo tutto?

RobertoBochet


Mi spiego, qual'è il vantaggio nello stare a rovinarsi il fegato con queste implementazioni quando hai già tutto pronto? La scelta di non affidarsi all'ambiente di Arduino di solito la si fa per ottimizzare le risorse ed avere prestazioni maggiori. Così non stai riappesantendo tutto?

Guarda non hai del tutto torto, in pratica sto ricreando le librerie piu importanti TWI, UART, SPI; quindi quelle funzionalità che a parer mio non credo sia comodo o utile riscrivere e riadattare ad ogni singolo progetto, parliamone, l'UART funziona in un solo modo, indipendentemente dal progetto(per UART ovviamente non intendo quello in SPI mode).
Come ripeto in tutti i miei topic questa rivisitazione la faccio piu che altro per il punto di vista didattico, l'utente medio che lavora con Arduino non ha idea di quale siano i processi che avvengono a livello di codice quando si eseguono le funzioni messe a disposizione dal core. Io preferisco un ambiente piu trasparente che faccia capire bene cosa realmente accada alla macchina(e dopo questa affermazione mi sono scavato una bella buca con pareti ricoperte di assembly)
Poi durante questa rivisitazione può capitare di riadattare un po il sistema come piu ritengo sia utile; non mi aspetto di fare un lavoro migliore del team Arduino ma sai qualche piccola idea nuova magari salta fuori.
Condividi la mia visione leo? Oppure la pensi in modo diverso?

leo72

Se è a scopo didattico, la condivido al 100% anche perché mettersi a riscrivere una porzione di codice è il modo migliore per capire come funziona nei minimi dettagli. D'altronde, anche Leonardo da Vinci (il mio omonimo meno famoso  :smiley-yell: ) squartava i cadaveri per capire come eravamo fatti dentro  ;)
Anch'io tempo fa mi ero messo a spippolare coi PIC (poi il tempo è quello che è e le cose da fare sempre tante...) ma nei pochi test che ho fatto ho cercato di replicare i metodi di Arduino: ad esempio, mi ero scritto una versione di millis che funziona in modo identico a quella dell'Arduino, usando un timer come contatempo. Divertente e molto didattico  ;)

Certo, se poi uno va di fretta e vuole tutto e subito, prende l'Arduino con i suoi pregi e difetti. Ma è per questo che è nato l'Arduino, difatti: rendere le cose semplici e pratiche.

RobertoBochet


Certo, se poi uno va di fretta e vuole tutto e subito, prende l'Arduino con i suoi pregi e difetti. Ma è per questo che è nato l'Arduino, difatti: rendere le cose semplici e pratiche.

Assolutamente vero, anche se Arduino la vedo prima come una scheda per "imbrogliare" gli utenti, attirandoli nel mondo dell'elettronica programmata con la promessa che si potranno creare tanti progetti interessanti con estrema semplicità; e il fatto più esaltante è che queste sono promesse mantenute, il fatto sta che poi ti trovi in un modo cosi ricco di possibilità che risulta difficile abbandonarlo. Arduino è forse una delle idee più geniali che potessero saltare fuori nel campo dell'elettronica, era il ponticello che mancava tra utente medio e elettronica.

RobertoBochet

#14
Oct 13, 2014, 01:32 am Last Edit: Oct 13, 2014, 01:57 am by RobertoBochet Reason: 1
Ci ho sbattuto la testa per un paio di giorni ma la soluzione rimane in cantiere ancora per un po' intanto ho pensato di ovviare al blocco con questa variante
Code: [Select]
bool TWIWait()
{
uint8_t t = 15;
while (!(TWCR & (1 << TWINT) && t-- != 0) _delay_ms(1);
return TWCR & (1 << TWINT);
}

A ogni ciclo metto l'AVR in pausa di 1ms per un massimo di 15ms(Valori totalmente forfettari, ora mi prenderò la briga di reperire dei valori appropriati) inoltre controllo non solo che l'interrupt sia finalmente arrivato ma che il contatore non abbia superato il valore di 15 (In questo caso l'opposto per semplificare il codice), approssimativamente 15ms, in ogni caso restituisco il valore dell'interrupt per far capire se la procedura sia andata a buon fine.
Puo' andare? Ci sono eventuali casi che non ho preso in considerazione?

Edit: Sono felice di comunicare che dalle prove effettuate si riscontra l'ottima capacità del codice di non far entrare in stasi l'AVR in caso un dispositivo venga scollegato mentre è in funzione. In ogni caso vi chiedo ancora qualche suggerimento su questa soluzione citata qui sopra.

Grazie :)

Go Up