Acquisition d'impulsion et Sleep mode

Bonjour à Tous !

J’utilise un raspberry Pi pour faire l’acquisition d’impulsion 0/5V pour un débitmètre.
Je souhaite compter les impulsions reçues par une IO du raspberry, et réémettre des impulsions sur une autre IO de celui-ci.

Pour cela, j’incrémente en compteur sur une interruption :
GPIO.add_event_detect(débitmètre, GPIO.RISING, callback=Counter_callback)
→ résultat, je compte bien les 1000 impulsions reçus

La ou cela deviens gênant, c’est lorsque je rajoute une fonction Send_Pulse() incluant un « sleep() »
→ résultat, je rate des impulsions ~2/1000 impulsions reçus

Apparemment, le délais en « Sleep mode » pose problème, et la fonction
Je pensais qu’étant sur interruption, la fonction « Counter_callback » ne devait pas rater d’impulsion ?
Auriez-vous une idée ou une solution pour gérer cela ?

def Counter_callback()
Pulse = Pulse + 1
if Pulse >= 10
Send_Pulse(0.002) # impulsion avec 2 tempo de 2ms

Merci pour votre attention.

Bonjour,

Pourquoi Send_Pulse contient un sleep alors que déjà il attend d’avoir 10 impulsions avant d’émettre ? Qu’apporte ce sleep ?

Je pense qu’il faudrait écrire/réecrie l’algorithme du programme pour corriger. Pour s’aider, faire un chronogramme avec les impulsions en entrées, les appels à Send Pulse, les trames à envoyer et les delay. Il faut faire en sorte que l’appel à Send_Pulse ne soit pas bloquant.

Dans le cas présent, plusieurs instances de Counter_callback peuvent cohabiter. La première est en attente que Send_Pulse rende la main, et pendant ce temps, une ou plusieurs continue à compter des Pulse. D’ailleurs quelle est la valeur de Pulse alors ?

Plusieurs axes de résolution:

  • séparer l’appel à la fonction if Pulse>=10 et Send_Pulse pour qu’elles ne soient plus dans Counter_callback
  • Implémenter le multithreading
  • ne plus faire de sleep mais compter le temps si temps >= delais entre 2 emissions alors envoi.
  • passer le montage sur un microcontroleur (qui sera plus temps réél) style Arduino ou ESP32.
    (pour résoudre le problème de charge machine le jour ou le PI sera affecté à faire qq chose de plus prioritaire et n’aura plus de ressources CPU pour compter!)

Bonnes bidouilles

EDIT: autre approche, utiliser Pulse comme un compteur dans une entité MQTT, memcache, ou redis (un logiciel, qui sérialisera deux appel simultané). Sur cette entité, la fonction Counter_callback ne fait que Pulse=Pulse+1 et la fonction Send_Pulse ne fait que Pulse=Pulse-10.

Bref pleins d’approches différentes, et il y a probablement d’autre, cela dépend de tes compétences et de la robustesse de la solution.

A+

Merci pour ce super retour ! J’ai mis du temps a répondre, le temps de faire mes tests les soirs…

Les sleeps, dans Send_Pulse servent juste à générer un signal avec des T-ON, T-OFF déterminés.
J’ai également un « sleep() » dans la boucle while(1) du script. de 0.1s qui tourne en boucle, fait une mesure et déclenche un watchdog toutes les Xs. Celui-ci bloc aussi mon code et empire les résultats.

GPIO.callback → Count() & SendPulse()

while true :
try :
mesure_adc

cycle = cycle +1
if cycle == 50
   print(debug)
   watchdog()
   cycle = 0
sleep(0.1)

except : print(« error »)

J’ai plus l’habitude de microcontrôleur type PIC ou ce genre de tâches est facile grâce à des bloc logique (conteurs physique notamment) et ou les sleep mode ne sont pas bloquant car les interruptions sortent le produit du sleep mode pour exécuter une fonction avant de reprendre le code la ou il l’a laisser. Mais je dois le faire avec un RPI et ne peux changer l’architecture hélas.

J’ai testé de diminuer le sleep de 0.5 à 0.1. les pertes sont divisées par 5. (Ca peut être une solution peut être acceptable, mais j’imagine que c’est pas très fiable)

1- Multithread : J’ai jamais cette notion en embarqué. Ca semble une très bonne solution pour éviter les conflits entre fonctions, mais j’ai pas d’experience la dessus, j’ai peur que cela me créer des problèmes plus gros que je ne saurai résoudre pour l’instant.

2- Séparer l’appel des fonctions count() & SendPulse() : Hélas on ne peut pas lancer 2 callbacks à partir du même event. Car il faudrait que cela soit déclenché par la réception de nouveaux pulses

3- remplacer sleep par comptage de temps : J’ai implémenté cette solution pour remplacer le sleep() de la boucle while → grosse amélioration d’un facteur 10 !!

from delay import NonBlockingDelay, delay_ms, millis
d0 = NonBlockingDelay()
if d0.timeout() :
etc…

Pour améliorer encore les performances :
a) il faudrait que j’implémente la solution (3) au temporisation utilisées pour généré les pulses…mais je que cela ne fonctionne pas de la même façon que pour la tempo de la boucle principale, car il va lancer plusieurs fois la fonction, et le comptage du temps posera problème.
b) Du coup, il faudrait que j’arrive à implémenter la solution (2) … je continue mes recherches…

Merci encore !!!

Bonjour,

