LA PROGRAMMATION

Un programme d'acquisition commandant la caméra Audine comportera 2 types de fonctions :

La description des fonctions de haut niveau sort du cadre de cette documentation. Le sujet est en effet trop vaste et le code trop dépendant de la vision que l'on a d'un logiciel d'acquisition. Notons que le langage importe peu pour ces fonctions, car la vitesse d'exécution n'est pas ici la priorité.

En revanche, un soin particulier doit être apporté à l'écriture des routines de bas niveau car la performance en vitesse de lecture en dépend directement. Un bon compilateur est nécessaire, voir même, l'écriture des routines en langage machine.

Le programme PISCO est assez caractéristique. L'interface est écrite en Visual Basic 6.0, un outil extrêmement performant pour cela. Les fonctions de lecture du CCD sont en revanche regroupées dans une librairie (une DLL) écrite en C (Visual C++ 6.0).

Donc, les informations qui suivent sont destinées aux programmeurs expérimentés qui souhaitent inclure le pilotage de la caméra Audine dans leur logiciel.
Le tableau ci-après est un rappel de la fonction des 8 bits du registre de données du port parallèle.
 

BIT
FONCTION
Bit 0 Horloge V1
Bit 1 Horloge V2
Bit 2 Horloges H1 et H2
Bit 3 Horloge R
Bit 4 Horloge de clamp
Bit 5 Début de conversion du CAN
Bit 6 Multiplexage au niveau du CAN 
Bit 7 Multiplexage au niveau du 74HCT157
 
Par ailleurs, les 4 bits de poids fort du registre d'état du port parallèle sont exploités pour recevoir en 4 passes le mot de 16 bits correspondant à la lecture d'un pixel.

