Newsgroup: it.hobby.elettronica.digitale
Titolo: Timer: implementare un driver

Vorrei implementare un driver per gestire un classico timer interno ad 
un microcontrollore. Diciamo un timer che mi fa scattare un interrupt 
ogni millisecondo.
Normalmente utilizzo un metodo semplice per scandire degli eventi 
temporizzati. Associo all'evento una variabile, diciamo tmr_event. Nella 
routine di interrupt vado a decrementare fino a zero:
   ...
   /* Nella routine di interrupt */
   if (tmr_event)
     tmr_event--;
   ...
Chi deve gestire questo evento temporizzato deve semplicemente 
"caricare" la variabile con il numero di tick ed aspettare che questa 
vada a zero.
Il metodo ï¿œ semplice e funziona, ma mi costringe a modificare ogni volta 
la funzione di interrupt per gestire l'evento in piï¿œ.
Stavo pensando di passare ad un altro metodo, cioï¿œ quello di avere una 
variabile, chiamiamola ticks, che viene semplicemente incrementata nella 
routine di interrupt (quindi ogni millisecondo). Per evitare problemi di 
atomicitï¿œ delle operazioni, vado a definire:
   int ticks;
Su un'architettura a 16 bit avrï¿œ ovviamente una durata massima degli 
eventi temporizzati pari a 65535ms, poco piï¿œ di un minuto. Diciamo che 
mi basta.
La routine di interrupt diventa semplicemente:
__interrupt void timer_isr (void)
{
   ticks++;
   ...reset del flag di interrupt...
}
Chi vuol creare un evento temporizzato potrï¿œ fare qualcosa del genere:
   extern int ticks;
   int tmr_event = ticks + 1000;      /* Evento fra 1000ms */
   while (ticks - tmr_event < 0)
     ;
Naturalmente l'esempio di sopra ï¿œ bloccante, ma potrebbe anche non esserlo.
Quello che mi chiedo ï¿œ: funziona il sistema anche quando c'ï¿œ overflow? A 
  me sembra di sï¿œ, perchï¿œ ho dichiarato sia ticks che tmr_event come 
interi con segno.
Facciamo un esempio. Diciamo che ticks vale 32000, quindi c'ï¿œ 
l'assegnamento tmr_event = 33000 = -32536 in complemento a due.
Se andiamo a controllare quando ticks vale -32530, avrï¿œ:
   ticks - tmr_event = -32530 - (-32536) = 6 positivo, quindi l'evento, 
giustamente, non viene generato (si rimane nel while dell'esempio 
precedente).
Se inceve si controlla quando ticks vale -32600 allora:
   ticks - tmr_event = -32600 - (-32536) = 64 negativo e l'evento viene 
generato.
Che ne dite, potrebbe funzionare?

 Che ne dite, potrebbe funzionare?
Sï¿œ, funziona se ticks ï¿œ signed.
La soluzione che hai proposto perï¿œ ï¿œ bloccante, per farla non 
bloccante e generica devi necessariamente implementare una lista di 
timer che viene controllata sotto ISR.
Detto cosï¿œ suona complicato, ma non lo ï¿œ affatto, dai un'occhiata al 
nostro driver:
http://www.bertos.org/browser/trunk/bertos/drv/timer.c
Detta in parole semplici, ogni volta che vuoi aggiungere un nuovo 
evento devi allocare una struttura Timer, che contiene tutte le 
informazioni legate all'evento: il delay, la funzione da chiamare ed 
anche il "nodo" della lista (2 puntatori).
Poi chiami la funzione timer_add passandogli la struttura 
precedentemente riempita. Il driver scorre la coda dei timer e 
inserisce la richiesta in ordine di scadenza.
Poi sotto IRQ si controlla la lista e si eseguono gli eventi scaduti 
chiamando le callback. Appena si trova un evento che non ï¿œ ancora 
scaduto si esce, perchï¿œ tanto gli eventi successivi scadranno 
sicuramente dopo, essendo ordinati.
Con questo sistema puoi implementare un driver per eventi generico, 
riusabile e potente che non usa praticamente risorse e carica 
pochissimo la CPU.
-- 
  _|/ Francesco Sacchi - Develer S.r.l., R&D dept.
   |\ 
http://www.develer.com/
 - 
http://www.bertos.org

 [...]
