mardi 18 août 2015

NTSC et Parkinson

Dans l'article précédent intitulé téléviseur conformiste je présentait un problème et sa solution concernant la génération d'un signal NTSC sur ATmega328P. Mais la solution présentée n'était pas optimum, il y avait un tremblement dans l'image. Aujourd'hui j'ai réglé ce problème et dans cet article j'explique la cause de ce tremblement et la solution que j'ai apportée.

Vidéo avant la correction du problème on voit le tremblement de l'image surtout apparent sur la ligne verticale à droite de l'écran.

Vidéo après correction du problème. Maintenant l'image est très stable. Il n'y a plus de Parkinson.

La gigue d'interruption

J'ai tapé dans google search: traduire: interrupt jitter et c'est la traduction que j'ai obtenu!

Le générateur de signal NTSC utilise une interruption pour modifier les paramètres du signal PWM qui génère la synchronisation mais aussi pour déterminer quand il est temps d'envoyer les pixels vidéo pour chaque ligne visible à l'écran. En ce qui concerne la synchronisation l'interrupt jitter n'affecte pas son fonctionnement puisque cette synchronisation utilise le compteur TCNT1 dont le fonctionnement est indépendant des interruptions. Par contre pour l'envoie des pixels vidéo il y a une influence. Il est primordial que l'envoie du signal vidéo débute toujours au même instant à chaque ligne ainsi qu'à chaque répétion de frame.

Le problème est que le temps de réponse de l'interruption est variable car il est influencé par la durée de l'exécution de l'instruction machine en cours au moment où se produit l'interruption. En effet le MCU doit attendre la fin de l'instruction en cours avant de répondre à l'interruption. Hors sur les AVR le temps d'exécution d'une instruction varie entre 1 et 4 cycles. La durée d'un pixel dans ce générateur vidéo est de 6 cycles machines. Donc une fluctuation de 4 cycles sur le départ de la ligne vidéo c'est 2/3 de pixel en largeur. C'est cette fluctuation qu'on perçoit dans l'image.

Comment régler ce problème

Comme je l'ai dit le fonctionnement du compteur TCNT1 est indépendant des interruptions on peut donc se fier à son fonctionnement pour avoir l'heure juste, si je peut dire. Dans cette application le compteur est cadencé à la même fréquence que le coeur du MCU. Donc lorsqu'il est temps de générer les pixels vidéo d'une ligne sa valeur est toujours la même à 4 cycles près. Le code assembleur que j'ai ajouté lit l'octet faible de TCNT1 qui est un compteur de 16 bits et ne garde que les 2 bits les plus faibles, donc cette valeur peut variée de 0 à 3 du au jitter. Je prends pour cible la valeur 3 et si le compte est inférieur à 3 on ajoute autant d'instruction NOP que nécessaire pour que lorsque le programme arrive au code qui génère le vidéo le compte de TCNT1 soit toujours le même. Puisque c'est TCNT1 qui détermine le début de la ligne vidéo, à compte égal de celui-ci on est à la même position sur la ligne vidéo.

Pour obtenir un timing précis j'ai du écrire le code en embedded assembly. Voici le code ajouté à la routine d'interruption vidéo TIMER1_COMPB_vect dans tvout.c.


 asm(// élimination de la gigue d'interuption (jitter)
 "ldi r30,lo8(jit)\n"
 "ldi r31,hi8(jit)\n"
 "clc\n"
 "ror r31\n"
 "ror r30\n"
 "lds r24,0x84\n"
 "andi r24,3\n"
 "add r30,r24\n"
 "adc r31,r1\n"
 "ijmp\n"
 "jit:"
 "nop\n"
 "nop\n"
 "nop\n"
 "nop\n"
 );
Ce code fonctionne de la façon suivante. l'adresse de l'étiquette jit (Just In Time) est chargée dans le registre d'index Z qui est en fait la concaténation des registres R31:R30 pour former un pointeur de 16 bits. Les 3 instructions suivantes divisent cette valeur par 2 parce que cette adresse est celle d'un octet mais le compteur ordinal du ATmega328P lui pointe sur des mots de 16 bits. Autrement dit si l'adresse de jit est 21590 en mémoire flash, pour que le PC pointe cette adresse il doit contenir 10795. Une fois le pointeur Z ajusté à la valeur désirée. On fait la lecture de l'octet faible de TCNT1 qui se trouve à l'adresse 0x84 et on met cette valeur dans le registre R24. L'instruction andi r24,3 à pour but de mettre tous les bits de R24 à zéro sauf les 2 plus faibles. On a maintenant dans R24 un nombre entre 0 et 3. On additionne ce nombre à la valeur dans le registre d'index Z. L'instruction ijmp prend la valeur dans Z et la transfert dans PC (le pointeur d'instruction). Si R24 contenait 0 l'instruction qui sera exécutée après le ijmp sera celle exactement à l'adresse jit. Si R24 contenait 1 l'instruction qui sera exécutée après le ijmp sera à l'adresse jit+2, etc. jit+2 parce que n'oubliez pas que le PC adresse des mots de 16 bits et non des octets et comme chaque instruction NOP occupe 2 octets on a le bon alignement.

Au bout du compte lorsque le PC arrive à l'instruction qui suis le dernier NOP, le compteur TNCT1 a toujours la même valeur puisqu'il est incrémenté au même rythme que le compteur ordinal (pointeur d'instruction).

routine TIMER1_COMPB_vect

Voici la routine d'interruption au complet. Je rappelle que ceci fait parti du projet open sourde CHIPcon v2.