samedi 30 mars 2013

pixdel: plus complexe qu'il n'y paraît

l'idée du pixdel a émergée dans mon esprit au début de juin 2012 puis après quelques expérimentations je l'ai mis de côté. Dernièrement je l'ai repris et j'ai réécris le code au complet sans regarder ce que j'avais fait l'an passé. J'espérais pouvoir augmenter la vitesse de transmission des commandes qui dans la version 2 était à 9600 BAUD. Mes premières réécritures m'ont permis de monter la vitesse à 38400 BAUD mais le résultat n'était pas acceptable à mes yeux. Ce problème est plus complexe qu'il en a l'air. Dans cette chronique je vais faire une analyse détaillé du problème et de la solution que j'ai trouvé. Solution qui cependant ne permet pas une vitesse de transmission supérieure à 14400 BAUD.

Description du pixdel

L'idée de départ est d'utiliser le plus économique des MCU disponibles actuellement sur le marché soit le PIC10F200 pour contrôler une LED RGB dans le but de créer un bus de LED dont chaque élément peut-être contrôler individuellement. Pour que l'idée soit intéressante il faut que le MCU soit vraiment à petit prix. Actuellement le PIC10F200T-I/OTCT-ND est à 0,422CAN$ chez Digikey.ca ce qui est le même prix qu'une LED RGB économique. Donc en achetant ces éléments en quantité on peut fabriquer un pixdel pour environ 1,00CAN$ l'unité.

Le pixdel présentente 3 avantages par rapport à un système de LEDs multiplexés. Le premier est que le contrôleur principal est décharger de la responsabilité de contrôler la couleur et l'intensité de chaque LED. Le deuxième est qu'il n'y a pas de multiplexage donc l'intensité de chaque PIXDEL est maximale. Et finalement le 3ième avantage est la simplicité des interconnections, il s'agit d'un simple bus à 3 fils, V+, V-, communication.

La complexité du multi-tâche en temps réel.

Le PIC10F200 n'a pas de périphérique RS-232 ni de PWM, tout doit donc être fait en software. Pour la réception des commandes RS-232 il faut un contrôle précis du timing sinon la lecture sera erronnée. Pour le contrôle d'intensité PWM des composantes rouge,verte et bleu il faut aussi un timing précis sinon il y aura une fluctuation de l'intensitée et de la couleur de la LED. Hors ces 2 tâches doivent partager le temps MCU sans se nuire l'une l'autre. Il s'agit d'un problème multi-tâche en temps réel. Prise individuellement chacune de ces tâches est très simple à implémenter mais les faire fonctionner ensemble sur ce petit MCU qui ne supporte pas les interruptions est beaucoup plus complexe car aucune de ces tâches ne tolère une pause. Les 2 tâches doivent-être imbriquées l'une dans l'autre en respectant toutes les contraintes de timing.

Calcul des contraintes de timing

La broche GP3 est utilisée pour la réception des commandes mais il n'y a pas de support d'interruption qui permet de déclencher une ISR lorsque cette broche passe du niveau Vdd à Vss qui indique le bit de démarrage. En conséquence il faut vérifier l'état de cette broche à un intervealle inférieur ou égal à la moitié de la durée d'un bit. A 9600 BAUD un bit dure 104usec. Donc il faut vérifier l'état de GP3 à toute les 52usec sinon le MCU peut manquer un réception. Une fois qu'un start bit est détecté, il faut échantillonné GP3 à un intervalle précis de 104uSec pour faire une lecture correcte de l'octet.

En ce qui concerne le PWM le fait qu'il est en software ne change rien à son fonctionnement. Un compteur est incrémenté à intervalle régulier et à chaque incrément la valeur de ce compteur est comparée avec la valeur de seuil du rapport cyclique des 3 canaux PWM rouge, vert, bleu. si le seuil est dépassé la sortie du canal est remise à zéro. Dans le cas du pixdel is s'agit d'un compteur 8 bits, donc il retourne à zéro après 256 incrément. La période PWM est donc:
période= Tc*256
Tc étant l'intervalle entre chaque incrément. Pour que l'oeil ne percoive pas de scintillement de la LED il faut que cette période soit au maximum 1/70Hz=14msec. Donc Tc doit-être <=0,014/256=55uSec.

Donc la durée d'un demi-bit à 9600 BAUD est de 52usec et l'intervalle maximal pour pwm_clock=55uSec. On peut donc prendre la valeur de 52uSec comme durée de la boucle du céduleur de tâches et s'assurer que chaque cycle s'exécute en exactement 52uSec.

