// UTS 2000 - Fichier 'serial_port.cpp' gestion du port série et des trames échangées.
// V3.3 - Le 19/12/2010
//
// Suivant si la variable 'VERSION_UTS_V3' est définie, on compile la partie V2.0 ou V3.3 de ce programme.
// Ainsi on compile un exécutable pour les cartes UTS 2.0 à 8 Mhz ou les cartes UTS V3.3 à 20 Mhz.
//
// Contrôle de l'intégrité des trames recues.
// Réenvoi automatique de trame en cas d'erreur.
// Reprise automatique de la connexion sur perte de liaison < 10 secondes.
// Reprise manuelle sur demande par fenêtre de dialogue.
/*****************************************************************************/
/* LIAISON SERIE                                                             */
/*****************************************************************************/
#include  <windows.h>
#include  <string.h>
#include  <stdlib.h>
#include  <stdio.h>
#include  "Train_UTS.h"
#include  "serial_port.h"

#define _CRT_SECURE_NO_WARNINGS  // Limite les messages de warning dans Visual C++

// Déclaration des variables externes
extern int vio_aig_cmd[NB_AIG];
extern int vio_aig_pos0[NB_AIG];
extern int vio_aig_pos1[NB_AIG];
extern int vio_aig_res[NB_AIG];

extern int vio_garage_lock_res[NB_GARAGE]; // 0 ou 1, 1 => Garage réservé.
extern int vio_garage_lock_cmd[NB_GARAGE]; // 0 ou 1, 1 => Demande d'isolation du garage (Sera réalisée si il a un train sur le canton).
extern int vio_garage_lock_status[NB_GARAGE]; // 0 ou 1, 1 => Le garage est isolé du canton (état réel).

extern int cfg_enable_leds[NB_CARTE];

extern int vio_det[NB_CARTE][NB_CANTON];
extern int vio_canton_vit[NB_CARTE][NB_CANTON];      //UU: n'en garder qu'un et mettre en debug total
extern int vio_canton_vit_profil[NB_CARTE][NB_CANTON];      //UU: n'en garder qu'un et mettre en debug total
extern int cfg_canton_inv[NB_CARTE][NB_CANTON];
extern int serial_debug_canton_vit[NB_CARTE][NB_CANTON];
extern int serial_debug_select_canton_vit[NB_CARTE][NB_CANTON];

extern int vio_out[NB_CARTE][NB_OUT];
extern int serial_debug_out[NB_CARTE][NB_OUT];
extern int serial_debug_select_out[NB_CARTE][NB_OUT];

extern int vio_in[NB_CARTE][NB_IN];

extern int vio_led[NB_CARTE][NB_LED];
extern int vio_led_cli[NB_CARTE][NB_LED];
extern int vio_led_phase[NB_CARTE][NB_LED];
extern int serial_debug_led[NB_CARTE][NB_LED];
extern int serial_debug_led_cli[NB_CARTE][NB_LED];
extern int serial_debug_led_phase[NB_CARTE][NB_LED];
extern int serial_debug_select_led[NB_CARTE][NB_LED];

extern int vio_srv[NB_CARTE][NB_SERVO];
extern int vio_srv_res[NB_CARTE][NB_SERVO];
extern int serial_debug_srv[NB_CARTE][NB_SERVO];
extern int serial_debug_select_srv[NB_CARTE][NB_SERVO];

extern int vio_srv_cmd[NB_CARTE][NB_SERVO+NB_SERVO_VIT];

extern int vio_can[NB_CARTE][NB_CAN];
extern int cfg_can_inv[NB_CARTE][NB_CAN];
extern int cmd_divers[CMD_DIVERS_TAILLE];   // Tableau pour commander directement les déplacements par clic sur les cases du TCO


int serial_tab_cmd[4][5]={ {'x','x','x','x','x'}, {'x','x','x','x','x'}, {'x','x','x','x','x'}, {'x','x','x','x','x'} };  //actions demandees à la maj série.
int serial_tab_adresse[4][5]={ {0,0,0,0,0}, {0,0,0,0,0}, {0,0,0,0,0}, {0,0,0,0,0} };
int serial_tab_data[4][5]=   { {0,0,0,0,0}, {0,0,0,0,0}, {0,0,0,0,0}, {0,0,0,0,0} };
int tab_auto_read[4][4]=     { {0,0,0,0},   {0,0,0,0},   {0,0,0,0},   {0,0,0,0}   };
int serial_error=0;

extern int cfg_show_cards_found;
#ifndef VERSION_UTS_V3 // Ne pas toucher à cette ligne !!!
extern int cfg_enable_servos[NB_CARTE][NB_SERVO];
#endif


cSerialPort serialPort;

