• Benvenuti su RaspberryItaly!
Benvenuto ospite! Login Login con Facebook Registrati Login with Facebook


Valutazione discussione:
  • 0 voto(i) - 0 media
  • 1
  • 2
  • 3
  • 4
  • 5

[-]
Tags
accavallano python interrupt si che

Python, interrupt che si accavallano
#1
Ciao.
Sto sperimentando Raspberry e quindi ho preso un po' tutto quello ho e ce l'ho attaccato... contemporaneamente!!

Ovviamente sono esperimenti per ora, ma la problematica può sorgere anche in casi reali.

Molti di questi elementi per essere pilotato necessitano di eventi e relativi callback.
Come input ho 3 pulsanti, un rotatory encoder (e suo pulsante), un joystick (e relativo pulsante), un ir_receiver, ciascuno collegato ad un gpio event.
Poi ho inpostato un evento di tipo itimer ogni 250 millisecondi che ci faccio:
1) stampa di un orologio su display lcd 1602a (una volta al secondo)
2) leggi lo stato del joystick e salva qualche stato
3) pilota alcuni led in base allo stato salvato di joystick e altro.

la callback dell'ir_receiver stampa su lcd (in una zona diversa dell'orologio) il tasto premuto.

la callback del rotatory encoder stampa lo stato su lcd.

il tutto con lo stesso programma python

Ogni tanto l'lcd stampa roba un po' farlocca e stramba, e sono riuscito a ricostruire che probabilmente mentre mi sta stampando l'orologio scatta un evento quale il rotatory o l'ir_receiver che per quanto sono programmati per scrivere in zone diverse del display, usano lo stesso buffer.
Quindi può capitare che il display riceve una seconda scrittura mentre non è terminata la prima e quindi si incarta.

Ora sto citando l'lcd perchè è il dispositivo più lento che ho installato al momento, ma può succedere con qualunque cosa, e peggio sarà quando andrò a pilotare motorini.

Inizialmente ho pensato di inserire un lock per le chiamate a display in questo modo:
- setta LCD_LOCK=1 (una variabile globale)
- stampa su display
- setta LCD_LOCK=0

la prima di queste 3 sarebbe preceduta da if LCD_LOCK == 1: aspetta che LCD_LOCK diventi zero.

Ma poi mi sono reso conto che non ha senso perchè l'approccio di tipo ad eventi non è un approccio di tempo multitask. Quando scatta il secondo evento, il primo viene messo in pausa finchè il nuovo non viene gestito, quindi LCD_LOCK a zero non ci ridiventa e il secondo rimane in attesa perenne (o timeout se lo imposto, come era da idea). Diverso se invece il display era gestito da due programmi separati con lock su filesystem o semafori, in cui il multitasking avrebbe provveduto a mandare avanti il primo programma che avrebbe poi rilasciato il lock.

Allora l'altra idea che mi è venuta in mente è quella di fermare tutte le interrupt durante l'esecuzione di una di queste, ma così si rischia di perdersi alcuni eventi. A meno che non si riesca ad accodarli.

Un'altra ancora è quella di demandare (e per un aspetto l'ho già fatto) le scritture su lcd ad un sistema asincrono, così solo l'evento timer scrive lì sopra mentre gli altri eventi si limitano a scrivere le operazioni richieste in una variabile (magari in una struttura a coda). Di sicuro l'aggiornamento di queste variabili è più veloce della scrittura sul display.