La routine en langage C, READ_AUDINE, contient le code de lecture standard de la caméra Audine en binning 1x1 (l'image numérique finale a une taille de 768x512 points).

Les deux paramètres de READ_AUDINE sont l'adresse de base du port parallèle, et un pointeur sur une zone mémoire allouée suffisamment grande pour pouvoir contenir l'image (ici, il faudra 2 x 768 x 512 = 786432 octets au minimum, l'image étant codée sur 16 bits).

La variable P contient l'adresse du registre de données et la variable P2 l'adresse du registre d'état.

Il faut commencer par éliminer les 4 premières lignes de l'image CCD, car elles sont masquées de la lumière et ont peu d'attrait en astronomie. Deux fonctions sont appelées (leur listing sera donné plus loin).

La première, ZI_ZH, a pour charge de générer la séquence d'horloge qui transfère le contenu d'une ligne de la zone image dans le registre horizontal. Une fois cette opération réalisée, la routine FAST_LINE effectue une lecture rapide du registre horizontal (sans numérisation), de manière à nettoyer ce registre avant qu'il ne reçoive le contenu de la ligne suivante :

for (i=0;i<4;i++)
   {
   zi_zh(P);
   fast_line(P);
   }

Les autres lignes sont numérisées, à concurrence de 512 lignes :

for (i=0;i<imax;i++)

La première chose à faire est de nettoyer le registre horizontal par une lecture rapide afin d'en retirer les charges thermiques qui ont pu y apparaître, alors que la ligne précédente était en cours de lecture. Ce n'est qu'ensuite que la ligne à convertir est transférée dans le registre horizontal :

fast_line(P);
zi_zh(P);

La routine READ_PEL_FAST est appelée 14 fois. Cette séquence d'instruction permet de lire rapidement (sans numérisation) les 14 premiers pixels d'une image, qui ne contiennent pas d'informations utiles pour nous :

for (j=0;j<14;j++)
   {
   read_pel_fast(P);
   }

La numérisation proprement dite des pixels de la ligne courante débute alors. Le couple d'instructions :

outp(P,247);
outp(P,255);

produit le top de reset. Rappelez-vous que les circuits d'interface d'Audine inversent tous les bits de commande. Pour  signifier à Audine que l'ensemble des bits sont à zéro, vous devez écrire 255 dans le registre de données. Dans le cas du top de reset, nous écrivons la configuration binaire suivante dans le registre : 11110111, soit 247 en décimal. Seul le bit 3 est mis à zéro dans le registre, ce qui signifie qu'au niveau de la caméra, la ligne correspondante passe à 1. Ce niveau retombe à 0 avec l'instruction suivante, outp(P,255), ce qui génère bien le top de reset.

Un délai d'attente est ménagé. Une instruction OUTP dure environ 1,7 microseconde, et ce de manière à peu près constante quelle que soit la vitesse de l'ordinateur. Ce délai laisse le temps au palier de référence de bien s'établir :

outp(P,255);
outp(P,255);
outp(P,255);
outp(P,255);
outp(P,255);

Le top de clamp est alors envoyé :

outp(P,239);   // clamp
outp(P,255);

Le palier vidéo s'établit ensuite, avec un délai d'attente suffisant pour effectuer la numérisation sur un signal bien stabilisé :

outp(P,251);   // palier vidéo
outp(P,251);
outp(P,251);
outp(P,251);

La numérisation débute alors. Le délai correspond au temps de conversion. On choisit 10 microsecondes, ce qui est correct pour le CAN AD976 :

outp(P,219);   // start convert
outp(P,219);
outp(P,219);
outp(P,219);
outp(P,219);
outp(P,219);

Les 4 nibbles sont lus avec les décalages de bits et les masquages adéquats (rappelez-vous que le bit 7 du registre de statut donne toujours une représentation inversée du signal qu'il reçoit). Entre chaque lecture de nibble, une commande est envoyée vers les circuits de multiplexage de la caméra.

// numérisation
a1=(inp(P2))>>4;
outp(P,91);
a2=(inp(P2))>>4;
outp(P,155);
a3=(inp(P2))>>4;
outp(P,27);
a4=(inp(P2))>>4;
x=(a1+(a2<<4)+(a3<<8)+(a4<<12))^0x8888;
if (x>32767) x=32767;
*(PTR--)=x;

La fonction READ_AUDINE permet de numériser l'intégralité de la matrice KAF-0400 en 15 secondes sur la plupart des PC. Voici son code complet :

/***************** read_audine *****************/
/* Audine                                      */
/*=============================================*/
/* Lecture en binning 1x1                      */
/***********************************************/
void read_audine(short base,short *buf)
{
short P,P2;
int x;
int a1,a2,a3,a4;
int i,j;
int imax,jmax;
short *PTR

imax=512;
jmax=768;

P=base;
P2=P+1;
PTR=buf;

/**** on retire les 4 premières lignes ****/
for (i=0;i<4;i++)
   {
   zi_zh(P);
   fast_line(P);
   }

for (i=0;i<imax;i++)
   {
   fast_line(P);
   zi_zh(P);
 
   /**** on retire les 14 premiers pixels ****/
   for (j=0;j<14;j++)
      {
      read_pel_fast(P);
      }

   p0=p0+imax-1;
   for (j=0;j<jmax;j++)
      {
      outp(P,247);   // reset
      outp(P,255);   // palier de référence
      outp(P,255);
      outp(P,255);
      outp(P,255);
      outp(P,255);
      outp(P,239);   // clamp
      outp(P,255);
      outp(P,251);   // palier vidéo
      outp(P,251);
      outp(P,251);
      outp(P,251);
      outp(P,219);   // start convert
      outp(P,219);
      outp(P,219);
      outp(P,219);
      outp(P,219);
      outp(P,219);

      // numérisation
      a1=(inp(P2))>>4;
      outp(P,91);
      a2=(inp(P2))>>4;
      outp(P,155);
      a3=(inp(P2))>>4;
      outp(P,27);
      a4=(inp(P2))>>4;
      x=(a1+(a2<<4)+(a3<<8)+(a4<<12))^0x8888;
      if (x>32767) x=32767;
      *(PTR--)=x;
      }
  p0=p0+jmax+1;

  /**** on retire 10 pixels à la fin ****/
  for (j=0;j<10;j++)
    {
    read_pel_fast(P);
    }
  }
}

La routine READ_AUDINE2 réalise une numérisation de l'image en binning 2x2 (le format final sera alors de 383x256 points). Cette routine est très semblable de la précédente. Attachons nous aux différences.

Bien sûr le format de l'image n'est plus le même :

imax=256;
jmax=384;

Ensuite nous transmettons 2 lignes dans le registre horizontal et non plus une seule. Ces 2 lignes s'additionnent dans le registre,  ce qui produit l'accumulation suivant l'axe vertical.

fast_line(P);
zi_zh(P);
zi_zh(P);
 
Enfin, nous lisons par paires les pixels, mais en prenant bien garde de n'envoyer le top de reset que tous les 2 pixels seulement. L'absence du top de reset, un pixel sur deux, produit l'effet désiré d'accumulation suivant l'axe horizontal, la capacité de sortie n'étant remise à zéro qu'un point image sur deux.

outp(P,247);   // reset
outp(P,255);   // palier de référence
outp(P,255);
outp(P,255);
outp(P,255);
outp(P,255);
outp(P,239);   // clamp
outp(P,255);
outp(P,251);   // palier vidéo
outp(P,255);   // palier de référence
outp(P,251);   // palier vidéo
outp(P,251);
outp(P,251);
outp(P,251);
outp(P,251);
outp(P,219);   // start convert
outp(P,219);
outp(P,219);
outp(P,219);
outp(P,219);
outp(P,219);

Ce principe de cumul des charges, soit dans le registre horizontal, soit dans la capacité de sortie est facile à étendre pour obtenir n'importe quel facteur de binning.

/**************** read_audine2 *****************/
/* Audine                                      */
/*=============================================*/
/* Lecture en binning 2x2                      */
/***********************************************/
void read_audine2(short base,short *buf)
{
short P,P2;
int x;
int a1,a2,a3,a4;
int i,j;
int imax,jmax;
short *PTR

imax=256;
jmax=384;

P=base;
P2=P+1;
PTR=buf;

/**** on retire les 4 premières lignes ****/
for (i=0;i<4;i++)
   {
   zi_zh(P);
   fast_line(P);
   }

for (i=0;i<imax;i++)
   {
   fast_line(P);
   zi_zh(P);
   zi_zh(P);
 
   /**** on retire les 14 premiers pixels ****/
   for (j=0;j<14;j++)
      {
      read_pel_fast(P);
      }

   p0=p0+imax-1;
   for (j=0;j<jmax;j++)
      {
      outp(P,247);   // reset
      outp(P,255);   // palier de référence
      outp(P,255);
      outp(P,255);
      outp(P,255);
      outp(P,255);
      outp(P,239);   // clamp
      outp(P,255);
      outp(P,251);   // palier vidéo
      outp(P,255);
      outp(P,251);   // palier vidéo
      outp(P,251);
      outp(P,251);
      outp(P,251);
      outp(P,251);
      outp(P,219);   // start convert
      outp(P,219);
      outp(P,219);
      outp(P,219);
      outp(P,219);
      outp(P,219);

      // numérisation
      a1=(inp(P2))>>4;
      outp(P,91);
      a2=(inp(P2))>>4;
      outp(P,155);
      a3=(inp(P2))>>4;
      outp(P,27);
      a4=(inp(P2))>>4;
      x=(a1+(a2<<4)+(a3<<8)+(a4<<12))^0x8888;
      if (x>32767) x=32767;
      *(PTR--)=x;
      }
  p0=p0+jmax+1;

  /**** on retire 10 pixels à la fin ****/
  for (j=0;j<10;j++)
    {
    read_pel_fast(P);
    }
  }
}
 
On trouvera ci-après le listing des fonctions élémentaires appelées par READ_AUDINE et READ_AUDINE2.

/******************** zi_zh ********************/
/* Audine                                      */
/*=============================================*/
/* Transfert zone image -> registre horizontal */
/***********************************************/
void zi_zh(short base)
{

_asm
      {
      mov dx,base
      mov al,11111011b
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      mov al,11111010b
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      mov al,11111001b
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      mov al,11111010b
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      mov al,11111011b
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      out dx,al
      }
}

/**************** read_pel_fast ****************/
/* Audine                                      */
/*=============================================*/
/* Lecture rapide d'un pixel                   */
/***********************************************/
void read_pel_fast_inv(short base)
{

_asm
      {
      mov dx,base
      mov al,11110111b
      out dx,al
      mov al,11111111b
      out dx,al
      mov al,11111011b
      out dx,al
      }
}

/********* fast_line **********/
/* Audine                     */
/*============================*/
/* Lecture rapide d'une ligne */
/******************************/
void fast_line(short base)
{
short j;

for (j=0;j<794;j++)
   {
   read_pel_fast(base);
   }
}

La routine suivante, FAST_VIDAGE est utilisée pour lire rapidement l'ensemble de la matrice CCD afin de supprimer toute charge électrique juste avant de commencer une phase d'intégration. Lorsque vous voyez apparaître le statut RAZ dans PISCO, juste avant que le décompte du temps d'intégration ne débute, c'est cette routine qui est appelée 3 ou 4 fois de suite. Notez que pour augmenter la vitesse, on procède à un binning d'un facteur 4 dans le registre horizontal, ce qui n'oblige à lire que 130 lignes environ.

/*********** fast_vidage ************/
/* Audine                           */
/*==================================*/
/* Lecture rapide de la matrice     */
/************************************/
void fast_vidage(short base)
{
short i,j;

/*---- LECTURE DU REGISTRE HORIZONTAL ----*/
for (i=0;i<130;i++)
   {
   zi_zh(base);
   zi_zh(base);
   zi_zh(base);
   zi_zh(base);
   for (j=0;j<794;j++)
      {
      read_pel_fast(base);
      }
   }
}

La routine suivante, READ_AUDINE_DOUBLE_ADC, illustre la souplesse de fonctionnement de la caméra Audine. La différence entre le palier vidéo et le palier de référence n'est plus effectuée par une méthode analogique (le circuit de clamp), mais par une méthode purement logicielle. La routine réalise deux numérisations, une sur le palier de référence, une autre sur le palier vidéo. Le signal vidéo proprement dit est la différence de ces deux conversions. Attention au AD976, la valeur numérique qu'il délivre est en fait le résultat de la conversion précédant celle que vous venez de réaliser. Il faut en tenir compte (avec ce listing, qui présente une routine expérimentale, le premier pixel de chaque ligne image n'a pas une valeur valide à cause de cela). Notez que le circuit de clamp n'est jamais activé lors de la lecture de l'image.

/********** read_audine_double_ADC *************/
/* Audine                                      */
/*=============================================*/
/* Lecture en binning 1x1                      */
/* et double échantillonnage numérique         */
/***********************************************/
void read_audine_double_ADC(short base,short *buf)
{
short P,P2;
int x,x1,x2;
int a1,a2,a3,a4;
int i,j;
int imax,jmax;
short *PTR

imax=512;
jmax=768;

P=base;
P2=P+1;
PTR=buf;

/**** on retire les 4 premières lignes ****/
for (i=0;i<4;i++)
   {
   zi_zh(P);
   fast_line(P);
   }

for (i=0;i<imax;i++)
   {
   fast_line(P);
   zi_zh(P);
 
   /**** on retire les 14 premiers pixels ****/
   for (j=0;j<14;j++)
      {
      read_pel_fast(P);
      }

   p0=p0+imax-1;
   x2=0;
   for (j=0;j<jmax;j++)
      {
      outp(P,247);   // reset
      outp(P,255);   // palier de référence
      outp(P,255);
      outp(P,255);
      outp(P,255);
      outp(P,255);
      outp(P,223);   // start convert sur la palier de référence
      outp(P,223);
      outp(P,223);
      outp(P,223);
      outp(P,223);
      outp(P,223);

      // numérisation
      a1=(inp(P2))>>4;
      outp(P,95);
      a2=(inp(P2))>>4;
      outp(P,159);
      a3=(inp(P2))>>4;
      outp(P,31);
      a4=(inp(P2))>>4;
      x1=(a1+(a2<<4)+(a3<<8)+(a4<<12))^0x8888;
      if (x1>32767) x1=32767;
      x=x1-x2;       // double échantillonnage numérique

      outp(P,255);
      outp(P,251);   // palier vidéo
      outp(P,251);
      outp(P,251);
      outp(P,251);
      outp(P,219);   // start convert sur le palier vidéo
      outp(P,219);
      outp(P,219);
      outp(P,219);
      outp(P,219);
      outp(P,219);

      // numérisation
      a1=(inp(P2))>>4;
      outp(P,91);
      a2=(inp(P2))>>4;
      outp(P,155);
      a3=(inp(P2))>>4;
      outp(P,27);
      a4=(inp(P2))>>4;
      x2=(a1+(a2<<4)+(a3<<8)+(a4<<12))^0x8888;
      if (x2>32767) x2=32767;
      *(PTR--)=x;
      }
  p0=p0+jmax+1;

  /**** on retire 10 pixels à la fin ****/
  for (j=0;j<10;j++)
    {
    read_pel_fast(P);
    }
  }
}
 

L'extrait de routine suivant montre comment il faut s'y prendre pour mettre en oeuvre le mode drift-scan. Il faut fournir la dimension du scan (NB_LIGNE) ainsi que le délai en secondes entre deux lectures de lignes consécutives (DELAI). La fonction CLOCK() retourne le temps courant absolu en centièmes de secondes.

imax=nb_ligne;
jmax=768;

/**** on supprime les 4 premières lignes ****/
for (i=0;i<4;i++)
   {
   zi_zh_13(P);
   fast_line(P);
   }

n=1.0;
first=clock();

i=0;
while(1)
   {
   courant=clock()-first;
   if ((double)courant/100.0>=n*delai)
      {
      fast_line(P);
      zi_zh_13(P);

      /**** on retire les 14 premiers pixels ****/
      for (j=0;j<14;j++)
         {
         read_pel_fast(P);
         }

      p0=p0+jmax-1;
      for (j=0;j<jmax;j++)
         {
         outp(P,247);   // reset
         outp(P,255);   // palier de référence
         outp(P,255);
         outp(P,255);
         outp(P,255);
         outp(P,255);
         outp(P,239);   // clamp
         outp(P,255);
         outp(P,251);   // palier vidéo
         outp(P,251);
         outp(P,251);
         outp(P,251);
         outp(P,219);   // start convert
         outp(P,219);
         outp(P,219);
         outp(P,219);
         outp(P,219);
         outp(P,219);

         // numérisation
         a1=(inp(P2))>>4;
         outp(P,91);
         a2=(inp(P2))>>4;
         outp(P,155);
         a3=(inp(P2))>>4;
         outp(P,27);
         a4=(inp(P2))>>4;
         x=(a1+(a2<<4)+(a3<<8)+(a4<<12))^0x8888;
         if (x>32767) x=32767;
         *(PTR--)=x;
         }
      p0=p0+jmax+1;

      /**** on retire 10 pixels à la fin ****/
      for (j=0;j<10;j++)
         {
         read_pel_fast(P);
         }
      i++;

     /* Gérer ici l'arret de l'acquisition en continu par l'appui d'une touche
        imax=i-1;
        ...
        ...
     */
     if (i==imax)
        {
        /* Arrèter ici l'acquisition en continu
           ...
           ...
        */
        }

     /* on affiche ici l'écart en seconde avec le temps nominal devant s'écouler entre
        la lecture de 2 lignes. Notez qu'un écart éventuel est compensé lors de la
        lecture de la ligne suivante.
     */
      sprintf(text,"ligne:%d   (delta T:%.3f)",i,(double)courant/100.0-n*delai);
      printf("%s\n",text);
      n=n+1.0;
      }
   }

L'extrait de routine qui suit montre comment il faut s'y prendre pour mettre en oeuvre le mode vidéo. La routine lit à grande vitesse une zone verticale de l'image, centrée sur la colonne POSX et de largeur TAILLE. L'acquisition fait NB_LIGNE de hauteur.

imax=nb_ligne;
jmax=taille;

/**** on retire les 4 premières lignes ****/
for (i=0;i<4;i++)
   {
   zi_zh(P);
   fast_line(P);
   }

posx=768-posx;
cx1=posx-taille/2+14;
cx2=768-(cx1+taille-10);

i=0;
while(1)
   {
   while(1)
      {
      for (k=0;k<taille;k++) zi_zh(P);
      fast_line(P);

      /* gérer ici le temps d'intégration (bref en mode vidéo)
         .....
         .....
      */

      for (k=0;k<taille;k++) zi_zh(P);
      fast_line(P);

      for (k=0;k<taille;k++)
         {
         zi_zh(P);
 
         /**** on retire les cx1 premiers pixels ****/
         for (j=0;j<cx1;j++)
            {
            read_pel_fast(P);
            }

         p0=p0+imax-1;
         for (j=0;j<jmax;j++)
            {
            outp(P,247);   // reset
            outp(P,255);   // palier de référence
            outp(P,255);
            outp(P,255);
            outp(P,255);
            outp(P,255);
            outp(P,239);   // clamp
            outp(P,255);
            outp(P,251);   // palier vidéo
            outp(P,251);
            outp(P,251);
            outp(P,251);
            outp(P,219);   // start convert
            outp(P,219);
            outp(P,219);
            outp(P,219);
            outp(P,219);
            outp(P,219);

            // numérisation
            a1=(inp(P2))>>4;
            outp(P,91);
            a2=(inp(P2))>>4;
            outp(P,155);
            a3=(inp(P2))>>4;
            outp(P,27);
            a4=(inp(P2))>>4;
            x=(a1+(a2<<4)+(a3<<8)+(a4<<12))^0x8888;
            if (x>32767) x=32767;
            *(PTR--)=x;
         }
      p0=p0+jmax+1;
      /**** on retire cx2 pixels à la fin ****/
      for (j=0;j<cx2;j++)
         {
         read_pel_fast(P);
         }
         i++;
     /* Gérer ici l'arret de l'acquisition en continu par l'appui d'une touche
        imax=i-1;
        ...
        ...
     */
     if (i==imax)
        {
        /* Arrèter ici l'acquisition en continu
           ...
           ...
        */
        }
     }
  }