/* protocole
; 0 sync
; 1 reply 83
; 2 set   current adr
; 3 write data at current adr
; 4 read  data at current adr
; 5 increment current adr and write data at current adr
; 6 increment current adr and read  data at current adr
; 7 increment current adr and read  data at current adr and clear current register
; 8-15 total transfere
; 16-255 only the 4 LSB are processed
;
; rx    0    1    2    3    4    5    6     7     8
; tx         83             data      +data +data
;
; rx2             adr  data      +data            data
; tx2                                             data
*/
/* adresses des registres du uc de la carte
;*** adresse des variables (bank 0) ***

20  pwm_r0   ; Valeur de la vitesse sur le canton 0 (= 0 à 64)
21  pwm_r1    ;     //  1
22  pwm_r2    ;     //  2
23  pwm_r3    ;     //  3
24  pwm_r4    ;
25  pwm_r5    ;
26  pwm_r6    ;
27  pwm_r7    ;
28  pwm_r8    ;
29  pwm_r9    ;
2A  pwm_r10   ;
2B  pwm_r11   ;
2C  pwm_r12   ;
2D  pwm_r13   ;
2E  pwm_r14   ;
2F  pwm_r15   ; 
30  pwm_s0_7  ; Valeur sens de déplacement 1 bit /canton 0 à 7
31  pwm_s8_15 ; Valeur sens de déplacement 1 bit /canton 8 à 15
32  out_out3  ; Valeur des bits des sorties   1 à  7  (IC14-ULN2008 [1])
33  out_out4  ;   //                          8 à 15  (IC15-ULN2008 [1])
34  out_out5  ;   //                         16 à 23  (IC16-ULN2008 [1])
35  out_out21 ; Valeur des bits des sorties           (ULN2008 [2]) V3.3
36  out_out22 ;   //
37  out_out23 ;   //
38  out_out24 ;   //
39  out_out25 ;   //
3A  led_on0  ; Leds 00-07 allumées             (1bit = une led)
3B  led_on1  ; Leds 12-19 allumées             ( bit = 1 = on )
3C  led_on01 ; Leds 08-11 et 20-23 allumées
3D  led_on2  ; Leds 24-31 allumées
3E  led_on3  ; Leds 36-43 allumées
3F  led_on23 ; Leds 32-35 et 43-47 allumées
40  led_cl0  ; Leds 00-07 clignotantes         (1bit = une led)
41  led_cl1  ; Leds 12-19 clignotantes         ( bit = 1 = cli)
42  led_cl01 ; Leds 08-11 et 20-23 clignotantes
43  led_cl2  ; Leds 24-31 clignotantes
44  led_cl3  ; Leds 36-43 clignotantes
45  led_cl23 ; Leds 32-35 et 43-47 clignotantes
46  led_in0  ; Leds 00-07 clignotant inversé    (1bit = une led)
47  led_in1  ; Leds 12-19 clignotant inversé    ( bit = 1 = inv)
48  led_in01 ; Leds 08-11 et 20-23 clignotant inversé
49  led_in2  ; Leds 24-31 clignotant inversé
4A  led_in3  ; Leds 36-43 clignotant inversé
4B  led_in23 ; Leds 32-35 et 43-47 clignotant inversé
4C  srv_0    ; Valeur de consigne du servos 0
4D  srv_1    ;     //                      1
4E  srv_2    ;     //                      2
4F  srv_3    ;     //                      3
50  srv_4    ;     //                      4
51  ruf1     ; LIBRE réservé utilisation future
52  ruf2     ; LIBRE réservé utilisation future
53  ruf3     ; LIBRE réservé utilisation future



; --------VARIABLES EN LECTURE  -----------
54  can_r0   ; Valeur lue sur l'entrée analogique (0) ( = 0 à 255 )
55  can_r1   ;   //                               (1)
56  can_r2   ;   //                               (2)
57  can_r3   ;   //                               (3)
58  can_r4   ;   //                               (4)
59  in_in1   ; Valeurs des bits des 40 entrées (75HCT165 [2]) V3.3
5A  in_in2   ;   //
5B  in_in3   ;   //
5C  in_in4   ;   //
5D  in_in5   ;   //
5E  det_i1   ; Un bit par canton, = 1 si le canton est occupé
5F  det_i2   ;   //
60  ruf3     ; LIBRE réservé utilisation future


; --------VARIABLES EN LECTURE ET EFFACEMENT -----------
61  det_i1m  ; Un bit par canton, le passage à l'état 1 est mémorisé
62  det_i2m  ; et effacé par la lecture de ce registre.
63  mat_in0  ; 4bits d'entrées colonne INA + ligne IN3 à IN0 + 4 bits mémorisation des
64  mat_in1  ;   //  INB + ligne IN3 à IN0                       états, effacés par
65  mat_in2  ;   //  INC + ligne IN3 à IN0                       la lecture de ces 
66  mat_in3  ;   //  IND + ligne IN3 à IN0                       registres.
67  mat_in4  ;   //  INE + ligne IN3 à IN0
68  mat_in5  ;   //  INF + ligne IN3 à IN0
69  mat_in6  ;   //  ING + ligne IN3 à IN0
6A  mat_in7  ;   //  INH + ligne IN3 à IN0
6B  mat_in8  ;   //  INI + ligne IN3 à IN0
;
;
; -------VARIABLE DE CALCULS INTERNES AU PIC -----------
6D  cpt      ; Compteur incrémenté à chaque interruption du Timer0
6E  cpt2     ; Compteur incrémenté tous les 256 interruptions du Timer0 (cpt)
6F  rs_adr   ; Variable = adresse du registre à lire/écrire = 0x20, 0x21, 0x22,,,
70  rs_mode  ; Type de traitement en réception d'un caractère, bit(7)= cmd / données
71  pwm_cpt  ;
72  out_out1 ; Valeur calculée dans le PIC des bits des leds 1 à  7 et validation des 4094
73  out_out2 ; Valeur calculée dans le PIC des bits des leds 8 à 11 + colonnes A à D (IC13)
74  out_pwm1 ; Valeur calculée dans le PIC de la commande des L293D (Alimentation cantons)
75  out_pwm2 ;      //    cantons  4- 7       (vitesse et sens)
76  out_pwm3 ;      //    cantons  8-11
77  out_pwm4 ;      //    cantons 12-15
78  cpt_srv  ; Numéro de servo actif bits(3,2,1) +  bit indicateur bit(0) =
79  var1_spg ; Variable temporaire n°1 utilisable dans tous les sous-programmes
7A  var2_spg ; Variable temporaire n°2 utilisable dans tous les sous-programmes
7B  it_err   ; Si le Timer0 TMR0 a débordé c'est une erreur, (it_err++)
7C  libre1   ; !!!! REGISTRE LIBRE  !!!!
7D  libre2   ; !!!! REGISTRE LIBRE  !!!!
7E  libre3   ; !!!! REGISTRE LIBRE  !!!!
7F  libre4   ; !!!! REGISTRE LIBRE  !!!!

Données en BANK1 de 0xA0 à 0xEF (80 adresses) car plus de place en BANK0
A0   det_i1_1  ; Octet de détection des cantons 0 à 7
A1   det_i1_2  ;    // coup d'avant
A2   det_i1_3  ;    // coup d'avant - avant
A3   det_i1_4  ;    // 
A4   det_i1_5  ;    // 
A5   det_i1_6  ;    // 
;
A6   det_i2_1  ; Octet de détection des cantons 8 à 15
A7   det_i2_2  ;    // coup d'avant
A8   det_i2_3  ;    // coup d'avant - avant
A9   det_i2_4  ;    // 
AA   det_i2_5  ;    // 
AB   det_i2_6  ;    // 
*/

/*------------------------------------------------------------------------------
NAME  : cSerial::cSerial
DESCR : constructeur de la classe serial.
        Désactive le module rs232.
        Il sera activé pendant init
ARG   : void, RETURN:, NOTES :
------------------------------------------------------------------------------*/
cSerial::cSerial(void)
{	enable=0;}

/*------------------------------------------------------------------------------
NAME  : init
DESCR : Essaye de trouver la carte sur COM1 ou COM2
ARG   : void
RETURN: 0=carte non trouvée, 1=carte sur COM1, 2=carte sur COM2 ...
NOTES : l'OS ferme automatiquement les ports lorsque l'application se termine
------------------------------------------------------------------------------*/
int cSerial::init(int card_adr_, int card_enable)
{	enable=card_enable;
	card_adr=card_adr_;
	return 0; }

