//************************************************************** //Aepl-Duino Allumage electronique programmable - Arduino Nano et compatibles //************************************************************** #include "TimerOne.h" char ver[] = "Version du 17-04-17";//Avec capteur pour cylindre 1 int unsigned long d[130] ; //Stock int x = 0; //son index //Pour 4 bobines crayon Exemple: Bosch Single Fire Coil PS-T 0221 604103 //Avec adaptation de trech fonction de la tension Vbat //Vbat >= 12V, trech = 2ms //Vbat < 12V trech= 11 - 0.00145VA0, Va0= Vbat(330/1330) //Vbat est mesurée au courant maxi, juste avant l'étincelle //Un capteur dans l'allumeur pour detecter le pmh etincelle du cylindre 1 et deux cibles sur le vilo //****************************************************************************** //************** Seulement 6 lignes à renseigner obligatoirement.**************** //**********Ce sont: Na Anga Ncyl AngleCapteur CaptOn Dwell****************** #define ps(v) Serial.print("Ligne_") ; Serial.print(__LINE__) ; Serial.print(#v) ; Serial.print(" = ") ;Serial.println((v)) ; Serial.println(" Sketch stop"); while (1); //Exemple, à la ligne 140, l'instruction ps(var1); //inprimera "Ligne_140var1 = 18 Sketch stop" //Macro pc(v)de debug pour imprimer le numero de ligne, le nom d'une variable, sa valeur, //puis s'arreter et attendre un clic de souris sur le bouton 'Envoyer'en haut de l'ecran seriel pour continuer. #define pc(v) Serial.print("Ligne_") ; Serial.print(__LINE__) ; Serial.print(#v) ;Serial.print(" = ") ; Serial.println((v)) ; Serial.println(" Clic bouton 'Envoyer' pour continuer") ;while (Serial.available()==0);{ int k_ = Serial.parseInt() ;} int Na[] = {0, 500, 800, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4200, 4600, 5100, 7000, 0};//t/*mn vilo //Na[] et Anga[] doivent obligatoirement débuter puis se terminer par 0, et contenir des valeurs entières >=1 //Le dernier Na fixe la ligne rouge, c'est à dire la coupure de l'allumage. //Le nombre de points est libre.L'avance est imposée à 0° entre 0 et Nplancher t/mn //Anga [] degrés d'avance vilebrequin correspondant ( peut croitre ou decroitre) int Anga[] = {0, 1 , 8 , 10 , 12, 14, 16, 22, 24, 24, 25, 26, 28, 28, 0}; int Ncyl = 4; //Nombre de cylindres, moteur 4 temps.Multiplier par 2 pour moteur 2 temps const int AngleCapteur = 45; //Position en degrès avant le PMH du capteur(Hall ou autre ). const int CaptOn = 0; //CapteurOn = 1 déclenchement sur front montant (par ex. capteur Hall "saturé") //CapteurOn = 0 déclenchement sur front descendant (par ex. capteur Hall "non saturé").Voir fin du listing //const int Dwell = 3; // Dwell =3 pour simuler un allumage à vis platinées: bobine alimentée 2/3 (66%) du cycle // Dwell = 2 pour alimentation de la bobine seulement trech ms par cycle, 3ms par exemple, //Obligatoirepour bobine 'electronique' ou de faible resistance: entre 2 et 0.5ohm //Dwell = 1 pour alimenter la bobine en permanence sauf 1ms/cycle.Elle doit pouvoir chauffer sans crainte //************************************************************************************ //****************************************************************************** //Valable pour tout type de capteur soit sur vilo soit dans l'allumeur (ou sur l'arbre à came) //La Led(D13) existant sur tout Arduino suit le courant dans la bobine //En option, multi-étincelles à bas régime pour denoyer les bougies //En option, connexion d'un potard de 100kohm enter la patte A0 et la masse //pour decaler "au vol" la courbe de quelques degrés, voir delAv plus bas //En option,multi courbes , 2 courbes supplementaires, b et c, selectionables par D8 ou D //Pour N cylindres,2,4,6,8,12,16, 4 temps, on a N cames d'allumeur ou N/2 cibles sur le vilo //Pour les moteurs à 1, 3 ou 5 cylindres, 4 temps, il FAUT un capteur dans l'allumeur (ou sur //l'arbre à cames, c'est la même chose) //Exception possible pour un monocylindre 4 temps, avec capteur sur vilo et une cible:on peut génèrer //une étincelle perdue au Point Mort Bas en utilisant la valeur Ncyl =2. //Avance 0°jusqu'a Nplancher t/mn, anti retour de kick. //**********************LES OPTIONS********************** //Si multi-étincelle désiré jusqu'à N_multi, modifier ces deux lignes const int Multi = 0;//1 pour multi-étincelles const int N_multi = 2500; //t/mn pour 4 cylindres par exemple //*******************MULTICOURBES****IF FAUT METTRE D8 ou D9 A LA MASSE!!!!!!!******* //A la place de la courbe a, on peut selectionner la courbe b (D8 à la masse)ou la c (D9 à la masse) //*******//*********Courbe b int Nb[] = {0, 3100, 0}; //Connecter D8 à la masse int Angb[] = {0, 12, 0}; //*******//*********Courbe c int Nc[] = {0, 6100, 0}; //Connecter D9 à la masse int Angc[] = {0, 16, 0}; //********************************************************************************** //************Ces 4 valeurs sont eventuellement modifiables***************** //Ce sont: Nplancher, trech , Dsecu et delAv const int Nplancher = 500; // vitesse en t/mn jusqu'a laquelle l'avance = 0° int unsigned long trech = 2000;//temps de recharge bobine, 3ms= 3000µs typique, 7ms certaines motos const int unsigned long Dsecu = 1000000;//Securite: bobine coupee à l'arret apres Dsecu µs int delAv = 2;//delta avance,par ex 2°. Quand Pot avance d'une position, l'avance croit de delAv //Uniquement si l'on veut jouer à translater les courbes d'avance en connectant un potard //Ceci est une option. Avec un potard de 100kohms entre A0 et la masse, de 0-1V environ, courbe originale //de 1 a 2V environ avance augmentée de delAv, au dela de 2V, augmentée de 2*delAv //Attention.....Pas de pleine charge avec trop d'avance, danger pour les pistons.. //***********************Variables du sketch************************************ int bobActive = 0; // Designe la bobine à activer. Sera 1 ou 3 ou 4 ou 2 ou 1 etc const int V12 = A0 ;//Entrée analogique pour Vbat après diviseur 330/1000 ohms const int Bob1 = 4; //Sortie D4 vers bobine du cylindre 1;Cible PMH allumage const int Bob2 = 5; //D5 pour Bob2 etc const int Bob3 = 6; // const int Bob4 = 7; // int Vbat = 0; //Sur A0, tension de batterie après diviseur,0 à 1023 unités const int Cible = 2; //Entrée sur D2 du capteur de cibles, R PullUp const int cyl1 = 3; //Entrée sur D3 du capteur dans l'allumeur pour cylindre 1 const int Pot = A0; //Entrée analogique sur A0 pour potard de changement de courbes. R PullUp const int Led13 = 13; //Temoin sur tout Arduino, suit le courant de bobine const int Courbe_b = 8; //Entré D8 R PullUp.Connecter à la masse pour courbe b const int Courbe_c = 9; //Entré D9 R PullUp. Connecter à la masse pour courbe c //Ici 3 positions:Decalage 0° si <1V, delAV° si 1 à 2 V, 2*delAv° pour > 2V int valPot = 0; //0 à 1023 selon la position du potentiomètre en entree float modC1 = 0; //Correctif pour C1[], deplace la courbe si potard connecté int unsigned long D = 0; //Delai en µs à attendre après la cible pour l'étincelle int milli_delay = 0; int micro_delay = 0; float RDzero = 0; //pour calcul delai avance 0° < Nplancher t/mn float Tplancher = 0; //idem//idem int tcor = 140; //correction en µs du temps de calcul pour D int unsigned long Davant_rech = 0; //Delai en µs avant la recharge de la bobine. int unsigned long prec_H = 0; //Heure du front precedent en µs int unsigned long T = 0; //Periode en cours int unsigned long Tprec = 0;//Periode precedant la T en cours, pour calcul de Drech int N1 = 0; //Couple N,Ang de debut d'un segment int Ang1 = 0; //Angle d'avance vilo en degrès int N2 = 0; //Couple N,Ang de fin de segment int Ang2 = 0; int* pN = &Na[0];//pointeur au tableau des régimes. Na sera la courbe par defaut int* pA = &Anga[0];//pointeur au tableau des avances. Anga sera la courbe par defaut float k = 0;//Constante pour calcul de C1 et C2 float C1[30]; //Tableaux des constantes de calcul de l'avance courante float C2[30]; //Tableaux des constantes de calcul de l'avance courante float Tc[30]; //Tableau des Ti correspondants au Ni //Si necessaire, augmenter ces 3 valeurs:Ex C1[40],C2[40],Tc[40] int Tlim = 0; //Période minimale, limite, pour la ligne rouge int j_lim = 0; //index maxi des N , donc aussi Ang int unsigned long NT = 0;//Facteur de conversion entre N et T à Ncyl donné int AngleCibles = 0;//Angle entre 2 cibles, 180° pour 4 cyl, 120° pour 6 cyl, par exemple int UneEtin = 1; //=1 pour chaque étincelle, testé et remis à zero par isr_GestionIbob() int Ndem = 60;//Vitesse estimée du vilo entrainé par le demarreur en t/mn int unsigned long Tdem = 0; //Periode correspondante à Ndem,forcée pour le premier tour int Mot_OFF = 0;//Sera 1 si moteur detecté arrété par l'isr_GestionIbob() int unsigned long T_multi = 0; //Periode minimale pour multi-étincelle //Permet d'icentifier le premier front et forcer T=Tdem, ainsi que Ibob=1, pour demarrer au premier front //********************LES FONCTIONS************************* void CalcD ()////////////////// // Noter que T1>T2>T3... { for (int j = 1; j <= j_lim; j++)//On commence par T la plus longue et on remonte { if (T >= Tc[j]) { //on a trouvé le bon segment de la courbe d'avance D = float(T * ( C1[j] - modC1 ) + C2[j]) ;//D en µs, C2 incorpore le temps de calcul tcor if ( T > Tplancher)D = T * RDzero;//Imposer 0° d'avance de 0 à 500t/mn break; //Sortir, on a D } } } void dlog() //debug { d[x] = 555; x++; //marqueur d[x] = bobActive; x++; d[x] = T; x++; d[x] = trech; x++; d[x] = Tprec; x++; d[x] = Davant_rech; x++; d[x] = UneEtin; x++; if (x >= 100) { x = 0; while (x <= 100) { Serial.println(d[x ]); x++; } while (1); } } void Etincelle ()////////// { //Attend D puis étincelle sur bobActive puis fait progresser bobActive pour activation par l'isr_GestionIbob() if (D < 14000) { // Currently, the largest value that will produce an accurate delay is 16383 µs delayMicroseconds(D); //Attendre D } } else { milli_delay = ((D / 1000) - 2);//Pour ces D longs, delayMicroseconds(D)ne va plus. micro_delay = (D - (milli_delay * 1000)); delay(milli_delay); // delayMicroseconds(micro_delay); } //On va ajuster trech en fonction de Vbat Vbat = analogRead(V12);//Si batterie à 12V, Vbat = 622 unités if (Vbat >= 622)trech = 2000; //Plus de 12V, 2ms else trech = 11000 - 14.5 * Vbat ; //trech augmenté qd Vbat faible if (trech >= 5000)trech = 3000; //Securité switch (bobActive) //Couper la bonne bobine puis preparer la recharge de la prochaine bobine { case 1: digitalWrite(Bob1, 0); //Etincelle sur bob1 bobActive = 3; //Preparer la recharge de la prochaine bobine break; case 3: digitalWrite(Bob3, 0);//Etincelle sur bob3 bobActive = 4; //Preparer la recharge de la prochaine bobine break; case 4: digitalWrite(Bob4, 0); //Etincelle sur bob4 bobActive = 2; //Preparer la recharge de la prochaine bobine break; case 2: digitalWrite(Bob2, 0);//Etincelle sur bob2 bobActive = 1; //Preparer la recharge de la prochaine bobine break; } digitalWrite(Led13, 0); //Temoin // Davant_rech = T - trech; Davant_rech = 2 * T - Tprec - trech;//On doit tenir compte des variations de régime moteur if (Davant_rech >= T)Davant_rech = T - 3000; //Limitation au demarrage surtout Tprec = T; //Maj de la future periode precedente Timer1.initialize(Davant_rech);//Attendre Drech µs avant de retablire le courant dans la bobine UneEtin = 1; //Pour signaler que le moteur tourne à l'isr_GestionIbob(). // dlog(); } void Init ()///////////// //Calcul de 3 tableaux,C1,C2 et Tc qui serviront à calculer D, temps d'attente //entre la detection d'une cible par le capteur et la generation de l'etincelle. //Le couple C1,C2 est determiné par la periode T entre deux cibles, correspondant au //bon segment de la courbe d'avance entrée par l'utilisateur: T est comparée à Tc { AngleCibles = 720 / Ncyl; //Cibles sur vilo.Ex pour 4 cylindres 180°, 120° pour 6 cylindres NT = 120000000 / Ncyl; //Facteur de conversion Nt/mn moteur, Tµs entre deux PMH étincelle //c'est à dire deux cibles sur vilo ou deux cames d'allumeur T_multi = NT / N_multi; //Periode minimale pour generer un train d'étincelle //T temps entre 2 étincelle soit 720° 1°=1/6N Tdem = NT / Ndem; //Periode imposée à la première étincelle qui n'a pas de valeur prec_H Tplancher = 120000000 / Nplancher / Ncyl; //T à vitesse plancher en t/mn: en dessous, avance centrifuge = 0 RDzero = float(AngleCapteur) / float(AngleCibles); Select_Courbe(); //Ajuster éventuellement les pointeurs pN et pA pour la courbe b ou c N1 = 0; Ang1 = 0; //Toute courbe part de 0 int i = 0; //locale mais valable hors du FOR pN++; pA++; //sauter le premier element de tableau, toujours =0 for (i = 1; *pN != 0; i++)//i pour les C1,C2 et Tc.Arret quand regime=0. //pN est une adresse (pointeur) qui pointe au tableau N.Le contenu pointé est *pN { N2 = *pN; Ang2 = *pA;//recopier les valeurs pointées dans N2 et Ang2 k = float(Ang2 - Ang1) / float(N2 - N1);//pente du segment (1,2) C1[i] = float(AngleCapteur - Ang1 + k * N1) / float(AngleCibles); C2[i] = - float(NT * k) / float(AngleCibles) - tcor; //Compense la durée de calcul de D Tc[i] = float(NT / N2); // N1 = N2; Ang1 = Ang2; //fin de ce segment, début du suivant pN++; pA++; //Pointer à l'element suivant de chaque tableau } j_lim = i - 1; //Revenir au dernier couple entré Tlim = Tc[j_lim]; //Ligne rouge Serial.print("Ligne_"); Serial.println(__LINE__); Serial.print("Tc = "); for (i = 1 ; i < 15; i++)Serial.println(Tc[i]); Serial.print("Tlim = "); Serial.println(Tlim); Serial.print("C1 = "); for (i = 1 ; i < 15; i++)Serial.println(C1[i]); Serial.print("C2 = "); for (i = 1 ; i < 15; i++)Serial.println(C2[i]); //Timer1 a deux roles: //1)couper le courant dans la bobine en l'absence d'etincelle pendant plus de Dsecu µs //2)après une étincelle, attendre le delais Drech avant de retablir le courant dans la bobine //Ce courant n'est retabli que trech ms avant la prochaine étincelle, condition indispensable //pour une bobine à faible resistance, disons inférieure à 3 ohms.Typiquement trech = 3ms à 7ms Timer1.attachInterrupt(isr_GestionIbob);//IT d'overflow de Timer1 (16 bits) Timer1.initialize(Dsecu);//Le courant dans la bobine sera coupé si aucune etincelle durant Dsecu µs // Mot_OFF = 1;// Signalera à loop() le premier front // digitalWrite(Bob, 0); //par principe, couper la bobine digitalWrite(Led13, 0); //Temoin } void isr_GestionIbob()////////// { //Cette isr executée soit aprés DavantRech, soit aprés Dsecu Timer1.stop(); //Arreter le decompte du timer if (UneEtin == 1) { //Le moteur tourne, activer la bonne bobine switch (bobActive)//Realimenter la prochaine bobine { case 1: //Prochaine etincelle sur bob1 digitalWrite(Bob1, 1); break; case 3: //Prochaine etincelle sur bob3 digitalWrite(Bob3, 1);//Realimenter bob3 break; case 4: //Prochaine etincelle sur bob4 digitalWrite(Bob4, 1); break; case 2: //Prochaine etincellesur bob2 digitalWrite(Bob2, 1); break; } digitalWrite(Led13, 1);//Temoin } else //Moteur arrété, tout couper { digitalWrite(Bob1, 0); digitalWrite(Bob3, 0); digitalWrite(Bob4, 0); digitalWrite(Bob2, 0); digitalWrite(Led13, 0); //Temoin//Moteur arrete, preserver les bobines, couper le courant // Mot_OFF = 1;//Permettra à loop() de detecter le premier front de capteur } UneEtin = 0; //Remet le detecteur d'étincelle à 0 Timer1.initialize(Dsecu);//Au cas où le moteur s'arrete, couper apres Dsecu µs } void Select_Courbe()/////////// //Par défaut, la courbe a est déja selectionnée { if (digitalRead(Courbe_b) == 0) { //D8 à la masse pN = &Nb[0]; // pointer à la courbe b pA = &Angb[0]; } if (digitalRead(Courbe_c) == 0) { //D9 à la masse pN = &Nc[0]; // pointer à la courbe c pA = &Angc[0]; } } void Tst_Pot()/////////// { valPot = analogRead(Pot); if (valPot < 240 || valPot > 900)modC1 = 0;//0° ou pas de potard connecté (valpot =1023 en théorie) else { if (valPot < 500)modC1 = float (delAv) / float(AngleCibles);//Position 1 else modC1 = 2 * float (delAv) / float(AngleCibles);//Position 2 } } void Wait_T() ///////////////////////////////// { volatile float monDelai = 1 ; float del = 200; while (monDelai < del) monDelai++ ; //del=100 pour 1.24ms environ, del =10000 pour 126 ms environ } //////////////////////////////////////////////////////////////////////// void setup()/////////////// ///////////////////////////////////////////////////////////////////////// { Serial.begin(9600);//Ligne suivante, 3 Macros du langage C Serial.println(__FILE__); Serial.println(__DATE__); Serial.println(__TIME__); Serial.println(ver); pinMode(Cible, INPUT_PULLUP); //Entrée front du capteur de cibles vilo sur D2 pinMode(cyl1, INPUT_PULLUP); //Entrée front montant du capteur dans l'allumeur sur D3 // pinMode(Cible, INPUT); //Pour simulateur // pinMode(cyl1, INPUT); //Pour simulateur pinMode(Bob1, OUTPUT); //Sortie sur bobine pinMode(Bob2, OUTPUT); pinMode(Bob3, OUTPUT); pinMode(Bob4, OUTPUT); pinMode(Pot, INPUT_PULLUP); //Entrée pour potard 100kohms, optionnel pinMode(Courbe_b, INPUT_PULLUP); //Entrée à la masse pour selectionner la courbe b pinMode(Courbe_c, INPUT_PULLUP); //Entrée à la masse pour selectionner la courbe c pinMode(Led13, OUTPUT);//Led d'origine sur tout Arduino, temoin du courant dans la bobine Init();// Executée une fois au demarrage et à chaque changement de courbe } /////////////////////////////////////////////////////////////////////////// void loop() //////////////// //////////////////////////////////////////////////////////////////////////// { //On veut detecter un front montant sur cyl1, capteur saturé dans l'allumeur while (digitalRead(cyl1) == 0); //Attendre que la pale entre dans la fourche //Le cylindre 1 est prêt pour activer sa bobine bobActive = 1; // Wait_T(); for (int n = 1; n <= 4; n++) //Execution de cette boucle pour les 4 cylindres { while (digitalRead(Cible) == !CaptOn);//Attendre front actif de la cible T = micros() - prec_H; //front actif, arrivé calculer T prec_H = micros(); //heure du front actuel qui deviendra le front precedent if ((T > Tlim) && (T < 500000)) //Sous la ligne rouge? { CalcD(); Etincelle();//Après etincelle, designe la bobine à alimenter pour l'isr_GestionIbob() } while (digitalRead(Cible) == CaptOn); //Attendre si la cible encore active //bobActive=0; } } /////////////////Exemples de CAPTEURS///////////////// //Capteur Honeywell cylindrique 1GT101DC,contient un aimant sur le coté,type non saturé, sortie haute à vide, //et basse avec une cible en acier. Il faut CapteurOn = 0, declenchement sur front descendant. //Le capteur à fourche SR 17-J6 contient un aimant en face,type saturé, sortie basse à vide, //et haute avec une cible en acier. Il faut CapteurOn = 1, declenchement sur front montant. //Pour les Ncyl pairs:2,4,6,8,10,12, le nombre de cibles réparties sur le vilo est Ncyl/2 //Dans les deux cas (capteur sur vilo ou dans l'alumeur) la periode entre deux cibles et la même car l'AàC tourne à Nvilo/2 //Pour les Ncyl impairs 1,3 5, 7?,9? il FAUT un capteur dans l'alumeur (ou AàC) ////////////////DEBUGGING//////////////////////// //Macro ps(v) de debug pour imprimer le numero de ligne, le nom d'une variable, sa valeur //puis s'arreter definitivement //#define ps(v) Serial.print("Ligne_") ; Serial.print(__LINE__) ; Serial.print(#v) ; Serial.print(" = ") ;Serial.println((v)) ; Serial.println(" Sketch stop"); while (1); //Exemple, à la ligne 140, l'instruction ps(var1); //inprimera "Ligne_140var1 = 18 Sketch stop" //Macro pc(v)de debug pour imprimer le numero de ligne, le nom d'une variable, sa valeur, //puis s'arreter et attendre un clic de souris sur le bouton 'Envoyer'en haut de l'ecran seriel pour continuer. //#define pc(v) Serial.print("Ligne_") ; Serial.print(__LINE__) ; Serial.print(#v) ;Serial.print(" = ") ; Serial.println((v)) ; Serial.println(" Clic bouton 'Envoyer' pour continuer") ;while (Serial.available()==0);{ int k_ = Serial.parseInt() ;} //Exemple, à la ligne 145, l'instruction pc(var2); // inprimera "Ligne_145var2 = 25.3 Clic bouton 'Envoyer' pour continuer" //float gf = 0;//pour boucle d'attente,gf GLOBALE et FLOAT indispensable // gf = 1; while (gf < 2000)gf++;//10= 100µs,100=1.1ms,2000=21.8ms //void Top()////////// //{ digitalWrite(Bob, 1); //Crée un top sur l'oscillo // gf = 1; while (gf < 10)gf++;//gf DOIT être Globale et Float 10=100µs,2000=21.8ms, retard/Cible=50µs // digitalWrite(Bob, 0); // //} //void software_Reset() //jamais testé // Redémarre le programme depuis le début mais ne // réinitialiser pas les périphériques et les registresivre... //{ // asm volatile (" jmp 0"); //}