Exercice 3 (TP6)

Organigramme

Programme

	/***************************************************************************************
	*       TPs sur  EID210 / Réseau CAN - VMD  (Véhicule Multiplexé Didactique)			
	****************************************************************************************
	*       APPLICATION: Commande Essuie-glace à distance
	****************************************************************************************
	*   TP Exercice n°3:  Commande du système essuie glace avant
	*---------------------------------------------------------------------------------------  		
	*   CAHIER DES CHARGES :					      				
	*  *********************						      			
	*  On souhaite une commande normale de l'essuie glace à partir du commodo destiné	
	*  à cet effet:
	*	- position 'arrêt' (noté P0)    (ni P1, ni P2,ni P3)
	*	- position P1 (GP4) 'intermittant' -> le balai fait des "aller-retours" séparés par  
	*	  une attente dont la durée est réglée par l'entrée analogique AN0  
	*	- position P2 (GP5) -> le balai fait des "aller-retours" avec une vitesse faible
	*	- position P3 (GP6) -> le balai fait des "aller-retours" avec une vitesse moyenne
	*	- position P4 (GP7) -> le balai fait des "aller-retours" avec une vitesse élevée
 	*   Dans le mode 'intermittent', l'intervalle de temps entre deux battements est générée 	
	*   par le 'temporisateur programmable intégré dans le micro-controleur
	*   On affichera à l'écran, les états des diverses entrées commodo.
	*---------------------------------------------------------------------------------------
	*  NOM du FICHIER :  TP_Exercice 3.C			       				
	* ***************** 									
	*****************************************************************************************/
// Déclaration des fichiers d'inclusion
#include <stdio.h>
#include"Structures_Donnees.h"
#include"cpu_reg.h"
#include "eid210_reg.h"
#include "CAN_vmd.h"
#include "Aton_can.h"
 
// Déclaration des variables 
// Pour les Indicateurs divers (variables binaires)
union byte_bits Indicateurs,FC; // Structures de bits
#define I_Autorise_Cycle Indicateurs.bit.b0 	// Pour autoriser 1 battement 
#define I_Intermittent Indicateurs.bit.b1 
#define I_Message_Pb_Affiche Indicateurs.bit.b2 
#define Etat_Arret Indicateurs.bit.b3		// Etat "balai à l'arrêt en fin de course droite"
#define Etat_Rot_Droite Indicateurs.bit.b4	// Etat "balai en rotation droite"
#define Etat_Rot_Gauche Indicateurs.bit.b5	// Etat "balai en rotation gauche"
// Pour les fins de course
#define Etat_FC FC.valeur // Pour l'ensemble des fins de course
#define fs FC.bit.b7	// Pour fin de surcourse
#define fcg FC.bit.b6	// Pour fin de course gauche
#define fcd FC.bit.b5	// Pour fin de course droit
// Pour la position Commodo_EG
#define Commodo_EG_Pos1 Commodo_EG.bit.GP4
#define Commodo_EG_Pos2 Commodo_EG.bit.GP5
#define Commodo_EG_Pos3 Commodo_EG.bit.GP6
#define Commodo_EG_Pos4 Commodo_EG.bit.GP7
// Déclarations des diverses trames de communication
Trame Trame_Recue;	// Pour la trame qui vient d'etre reçue par le controleur
// Trames de type "IM" (Input Message -> trame de commande)
Trame T_IM_Asservissement; 	// Pour la commande du moteur
Trame T_IM_Commodo_EG;		// Pour l'initialisation Commodo Essuie Glace
// Trames de type "IRM" (Input Message -> trame interrogative)
Trame T_IRM_Acquisition_FC;	// Pour l'acquisition de l'état des fins de courses
Trame T_IRM_Etat_Commodo_EG;		// Pour l'acquisition de l'état Commodo Essuie Glace
// Varibles diverses
unsigned char Valeur_Analogique,Cde_Vitesse,Tempo_Fin,Compteur_Passage_Irq,Compteur_Secondes;
unsigned char Valeur_Commodo_EG_Mem,Valeur_Analogique_Mem;
// Pour les temporisation	
// Déclaration constantes
#define Vitesse_Lente 30
#define Vitesse_Moyenne 50
#define Vitesse_Rapide 70
 