Sì certo, la gestione della lista dei Timer sarebbe l'ideale, ma mi
costringerebbe ad inventarmi qualche meccanismo di allocazione
(per allocare/deallocare le strutture dei Timer) che attualmente non
ho
e non vorrei avere (magari in futuro...)
Per evitare le pause bloccanti potrei semplicemente salvarmi il
valore di ticks in una variabile e, quando libero, controllare la
differenza
tra ticks e la variabile.
Certo non sarò preciso nel far scattare l'evento perchè non lo
gestisco
nell'ISR, ma non mi serve molta precisione e inoltre il metodo è più
semplice.
Tra l'altro, non so perchè, ma ho una certa paura nel gestire troppe
cose
in un ISR, figurati se devo richiamare una funzione (quella
dell'evento) dall'ISR.
Immagina che l'evento deve scrivere a display, nell'ISR richiamo una
routine
di scrittura del display che potrebbe tranquillamente essere stata
interrotta
proprio dall'ISR. Cosa succede? Non si rischia qualche conflitto tra
la routine
del display ante-ISR e quella richiamata dall'ISR stessa?
Se non sbaglio, per evitare questo tipo di cose, devi implementare
meccanismi
di salvataggio del contesto che non saprei neanche fare...

 del display ante-ISR e quella richiamata dall'ISR stessa?
Gli eventi in questo caso devono ovviamente essere cose velocissime, 
tipo muovere un piedino o cose simili, perch� avvengono sotto IRQ.
Quello di cui stai parlando, in realt�, non sarebbe propriamente un 
driver del timer.
Quello che tu chiami timer in realt� sarebbe uno scheduler e gli 
eventi sarebbero in realt� processi.
Un'implementazione semplice ma generica e potente potrebbe essere 
fatta anche qui con una lista, in cui inserisci i processi e il loro 
tempo di scadenza.
Periodicamente nel main loop chiami una funzione di poll che scandisce 
la lista ed esegue i processi scaduti. I processi in realt� sarebbero 
semplici chiamate a funzione. Quest'ultime non necessariamente devono 
essere brevissime, ma comunque non dovrebbero rimanere in attesa di 
I/O in modo bloccante, altrimenti non trai vantaggio da questo approccio.
Implementare una cosa del genere � piuttosto semplice se hai a 
disposizione liste e un timer funzionante.
Quasi quasi mi hai fatto venire voglia di farlo su BeRTOS :-P


 Quasi quasi mi hai fatto venire voglia di farlo su BeRTOS :-P
Praticamente ï¿œ un cooperative multitasking dove ogni task viene fatto 
girare ogni x secondi, dove x puï¿œ dipendere dal processo.
Io evito questo approccio perchï¿œ ho sempre dei processi che potrebbero 
comunque "girare" se la CPU non sta facendo nulla (per esempio, il 
display potrebbe sempre essere rinfrescato se la CPU ï¿œ ferma, cosï¿œ come 
per l'acquisizione dei segnali analogici, ecc.)
Quindi alla fine faccio un main del tipo:
   ...
   while (1) {
     task1 ();
     task2 ();
     task3 ();
   }
Ad ogni istante la CPU comunque sta eseguendo un task. Se task1() deve 
fermarsi per 10 secondi, gestirï¿œ internamente un suo timer e farï¿œ in 
modo di uscire subito se il timer non ï¿œ scattato. Cosï¿œ dï¿œ subito la 
possibilitï¿œ agli altri task di "girare".

 farei nel modo di cui sopra. Variabile tmr e stato per gestire l'evento.
Questo ï¿œ l'approccio che ho indicato nel mio post precedente solo che 
io ho centralizzato la logica in un modulo a parte, tu invece l'hai 
messa nel task chiamante.
Col mio sistema aggiungi il task del lampeggio alla lista e chiami 
periodicamente nel main una funzione di poll per vedere se gli eventi 
sono scaduti.
In quest modo l'esecuzione ï¿œ sincrona, non hai problemi di concorrenza 
e all'interno del tuo task avrai solo 2 funzioni (lampeggio_start e 
lampeggio_stop) per avviare/fermare il lampeggio.

Ok, ï¿œ l'approccio giusto, ma ancora una volta quello che stai 
proponendo ha sempre meno a che vedere con il timer e sempre piï¿œ con 
uno scheduler cooperativo.
Il sistema che usi ï¿œ quello classicamente usato nelle applicazioni 
senza un kernel vero e proprio. Funziona bene finchï¿œ i processi sono 
pochi e non c'ï¿œ problema se rimani bloccato sull'I/O per un po' di tempo.
Quello che ci tenevo a far notare ï¿œ che non ï¿œ per nulla complesso 
invece implementare un meccanismo generico (si fa davvero con poche 
righe di codice) che puoi fare una volta per tutte e riusare tutte le 
volte che vuoi.


 vuoi.
In effetti hai ragione...

 capito cosa intendi.
Hai ragione, mi sono espresso male io.


 iniziano a essere diversi uso il kernel :-)
Allora vado tranquillo con il mio metodo :)