PROGRAMMING THE AUDINE CAMERA

Any acquisition program driving the Audine camera will include 2 types of function :

The description of the high-level functions is beyond the scope of this document. The topic is indeed too vast and the code too dependant of the intent one has in an acquisition software. Please note that the programming language selection is secondary for those functions for which the priority is not the speed of execution.

On the other hand, particular care should be applied in writing the low-level routines because the reading performance in terms of speed depends critically on them. A good compiler is needed, and eventually some routines should be coded in machine code.

The program PISCO is a good example. The interface is written in Visual Basic 6.0, a tool particularly well suited for this purpose. The reading functions of the CCD are on the other hand confined in a DLL library written in C (Visual C++ 6.0).

Therefore the following informations concern primarily skilled programmers who would like to drive the Audine camera from their software.

The table hereunder is a summary of the meaning of the 8 bits of the parallel port data register.

 

BIT
FONCTION
Bit 0 Clock V1
Bit 1 Clock V2
Bit 2 Clocks H1 and H2
Bit 3 Clock R
Bit 4 Clamp Clock
Bit 5 Start of the CAN conversion
Bit 6 Multiplexing at the CAN stage 
Bit 7 Multiplexing at the 74HCT157 level

 
Besides, the 4 main significant bits (MSB) of the parallel port state register are used to receive in 4 passes the 16 bits-word corresponding to one pixel reading.

The C routine READ_AUDINE contains the code for the standard reading of the Audine camera reading in binning 1x1 (the final digital image has a size of 768x512 pixels).

The two parameters of READ_AUDINE are the base address of the parallel port and a pointer to a buffer alocated over a memory zone of a size sufficient to receive the image (in the example case 2 x 768 x 512 = 786432 bytes at least will be needed, the image having a depth of 16 bit).

The variable P contains the address of the data register and the variable P2 contains the address of the state register.

One has to get rid of the 4 first lines of the CCD image because those are masked from the direct !? Ligth exposure and have few interest in astronomy. Two functions are called (see further for their listing).

The first function, ZI_ZH, generates the clock signal which transfers the contents of one line of the image area in the horizontal register. Once this is realized, the routine FAST_LINE performs a fast reading of the horizontal register (without digitization) so as to clean this register and allow it to receive the contents of the next line:

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

The other lines are digitized up to the 512-th:

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

The first thing to do is to clean horizontal register by a fast reading so as to remove from it the thermal loads that might have built in it while the previous line was read. Only then the line to-be converted is transferred to the horizontal register:

fast_line(P);
zi_zh(P);

The routine READ_PEL_FAST is called 14 times. This sequence of instructions allows to quickly read (without digitization) the first 14 pixels of an image which do not contain any interesting information for us:

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

The digitisation of the pixels of the current line can then start. The pair of instructions :

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

issues the reset signal. Please keep in mind that the interface circuitry of the Audine camera invert all command bits. To tell to Audine that all the bits are zero one must write 255 in the date register. In the case of the reset signalwe write the following binary number in the register: 11110111, which is 247 decimal. Only the bit 3 is set to zero in the register which means that at the camera level, the corresponding line passes to 1. This status gets back to 0 with the next instruction, outp(P,255), which effectively generates the reset signal.

A waiting delay is foreseen. On OUTP instruction lasts about 1.7 microsecond, which rules about constantly whatever the computer speed is. This delay leaves some time for the reference level to settle:

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

The clamp signal is then sent:

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

The video signal takes then place, with a waiting delay sufficient to perform the digitization of a well stabilized signal:

outp(P,251);   // video level
outp(P,251);
outp(P,251);
outp(P,251);

The digitization then starts. The delay corresponds to the conversion duration. We choose 10 microseconds which is correct for the CAN AD976 :

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

The 4 nibbles are read with bit shifts and the adequate masks (please keep in mind that the bit 7 of the status register always gives the inverse representation of what it receives). Between each reading of the nibble, one command is sent to the multiplexing circuitry of the camera.

// digitization
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;

The function READ_AUDINE allows to digitize the whole array of the KAF-0400 in 15 seconds on most PCs. Here is its complete code :

/***************** 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);
    }
  }
}

The routine READ_AUDINE2 performs a digitization of the image in binning 2x2 (thus the final format will be 383x256 pixels). This routine is very similar to the previous one. Let us exhibit the differences.

Of course the format of the image is not the same anymore:

imax=256;
jmax=384;

Then we transmit two lines in the horizontal register instead of a single one previously. Those 2 lines get added in the register which realizes a cumulative effect in the vertical direction.

fast_line(P);
zi_zh(P);
zi_zh(P);
 
Then we read pixels two at a time, caring of course to send the reset signal only after 2 pixels. The absence of the reset signal, each second pixel, gives the desired effect of accumulation along the horizontal axis, the output capacitance being reset to zero only each second image point.

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);

This principle of charge accumulation, either in the horizontal register, or the output capacitance, can easily be applied to obtain any binning factor.

/**************** read_audine2 *****************/
/* Audine                                &n */
/*=============================================*/
/* 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);
    }
  }
}
 
One can find hereunder the listing of the basic functions called by READ_AUDINE and 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);
   }
}

The next routine, FAST_VIDAGE is used to quickly read the whole CCD array so as to remove any electric charge before to begin an integration phase. When one sees the status RAZ in the PISCO software just before the start of the integration, this routine is then called 3 or 4 consecutive times. Please note that to increase the performance one uses a binning factor of 4 in the horizontal register, which consequently requires to read only about 130 lines.

/*********** 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);
      }
   }
}

The next routine, READ_AUDINE_DOUBLE_ADC, illustrates the performance flexibility of the Audine camera. The difference between the video leveland the reference levelis not performed anymore following an analogic procedure(the clamp circuit), but purely by software. The routine performs two digitization's, one over the reference level, another over the video level. The video signal itself results from the difference of those two converted signals. One has to keep in mind that the numerical value supplied by the AD976 in fact results from the conversion preceding the current one. This fact has to be accounted for (which is why, in the listing which shows an experimental routine, the first pixel of each line has novalid contents). Please also note that the clamp circuitry is never activated during the image reading.

/********** 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);
    }
  }
}
 

The following piece of code shows how to make use of the drift-scan mode. One has to supply the scan size (NB_LIGNE) as well as the delay in seconds between 2 consecutive line readings (DELAI). The CLOCK() function returns the absolute current time in hundreds of seconds.

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;
      }
   }

The following piece of code shows how to make use of he video mode. The routine reads at an high speed a vertical area of the image, centered on the column POSX and of width TAILLE. The acquisition has an height of NB_LIGNE.

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
           ...
           ...
        */
        }
     }
  }