#define Tempo1 3	// en secondes
#define Tempo2 6
#define Tempo3 9
#define Tempo4 12
#define Tempo5 15
//  Fonction d'interruption "Base de Temps"
//========================================
void irq_bt()
// Fonction exécutée toute les 10 mS
{if(I_Intermittent) // Si mode intermittent actif
	{Compteur_Passage_Irq++;
	 if(Compteur_Passage_Irq==100)  // Une  Seconde s'est écoulée
		{Compteur_Passage_Irq=0;
	 	 Compteur_Secondes++;
	 	 if(Compteur_Secondes>=Tempo_Fin)
			{Compteur_Secondes=0;
			 I_Autorise_Cycle=1;}
		}}
} // Fin de la fonction d'interruption
 
//======================
// FONCTION PRINCIPALE
//======================
main()
{
// Déclaration de variables locales à la fonction principale
int Cptr_Affichage=0,Cptr_TimeOut;
//  INITIALISATIONS
//------------------
// Pour initialiser la carte industrielle controleur CAN
Init_Aton_CAN();
clsscr(); // Pour effacer l'écran
// Trame de type "IM" (trame de commande): Données d'identification
T_IM_Asservissement.trame_info.registre=0x00;
T_IM_Asservissement.trame_info.champ.extend=1;
T_IM_Asservissement.trame_info.champ.dlc=0x03;
T_IM_Asservissement.trame_info.champ.rtr=0;
T_IM_Asservissement.ident.extend.identificateur.ident=Ident_T_IM_Asservissement;
// Pour définir des Entrées/Sorties
T_IM_Asservissement.data[0]=0x1F; 	// Adresse du registre GPDDR  (direction de E/S)
						// doc MCP25050 Page 16 
T_IM_Asservissement.data[1]=0xEF; 	// Masque -> Bit 7 non concerné 
T_IM_Asservissement.data[2]=0xE3;	// Valeur ->  1 si Entrée et 0 si Sortie
	// GP7=fs Entrée; GP6=fcg Entree; GP5=fcd Entrée;  GP4=ValidIP Sortie; 
	// GP3=PWM2 Sortie; GP2=PWM1 Sortie; GP1=AN1 Entrée; GP0=AN0 Entrée; 
I_Message_Pb_Affiche=0;
do {Ecrire_Trame(T_IM_Asservissement); 	// C'est la première trame envoyée au module 
	 Cptr_TimeOut=0;		// 'Asservissement' -> on teste s'il répond bien			
	 do{Cptr_TimeOut++;}while((Lire_Trame(&Trame_Recue)==0)&&(Cptr_TimeOut<100));
	 if(Cptr_TimeOut==100)
		{if(I_Message_Pb_Affiche==0)
		 	{I_Message_Pb_Affiche=1;
		  	 gotoxy(2,10);
	 	  	 printf(" Pas de reponse a la trame de commande en initialisation \n");
	 	 	 printf("  Verifier si alimentation 12 V  est OK \n");}}
    }while(Cptr_TimeOut==100);
clsscr(); // Pour effacer l'écran au cas ou message afiché
// Pour mettre à 0 les sorties 
T_IM_Asservissement.data[0]=0x1E; 	// Adresse du registre GPLAT  (Registre I/O)
T_IM_Asservissement.data[1]=0x1C; 	// Masque -> sorties GP4,3,2 sont consernées
T_IM_Asservissement.data[2]=0x00;	// Valeur ->  les 3 sorties à 0
Ecrire_Trame(T_IM_Asservissement);
do{}while(Lire_Trame(&Trame_Recue)==0); // Attendre réponse
// Pour définir sortie GP2 en PWM1  
T_IM_Asservissement.data[0]=0x21; 	// Adresse du registre T1CON
T_IM_Asservissement.data[1]=0xB3; 	// Masque -> seuls bit 7;5;4;1;0 consernés
T_IM_Asservissement.data[2]=0x80;	// Valeur ->  TMR1ON=1; Prescaler1=1
Ecrire_Trame(T_IM_Asservissement);
do{}while(Lire_Trame(&Trame_Recue)==0); // Attendre réponse
// Pour définir fréquence signal sortie PWM1  
T_IM_Asservissement.data[0]=0x23; 	// Adresse du registre PR1
T_IM_Asservissement.data[1]=0xFF; 	// Masque -> tous les bits sont consernés
T_IM_Asservissement.data[2]=0xFF;	// Valeur ->  PR1=255
Ecrire_Trame(T_IM_Asservissement);
do{}while(Lire_Trame(&Trame_Recue)==0); // Attendre réponse
// Pour définir sortie GP3 en PWM2  
T_IM_Asservissement.data[0]=0x22; 	// Adresse du registre T2CON
T_IM_Asservissement.data[1]=0xB3; 	// Masque -> seuls bit 7;5;4;1;0 consernés
T_IM_Asservissement.data[2]=0x80;	// Valeur ->  TMR2ON=1; Prescaler2=1
Ecrire_Trame(T_IM_Asservissement);
do{}while(Lire_Trame(&Trame_Recue)==0); // Attendre réponse
// Pour définir fréquence signal sortie PWM2  
T_IM_Asservissement.data[0]=0x24; 	// Adresse du registre PR2
T_IM_Asservissement.data[1]=0xFF; 	// Masque -> tous les bits sont consernés
T_IM_Asservissement.data[2]=0xFF;	// Valeur ->  PR2=255
Ecrire_Trame(T_IM_Asservissement);
do{}while(Lire_Trame(&Trame_Recue)==0); // Attendre réponse
// Pour initialiser le rapport cyclique du signal PWM1 
T_IM_Asservissement.data[0]=0x25; 	// Adresse du registre PWM1DC
T_IM_Asservissement.data[1]=0xFF; 	// Masque -> tous les bits sont consernés
T_IM_Asservissement.data[2]=0;	// Valeur ->  PWM1DC=0 
Ecrire_Trame(T_IM_Asservissement);
do{}while(Lire_Trame(&Trame_Recue)==0); // Attendre réponse
// Pour initialiser le rapport cyclique du signal PWM2 à 0
T_IM_Asservissement.data[0]=0x26; 	// Adresse du registre PWM2DC
T_IM_Asservissement.data[1]=0xFF; 	// Masque -> tous les bits sont consernés
T_IM_Asservissement.data[2]=0;	// Valeur ->  PWM2DC=0 
Ecrire_Trame(T_IM_Asservissement);
do{}while(Lire_Trame(&Trame_Recue)==0); // Attendre réponse
// Pour Valider le circuit de puissance
T_IM_Asservissement.data[0]=0x1E; 	// Adresse du registre GPLAT  (Registre I/O)
T_IM_Asservissement.data[1]=0x10; 	// Masque -> sortie GP4 (ValidIP) est conserné
T_IM_Asservissement.data[2]=0x10;	// Valeur ->  ValidIP=1
Ecrire_Trame(T_IM_Asservissement);
do{}while(Lire_Trame(&Trame_Recue)==0); // Attendre réponse
// Masque pour les commandes IM futures
T_IM_Asservissement.data[1]=0xFF; 	// Masque -> tous les bits sont consernés
// Pour acquérir l'état des fin de course
// Trame de type "IRM" (trame interrogative): Données d'identification
T_IRM_Acquisition_FC.trame_info.registre=0x00;
T_IRM_Acquisition_FC.trame_info.champ.extend=1;
T_IRM_Acquisition_FC.trame_info.champ.dlc=1;
T_IRM_Acquisition_FC.trame_info.champ.rtr=1;
T_IRM_Acquisition_FC.ident.extend.identificateur.ident=Ident_T_IRM1_Asservissement; 
				// Demande état registre GPIN
 
// Pour initialiser le module "commmodo EG"
// Trame de type "IM" (trame de commande): Données d'identification
T_IM_Commodo_EG.trame_info.registre=0x00;
T_IM_Commodo_EG.trame_info.champ.extend=1;
T_IM_Commodo_EG.trame_info.champ.dlc=0x03; // On demande les valeurs de 8 rgistres
T_IM_Commodo_EG.trame_info.champ.rtr=0;
T_IM_Commodo_EG.ident.extend.identificateur.ident=Ident_T_IM_Commodo_EG; // 
// Pour activer les conversions Ana -> Num
T_IM_Commodo_EG.data[0]=0x2A; 	// Adresse du registre ADCON0  
T_IM_Commodo_EG.data[1]=0xF0; 	// Masque -> bits 7..4 concernés
T_IM_Commodo_EG.data[2]=0x80;	// Valeur ->  ADON=1 et "prescaler rate"=1:32
Ecrire_Trame(T_IM_Commodo_EG);
do{}while(Lire_Trame(&Trame_Recue)==0); // Attendre réponse
// Pour définir le mode de conversion
T_IM_Commodo_EG.data[0]=0x2B; 	// Adresse du registre ADCON1  
T_IM_Commodo_EG.data[1]=0xFF; 	// Masque -> tous les bits sont concernés
T_IM_Commodo_EG.data[2]=0x0E;	// Valeur ->  voir doc MCP25050 page 36 (GP0 -> Entrée Analogique)
Ecrire_Trame(T_IM_Commodo_EG);
do{}while(Lire_Trame(&Trame_Recue)==0); // Attendre réponse
// Pour acquérir les résultats de conversion A->N et états commmodo EG
// Trame de type "IRM" (trame interrogative) Commodo EG: Données d'identification
T_IRM_Etat_Commodo_EG.trame_info.registre=0x00;
T_IRM_Etat_Commodo_EG.trame_info.champ.extend=1;
T_IRM_Etat_Commodo_EG.trame_info.champ.dlc=0x08; // On demande les valeurs de 8 rgistres
T_IRM_Etat_Commodo_EG.trame_info.champ.rtr=1;
T_IRM_Etat_Commodo_EG.ident.extend.identificateur.ident=Ident_T_IRM8_Commodo_EG; // 
 
// Pour initialiser système (Enmener le balai en position sur fin de course droit)
 T_IM_Asservissement.data[0]=0x25; 	// Adresse du registre PWM1DC
 T_IM_Asservissement.data[1]=0xFF; 	// Masque -> tous les bits sont consernés
 Ecrire_Trame(T_IRM_Acquisition_FC); 	// Envoi trame d'acquisition de l'état des fins de course
 do{}while(Lire_Trame(&Trame_Recue)==0); 	//On attend la réponse
 Etat_FC=~Trame_Recue.data[0]; 	// On récupére l'état des fin de course
 if(fcd==0)	//SI le balai EG n'est pas en position droit, on commande une rotation à droite	
	{T_IM_Asservissement.data[2]=Vitesse_Lente;	// Valeur ->  Pour vitesse lente
	 Ecrire_Trame(T_IM_Asservissement); 	// Envoi trame de commande moteur
	 do{}while(Lire_Trame(&Trame_Recue)==0); 	//On attend la réponse
	 while(fcd==0)	
		{Ecrire_Trame(T_IRM_Acquisition_FC); 	// Envoi trame d'acquisition états fins de course
		 do{}while(Lire_Trame(&Trame_Recue)==0); 	//On attend la réponse
		 Etat_FC=~Trame_Recue.data[0]; 	// On récupére l'état des fin de course
	 	}
	}
 T_IM_Asservissement.data[2]=0;	// Valeur ->  Pour vitesse nulle
 Ecrire_Trame(T_IM_Asservissement); 	
 do{}while(Lire_Trame(&Trame_Recue)==0); 	//On attend la réponse
 
// Initialisation des variables d'état système
Etat_Arret=1,Etat_Rot_Droite=0,Etat_Rot_Gauche=0;
I_Autorise_Cycle=0,I_Intermittent=0;
Compteur_Secondes=0,Compteur_Passage_Irq=0;
// Pour afficher titre du TP
gotoxy(1,2);
printf("***********************\n");
printf(" TPs sur Reseau CAN\n"); 
printf(" Commande Essui-Glace\n");
printf(" A distance\n");
printf(" -----------------------\n");
printf("     TP  Exercice 3:\n");   
printf(" Commande systeme ESSUI GLACE\n");
printf("******************************\n");
// Pour initialiser la base de temps et temporisations
SetVect(96,&irq_bt);	// mise en place de l'autovecteur 
PITR = 0x0048;		// Une interruption toutes les 10 millisecondes 
PICR = 0x0760; 		//  96 = 60H
 
// BOUCLE PRINCIPALE
//*******************
while(1) 
  {	// Acquérir l'état des fins de courses
	//-------------------------------------
	Ecrire_Trame(T_IRM_Acquisition_FC); // Envoi trame d'acquisition des états fin de course
	Cptr_TimeOut=0;
	do{Cptr_TimeOut++;}while((Lire_Trame(&Trame_Recue)==0)&&(Cptr_TimeOut<10000));
	 if(Cptr_TimeOut==10000)
		{clsscr(),gotoxy(2,10);
		 printf(" Pas de reponse a la trame interrogative pour fins de courses \n");
		 printf("  Il faut recharger et relancer le programme \n");
		 do{}while(1);} // Stop
	 else { if(Trame_Recue.ident.extend.identificateur.ident==Ident_T_IRM1_Asservissement) 
			// On teste si l'identificateur est correct
			{Etat_FC=~Trame_Recue.data[0];}} 	// On récupére l'état des fin de course
	// Acquérir l'état Commodo Essuie Glace
	//--------------------------------------
	Ecrire_Trame(T_IRM_Etat_Commodo_EG); // Envoi trame d'acquisition des états fin de course
	Cptr_TimeOut=0;
	do{Cptr_TimeOut++;}while((Lire_Trame(&Trame_Recue)==0)&&(Cptr_TimeOut<10000));
	 if(Cptr_TimeOut==10000)
		{clsscr(),gotoxy(2,10);
		 printf(" Pas de reponse a la trame interrogative pour fins de courses \n");
		 printf("  Il faut recharger et relancer le programme \n");
		 do{}while(1);} // Stop
	 else { if(Trame_Recue.ident.extend.identificateur.ident==Ident_T_IRM8_Commodo_EG) 
			// On teste si l'identificateur est correct
			{Valeur_Commodo_EG=~(Trame_Recue.data[1]&0xF0);  	// On récupére l'état commodo EG
			 Valeur_Analogique=Trame_Recue.data[2];}} // On récupére l'état molette	EG
	// Traiter l'état Commodo Essuie Glace
	//-------------------------------------
	if(Valeur_Commodo_EG!=Valeur_Commodo_EG_Mem) // Si changement sur entrées binaires
	{if(Commodo_EG_Pos4){I_Autorise_Cycle=1,I_Intermittent=0,Cde_Vitesse=Vitesse_Rapide;} 
	// (on autorise les cycles du balai, avec une vitesse rapide, si on est en position 4 sur le commodo)
	else {if(Commodo_EG_Pos3){I_Autorise_Cycle=1,I_Intermittent=0,Cde_Vitesse=Vitesse_Moyenne;} 
	// (on autorise les cycles du balai, avec une vitesse rapide, si on est en position 3 sur le commodo)
	else {if(Commodo_EG_Pos2){I_Autorise_Cycle=1,I_Intermittent=0,Cde_Vitesse=Vitesse_Lente;} 
	// on autorise les cycles du balai, avec une vitesse lente, si on est en position 2 sur le commodo)
	else {if(Commodo_EG_Pos1)
				 {I_Intermittent=1,Cde_Vitesse=Vitesse_Lente; // Position "Intermittent"
				  Compteur_Passage_Irq=0;
				  Compteur_Secondes=0;
				 }
	else {I_Intermittent=0; // Position "Arret"
	}}}}
	printf("Bh %x\n",Valeur_Commodo_EG);
	}// Fin si changement etat entrées binaires
	if(Valeur_Analogique!=Valeur_Analogique_Mem)
	{printf("Ad %d\n",Valeur_Analogique);
		if(Valeur_Analogique>=200)Tempo_Fin=Tempo5;	// Selon la position de la molette
		else {if(Valeur_Analogique>=150)Tempo_Fin=Tempo4;	// la tempo plus ou moins longue
		else {if(Valeur_Analogique>=100)Tempo_Fin=Tempo3;	
		else {if(Valeur_Analogique>=50)Tempo_Fin=Tempo2;
		else {if(Valeur_Analogique>=1)Tempo_Fin=Tempo1;}}}}
	}
	// Mise à jour des grandeurs mémorisées
	Valeur_Analogique_Mem=Valeur_Analogique;
	Valeur_Commodo_EG_Mem=Valeur_Commodo_EG;
 
	// Traitement du diagramme des états "système"
	//--------------------------------------------
	if(Etat_Arret) 	// Si le système est dans l'état "Arret"
		{if(I_Autorise_Cycle) 		// Si on un cycle a été autorisé
			{I_Autorise_Cycle=0;
			 Etat_Arret=0,Etat_Rot_Gauche=1;	// Evolution etat système
			 T_IM_Asservissement.data[2]=Cde_Vitesse; // On commande la rotation à gauche
			 T_IM_Asservissement.data[0]=0x26; 	// Adresse du registre PWM2DC
		  	 Ecrire_Trame(T_IM_Asservissement);
			 while(Lire_Trame(&Trame_Recue)==0){}; 	// On attend la réponse
			 //if(I_Intermittent)Compteur_Secondes=0,Compteur_Passage_Irq=0;
		 	}}
	if(Etat_Rot_Gauche) 	// Si le système est dans l'état "Rotation à Gauche"
		{if(fcg) 		// Si on est arrivé en fin de course gauche
			{Etat_Rot_Gauche=0,Etat_Rot_Droite=1;	// Evolution etat système
			 T_IM_Asservissement.data[2]=0; 	// On arrête la rotation à gauche
			 T_IM_Asservissement.data[0]=0x26; 	// Adresse du registre PWM2DC
			 Ecrire_Trame(T_IM_Asservissement);
			 while(Lire_Trame(&Trame_Recue)==0){}; 	// On attend la réponse
			 T_IM_Asservissement.data[2]=Cde_Vitesse; // On commande la rotation gauche
			 T_IM_Asservissement.data[0]=0x25; 	// Adresse du registre PWM1DC
			 Ecrire_Trame(T_IM_Asservissement);
			 while(Lire_Trame(&Trame_Recue)==0){}; 	// On attend la réponse
			}}
	if(Etat_Rot_Droite) 	// Si le système est dans l'état "Rotation à droite"
		{if(fcd) 		// Si on est arrivé en fin de course droit
		   {if(Commodo_EG_Pos2|Commodo_EG_Pos3|Commodo_EG_Pos4)
			 {Etat_Rot_Droite=0,Etat_Rot_Gauche=1;	// Evolution etat système
			  T_IM_Asservissement.data[2]=0; 	// On arrête la rotation à droite
			  T_IM_Asservissement.data[0]=0x25; 	// Adresse du registre PWM1DC
		 	  Ecrire_Trame(T_IM_Asservissement);
			  while(Lire_Trame(&Trame_Recue)==0){}; 	// On attend la réponse
			  T_IM_Asservissement.data[2]=Cde_Vitesse; // On commande la rotation à gauche
			  T_IM_Asservissement.data[0]=0x26; 	// Adresse du registre PWM2DC
		  	  Ecrire_Trame(T_IM_Asservissement);
			  while(Lire_Trame(&Trame_Recue)==0){}; 	// On attend la réponse
			 }
		    else
			{Etat_Rot_Droite=0,Etat_Arret=1;	// Evolution etat système
			 T_IM_Asservissement.data[2]=0; 	// On arrête la rotation à droite
			 T_IM_Asservissement.data[0]=0x25; 	// Adresse du registre PWM1DC
		 	 Ecrire_Trame(T_IM_Asservissement);
			 while(Lire_Trame(&Trame_Recue)==0){}; 	// On attend la réponse
			 if(I_Intermittent)Compteur_Passage_Irq=0,Compteur_Secondes=0;
			}}}
	// Afficher l'état système
	//-------------------------
	/*
      Cptr_Affichage++;
	if(Cptr_Affichage==200)
		{Cptr_Affichage=0;
		 gotoxy(1,6);
		 if(I_Intermittent)printf("  Essuie glace avant en intermittent     \n");
		 else {if(Commodo_EG_Pos2)printf("  Essuie glace avant en vitesse lente     \n");
	 	 else {if(Commodo_EG_Pos3)printf("  Essuie glace avant en vitesse rapide    \n");
		 else {printf("  Essuie glace avant à l'arret          \n");}}}
		 printf("  Commande lave glace avant: %d\n",Cde_Lave_Glace_Av);
	  	 gotoxy(1,10);
		 printf("  Commande essuie glace arriere: %d\n",Cde_EG_Ar);
		 printf("  Commande lave glace arriere: %d\n",Cde_Lave_Glace_Ar);
		} // Fin "Afficher l'état"
	*/
	} //  Fin boucle principale
} // Fin fonction principale
fr/supervisors/solutions/didalab/exercice3.txt · Last modified: 2020/07/20 09:00 by 127.0.0.1
CC Attribution-Share Alike 4.0 International
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0