Récemment, j’ai été amené à devoir prendre des mesures de temps très précises pour répondre à une problématique de contrainte temps-réel. Sur un SoC particulier avec un système Linux dessus, la question était de savoir quelle était la fluctuation de la latence entre le déclenchement d’un événement matériel et la réponse d’une tâche logicielle associée à cet événement.
La gigue ou fluctuation du signal
Qu’est ce que la gigue ou jitter en anglais ? Prenons, par exemple, un événement avec une fréquence parfaite — disons l’heure à laquelle sonne mon réveil — et un autre événement moins parfait qui lui est associé, comme le moment où j’ouvre les yeux. Chaque jour, l’heure de mon réveil reste la même. En revanche, le moment où j’ouvre les yeux pour éteindre mon réveil varie… au désespoir de mon patron. :/
Cette fluctuation est la gigue. Dans ce cas, il s’agit d’une gigue temporelle, comme dans ma problématique.
Mesurer la gigue avec un oscilloscope
Typiquement, mesurer la latence entre deux événements matériels se fait très bien avec un oscilloscope à deux canaux. Il suffit de savoir où sonder, sur notre circuit, ces événements. Après quelques réglages, on peut visualiser la latence et, notamment, avoir une persistance sur chaque prise de mesure qui va nous donner une enveloppe de cette fluctuation.
Ma première démarche à donc été d’associer à ma tâche logicielle un autre événement matériel pour m’aider à prendre ces mesures. Mon événement matériel initial était une interruption et je savais où la sonder sur le SoC. À celui-ci, je réponds par un autre événement matériel qui consiste à faire commuter une GPIO que je sais également où sonder sur le SoC.
Ainsi à chaque fois que l’interruption se déclenche, la GPIO change d’état.
Ce qui nous donnera par exemple sur un oscilloscope quelque chose comme ça :
Avec en jaune l’interruption qui est réguliére, et en vert la fluctuation des temps de réponse de la GPIO.
Il ne me restait plus qu’à prendre mes mesures avec un oscilloscope. Mais là c’est le drame… je ne disposais pas d’oscilloscope suffisamment récent pour avoir une enveloppe consistante de mes signaux; faute de mémoire, je pouvais conserver tout au plus les 32 dernières mesures et je ne pouvais donc pas voir ma fluctuation sur de longues périodes.
Le seul autre équipement à ma disposition pour prendre des mesures était un analyseur logique Saleae Logic. Bien que très pratique, ce dernier ne me permettait pas de visualiser ma fluctuation du signal.
Comment visualiser la gigue avec l’analyseur logique de Saleae ?
Avec cet analyseur, ce que je peux obtenir comme résultat ressemble à cela:
En zoomant on voit bien la latence entre le déclenchement de l’interruption (en haut) et la commutation de ma GPIO (en bas):
Ce qui m’intéresse, c’est mesurer étant le temps entre le drapeau 1 et le drapeau 2 pour chaque interruption et le visualiser de façon facilement compréhensible, comme ceci:
Mais je ne peux pas faire ça à la main pour chaque interruption et recommencer à chaque prise de mesure.
C’est en discutant de mon problème, qu’un collègue et ami (Emeric Vigier, pour ne pas le nommer), me pointa à juste titre sur la fonction d’exportation de ce logiciel. Et, effectivement, l’ensemble des informations qui me sont nécessaires sont à ma disposition, le dernier problème étant juste qu’elles ne sont pas organisées comme je le désire.
Visualisation des données : du CSV au Graphique
Donc, comme précisé précédemment, l’analyseur logique de Saleae me permet d’exporter mes données au format CSV sous la forme:
Time[s], Interrupt Trigger, Gpio 0
0, 0, 1
0.00258295833333333, 1, 1
0.00258408333333333, 0, 1
0.002633, 0, 0
0.00758641666666667, 1, 0
0.00758758333333333, 0, 0
0.007629625, 0, 1
0.0125899166666667, 1, 1
...
La première valeur me donnant le temps en seconde, la deuxième l’état de mon interruption (0 = bas, 1 = haut), et la troisième l’état de ma GPIO. Donc, l’algorithme me permettant de récupérer ma latence sera:
Pour chaque ligne,
- Si mon interruption passe de 0 à 1, Alors je note son temps dans A
- Si j’ai A et que ma GPIO change d’état, Alors je note son temps dans B
- Si j’ai A et B, Alors je soustrais A à B et je garde le résultat dans le tableau C
Le tableau C contiendra ainsi l’ensemble des temps de latence. En quelques lignes de Python, cela donne le code suivant :
STEP_INTERRUPT = 0
STEP_GPIO = 1
with open(file_csv, 'r') as csvfile:
cvsreader = csv.reader(csvfile, delimiter=',')
# the first entry give us the initial state
init_state = next(cvsreader)
interrupt_state = init_state[interrupt]
gpio_state = init_state[gpio]
step = STEP_INTERRUPT
for row in cvsreader:
# The first step is to find an interrupt edge
if step == STEP_INTERRUPT:
if (interrupt_state == 0 and int(row[interrupt]) == 1):
interrupt_value = float(row[0])
step = STEP_GPIO
# The second step is to find a gpio commutation
elif step == STEP_GPIO:
if (gpio_state != int(row[gpio])):
gpio_value = float(row[0])
# here we know we have an interrupt edge value
# and the gpio commutation time
# we can take the delay mesure between the two
delay = gpio_value - interrupt_value
# store the delay value in millisecond.
dic.append((delay * 1000, 0.5))
# we can now reinit the step
step = STEP_INTERRUPT
interrupt_state = int(row[interrupt])
gpio_state = int(row[gpio])
À ce stade, je dispose de l’ensemble de mes latences, maintenant j’aimerais bien pouvoir les mettre en forme avec quelque chose de plus parlant — et ce n’est pas comme si l’on manquait de librairies pour réaliser des graphiques dans Python. Donc, prenons en une au hasard, et dessinons!
J’ai choisi la librairie pygal, qui permet de dessiner des points dans un espace à 2 dimensions. Il m’a suffit de rajouter ces quatre lignes de Python à mon script:
xy_chart = pygal.XY(stroke=False, fill=False, show_y_labels=False, legend_at_bottom=True, show_dots=True, dots_size=0.8, print_values=False)
xy_chart.title = 'Jiffies'
xy_chart.add('values in milli-second', sorted(dic))
xy_chart.render_to_png('jitter.png')
Et voilà le résultat tant désiré car nettement plus parlant:
On peut ainsi voir quelque chose qui se rapproche de notre première capture d’écran (l’exemple d’un oscilloscope) tout en économisant quelques milliers de dollars… cette fois, au grand bonheur de mon patron. 😉
Le script complet est disponible ici : github.com/sbourdelin/logic-jitter
Pour aller plus loin :Le script actuel ne tient pas compte de la répartition des mesures, si la gigue tombe plusieurs fois au même instant on ne verra qu’un seul point.
Il pourrait être intéressant de modifier la visualisation pour savoir combien de fois la gigue est tombé sur un instant T.
De plus Saleae fournit un SDK qui permet de récupérer toutes les données de l’analyseur logique sur une socket. On pourra envisager d’effectuer la même analyse, non plus en parsant un fichier CSV préalablement exporté, mais en temps-réel. À suivre…
Excellent cet article sur « comment sauver 40 000 dollars avec 50 lignes de Python »!
Tu as oublié de préciser: « Article terminé 2h avant que le réveil ne sonne, d’où le jitter à l’ouverture des yeux. 😉
Merci Emeric 😉
Très bonne utilisation de Python dans ce type de contexte!
Par contre il y a bug: tu as un état de trop, STEP_GPIO!
Durant l’état STEP_INTERRUPT tu obtiens toutes les valeurs nécessaire
durant l’état STEP_GPIO, donc tu peux déjà calculer ‘delay’ dedans
et passer directement de STEP_INTERRUPT à STEP_INIT.
De plus passer par STEP_GPIO mange une entrée de tes données pour rien.
J’ai posté un bug dans sur ton github 😉
Merci guillaume pour la revue!
Tu as raison, je corrige ça.