/*------------------------------------------------------------------------------
NAME  : cSerial::maj
DESCR : Fonction principale de transfert PC-PIC.
        Enchaine:  /1 Variables UTS de sorties -> Trame à envoyer, 
                   /2 Transfert PC<->PIC, 
				   /3 Trame reçue -> Variables UTS d'entrées.
ARG   : void, RETURN: void, NOTES :
------------------------------------------------------------------------------*/
void cSerial::maj(void)
{
	int i;
	char  szErr[200];
	if(serialPort.enable==0) return;
	if(enable==0) return;	// Liaison active ?, non !
	import_tab_out();
	for(i=0;i<NB_CARTE;i++){
		if(transfer()==0) break;
		sprintf(szErr,"Gros Problème avec la liaison série (carte %d)",card_adr);
		if(i==3){ MessageBox(NULL,szErr,"UTS - Erreur",MB_OK|MB_ICONERROR); enable=0;	}
	}
	export_tab_in();
}
#ifdef VERSION_UTS_V3
/*------------------------------------------------------------------------------
NAME  : cSerial::import_tab_out
DESCR : Met à jour la trame de données à envoyer au PIC en fonction 
        des variables utilisateurs  UTS.
ARG   : void, RETURN: void, NOTES :
------------------------------------------------------------------------------*/
int get_canton_vit(int card, int i)    { if(serial_debug_select_canton_vit[card][i]==0) return vio_canton_vit_profil[card][i]; else return serial_debug_canton_vit[card][i];}
int get_out_val(int card, int i)       { if(serial_debug_select_out[card][i]==0)        return vio_out[card][i];        else return serial_debug_out[card][i];       }
int get_led_val(int card, int i)       { if(serial_debug_select_led[card][i]==0)        return vio_led[card][i];        else return serial_debug_led[card][i];       }
int get_led_cli_val(int card, int i)   { if(serial_debug_select_led[card][i]==0)        return vio_led_cli[card][i];    else return serial_debug_led_cli[card][i];   }
int get_led_phase_val(int card, int i) { if(serial_debug_select_led[card][i]==0)        return vio_led_phase[card][i];  else return serial_debug_led_phase[card][i]; }
// Passage de 0-1023 à 0-255 pour la valeur de consigne des servo-moteur. valeur>>2 = valeur/4
int get_srv(int card, int i)           { if(serial_debug_select_srv[card][i]==0)        return vio_srv[card][i]>>2;     else return serial_debug_srv[card][i];       }
void cSerial::import_tab_out(void)
{
	int i,j,data,adr;
	if(enable==0) return;
/* Remplie la trame d'envoi avec le contenu des variables */
// pwm + sens
	adr=0x20;

	if (cmd_divers[CMD_DIVERS_OPT4_ARRET]==1 ) {    // si cmd_divers[CMD_DIVERS_OPT4_ARRET]=1, Arrêt d'urgence des trains, alimentation des cantons à zéro.
		for(i=0;i<NB_CANTON;i++) {
			tab_out[adr++]=0;  // =0 quelque soit le numéro de carte on rempli la trame.
		}

	}
	else {
		for(i=0;i<NB_CANTON;i++) {
			if(get_canton_vit(card_adr,i)>=0) tab_out[adr++]= get_canton_vit(card_adr,i);
			else                              tab_out[adr++]=-get_canton_vit(card_adr,i);
		}
	}

	for(i=0;i<2;i++) {
		data=0;
		if(get_canton_vit(card_adr,8*i+0)>=0) data|=1;
		if(get_canton_vit(card_adr,8*i+1)>=0) data|=2;
		if(get_canton_vit(card_adr,8*i+2)>=0) data|=4;
		if(get_canton_vit(card_adr,8*i+3)>=0) data|=8;
		if(get_canton_vit(card_adr,8*i+4)>=0) data|=16;
		if(get_canton_vit(card_adr,8*i+5)>=0) data|=32;
		if(get_canton_vit(card_adr,8*i+6)>=0) data|=64;
		if(get_canton_vit(card_adr,8*i+7)>=0) data|=128;
		
        if(cfg_canton_inv[card_adr][8*i+0]) data^=0x01;
        if(cfg_canton_inv[card_adr][8*i+1]) data^=0x02;
        if(cfg_canton_inv[card_adr][8*i+2]) data^=0x04;
        if(cfg_canton_inv[card_adr][8*i+3]) data^=0x08;
        if(cfg_canton_inv[card_adr][8*i+4]) data^=0x10;
        if(cfg_canton_inv[card_adr][8*i+5]) data^=0x20;
        if(cfg_canton_inv[card_adr][8*i+6]) data^=0x40;
        if(cfg_canton_inv[card_adr][8*i+7]) data^=0x80;
		
		tab_out[adr++]=data;
    	}

// Sorties n° 00 à 23 = out3, out4 et out5
	adr=0x32;
	for(j=0;j<3;j++) {
		data=0;
		for(i=0;i<8;i++) if(get_out_val(card_adr,8*j+i)) data|=1<<i;
		tab_out[adr]=data;
		adr++;
	}

// Sorties[2] n° 40 à 79 = out21, out22, out23, out24 et out25
	adr=0x35;
	for(j=0;j<5;j++) {
		data=0;
		for(i=0;i<8;i++) if(get_out_val(card_adr,8*j+i+40)) data|=1<<i;
		tab_out[adr]=data;
		adr++;
	}

// Sortie n° 24 à 39 -> (si elles sont utilisees a la place des leds en V3.3 car 'proc_on' n'existe plus)
// Remplacement des leds par des sorties. Plus utilisé en V3.3 LED obligatoires
    
// LEDS multiplexées
	adr=0x3A;
	for(j=0;j<2;j++) {
		data=0; for(i=0;i<8;i++) if(get_led_val(card_adr,     i+24*j)) data|=1<<i;     tab_out[adr]=data; adr++;
		data=0; for(i=0;i<8;i++) if(get_led_val(card_adr,  12+i+24*j)) data|=1<<i;     tab_out[adr]=data; adr++;
		data=0; for(i=0;i<4;i++) if(get_led_val(card_adr,   8+i+24*j)) data|=1<<i;
		for(i=0;i<4;i++) if(get_led_val(card_adr,12+8+i+24*j)) data|=1<<(i+4); tab_out[adr]=data; adr++;
	}
	for(j=0;j<2;j++) { //led clignotement (mettre à '0' pour un clignotement)
		data=0; for(i=0;i<8;i++) if(get_led_cli_val(card_adr,     i+24*j)==0) data|=1<<i;     tab_out[adr]=data; adr++;
		data=0; for(i=0;i<8;i++) if(get_led_cli_val(card_adr,  12+i+24*j)==0) data|=1<<i;     tab_out[adr]=data; adr++;
		data=0; for(i=0;i<4;i++) if(get_led_cli_val(card_adr,   8+i+24*j)==0) data|=1<<i;
		for(i=0;i<4;i++) if(get_led_cli_val(card_adr,12+8+i+24*j)==0) data|=1<<(i+4); tab_out[adr]=data; adr++;
	}
	for(j=0;j<2;j++) { //led inversion de phase de clignotement
		data=0; for(i=0;i<8;i++) if(get_led_phase_val(card_adr,     i+24*j)) data|=1<<i;     tab_out[adr]=data; adr++;
		data=0; for(i=0;i<8;i++) if(get_led_phase_val(card_adr,  12+i+24*j)) data|=1<<i;     tab_out[adr]=data; adr++;
		data=0; for(i=0;i<4;i++) if(get_led_phase_val(card_adr,   8+i+24*j)) data|=1<<i;
		for(i=0;i<4;i++) if(get_led_phase_val(card_adr,12+8+i+24*j)) data|=1<<(i+4); tab_out[adr]=data; adr++;
	}

// Servo0 à Servo4 = servo0, servo1, servo2, servo3 et servo4
	adr=0x4C;
	for(j=0;j<NB_SERVO;j++) {
		data = get_srv(card_adr,j);
		tab_out[adr]=data;
		adr++;
	}
// Libre, réservé pour une utilisation future dans les prochaines versions
	adr=0x51; 	tab_out[adr]=0x12; // Pour test
	adr=0x52;	tab_out[adr]=0x34; // Pour test
	adr=0x53;	tab_out[adr]=0x56; // Pour test
}

/*------------------------------------------------------------------------------
NAME  : cSerial::transfer
DESCR : Fait un transfert de trame de données entre le PC et le PIC.
        Envoit 'tab_out' et reçoit 'tab_in'.
ARG   : void
RETURN: 0: ok, 1: PB de lecture
NOTES : Utilise la fonction cSerial::read pour une lecture securisee
------------------------------------------------------------------------------*/
int cSerial::transfer(void)
{
	char  szErr[200];
	int i;
	if(serialPort.enable==0) return 0;
	if(enable==0)            return 0;	// Liaison active ?, non !

/* Transfert normal =  
 1 / Envoi d'une trame de 55 octets = octet '0x01' "sélection de carte" 
                                    + octet '0x08' "Cmd global de transfert"
                                    + 52 octets de données vers le PIC
                                    + octet '0x00' de synchro

 2 / Réception en même temps de 25 octets = octet '0x83' de réponse
                                          + 24 octets depuis le PIC  */

// Cet échange complet dure 55x10(bits par octet)/38400 = 13 msec !
// On peut donc avoir un Timer qui relance ce programme sous Windows toutes les 80 à 100 msec sans aucun soucis (14 msec par carte !).
// Voir dans le programme principal la ligne = "SetTimer(hwnd,1,xxx,NULL);" avec xxx = timer en msec
// Essais OK avec Timer = 20 msec et charge CPU < 2 %.

 	tab_out[0x1E]=(card_adr<<4)+0x01;
	serialPort.ecriture_trame_rs232(tab_out);		// Envoi une trame = sélection de carte + Cmd global + données out + octet '0x00' de synchro
	if (!serialPort.lecture_trame_rs232(tab_in)) {	// Réception d'une trame.
		serialPort.purge_rs232_rx();						// 1ere erreur de transmission, on purge le buffer d'entrée
		serialPort.ecriture_trame_rs232(tab_out);
		if (!serialPort.lecture_trame_rs232(tab_in)) {		// 2eme erreur, on envoi des 0x00 + purge buffer d'entrée
			serialPort.purge_rs232_rxtx();
			serialPort.ecriture_trame_rs232(tab_out);
			if (!serialPort.lecture_trame_rs232(tab_in)) {	// 3eme erreur, on envoi des 0x00 + purge buffer d'entrée X 2
				serialPort.purge_rs232_rxtx();
				Sleep(10);
				serialPort.purge_rs232_rxtx();
				serialPort.ecriture_trame_rs232(tab_out);	// On essaye une derniere fois
				if (!serialPort.lecture_trame_rs232(tab_in)) {
					sprintf(szErr,"Perte de liaison avec la (carte UTS %d)\n Si vous voulez rétablir la liaison :\n  1 - rebranchez le câble, \n  2 - cliquez sur [OUI] ",card_adr);
					if (MessageBox(NULL,szErr,"UTS - Erreur",MB_YESNO|MB_ICONERROR)==IDNO) { enable= 0; return 1;	}
					serialPort.purge_rs232_rxtx();
				}
			}
		}
	}


/* 3. transferts additionels (rajoutes a partir de 873_rs11) */
// Plus utilisé en V3.3 Leds et servos actifs par défaut. 


/* 3. Transfets de debug quand on est sur l'écran 'DEBUG', On peut lire ou écrire 4 variables dans le PIC
      Ces variables sont affichées dans le tableau de l'écran 'DEBUG' */
	for(i=0;i<5;i++){
		if(serial_tab_cmd[card_adr][i]=='w') {
			serial_tab_cmd[card_adr][i]='x';
			serialPort.simple_write(2);                               //cmd set adr
			serialPort.simple_write(serial_tab_adresse[card_adr][i]); //start adress
			serialPort.simple_write(3);                               //cmd write data
			serialPort.simple_write(serial_tab_data[card_adr][i]);    //data
		}
		if(serial_tab_cmd[card_adr][i]=='r' || serial_tab_cmd[card_adr][i]=='a') {
			if(serial_tab_cmd[card_adr][i]=='r') serial_tab_cmd[card_adr][i]='x';
			serialPort.simple_write(2);                                      //cmd set adr
			serialPort.simple_write(serial_tab_adresse[card_adr][i]);        //start adress
			serialPort.simple_write(4);                                      //cmd read data
			if(serialPort.read(&serial_tab_data[card_adr][i])==0) return 1;  //data
		}
	}
	return 0;
}