Je pense qu’il faudrait que Counter_callback et Send_Pulse soient asynchrone. Le premier écrit dans une variable à chaque impulsion, le second vérifie périodiquement si la taille de la variable est supérieure à 10 quand c’est le cas, il fait un envoi après avoir enlevé 10 à la variable. (Je ne sais pas comment ça s’intègre au programme!)

Pour aller plus loin, créer une liste (Pulse=[] ) plutôt qu’une variable. A chaque impulsion , enregistrer un horodatage(Pulse.append(time.time())). pour l’envoi mettre à jour la liste en supprimant les 10 premières impulsions ( Pulse=Pulse[10:]). On a le temps passé entre la première et la dixième impulsion donc on peut calculer un débit.

https://python.doctor/page-programmation-asynchrone-python-thread-threading

Bonne bidouilles

A+

Salut,

Effectivement.
Disons que j’avais une réticence à faire le SendPulse() dans la boucle while() avec une tempo non-bloquante de peur que cela soit moins fiable que « GPIO.true, wait(0.0005), GPIO.false, wait(0.0005) », car ce que j’ai pas précisé, c’est la tempo du signal émis n’est que de 0.5ms (période de 1ms). Du ca parait difficile d’être à peut prêt précis sans code bloquant. Je vais étudier/tester tes 2 propositions. Merciii :star_struck: !! y a des approches intéressantes que je pourrai réutiliser.

Pour voir jusqu’a ou le RPI pouvais être précis dans l’acquisition d’impusion, j’ai fait un code vide pour comparer (sans mesures, ni SendPulse()…). En entrée, j’ai un arduino qui émet 1000pulses / une pause. Le RPI affiche toutes les 500ms, le nombre de pulses reçut lors de la pause. (j’ai fait ainsi pour être sûr de ne pas avoir trop de print() ni quoique ce soit qui interfère.

def PulseCount() : 
    Pulse_Received  = Pulse_Received  +1

last_Pulse_Received = 0
Pulse_Received = 0
d2 = NonBlockingDelay()
GPIO.add_event_detect(26, GPIO.RISING, callback=PulseCount)

while True:     # Create Infinite Loop  
    if d2.timeout() : # 500ms delay for print_debug
        if last_Pulse_Received == Pulse_Received :   # if no pulse received
            print(Pulse_Received)
        last_Pulse_Received = Pulse_Received 
        d2.delay_ms(500)
    #sleep(0.001) 

Je ne comprend pas le résultat.

  • Sans aucun temps mort (sleep(0)) , je perd 14 pulses par million à très faible débit (150Hz)
  • Avec une pause de 1ms (sleep(1)), je perd 4 pulses par million à très faible débit (150Hz)

Je pensais jusqu’à présent que le code bloquant était ce qui provoquait des pertes. En diminuant le temps de sleep de ma boucle principale, les pertes diminue jusqu’à un certain point. Si je supprime, le sleep(0.001) , les pertes augmentes.

Why :thinking:?
Le test est a tres faible débit et il n’y a presque plus rien dans le code…

Bonjour,
Je suis un peu perdu dans les chiffres
perte de 4 ou 14 pulses par million à 150Hz.
150 Hz => 150 impulsions/secondes ?
durée de 1 million de mesures = 1 000 000 / 150 = 6666 secondes => presque 2 heures.
Des séquences de mesures de 2 heures ont été lancées pour mesurer le nombre d’erreurs ?

Plusieurs séries de mesures dans le même conditions ont-elles été menées pour valider que c’est bien la présence du sleep qui change le résultat. Pour ma part 4 ou 14 par rapport à 1 million c’est peu ou prou la même valeur.
Quel est le taux d’erreur toléré pour la mesure, le débit mesuré doit il être si précis ?

A+

Oui, 150 Hz = 150 impulsion / s.

Je ne lance pas les millions de mesures à chaque fois (trop long) j’ai fait des tests au début à plus haute fréquences, avec des pertes de 5/1000 pulses/s, puis au fur à mesure de l’amélioration, j’ai ramené à x par million.

Désolé, J’ai pas posé tout le contexte de ma démarche, vu comme cela, ca a l’air de bien fonctionner. Je suis descendu en fréquence pour occulter les problèmes de débits et voir ce qui pouvait poser problème dans le code même à bas débit. Au delà de 5KHz, les résultats sont catastrophique. J’aurai du commencer par là.

Mais c’est vrai que comme c’est le même ordre de grandeur, et parait très faible sous cette forme.

Si je monte à 500 Hz, c’est plus flagrant :
Sans le sleep() → pertes ~500/1000 pps (pulse per second)
Avec le sleep(0.001) → pertes ~0.3/1000 pps

Je voudrai ne pas dépasser les 20 p / 1000000 p à un débit de 500 Hz. J’ai une premiere version à 150Hz et un autre qui pourra monté à 500Hz.

Je pensai même au départ, qu’il n’en raterai aucun comme cela aurait été le cas si c’était un microcontrôleur avec un compteur numérique, on même grace aux interruptions, qui ont une priorité absolues et se fichent de ce que fait la boucle principale.

Bonjour,

Merci pour les précisions. Je ne m’explique pas pourquoi les résultats sont meilleurs avec un sleep(). C’est surprenant ces détériorations des mesure si bas en fréquence.

Les librairies de gestion des port GPIO ont évoluées, la lecture de ces pages peut donner des
pistes.

A+

Ok, merci.

Je me le garde sous la main, pour le tester plus tard.
Je viendrai faire un retour si j’ai la solution.