Un programme d'acquisition commandant la caméra Audine comportera 2 types de fonctions :
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 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 |
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
...
...
*/
}
}
}
|
|
|