/*------------------------------------------------------------------------------
NAME  : cSerial::export_tab_in
DESCR : Met à jour les variables utilisateurs UTS en fonction de la trame
        de données reçue du PIC.
ARG   : void, RETURN: void, NOTES :
------------------------------------------------------------------------------*/
void cSerial::export_tab_in(void)
{
	int i,j,data,adr;
	if(enable==0) return;	// Liaison active ?, non !
// can = 5 entrées analogiques = 5 potentiomètres = can_r0, can_r1, can_r2, can_r3, can_r4
	adr=0x54;
	for(i=0;i<NB_CAN;i++){
		vio_can[card_adr][i]=tab_in[adr++];
	if(cfg_can_inv[card_adr][i]) vio_can[card_adr][i]=255-vio_can[card_adr][i];
	}

// Détecteur de présence sur les 16 cantons, détection d'occupation avec mémoire éffacée à la lecture dans le PIC
	adr=0x5e;
	data=tab_in[adr++];
	for(i=0;i<8;i++) if(data&(1<<i)) vio_det[card_adr][i]=1; else vio_det[card_adr][i]=0;
	data=tab_in[adr++];
	for(i=0;i<8;i++) if(data&(1<<i)) vio_det[card_adr][8+i]=1; else vio_det[card_adr][8+i]=0;

// Entrées matricées [1] = mat_in0 à mat_in8
	adr=0x63;
	for(j=0;j<9;j++) {
		for(i=0;i<NB_CARTE;i++) if(tab_in[adr]&(1<<i)) vio_in[card_adr][4*j+i]=1; else vio_in[card_adr][4*j+i]=0;
		adr++;
	}
// Entrées directes [2], in_in1, in_in2, in_in3, in_in1, in_in4 et in_in5
	adr=0x59;
	for(j=0;j<5;j++){
		for(i=0;i<8;i++) if(tab_in[adr]&(1<<i)) vio_in[card_adr][8*j+i+40]=1; else vio_in[card_adr][8*j+i+40]=0;
		adr++;
	}
}
#endif
/*------------------------------------------------------------------------------
NAME  : cSerialPort::cSerialPort
DESCR : constructeur de la classe serial.
        Désactive le module RS232. Il sera active pendant l'init.
ARG   : void, RETURN:NOTES :
------------------------------------------------------------------------------*/
cSerialPort::cSerialPort(void)
{
// Mise à zéro des variables = aucune carte présente.
	card0_ok=0; card1_ok=0; card2_ok=0; card3_ok=0;
	enable=0;
}
/*------------------------------------------------------------------------------
NAME  : cSerialPort::simple_write
NAME  : cSerialPort::ecriture_trame_rs232 (nouveau en V3.3)
DESCR : Effectue une ecriture d'un octet sur la RS232
ARG   : DWORD buf[]: tableau pour stocker l'octet en position 0 .
RETURN: 1: ok, 0: PB, NOTES : Aucun
------------------------------------------------------------------------------*/
int cSerialPort::simple_write(int data)
{
	DWORD num;				// byte number wrote
	DWORD buffer[60];		// buffer de 60 * 4 caractères
	if(enable==0) return 0;	// Liaison active ?, non !
	buffer[0]=(DWORD) data;
	WriteFile(hCom,buffer,1,&num,NULL);
	return 1;
}
/*----------------------------------------------------------------------------*/
// En V3.3 écriture d'une trame d'un seul bloc de 55 octets.
// Envoi une seule trame = Sélection de carte + Cmd global '0x08' + données out + octet '0x00' de synchro
// Envoi les données à partir de l'offset 0x1E = Octet de sélection de carte.
// L'envoi d'une trame facilitera le passage à l'usb en terme de performance.
/*----------------------------------------------------------------------------*/
int cSerialPort::ecriture_trame_rs232(DWORD* pdata){
	DWORD num;					// Nombre d'octets écrits sur la ligne RS232.
	char buffer[60];			// buffer de 60 * 4 caractères (trame = 49 car)
	int i;
	if(enable==0) return 0;		// Liaison active ?, non !
	for(i=0;i<0x1E;i++){		// Se positionne sur l'octet de sélection de la carte (en '0x1E').
		pdata++;
	}
	for(i=0;i<=0x53-0x1E+0x01;i++){	// Transfert des données dans le buffer à partir de : buffer[1] !
		buffer[i]=(char) *pdata;
		pdata++;
	}
	buffer[1]=0x08;				// '0x08' = Octet de commande de transfert globale de la part du PIC
	buffer[i]=0x00;				// Impose en plus un octet de fin = '0x00' pour synchro éventuelle avec la carte
	WriteFile(hCom,buffer,0x53-0x1E+0x01+0x01,&num,NULL);
    return 1;
}
#ifdef VERSION_UTS_V3
/*------------------------------------------------------------------------------
NAME  : cSerialPort::simple_read
NAME  : cSerialPort::lecture_trame_rs232 (nouveau en V3.3)
DESCR : Effectue une lecture non bloquante d'un octet sur la RS232
ARG   : DWORD buf[]: tableau pour stocker l'octet en position 0 .
RETURN: 1: ok, 0: PB, NOTES : essaye de lire pendant 100ms
------------------------------------------------------------------------------*/
int cSerialPort::simple_read(int* pdata)
{
	DWORD num;            // Nombre d'octet lus.
	DWORD start_time;     // Mesure le temps passé pour le calcul du timeout.
	DWORD No_Error;       // Numéro d'erreur.
	DWORD buffer[20];     // buffer = 4 x 20 caractères. 
	COMSTAT Status;       // Pour voir si rx ok
	if(enable==0) return 0;	// Liaison active ?, non !
	start_time=GetTickCount();
	do {
		ClearCommError(hCom,&No_Error,&Status);
		if(Status.cbInQue!=0) { // Nombre de caractères lus dans le buffer d'entrée
			ReadFile(hCom,buffer,1,&num,NULL);
			*pdata= (unsigned char) buffer[0]; //     (int->uchar Modif FF 22/02/2009)
			return 1;
		}
		Sleep(1);  // Attends un caractère pendant 100 x 1 msec
	}
	while((GetTickCount()-start_time)<100);
	return 0;
}
/*----------------------------------------------------------------------------*/
// En V3.3 lecture d'un seul bloc de 25 octets
// Réception d'une seule trame = octet de réponse 0x83 + 24 octets de données du PIC
// On lit une seule trame, c'est plus rapide si on passe sur USB
// Si buffer[0]='0x83' alors la trame est correcte
int cSerialPort::lecture_trame_rs232(DWORD* pdata) {
	DWORD num;			// Nombre d'octet à lire
	DWORD start_time;	// Mesure le temps passé pour le calcul du timeout.
	DWORD No_Error;		// Numéro d'erreur.
	char buffer[60];	// buffer de 60 caractères.
	int i;
	COMSTAT Status;		// pour voir si rx ok
	if(enable==0) return 0;	// Liaison active ?, non !
	buffer[0]=0x00;
	Sleep(1);  // Cela ne sert à rien de venir lire tout de suite le nb de caractères arrivés
	start_time=GetTickCount();
	do {
		ClearCommError(hCom,&No_Error,&Status);
		if(Status.cbInQue >= 25) {					// Nombre de caractères lus dans le buffer d'entrée
			ReadFile(hCom,buffer,25,&num,NULL);		// On cherche à lire 25 caractères en une fois.
			if ((unsigned char)buffer[0]!=0x83) return 0;			// Erreur de trame !
			for(i=0;i<0x54-0x01;i++){					// On se possitionne sur l'octet can_r0 - 0x01
				pdata++;
				}
			for (i=0; i<25; i++) {
				*pdata= (unsigned char) buffer[i]; // (int->uchar en Visual C++)
				pdata++;
			}
			return 1;
		}
		Sleep(1);  // Attends le bloc de 25 caractères pendant 100 x 1 msec
	}
	while((GetTickCount()-start_time)<100);			// On sort en échec au bout de 100 ms 
    serial_error++;									// Compteur d'erreur RS232
	return 0;
}
/*------------------------------------------------------------------------------
NAME  : cSerialPort::read
DESCR : effectue une lecture securise d'un octet sur la rs232
ARG   : DWORD buf[]: tableau pour stocker l'octet en position 0 .
RETURN: 0: ok, 1: PB
NOTES : si ne peut pas lire au bout de 100ms, alors essaye une resynchronisation
        et on incrémente un compteur d'erreur.

------------------------------------------------------------------------------*/
int cSerialPort::read( int* pdata)
{
    DWORD num;            // byte number readed
    DWORD start_time;     // to measure time (used for timeout)
    DWORD No_Error;       // error number
    DWORD buffer[20];     // buffer fr 20 * 4 caractères
	COMSTAT Status;       // pour voir si rx ok
    int i;                // for loops
    if(enable==0) return 0;	// Liaison active ?, non !
    start_time=GetTickCount();
    do
    {
        ClearCommError(hCom,&No_Error,&Status);
        if(Status.cbInQue!=0)		// Nombre de caractères lus dans le buffer d'entrée
        {
            ReadFile(hCom,buffer,1,&num,NULL);
            *pdata= (unsigned char) buffer[0]; //     (int->uchar en Visual C++)
            return 1;
        }
        Sleep(1);
    }
    while((GetTickCount()-start_time)<100);
    for(i=0;i<60;i++) {simple_write(0); Sleep(1); }		// Envoi d'une série de 0
    serial_error++;		// Compteur d'erreur RS232
    return 0;
}
/*------------------------------------------------------------------------------
NAME  : cSerialPort::init
DESCR : essaye de trouver la carte sur COM1 ou COM2
ARG   : void
RETURN: 0: carte non trouvee, 1: carte sur COM1, 2: carte sur COM2 ...
NOTES : l'OS ferme automatiquement les ports lorsque l'application se termine

ATTENTION : Dans le cas de plusieurs cartes UTS, elles doivent toutes se trouver sur la même prise série COMx. !!!
------------------------------------------------------------------------------*/
int cSerialPort::init(void)
{
    int ok_com;
	#ifdef VERSION_UTS_V3
	char ch_err[370]="Pas trouvé de carte à PIC à 20 Mhz en version V3   \nconnectée au PC :\n"; // Message final > 320 octets !
	#else
	char ch_err[370]="Pas trouvé de carte à PIC à 8 Mhz en version V2   \nconnectée au PC :\n"; // Message final > 320 octets !
	#endif
    int com_no;
    char com_name[10];
    int data;
    // Cherche les cartes sur les COM1,2,3,4,5,6
    for(com_no=1;com_no<=6;com_no++)
    {
	ok_com=1;
	data=0;
	sprintf(com_name,"COM%d",com_no);
        strcat(ch_err,"\n    "); strcat(ch_err,com_name); strcat(ch_err," : "); 
        hCom=CreateFile(com_name,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
        if(hCom==INVALID_HANDLE_VALUE) { ok_com=0; strcat(ch_err,"Pas de port série"); }
        if(ok_com) if(!GetCommState(hCom,&dcb)) { ok_com=0; strcat(ch_err,"Impossible d'ouvrir le port série"); }
	if(ok_com) {

		#ifdef VERSION_UTS_V3
		dcb.BaudRate = CBR_38400;  // CBR_38400 en V3.x
		#else
		dcb.BaudRate = CBR_19200;   // CCBR_19200 en V2.x
		#endif

		dcb.ByteSize = 8;
		dcb.Parity   = NOPARITY;
		dcb.StopBits = ONESTOPBIT;
		dcb.fTXContinueOnXoff = FALSE;
		dcb.fOutX = FALSE;						// XON/XOFF control désactivé en émission
		dcb.fInX  = FALSE;	
	}
	if(ok_com) if(!SetCommState(hCom,&dcb)) { ok_com=0; strcat(ch_err,"Impossible d'ouvrir le port série (SetCommState)\n"); }
	if(ok_com) {
		PurgeComm(hCom,PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
		COMMTIMEOUTS tTimeout;
		GetCommTimeouts(hCom,&tTimeout);			// Timeout implanté en V3.3
		tTimeout.ReadIntervalTimeout = 1000;		// msec = temps max d'arrivé entre 2 car.
		tTimeout.ReadTotalTimeoutMultiplier = 1;	// msec x nb octet reçus
		tTimeout.ReadTotalTimeoutConstant = 5;		// msec à ajouter aux msec ci-dessus
		tTimeout.WriteTotalTimeoutMultiplier = 1;	// msec x nb octet envoyés
		tTimeout.WriteTotalTimeoutConstant = 5;		// msec à ajouter aux msec ci-dessus
		SetCommTimeouts(hCom,&tTimeout);			// Configure les timeouts sur la ligne RS232
	}
	if(ok_com) {
//	Version 11 : Envoi 0x41   Retour 0x83
//	Version 13 : Envoi 0x01   Retour 0x83 (carte n°1)
//          13 : Envoi 0x11   Retour 0x83 (carte n°2)
//          13 : Envoi 0x21   Retour 0x83 (carte n°3)
//          13 : Envoi 0x31   Retour 0x83 (carte n°4)
//
		enable=1;
		serialPort.purge_rs232_rxtx();			// on envoi des 0x00 + purge buffer d'entrée
		simple_write(0x41);  if(simple_read(&data)) { if(data==0x83) card0_ok=1; }  //test non adressable card (873_rs11)
		if(card0_ok==0)	{
			simple_write(0x01);  if(simple_read(&data)) { if(data==0x83              ) card0_ok=1; }  //test adressable card 0 (873_rs13 / next)
			simple_write(0x11);  if(simple_read(&data)) { if(data==0x83 || data==0x93) card1_ok=1; }  //test adressable card 1
			simple_write(0x21);  if(simple_read(&data)) { if(data==0x83 || data==0xa3) card2_ok=1; }  //test adressable card 2
			simple_write(0x31);  if(simple_read(&data)) { if(data==0x83 || data==0xb3) card3_ok=1; }  //test adressable card 3
		}
		enable=0;
		if(card0_ok==0 && card1_ok==0 && card2_ok==0 && card3_ok==0) { ok_com=0; CloseHandle(hCom); strcat(ch_err,"Pas trouvé de carte UTS."); }
	}
	if(ok_com) { // OK, au moins une carte est là
		enable=1;
		sprintf(ch_err,"Présence de la directive 'show_cards_found = on' dans 'config.txt'  \n\nCartes UTS trouvées  (trouvé=1, Absent=0)  :\n\nCarte n°0=%d   Carte n°1=%d   Carte n°2=%d   Carte n°4=%d",card0_ok,card1_ok,card2_ok,card3_ok);
		if(cfg_show_cards_found) MessageBox(NULL,ch_err,"UTS - Débug",MB_OK|MB_ICONERROR);
		return com_no;
		}
	}
	strcat(ch_err,"\n\n\nLe programme va fonctionner sans carte UTS.\n\n");
	MessageBox(NULL,ch_err,"UTS - Avertissement",MB_OK|MB_ICONERROR);
	return 0;
}
#endif
/*------------------------------------------------------------------------------
NAME  : cSerialPort::close
DESCR : ferme le port serie
ARG   : Aucun, RETURN: Aucun, NOTES : Aucun
------------------------------------------------------------------------------*/
void cSerialPort::close(void)
{
	if(enable) {
		CloseHandle(hCom);
		card0_ok=0; card1_ok=0; card2_ok=0; card3_ok=0;
		enable=0;
	}
}

/*------------------------------------------------------------------------------
NAME  : cSerialPort::dtr
DESCR : ermet de mettre un etat sur la ligne DTR
ARG   : int state: etat de la ligne DTR: 0 pour -12V et 1 pour +12V
RETURN: void, NOTES :
------------------------------------------------------------------------------*/
void cSerialPort::dtr(int state)
{
	if(enable==0) return;	// Liaison active ?, non !
	if(state==0) EscapeCommFunction(hCom,CLRDTR);
	else         EscapeCommFunction(hCom,SETDTR);
}

/*------------------------------------------------------------------------------
NAME  : cSerialPort::rts
DESCR : ermet de mettre un etat sur la ligne RTS
ARG   : int state: etat de la ligne RTS: 0 pour -12V et 1 pour +12V
RETURN: void,  NOTES :
------------------------------------------------------------------------------*/
void cSerialPort::rts(int state)
{
	if(enable==0) return;	// Liaison active ?, non !
	if(state==0) EscapeCommFunction(hCom,CLRRTS);
	else         EscapeCommFunction(hCom,SETRTS);
}

/*------------------------------------------------------------------------------
NAME  : cSerialPort::purge_rs232_rx
DESCR : Effectue une lecture de tous les octets reçus RS232 pendant 100 msec
ARG   : void,  RETURN: void
------------------------------------------------------------------------------*/
void cSerialPort::purge_rs232_rx(void)
{
	DWORD num;            // Nombre d'octet lus.
	DWORD start_time;     // Mesure le temps passé pour le calcul du timeout.
	DWORD No_Error;       // Numéro d'erreur.
	DWORD buffer;         // buffer = 1 x 4 caractères. 
	COMSTAT Status;       // Pour voir si rx ok

	if(enable==0) return;	// Liaison active ?, non !
	
	start_time=GetTickCount();
	do {
		Sleep(5);  // Attends pendant 5 msec
		ClearCommError(hCom,&No_Error,&Status);  // Status de la ligne ?
		while (Status.cbInQue!=0){
			ReadFile(hCom,&buffer,1,&num,NULL); // Lit un caractère tant que le buffer n'est pas vide
			ClearCommError(hCom,&No_Error,&Status); // Status de la ligne ?
		}
	}
	while((GetTickCount()-start_time)<100); // Boucle au maximum pendant 100 msec
}

/*------------------------------------------------------------------------------
NAME  : cSerialPort::purge_rs232_rxtx
DESCR : 1/ Envoi une série de caractères '0x00' de syncho et effectue une 
        2/ Lecture de tous les octets reçus RS232 pendant 100 msec.
ARG   : void,  RETURN: void
------------------------------------------------------------------------------*/
void cSerialPort::purge_rs232_rxtx(void)
{
	DWORD num;            // Nombre d'octet lus.
	DWORD start_time;     // Mesure le temps passé pour le calcul du timeout.
	DWORD No_Error;       // Numéro d'erreur.
	DWORD buffer;         // buffer = 1 x 4 caractères. 
	COMSTAT Status;       // Pour voir si rx ok
	int i;

	if(enable==0) return;	// Liaison active ?, non !
	buffer=0;
	for (i=0;i<60;i++ ) {
		WriteFile(hCom,&buffer,1,&num,NULL);
		Sleep (1);
	}

	start_time=GetTickCount();
	do {
		ClearCommError(hCom,&No_Error,&Status);  // Status de la ligne ?
		while (Status.cbInQue!=0){
			ReadFile(hCom,&buffer,1,&num,NULL); // Lit un caractère tant que le buffer n'est pas vide
			ClearCommError(hCom,&No_Error,&Status); // Status de la ligne ?
		}
		Sleep(5);  // Attends pendant 5 msec
	}
	while((GetTickCount()-start_time)<100); // Boucle au maximum pendant 100 msec
}


#ifndef VERSION_UTS_V3
// VERSION V2.0

/*------------------------------------------------------------------------------
NAME  : cSerialPort::simple_read
DESCR : effectue une lecture non bloquante d'un octet sur la rs232
ARG   : DWORD buf[]: tableau pour stoquer l'octet en position 0 .
RETURN: 1: ok, 0: PB
NOTES : essaye de lire pendant 500ms
------------------------------------------------------------------------------*/
int cSerialPort::simple_read(int* pdata)
{
    DWORD num;            // byte number readed
    DWORD start_time;     // to measure time (used for timeout)
    DWORD No_Error;       // error number
    DWORD buffer[100];
    COMSTAT Status;       // pour voir si rx ok
    if(enable==0) return 0;
    start_time=GetTickCount();
    do
    {
        ClearCommError(hCom,&No_Error,&Status);
        if(Status.cbInQue!=0)
        {
            ReadFile(hCom,buffer,1,&num,NULL);
            *pdata= (unsigned char) buffer[0]; //     (int->uchar Modif FF 22/02/2009)
            return 1;
        }
        Sleep(10);
    }
    while((GetTickCount()-start_time)<500);
    return 0;
}
/*------------------------------------------------------------------------------
NAME  : cSerial::read
DESCR : effectue une lecture securise d'un octet sur la rs232
ARG   : DWORD buf[]: tableau pour stocker l'octet en position 0 .
RETURN: 0: ok, 1: PB
NOTES : si ne peut pas lire au bout de 500ms, alors essaye une resynchronisation
        et incremente un compteur d'erreur

------------------------------------------------------------------------------*/
int cSerialPort::read( int* pdata)
{
    DWORD num;            // byte number readed
    DWORD start_time;     // to measure time (used for timeout)
    DWORD No_Error;       // error number
    DWORD buffer[100];    // buffer
	COMSTAT Status;       // pour voir si rx ok
    int i;                // for loops
    if(enable==0) return 0;
    start_time=GetTickCount();
    do
    {
        ClearCommError(hCom,&No_Error,&Status);
        if(Status.cbInQue!=0)
        {
            ReadFile(hCom,buffer,1,&num,NULL);
            *pdata= (unsigned char) buffer[0]; //     (int->uchar Modif FF 22/02/2009)
            return 1;
        }
        Sleep(10);
    }
    while((GetTickCount()-start_time)<500);
    for(i=0;i<50;i++) {simple_write(0); Sleep(1); }
    serial_error++;
    return 0;
}
/*------------------------------------------------------------------------------
NAME  : init
DESCR : essaye de trouver la carte sur COM1 ou COM2
ARG   : void
RETURN: 0: carte non trouvee, 1: carte sur COM1, 2: carte sur COM2 ...
NOTES : l'OS ferme automatiquement les ports lorsque l'application se termine
------------------------------------------------------------------------------*/
int cSerialPort::init(void)
{
    int i;
    int ok_com;
	char ch_err[400]="Impossible de trouver une carte à PIC à 8 Mhz en version V2.0 sur un port série\n"; // Message final > 320 octets !

    int com_no;
    char com_name[10];
    int data;
    //Cherche les cartes sur les COM1,2,3,4
    for(com_no=1;com_no<=4;com_no++)
    {
        ok_com=1;
		data=0;
        sprintf(com_name,"COM%d",com_no);
        strcat(ch_err,"\n"); strcat(ch_err,com_name); strcat(ch_err," : ");
        hCom=CreateFile(com_name,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
        if(hCom==INVALID_HANDLE_VALUE) { ok_com=0; strcat(ch_err,"Pas de port série"); }
        if(ok_com) if(!GetCommState(hCom,&dcb)) { ok_com=0; strcat(ch_err,"Impossible d'ouvrir le port serie (GetCommState)"); }
        if(ok_com)
        {
            dcb.BaudRate = CBR_19200;  // 38400 en V3.3
            dcb.ByteSize = 8;
            dcb.Parity   = NOPARITY;
            dcb.StopBits = ONESTOPBIT;
        }
        if(ok_com) if(!SetCommState(hCom,&dcb)) { ok_com=0; strcat(ch_err,"Impossible d'ouvrir le port serie (SetCommState)"); }
        if(ok_com)
        {
            enable=1;
            for(i=0;i<64;i++) { simple_write(0); }  //send 64 sync
            simple_write(0x41);  if(simple_read(&data)) { if(data==0x83) card0_ok=1; }  //test non adressable card (873_rs11)
            if(card0_ok==0)
            {
            simple_write(0x01);  if(simple_read(&data)) { if(data==0x83              ) card0_ok=1; }  //test adressable card 0 (873_rs13 / next)
            simple_write(0x11);  if(simple_read(&data)) { if(data==0x83 || data==0x93) card1_ok=1; }  //test adressable card 1
            simple_write(0x21);  if(simple_read(&data)) { if(data==0x83 || data==0xa3) card2_ok=1; }  //test adressable card 2
            simple_write(0x31);  if(simple_read(&data)) { if(data==0x83 || data==0xb3) card3_ok=1; }  //test adressable card 3
            }
            enable=0;
            if(card0_ok==0 && card1_ok==0 && card2_ok==0 && card3_ok==0) { ok_com=0; CloseHandle(hCom); strcat(ch_err,"Pas trouvé de carte UTS"); }
        }
        if(ok_com) // OK, au moins une carte est la
        {
            enable=1;
            sprintf(ch_err,"card0=%d card1=%d card2=%d card4=%d",card0_ok,card1_ok,card2_ok,card3_ok);
            if(cfg_show_cards_found) MessageBox(NULL,ch_err,"Debug",MB_OK|MB_ICONERROR);
            return com_no;
        }
    }
	strcat(ch_err,"\n\nUTS va fonctionner en mode isolé sans dialoguer avec une carte");
	MessageBox(NULL,ch_err,"Avertissement",MB_OK|MB_ICONERROR);
    return 0;
}
/*------------------------------------------------------------------------------
NAME  : cSerial::transfer
DESCR : fait un transfert entre la carte et les registres images
ARG   : void
RETURN: 0: ok, 1: PB de lecture
NOTES : utilise la fonction cSerial::read pour une lecture securisee
------------------------------------------------------------------------------*/
int cSerial::transfer(void)
{
/*    DWORD num; */
    int adr_out;
    int adr_in;
    int i;
    int data;
    if(serialPort.enable==0) return 0;
    if(enable==0)            return 0;
    /* 1. transfert normal 1 cmd + 41 out + 18 in = 60 octets @ 19200 = 30ms */
    //buff[0]=0   ;    WriteFile(hCom,buff,1,&num,NULL); //sync
    //buff[0]=0   ;    WriteFile(hCom,buff,1,&num,NULL); //sync
    serialPort.simple_write((card_adr<<4)+0x01);   //select correct card 0x01 (card0), 0x11 (card1), 0x21 (card2), 0x31 (card3)
    if(serialPort.read(&data)==0) return 1;        //read ping reply           (0x83)        (0x93)        (0xa3)        (0xb3)
    serialPort.simple_write(8);             //cmd global exchange
    for(adr_out=0x20;adr_out<=0x48;adr_out++)
    {
        // ecriture
        serialPort.simple_write(tab_out[adr_out]); //data out global
        // lecture
        adr_in=adr_out+(0x4a-0x20);
        if(adr_in<=0x5b)
        {
            //ReadFile( hCom,buff,1,&num,NULL); tab_in[adr_out+0x4a-0x20]=buff[0];
            if(serialPort.read(&data)==0) return 1;
            tab_in[adr_in]=data;
        }
    }
    /* 2. transfert d'effacement pour les det et in */
    // plus la peine car a chaque transfer global, 0x51 a 0x5b sont effaces
    // buff[0]=2;    WriteFile(hCom,buff,1,&num,NULL); //cmd set adr
    // buff[0]=0x51; WriteFile(hCom,buff,1,&num,NULL); //start adress det1m
    // buff[0]=3;    WriteFile(hCom,buff,1,&num,NULL); //cmd write data
    // buff[0]=0;    WriteFile(hCom,buff,1,&num,NULL); //data (det1m)
    // buff[0]=5;    WriteFile(hCom,buff,1,&num,NULL); //cmd inc adr and write data
    // buff[0]=0;    WriteFile(hCom,buff,1,&num,NULL); //data (det2m)
    // buff[0]=5;    WriteFile(hCom,buff,1,&num,NULL); //cmd inc adr and write data
    // buff[0]=0;    WriteFile(hCom,buff,1,&num,NULL); //data (in0)
    //..
    /* 2. transferts additionels (rajoutes a partir de 873_rs11) */
    if(tab_out[0x75]!=NO_CHANGE && tab_out[0x76]!=NO_CHANGE)  // 0x75 & 0x76 : registres des sorties qui remplacent les leds
    {
        serialPort.simple_write(2);              //cmd set adr
        serialPort.simple_write(0x75);           //reg adr
        serialPort.simple_write(3);              //cmd write data
        serialPort.simple_write(tab_out[0x75]);  //data
        serialPort.simple_write(5);              //cmd inc adr and write data
        serialPort.simple_write(tab_out[0x76]);  //data
    }
    else if(tab_out[0x75]!=NO_CHANGE)
    {
        serialPort.simple_write(2);              //cmd set adr
        serialPort.simple_write(0x75);           //reg adr
        serialPort.simple_write(3);              //cmd write data
        serialPort.simple_write(tab_out[0x75]);  //data
    }
    else if(tab_out[0x76]!=NO_CHANGE)
    {
        serialPort.simple_write(2);              //cmd set adr
        serialPort.simple_write(0x76);           //reg adr
        serialPort.simple_write(3);              //cmd write data
        serialPort.simple_write(tab_out[0x76]);  //data
    }
    if(tab_out[0x71]!=NO_CHANGE)  // 0x71 : registre du repartiteur de charge (utilise pour activer ou desactiver les leds)
    {
        serialPort.simple_write(2);              //cmd set adr
        serialPort.simple_write(0x87);           //reg adr
        serialPort.simple_write(3);              //cmd write data
        serialPort.simple_write(tab_out[0x71]);  //data
    }
    if(tab_out[0x49]!=NO_CHANGE)  // 0x49 : registre du premier servo moteur
    {
        serialPort.simple_write(2);              //cmd set adr
        serialPort.simple_write(0x49);           //reg adr
        serialPort.simple_write(3);              //cmd write data
        serialPort.simple_write(tab_out[0x49]);  //data
    }
    if(tab_out[0x5f]!=NO_CHANGE)  // 0x5f : registre du second servo moteur
    {
        serialPort.simple_write(2);              //cmd set adr
        serialPort.simple_write(0x5f);           //reg adr
        serialPort.simple_write(3);              //cmd write data
        serialPort.simple_write(tab_out[0x5f]);  //data
    }
    if(tab_out[0x87]!=NO_CHANGE)  // 0x87 : registre de direction du portC (utilise pour activer ou desactiver les servos)
    {
        serialPort.simple_write(2);              //cmd set adr
        serialPort.simple_write(0x87);           //reg adr
        serialPort.simple_write(3);              //cmd write data
        serialPort.simple_write(tab_out[0x87]);  //data
    }
    /* 3. transfets de debug */
    for(i=0;i<5;i++)
    {
        if(serial_tab_cmd[card_adr][i]=='w')
        {
            serial_tab_cmd[card_adr][i]='x';
            serialPort.simple_write(2);                               //cmd set adr
            serialPort.simple_write(serial_tab_adresse[card_adr][i]); //start adress
            serialPort.simple_write(3);                               //cmd write data
            serialPort.simple_write(serial_tab_data[card_adr][i]);    //data
        }
        if(serial_tab_cmd[card_adr][i]=='r' || serial_tab_cmd[card_adr][i]=='a')
        {
            if(serial_tab_cmd[card_adr][i]=='r') serial_tab_cmd[card_adr][i]='x';
            serialPort.simple_write(2);                                      //cmd set adr
            serialPort.simple_write(serial_tab_adresse[card_adr][i]);        //start adress
            serialPort.simple_write(4);                                      //cmd read data
            if(serialPort.read(&serial_tab_data[card_adr][i])==0) return 1;  //data
        }
    }
    return 0;
}
/*------------------------------------------------------------------------------
NAME  : cSerial::import_tab_out
DESCR : copie les variables utilisateurs dans le registre image out
ARG   : void
RETURN: void
NOTES :
------------------------------------------------------------------------------*/
int get_canton_vit(int card, int i)    { if(serial_debug_select_canton_vit[card][i]==0) return vio_canton_vit[card][i]; else return serial_debug_canton_vit[card][i];}
int get_out_val(int card, int i)       { if(serial_debug_select_out[card][i]==0)        return vio_out[card][i];        else return serial_debug_out[card][i];       }
int get_led_val(int card, int i)       { if(serial_debug_select_led[card][i]==0)        return vio_led[card][i];        else return serial_debug_led[card][i];       }
int get_led_cli_val(int card, int i)   { if(serial_debug_select_led[card][i]==0)        return vio_led_cli[card][i];    else return serial_debug_led_cli[card][i];   }
int get_led_phase_val(int card, int i) { if(serial_debug_select_led[card][i]==0)        return vio_led_phase[card][i];  else return serial_debug_led_phase[card][i]; }
int get_srv(int card, int i)           { if(serial_debug_select_srv[card][i]==0)        return vio_srv[card][i];        else return serial_debug_srv[card][i];       }
void cSerial::import_tab_out(void)
{
    int i,j,data,adr;
    static int old_0x71=NO_CHANGE; int req_0x71; // 0x71 : registre du repartiteur de charge (utilise pour activer ou desactiver les leds)
    static int old_0x75=NO_CHANGE; int req_0x75; // 0x75 : registres des sorties qui remplacent les leds
    static int old_0x76=NO_CHANGE; int req_0x76; // 0x76 : "
    static int old_0x87=NO_CHANGE; int req_0x87; // 0x87 : registre de direction du portC (utilise pour activer ou desactiver les servos)
    static int old_0x49=NO_CHANGE; int req_0x49; // 0x49 : registre du premier servo moteur
    static int old_0x5f=NO_CHANGE; int req_0x5f; // 0x5f : registre du second servo moteur
	req_0x87=0;
    if(enable==0) return;
    /* fill tab_out buffer tfrom user variables*/
    //pwm+sens
    adr=0x20;
    for(j=0;j<4;j++)
    {
        for(i=0;i<4;i++)
        {
            if(get_canton_vit(card_adr,4*j+i)>=0) tab_out[adr++]= get_canton_vit(card_adr,4*j+i);
            else                                  tab_out[adr++]=-get_canton_vit(card_adr,4*j+i);
        }
        data=0;
        if(get_canton_vit(card_adr,4*j+0)>=0) data|=1;  else data|=2;
        if(get_canton_vit(card_adr,4*j+1)>=0) data|=4;  else data|=8;
        if(get_canton_vit(card_adr,4*j+2)>=0) data|=16; else data|=32;
        if(get_canton_vit(card_adr,4*j+3)>=0) data|=64; else data|=128;
        if(cfg_canton_inv[card_adr][4*j+0]) data^=0x03;
        if(cfg_canton_inv[card_adr][4*j+1]) data^=0x0c;
        if(cfg_canton_inv[card_adr][4*j+2]) data^=0x30;
        if(cfg_canton_inv[card_adr][4*j+3]) data^=0xc0;
        tab_out[adr++]=data;
    }
    //out 0-23
    adr=0x34;
    for(j=0;j<3;j++)
    {
         data=0;
         for(i=0;i<8;i++) if(get_out_val(card_adr,8*j+i)) data|=1<<i;
         tab_out[adr]=data;
         adr++;
    }
    //out 24-39 (si elles sont utilisees a la place des leds)
    // remplacement des leds par des sorties
    adr=0x71;
    if(cfg_enable_leds[card_adr]==0) req_0x71=0xef; else req_0x71=0xeb;
    if(req_0x71!=old_0x71) tab_out[adr]=req_0x71; else tab_out[adr]=NO_CHANGE;
    old_0x71=req_0x71;
    adr=0x75;
    j=0;
    data=0;
    for(i=0;i<8;i++) if(get_out_val(card_adr,24+8*j+i)) data|=1<<i;
    req_0x75=data;
    if(req_0x75!=old_0x75) tab_out[adr]=req_0x75; else tab_out[adr]=NO_CHANGE;
    old_0x75=req_0x75;
    adr=0x76;
    j=1;
    data=0;
    for(i=0;i<8;i++) if(get_out_val(card_adr,24+8*j+i)) data|=1<<i;
    req_0x76=data;
    if(req_0x76!=old_0x76) tab_out[adr]=req_0x76; else tab_out[adr]=NO_CHANGE;
    old_0x76=req_0x76;
    // led
    adr=0x37;
    for(j=0;j<2;j++) //led on
    {
        data=0; for(i=0;i<8;i++) if(get_led_val(card_adr,     i+24*j)) data|=1<<i;     tab_out[adr]=data; adr++;
        data=0; for(i=0;i<8;i++) if(get_led_val(card_adr,  12+i+24*j)) data|=1<<i;     tab_out[adr]=data; adr++;
        data=0; for(i=0;i<4;i++) if(get_led_val(card_adr,   8+i+24*j)) data|=1<<i;
                for(i=0;i<4;i++) if(get_led_val(card_adr,12+8+i+24*j)) data|=1<<(i+4); tab_out[adr]=data; adr++;
    }
    for(j=0;j<2;j++) //led clignotement (mettre a 0 pour un clignotement)
    {
        data=0; for(i=0;i<8;i++) if(get_led_cli_val(card_adr,     i+24*j)==0) data|=1<<i;     tab_out[adr]=data; adr++;
        data=0; for(i=0;i<8;i++) if(get_led_cli_val(card_adr,  12+i+24*j)==0) data|=1<<i;     tab_out[adr]=data; adr++;
        data=0; for(i=0;i<4;i++) if(get_led_cli_val(card_adr,   8+i+24*j)==0) data|=1<<i;
                for(i=0;i<4;i++) if(get_led_cli_val(card_adr,12+8+i+24*j)==0) data|=1<<(i+4); tab_out[adr]=data; adr++;
    }
    for(j=0;j<2;j++) //led inversion de phase de clignotement
    {
        data=0; for(i=0;i<8;i++) if(get_led_phase_val(card_adr,     i+24*j)) data|=1<<i;     tab_out[adr]=data; adr++;
        data=0; for(i=0;i<8;i++) if(get_led_phase_val(card_adr,  12+i+24*j)) data|=1<<i;     tab_out[adr]=data; adr++;
        data=0; for(i=0;i<4;i++) if(get_led_phase_val(card_adr,   8+i+24*j)) data|=1<<i;
                for(i=0;i<4;i++) if(get_led_phase_val(card_adr,12+8+i+24*j)) data|=1<<(i+4); tab_out[adr]=data; adr++;
    }
    // servo
    adr=0x87;
    if(cfg_enable_servos[card_adr][0]==0 && cfg_enable_servos[card_adr][1]==0) req_0x87=0x8f;
    if(cfg_enable_servos[card_adr][0]==1 && cfg_enable_servos[card_adr][1]==0) req_0x87=0x8b;
    if(cfg_enable_servos[card_adr][0]==0 && cfg_enable_servos[card_adr][1]==1) req_0x87=0x8d;
    if(cfg_enable_servos[card_adr][0]==1 && cfg_enable_servos[card_adr][1]==1) req_0x87=0x89;
    if(req_0x87!=old_0x87) tab_out[adr]=req_0x87; else tab_out[adr]=NO_CHANGE;
    old_0x87=req_0x87;
    adr=0x49;
    req_0x49=get_srv(card_adr,0);
    if(req_0x49!=old_0x49) tab_out[adr]=req_0x49; else tab_out[adr]=NO_CHANGE;
    old_0x49=req_0x49;
    adr=0x5f;
    req_0x5f=get_srv(card_adr,1);
    if(req_0x5f!=old_0x5f) tab_out[adr]=req_0x5f; else tab_out[adr]=NO_CHANGE;
    old_0x5f=req_0x5f;
}
/*------------------------------------------------------------------------------
NAME  : cSerial::export_tab_in
DESCR : met a jour les variables utilisateurs en fonction du registre image in
ARG   : void
RETURN: void
NOTES :
------------------------------------------------------------------------------*/
void cSerial::export_tab_in(void)
{
    int i,j,data,adr;
    if(enable==0) return;
    /* tab_in buffer to user variables*/
    //can
    adr=0x4a;
    for(i=0;i<5;i++)
    {
        vio_can[card_adr][i]=tab_in[adr++];
        if(cfg_can_inv[card_adr][i]) vio_can[card_adr][i]=255-vio_can[card_adr][i];
    }
    //det
    //adr=0x51;  //mem
    adr=0x4f;  //sans mem
    data=tab_in[adr++];
    for(i=0;i<8;i++) if(data&(1<<i)) vio_det[card_adr][i]=1; else vio_det[card_adr][i]=0;
    data=tab_in[adr++];
    for(i=0;i<8;i++) if(data&(1<<i)) vio_det[card_adr][8+i]=1; else vio_det[card_adr][8+i]=0;
    //in
    adr=0x53;
    for(j=0;j<9;j++)
    {
        for(i=0;i<4;i++) if(tab_in[adr]&(1<<i)) vio_in[card_adr][4*j+i]=1; else vio_in[card_adr][4*j+i]=0;
        adr++;
    }
}
#endif