Il problema di questo approccio è che così sembra che sto reinventando la ruota (il kernel è scritto esattamente per gestire tutto questo) e introduce molte latenze (ho visto che non ho possibilità di mettere un timer troppo frequente perchè si rischia l'esecuzione di un nuovo evento mentre non è terminata la esecuzione del precedente.


Altre idee prima che continuo a scervellarmi?


Grazie.
Risposta
#2
Difficile darti una risposta se non si sa come hai scritto il programma. E anche sapendolo, occorrerebbe avere le competenze per comprenderlo. Ma la questione sta tutta lì. Non devi inventarti tu un clock, ma usare gli interrupt offerti dal sistema. Ad esempio, come hai gestito i tasti? Andando a controllare ogni 250 millisecondi se sono premuti o meno? Benché funzionante, non è il modo migliore, e te ne sei accorto. Puoi fare in modo che il sistema controlli per te il cambiamento di stato usando un interrupt (e non svrivendolo) ad esempio la libreria Gpiozero incorpora la gestione degli interrupt in modo semplice.
Codice:
from gpiozero import Button
from signal import pause

def say_hello():
   print("Ciao Amico!")

button = Button(2)
button.when_pressed = say_hello
pause()


Come vedi non c'è alcun ciclo, è il sistema che si occupa di controllare quando lo stato del pin 2 corrisponde alla pressione di un tasto.
Questo, se stai usando Python
Risposta
#3
nono, niente pooling. speravo di averlo spiegato ma in effetti sono stato un po' generico
Il timer mi serve per scrivere l'orologio e per accendere qualche led in base a qualche variabile che hanno settato gli eventi generati dai pulsanti.

Per lo più ho copiato/modificato il codice fornito con i vari kit (che - per inciso - avevo comprato per arduino e riportato su raspberry)
https://www.sunfounder.com/learn#Raspberry%20Pi

La gestione dei pulsanti è fatta per lo più con
GPIO.add_event_detect(butt_uno, GPIO.RISING, callback=callback_button, bouncetime=200)
Il rotatory e l'ir_receiver con le classi indicate in quel link:
import ir_hasher
import rotary_encoder
che inizializzano una callback
def callback_rotatory(way):
def callback_ir(hash):
il timer con i segnali di sistema
signal.signal(signal.SIGALRM, callback_timer)
signal.setitimer(signal.ITIMER_REAL, 1, 0.25)

l'esempio basico del rotatory è

def callback_rotatory(way):
global rotpos
global rota_curchar,rota_string,rota_stat
rotpos += way
print("pos={}".format(rotpos))

che funziona (anche se quando comincio a girare non lentissimo comincia a sfarfallare, ma è relativo).
Il problema maggiore sussiste quando in questo pezzo di codice ci aggiungo la gestione dell'lcd con la classe Adafruit_CharLCD
lcd.set_cursor(rota_curchar,0)
lcd.message(p)

in cui p è un char che genero in quel momento
mentre su callback_timer, ogni secondo (ovvero una esecuzione su 4) stampo due valori presi da un ADC ed un orologio, totale c.a. 14 caratteri
lcd.set_cursor(0,1)
lcd.message(str(get_adc(ADJOYX)))
lcd.message(" ")
lcd.set_cursor(4,1)
lcd.message(str(get_adc(ADJOYY)))
lcd.message(" ")

lcd.set_cursor(8,1)
now = datetime.datetime.now()
lcd.message(now.strftime("%H:%M:%S"))


ora quello che mi preoccupa - o almeno quello che sospetto, e sarebbe un po' difficile tracciarlo - è che la scrittura su lcd non è proprio un fulmine, e se mentre sta scrivendo l'orologio scatta la callback_rotatory si impicciano perchè il sistema freeza la stampa in corso ed invia segnali all'lcd che a questo punto sono incongruenti con quelli in corso.

Di fatto se giro il rotatory molto lentamente - riducendo quindi la probabilità di conflitto - il tutto funziona correttamente.


Dimmi che altri dettagli di funzionamento del codice ti servono (credo sia poco utile postarlo tutto forzando un reverse enginering Smile )


Grazie.

p.s.: gli darò una occhiata a quella classe, che sembra interessante.
Risposta
#4
gira e rigira, mi sono scontrato pure io con questo problema (usando un LCD 1602, cercando di cambiare la visualizzazione basandosi su interrupt, si ottengono caratteri anomali/strani, visto che l'LCD non aveva terminato la sua sequenza di dati).
Hai poi risolto in qualche modo? Ero arrivato pure io alla stessa "soluzione" crearmi una attesa breve, e le varie attese che servono a rendere leggibili i messaggi scritti sul display, calcolarle come multipli di questa attesa breve, e ciclarle con cicli FOR. All'interno del ciclo, verificare se si è verificato un evento in modo da poter cancellare il display, e interrompere il ciclo, eseguendo la funzione relativa.
Più o meno quello che avevi pensato tu, e che ti avevo risposto che non era una buonissima idea (e nemmeno me lo ricordavo, prima di ritrovare questa discussione).
In effetti, ancora non mi convince come soluzione, e ancora non la ho messa in pratica, ma solo teorizzata, in attesa di discuterne o ricevere consigli..
Mi sembra strano che nessuno mai abbia affrontato tale problema
Risposta
#5
Ni.
Da allora è passato un po' di tempo e
1-ho smontato e rimontato il circuito per altri scopi (visto che è una sessione di esperimenti)
2-è un po' che non mi ci dedico, quindi sta smontato lì in attesa di nuova applicazione

Non ricordo esattamente come ero rimasto, ma l'idea è quella di spostare tutte le scritture - dove applicabile - su una soluzione asincrona.

Credo che il trucco sia rendere più atomiche possibili le operazioni.

Un po' replicando il sistema delle vecchie schede video, una variabile globale memorizzerebbe il contenuto dello schermo. Il rotatory, i pulsanti, le interrupt e vari eventi scriverebbero qui. Operazione rapida.
Un evento on timer scriverebbe il contenuto della variabile su schermo in full refresh. Così anche se per qualche interferenza esterna il display diventa sporco viene ripulito.
In pratica si delega ad un singolo processo (come un driver di una scheda video) il lavoro di I/O, magari - a regime - estendendo la classe originale.

Anche questa ha le sue pecche, ma ci si potrebbe convivere. Credo si debba calcolare il tempo totale di un full refresh e fare tuning.
Risposta
#6
(26/12/2018, 02:27)ZeroUno Ha scritto: Ni.
Da allora è passato un po' di tempo e
1-ho smontato e rimontato il circuito per altri scopi (visto che è una sessione di esperimenti)
2-è un po' che non mi ci dedico, quindi sta smontato lì in attesa di nuova applicazione

Non ricordo esattamente come ero rimasto, ma l'idea è quella di spostare tutte le scritture - dove applicabile - su una soluzione asincrona.

Credo che il trucco sia rendere più atomiche possibili le operazioni.

Un po' replicando il sistema delle vecchie schede video, una variabile globale memorizzerebbe il contenuto dello schermo. Il rotatory, i pulsanti, le interrupt e vari eventi scriverebbero qui. Operazione rapida.
Un evento on timer scriverebbe il contenuto della variabile su schermo in full refresh. Così anche se per qualche interferenza esterna il display diventa sporco viene ripulito.
In pratica si delega ad un singolo processo (come un driver di una scheda video) il lavoro di I/O, magari - a regime - estendendo la classe originale.

Anche questa ha le sue pecche, ma ci si potrebbe convivere. Credo si debba calcolare il tempo totale di un full refresh e fare tuning.

Si, più o meno è la stessa conclusione a cui sono arrivato io.
Solo che non è semplicissimo.
Trovo solo strano che su mille librerie per comandare display di questo tipo, non c'è ne sia una che affronti il problema. Mi sono dovuto inventare anche la funzione che fa scorrere il messaggio se è più lungo dei 16 caratteri dell'LCD.

Inviato dal mio A0001 utilizzando Tapatalk
Risposta
  


Vai al forum:


Navigazione: 2 Ospite(i)
Forum con nuovi Post
Forum senza nuovi post
Forum bloccato
Forum Redirect