La tâche pwm_clock s'exécute à chaque cycle du céduleur et est suivit d'une autre tâche. Au début de la boucle le TIMER0 est réinitialisé pour qu'il revienne à zéro au bout de 52uSec. Ensuite pwm_clock qui est définit comme une macro, s'exécute (12uSec), après quoi le céduleur effectue un compute goto vers la tâche à exécuter pour ce cycle. Finalement la boucle se termine par idle_loop qui attend que le TIMER0 termine son compte de 52uSec avant de revenir au départ. Chaque tâche détermine quel sera la tâche à exécuter dans la boucle suivante.

A 9600 BAUD il faut ~1msec pour recevoir un octet il n'est donc pas question d'interrompre le PWM pour s'occuper exclusivement de la réception de cet octet. C'est pourquoi la réception RS-232 est découpée en tranche (tâche) dont la durée est suffisamment courte pour ne pas perturber le cycle principal de 52uSec.
Boucle principale (projet au complet ici)

Il y a 8 tâches qui se partage le temps CPU dont 2 s'exécutent à chaque cycle.

  • T0: pwm_clock, s'exécute à chaque cycle et incrémente le compteur PWM et vérifie les seuils pour chaque composante RGB.
  • T1: task_wait_sync_start, attend le bit de démarrage pour réception de l'octet de synchronisation. Lorsque ce start bit est détecté commute vers la tâche task_sync.
  • T2: task_sync, réception de l'octet de synchronisation et vérification de sa validité. Si l'octet reçu est valide commute vers la tâche task_wait_start_bit, sinon retourne à la tâche task_wait_sync_start
  • T3: task_wait_start_bit, attend le bit de démarrage pour les 4 octets de la commande. Lorsque ce bit est détecté commute vers la tâche task_cmd_rcv.
  • T4: task_cmd_rcv, reçoit un octet de commande. Lorsque la réception d'un octet est complétée, vérifie si tous les octets ont été reçus. Si tous reçus commute vers task_chk_id, sinon retourne à la tâche task_wait_start_bit en attente de la réception de l'octet suivant.
  • T5: task_chk_id, vérifie si la commande en est une de diffusion (id=0) ou si l'id correpond à ce pixdel. Si la commande est acceptée la prochaîne tâche sera task_cmd sinon ce sera task_wait_sync_start.
  • T6: task_cmd, mais à exécution la commande reçu, c'est à dire modifie les valeurs du rapport cyclique de chaque canal au nouvelles valeurs reçues. La prochaine tâche à exécuter sera task_wait_sync_start.
  • T7: idle_loop, cette tâche s'exécute à la fin de chaque boucle et s'assure que la durée totale de la boucle est fixée à 52uSec.

Une seule des tâches task_* est exécutée par cycle de 52uSec. Le temps total d'exécution de pwm_clock+task_* doit-être inférieur au délais programmé dans TIMER0 pour que le système fonctionne correctement. Tout délais supplémentaire se traduira par une erreur de réception des octets de commandes et par une variation du cycle PWM.

Script python de test

Dans le dépôt github il y a un script python qui sert à vérifier le bon fonctionnement du pixdel. En autre même si le bus de commandes est saturé de commandes l'intensité ou la couleur du PIXDEL ne doit pas fluctuer.

Les tests que j'ai fait montre que cette version du firmware est meilleure que celle que j'avais créer l'an passé. La version 2 avait tendance à se désynchroniser et parfois le pixdel ne répondais plus aux commandes du contrôleur. J'ai modifier la stucture des commandes. Dans la version 4 les commandes sont précédées par un octet de synchronisation 0xFF, cette valeur ne doit pas être utilisée autrepart dans le paquet de donnée transmis sinon le système de synchronisation ne fonctionnera plus. Mais si on respecte cette règle le pixdel même s'il rate une réception se resynchronise rapidement avec le contrôleur.

augmentation de la vitesse des commandes

Mon objectif principal qui consistait à augmenter la vitesse de transmission n'a cependant pas été atteint. La contrainte principale est la durée d'un demi-bit pour un BAUD donné. A 9600 BAUD comme mentionné précédemment cette durée est de 52uSec. A 14400 BAUD on tombe à 35uSec hors dans la version actuelle certains cycles requièrent plus de 35uSec pour clore la boucle.

J'ai réussi à augmenter la vitesse RS-232 à 14400 BAUD en overclockant le mcu à 4,619Mhz (+15%). J'ai choisi cette valeur car elle correspond à un multiple entier de la durée d'un demi-bit pour 14400 BAUD. (1/14400)/2/40~=(1/4,619Mhz)/4. On dispose donc de 40 Tcy par boucle et cette durée corresponds précicément à 1 demi-bit pour 14400 BAUD.


liens

Aucun commentaire:

Publier un commentaire