Javascript Menu by Deluxe-Menu.com
JAGUAR MK2 & Daimler V8 250 Index du Forum JAGUAR MK2 & Daimler V8 250 Index du Forum FAQ Liste des Membres Rechercher Groupes d'utilisateurs Profil Se connecter pour vérifier ses messages privés Connexion S'enregistrer CarteCarte des membres

JAGUAR MK2 & Daimler V8 250
Rassemblement de tous les fans de la Jaguar mk2 et de ses dérivés


Un allumage électronique entièrement fait par un robot
Poster un nouveau sujet Répondre au sujet
JAGUAR MK2 & Daimler V8 250 Index du Forum » Les Paddocks   
Voir le sujet précédent :: Voir le sujet suivant
Auteur Message
LHD
Actionnaire


Inscrit le: 18 Oct 2017
Age: 75
Messages: 1456
Localisation: Morbihan

"Possède une Jaguar"

Daimler V8 2.5 1966, Jaguar Mk2 3.8 1962, S-Type 3.0 V6 2001, XJ6 2.8 BVA 1972

"Cherche une Jaguar"

Plus de place ! J'en ai même une en trop. Si ça vous dit ...


MessagePosté le: Dim 10 Mai 2026, 13:01    Sujet du message: Un allumage électronique entièrement fait par un robot Répondre en citant

Je n'ai jamais utilisé d'IA et pour voir comment ça marche je me suis fait un prompt regroupant les différentes idées sur lesquelles j'avais planché il y a quelques années pour finalement abandonner avant l'industrialisation du projet.
Si vous avez accès à une IA, copiez-collez le texte ci-dessous dans la boîte de dialogue et et vous serez absolument bluffé par le résultat !





Définition du projet :
--------------------------
Je souhaite créer un allumage électronique pour voiture ancienne à insérer dans le corps d'un allumeur Lucas 25D6, communicant en wifi avec une application externe destinée à afficher les valeurs en cours et modifier certains paramètres.

Principe:
----------
On souhaite insérer dans l'allumeur un disque rotatif remplaçant le doigt d'allumage et comportant une série d'aimants permanents sur sa périphérie dont le passage près de capteurs à effet hall constitueront les signaux d'entrée du module électronique.
Un de ces aimants est décentré de 10mm vers le centre et sera détecté en passant devant le capteur hall N°1, Tous les autres aimants seront détecté par le capteur N°2
Les capteurs hall sont soudés sur une carte électronique circulaire incluant un modulepour Arduino ESP32-S3 N16R8 CH343 Type-C ESP32S3 avec Wifi 2.4G , et les drivers de puissance pour les bobine d'allumage crayon (modèle équipant les Renault Clio) .
Le circuit devra comporter autant de sorties que de cylindres, car on utilise une bobine d'allumage de type crayon par cylindre .
Le nom des ports de sortie sera repéré avec des numéros correspondant à l'ordre d'allumage des cylindres qui est 1,5,3,6,2,4
Le capteur 1 est associé au PMH du cylindre 1
On devra mémoriser dix courbes d'allumage de dix points, chaque courbe possédant un nom de 20 caractères maximum.
On definit une seule courbe de correction d'avance basée sur le signal analogique fourni par un capteur mesurant la dépression prélevée entre le papillon du carburateur et le collecteur d'admission.
Le programme devra mémoriser toutes les courbes et variables en NVRAM.
Le programme devra mémoriser la dernière courbe utilisée
Une entrée est prévue pour le signal analogique d'un capteur piezoelectrique de détection de cognement
On devra pouvoir modifier le dwell des bobines (dwell= le temps de montée du courant dans la bobine avant la décharge)
Le programme communiquera par wifi avec une application extérieure en envoyant chaque demi-seconde la vitesse de rotation du moteur, l'avance actuelle, le numéro de la courbe utilisée.
L'application extérieure enverra au module la courbe d'allumage à utiliser, qui pourra être mofifiée pendant le fonctionnement et remplacer la courbe actuelle.
Une seule courbe de correction de la dépression est prévue, elle doit être modifiable par l'application extérieure.

Précisions :
-------------
Prévoir six cylindres avec l'ordre d'allumage 1,5,3,6,2,4
Lorsque la vitesse de rotation est inférieure à 500 tours/minute il faut commander à la bobine en cours trois étincelles pour favoriser le démarrage.
Le module commence par une fonction de règlage qui allume une led lorsque l'aimant du cylindre 1 actionne le capteur correspondant. Ce mode règlage est déselectionné dès que la vitesse de rotation est supérieur à 60 tours/minute.
Le calcul de la vitesse doit être effectué six fois par tour en mesurant l'écart de temps entre deux impulsions des capteurs à effet hall
On ne doit pas déclencher d'étincelle avant d'avoir détecté une vitesse de rotation >60 RPM et le PMH du cylindre 1
insérer une fonction facultative de detection de cognement pour corriger l'avance.
Le circuit électronique devra comprendre les composants nécessaires à un antiparasitage efficace
Le diamètre intérieur de l'allumeur étant de 13cm, le circuit imprimé devra être circulaire et d'un diamètre de 13cm
Le disque de 12,8mm de diamètre et 4mm d'épaisseur devra posséder un support central cylindrique creux de diamètre intérieur 16mm, diametre extérieur 22mm, hauteur 15mm, comportant sur sa face interne une rainure en relief faisant fonction d'ergot de blocage placée en face de l'aimant N°1, de 3 mm de large 5mm de profondeur, 14mm de haut
Le disque comporte des logements permettant d'insérer les aimants N42 sans dépassement

Fonctionnalités de l'application externe au format android :
L'application externe communique par wifi avec le module d'allumages.
Au démarrage, l'application Android emet une requête pour charger en mémoire toutes les courbes du module d'allumage et tous les paramètres modifiables.

Un onglet contient l'écran par defaut : Il affiche la vitesse et l'avance au moyen de deux jauges circulaires (avec aiguille) imitant l'apparence de jauges SMITHS chromées avec gravures blanches sur fond noir, placées sur un fond d'écran au décor ronce de noyer. Une liste déroulante permet de sélectionner une des courbes enregistrées.

Un onglet destiné à l'écran de réglage : Il affiche le graphique de la courbe d'avance sélectionnée par un choix dans une liste déroulante modifiable.
La liste déroulante est présélectionnée sur la courbe utilisée par le module d'allumage. Le graphique de la courbe de correction liée à la dépression.
Chaque point de chaque courbe est ajustable par glissement tactile vers le haut ou le bas.
Un bouton permet d'enregistrer les modification après confirmation.
Une zone de saisie numérique en bas d'écran est prévue pour le dwell .

Travail demandé :
---------------------
Ecrire l'application pour le module Arduino
Ecrire l'application externe pour android 9 et suivants.
Dessiner le disque porte aimants et Fournir un fichier au format f3d pour l'application "Fusion"
Concevoir le circuit électronique et dessiner son implantation,
fournir le schéma electronique,
Créer les fichiers du circuit imprimé double-face au format .pcb et gerber.
Creer un fichier du circuit imprimé au format .sch pour eagle
Proposer les câbles et connecteurs destinés à la commande des bobines, de grade automobile résistant à la chaleur.
_________________
“Why do the Brits drink warm beer?
- Cause Lucas also makes refrigerators.”
(Private joke Yankee de très mauvais goût)
Revenir en haut de page Voir le profil de l'utilisateur Envoyer un message privé
Henri-47
Concessionnaire


Inscrit le: 02 Jan 2014
Age: 74
Messages: 226
Localisation: 47

"Possède une Jaguar"

MK2 3.8L

"Cherche une Jaguar"

????


MessagePosté le: Dim 10 Mai 2026, 17:17    Sujet du message: Répondre en citant

Un lien vers un projet réalisé par des moustachus !
https://www.old-droppers.com/index.php?s=afeb74e6f6fe99299ad0717a6e1f5d77&showtopic=38723&page=1

Je m'en suis servi pour faire l'alimentation électronique de ma dauphine (roue phonique avec capteur PMH, capteur depression, allumage EDIS4 piloté par arduino et cartographie).

PhotoService.com
Revenir en haut de page Voir le profil de l'utilisateur Envoyer un message privé
Patrickdup
Actionnaire


Inscrit le: 02 Déc 2007
Messages: 5214
Localisation: Savoie Chambéry

"Possède une Jaguar"

MK 2 3L8 '61

"Cherche une Jaguar"

Pas encore, mais sur un malentendu...


MessagePosté le: Dim 10 Mai 2026, 21:08    Sujet du message: Répondre en citant

J'ai collé dans chatgpt!

Bluffant. Tu vas tenter le coup? Tu as demandé les 6 bobines?
Revenir en haut de page Voir le profil de l'utilisateur Envoyer un message privé
LHD
Actionnaire


Inscrit le: 18 Oct 2017
Age: 75
Messages: 1456
Localisation: Morbihan

"Possède une Jaguar"

Daimler V8 2.5 1966, Jaguar Mk2 3.8 1962, S-Type 3.0 V6 2001, XJ6 2.8 BVA 1972

"Cherche une Jaguar"

Plus de place ! J'en ai même une en trop. Si ça vous dit ...


MessagePosté le: Lun 11 Mai 2026, 01:13    Sujet du message: Répondre en citant

J'ai essayé ChatGPT et Claude. Claude produit un code pour Android SDK beaucoup plus clean que ChatGPT. J'ai pu compiler le APK sans rencontrer d'erreur.
Les deux gèrent bien l'arduino.
Je vais faire un autre essai avec un aimant sur le doigt d'allumeur et 6 capteurs Hall pour voir ..
_________________
“Why do the Brits drink warm beer?
- Cause Lucas also makes refrigerators.”
(Private joke Yankee de très mauvais goût)
Revenir en haut de page Voir le profil de l'utilisateur Envoyer un message privé
LHD
Actionnaire


Inscrit le: 18 Oct 2017
Age: 75
Messages: 1456
Localisation: Morbihan

"Possède une Jaguar"

Daimler V8 2.5 1966, Jaguar Mk2 3.8 1962, S-Type 3.0 V6 2001, XJ6 2.8 BVA 1972

"Cherche une Jaguar"

Plus de place ! J'en ai même une en trop. Si ça vous dit ...


MessagePosté le: Lun 11 Mai 2026, 13:10    Sujet du message: Répondre en citant

Je lui ai demandé de faire un autre modèle, cette fois avec un aimant sur le doigt d'allumage et six capteurs soudés sur une platine circulaire en dessous.
Le résultat est impressionnant, j'aurais mis plusieurs jours à l'écrire, et en plus il a pensé à des trucs que je n'aurais trouvés qu'après les essais !

Voici le code Arduino tel qu'il me l'a écrit, sans aucune correction de ma part :


=============================================
*/

#include <Arduino>/*
* =========================================================================
* Allumage électronique pour Lucas 25D6 (6 cylindres, ordre 1-5-3-6-2-4)
* Plate-forme : ESP32-WROOM-32
* Communication : Bluetooth Classic (SPP), JSON-Lines
* Stockage : Preferences (NVRAM/flash)
* =========================================================================
* Auteur : généré pour le projet d'allumage Lucas 25D6
* Licence : MIT
* Dépendances Arduino :
* - ArduinoJson (Benoit Blanchon) >= 6.21
* - BluetoothSerial (incluse dans le core ESP32)
* - Preferences (incluse dans le core ESP32)
* Cible Arduino IDE : ESP32 board package (Espressif) >= 2.0.14
* ============================
#include <BluetoothSerial>
#include <Preferences>
#include <ArduinoJson>
#include <esp_task_wdt>

#if !defined(CONFIG_BT_SPP_ENABLED)
#error "Activer Bluetooth Classic + SPP dans les outils Arduino IDE"
#endif

// =========================================================================
// CONFIGURATION GPIO
// =========================================================================
// Capteurs Hall individuels (entrées avec pull-up interne).
// L'indice 0 du tableau = capteur 1 (PMH cyl. 1), etc.
static const uint8_t PIN_HALL[6] = { 13, 14, 16, 17, 25, 26 };
static const uint8_t PIN_TRIGGER = 27; // OU câblé des 6 capteurs via diodes 1N4148

// Sorties bobines indexées par numéro de cylindre 1..6 (index 0 inutilisé).
static const uint8_t PIN_COIL[7] = { 0, 18, 23, 21, 33, 19, 22 };
// cyl1 cyl2 cyl3 cyl4 cyl5 cyl6

static const uint8_t PIN_LED_SETUP = 2; // LED on-board
static const uint8_t PIN_MAP = 36; // ADC1_CH0
static const uint8_t PIN_KNOCK = 39; // ADC1_CH3

// =========================================================================
// CONSTANTES MOTEUR
// =========================================================================
// Ordre d'allumage : 1, 5, 3, 6, 2, 4
static const uint8_t FIRING_ORDER[6] = { 1, 5, 3, 6, 2, 4 };
static const uint8_t NUM_CYL = 6;
static const uint8_t NUM_CURVES = 10;

// Seuils RPM
static const float RPM_MIN_SPARK = 60.0f;
static const float RPM_MULTISPARK_MAX = 500.0f;
static const float RPM_NO_TIMEOUT_BELOW = 30.0f; // en dessous on autorise l'arrêt

// Degrés vilebrequin par événement (6 événements / 2 tours = 120°)
static const float CRANK_DEG_PER_EVENT = 120.0f;

// Sécurité dwell : limite haute (protège la bobine en cas de calage)
static const uint16_t DWELL_MAX_US = 8000;
static const uint16_t DWELL_MIN_US = 500;

// Timeout RPM : pas de trigger pendant 1 s → arrêt
static const uint32_t TRIGGER_TIMEOUT_US = 1000000UL;

// =========================================================================
// STRUCTURES & DONNÉES
// =========================================================================
struct CurvePoint {
float rpm; // ou kPa pour la courbe dépression
float advance; // degrés vilebrequin (ou correction °)
};

struct IgnitionCurve {
char name[21];
CurvePoint points[10];
};

static IgnitionCurve gCurves[NUM_CURVES];
static IgnitionCurve gVacuumCurve; // on réutilise la même struct ; name peut rester "VAC"

static uint8_t gActiveCurve = 0;
static uint16_t gDwellUs = 3000;
static bool gKnockEnable = false;

// Correction adaptative cognement par cylindre (degrés à retirer)
static float gKnockCorr[7] = {0,0,0,0,0,0,0};

// =========================================================================
// ÉTAT TEMPS RÉEL (volatile)
// =========================================================================
volatile uint64_t lastTriggerUs = 0;
volatile uint64_t prevTriggerUs = 0;
volatile uint32_t periodUs = 0; // intervalle entre 2 triggers
volatile uint32_t periodUsFilt = 0; // filtré (moyenne glissante simple)
volatile int8_t currentSensor = -1; // 0..5
volatile bool setupMode = true;
volatile bool sparkArmed = false; // false tant que RPM<60

// Détection cognement : flag posé par ISR ADC ou tâche
volatile uint8_t knockDetectedForCyl = 0;

// Compteur multi-spark : nombre d'étincelles restantes pour le cycle en cours
volatile uint8_t multiSparkCount = 0;

// Dernier cylindre tiré, pour log
volatile uint8_t lastFiredCyl = 0;

// =========================================================================
// FILE D'ÉVÉNEMENTS BOBINE
// =========================================================================
// Petite file pour planifier dwell-start / spark / multi-spark.
// 16 emplacements largement suffisants à 6000 tr/min.
struct PendEvent {
uint64_t when_us;
uint8_t cyl; // 1..6
bool setHigh; // true = début dwell, false = front descendant = étincelle
bool valid;
};
#define EVENT_QUEUE_SIZE 16
static volatile PendEvent eventQueue[EVENT_QUEUE_SIZE];
static portMUX_TYPE queueMux = portMUX_INITIALIZER_UNLOCKED;

// =========================================================================
// PÉRIPHÉRIQUES
// =========================================================================
static BluetoothSerial SerialBT;
static Preferences prefs;
static hw_timer_t* sparkTimer = nullptr;

// =========================================================================
// UTILITAIRES COURBE
// =========================================================================
// Interpolation linéaire dans une courbe à 10 points (triée par x croissant).
// Hors bornes : on étend la valeur extrême (clipping).
static float interpCurve(const CurvePoint* pts, uint8_t n, float x) {
if (n == 0) return 0.0f;
if (x <pts>= pts[n-1].rpm) return pts[n-1].advance;
for (uint8_t i = 0; i <n>= pts[i].rpm && x <= pts[i+1].rpm) {
float dx = pts[i+1].rpm - pts[i].rpm;
if (dx < 1e-6f) return pts[i].advance;
float t = (x - pts[i].rpm) / dx;
return pts[i].advance + t * (pts[i+1].advance - pts[i].advance);
}
}
return pts[n-1].advance;
}

// =========================================================================
// NVRAM (Preferences)
// =========================================================================
static void initDefaultCurves();

static void saveAllToNvram() {
prefs.begin("ign", false);
prefs.putUChar("active", gActiveCurve);
prefs.putUShort("dwell", gDwellUs);
prefs.putBool("knock", gKnockEnable);
for (uint8_t i = 0; i < NUM_CURVES; ++i) {
char key[12];
snprintf(key, sizeof(key), "c%u", i);
prefs.putBytes(key, &gCurves[i], sizeof(IgnitionCurve));
}
prefs.putBytes("vac", &gVacuumCurve, sizeof(IgnitionCurve));
prefs.end();
}

static void loadAllFromNvram() {
prefs.begin("ign", true);
if (!prefs.isKey("active")) {
prefs.end();
initDefaultCurves();
saveAllToNvram();
return;
}
gActiveCurve = prefs.getUChar("active", 0);
gDwellUs = prefs.getUShort("dwell", 3000);
gKnockEnable = prefs.getBool("knock", false);
for (uint8_t i = 0; i < NUM_CURVES; ++i) {
char key[12];
snprintf(key, sizeof(key), "c%u", i);
prefs.getBytes(key, &gCurves[i], sizeof(IgnitionCurve));
}
prefs.getBytes("vac", &gVacuumCurve, sizeof(IgnitionCurve));
prefs.end();
}

// Courbes par défaut (au premier flash, ou si NVRAM vierge)
static void initDefaultCurves() {
// Courbe 0 : Stock
strncpy(gCurves[0].name, "Stock", 20);
CurvePoint stock[10] = {
{500,5},{1000,10},{1500,15},{2000,20},{2500,25},
{3000,28},{3500,30},{4000,32},{4500,33},{5000,34}
};
memcpy(gCurves[0].points, stock, sizeof(stock));
// Les 9 autres : copie de la courbe 0 avec un nom différent
for (uint8_t i = 1; i < NUM_CURVES; ++i) {
snprintf(gCurves[i].name, 21, "Courbe %u", i);
memcpy(gCurves[i].points, stock, sizeof(stock));
}
// Courbe dépression : neutre (pas de correction)
strncpy(gVacuumCurve.name, "VAC", 20);
CurvePoint vac[10] = {
{10,-4},{20,-2},{30,0},{40,2},{50,4},
{60,5},{70,6},{80,7},{90,8},{100,8}
};
memcpy(gVacuumCurve.points, vac, sizeof(vac));
}

// =========================================================================
// CAPTEURS ANALOGIQUES
// =========================================================================
// MAP : capteur typique 1 bar absolu, sortie 0,5–4,5 V → après diviseur 1:2 → 0,25–2,25 V
// Sur ADC 12 bits avec atténuation ADC_11db (0–3,3 V) : 1 LSB ≈ 0,8 mV
// On exprime la dépression en kPa (atmosphère ≈ 100 kPa, dépression -50 kPa ≈ ralenti)

static float readMAP_kPa() {
// Moyenne sur 8 échantillons pour lisser
uint32_t acc = 0;
for (uint8_t i = 0; i < 8; ++i) acc += analogRead(PIN_MAP);
float v_adc = (acc / 8.0f) * (3.3f / 4095.0f); // tension sur ADC (après diviseur)
float v_sensor = v_adc * 2.0f; // avant diviseur (1:2)
// Calibration MAP MPX4250 : V = 0,2 + 0,04 × P(kPa) → P = (V - 0,2) / 0,04
float kPa = (v_sensor - 0.2f) / 0.04f;
if (kPa <0> 110) kPa = 110;
return kPa;
}

// Lecture brute knock : valeur moyenne quadratique sur une fenêtre.
// Une détection plus fine (filtrage passe-bande numérique) demande un échantillonnage
// régulier sur tâche dédiée ; ici on fait simple.
static volatile float knockEnergy = 0;

// =========================================================================
// PLANIFICATEUR D'ÉVÉNEMENTS
// =========================================================================
// IRAM_ATTR : code logé en RAM rapide pour les ISR
static void IRAM_ATTR onSparkTimer();

// Ajoute un événement dans la file, trié par when_us croissant.
// Si premier événement, arme le timer.
static void enqueueEvent(uint64_t when_us, uint8_t cyl, bool setHigh) {
portENTER_CRITICAL(&queueMux);
// Trouve un slot libre
int slot = -1;
for (uint8_t i = 0; i < EVENT_QUEUE_SIZE; ++i) {
if (!eventQueue[i].valid) { slot = i; break; }
}
if (slot < 0) {
portEXIT_CRITICAL(&queueMux);
return; // file pleine, on droppe (cas dégradé)
}
eventQueue[slot].when_us = when_us;
eventQueue[slot].cyl = cyl;
eventQueue[slot].setHigh = setHigh;
eventQueue[slot].valid = true;

// Trouve la prochaine échéance
uint64_t next = UINT64_MAX;
for (uint8_t i = 0; i < EVENT_QUEUE_SIZE; ++i) {
if (eventQueue[i].valid && eventQueue[i].when_us < next) {
next = eventQueue[i].when_us;
}
}
portEXIT_CRITICAL(&queueMux);

// Programme le hardware timer
uint64_t now = (uint64_t) esp_timer_get_time();
int64_t delta = (int64_t)(next - now);
if (delta < 10) delta = 10;
timerAlarmDisable(sparkTimer);
timerWrite(sparkTimer, 0);
timerAlarmWrite(sparkTimer, delta, false); // one-shot
timerAlarmEnable(sparkTimer);
}

// ISR du timer : exécute les événements dont l'échéance est atteinte
static void IRAM_ATTR onSparkTimer() {
portENTER_CRITICAL_ISR(&queueMux);
uint64_t now = (uint64_t) esp_timer_get_time();
bool acted;
do {
acted = false;
int idx = -1;
uint64_t earliest = UINT64_MAX;
for (uint8_t i = 0; i < EVENT_QUEUE_SIZE; ++i) {
if (eventQueue[i].valid && eventQueue[i].when_us <= now + 5
&& eventQueue[i].when_us <earliest>= 0) {
uint8_t cyl = eventQueue[idx].cyl;
bool setHigh = eventQueue[idx].setHigh;
eventQueue[idx].valid = false;
if (cyl >= 1 && cyl <= 6) {
digitalWrite(PIN_COIL[cyl], setHigh ? HIGH : LOW);
if (!setHigh) lastFiredCyl = cyl;
}
acted = true;
}
} while (acted);

// Reprogramme l'alarme pour le prochain événement
uint64_t next = UINT64_MAX;
for (uint8_t i = 0; i < EVENT_QUEUE_SIZE; ++i) {
if (eventQueue[i].valid && eventQueue[i].when_us < next) {
next = eventQueue[i].when_us;
}
}
portEXIT_CRITICAL_ISR(&queueMux);

if (next != UINT64_MAX) {
int64_t delta = (int64_t)(next - (uint64_t)esp_timer_get_time());
if (delta < 10) delta = 10;
timerAlarmDisable(sparkTimer);
timerWrite(sparkTimer, 0);
timerAlarmWrite(sparkTimer, delta, false);
timerAlarmEnable(sparkTimer);
}
}

// =========================================================================
// ISR : capteurs individuels
// =========================================================================
// Mémorise quel capteur a été vu en dernier (utilisé par l'ISR trigger).
static void IRAM_ATTR onHall1() { currentSensor = 0; }
static void IRAM_ATTR onHall2() { currentSensor = 1; }
static void IRAM_ATTR onHall3() { currentSensor = 2; }
static void IRAM_ATTR onHall4() { currentSensor = 3; }
static void IRAM_ATTR onHall5() { currentSensor = 4; }
static void IRAM_ATTR onHall6() { currentSensor = 5; }

// Retourne l'index dans FIRING_ORDER du cylindre dont le capteur N est associé
static inline int cylinderIdxInFiring(uint8_t cylNumber) {
for (uint8_t i = 0; i <NUM_CYL>> 2);

// RPM = 20 / T(s) = 20e6 / T(µs)
float rpm = 20000000.0f / (float)periodUsFilt;

// Mode réglage : LED reflète l'état du capteur 1, on n'arme rien
if (rpm < RPM_MIN_SPARK) {
sparkArmed = false;
return;
}
sparkArmed = true;
if (setupMode) setupMode = false;

int sensor = currentSensor;
if (sensor <0>= 6) return; // info perdue, on ne planifie pas ce coup

// sensor = N-1 (0..5), associé au PMH du cyl N = sensor+1
uint8_t cylAtTDC = sensor + 1;
int idxFiring = cylinderIdxInFiring(cylAtTDC);
if (idxFiring < 0) return;

// Prochain cylindre à allumer dans l'ordre d'allumage
uint8_t nextCyl = FIRING_ORDER[(idxFiring + 1) % NUM_CYL];

// Extrapolation : prochain PMH dans T µs
uint64_t tNextTDC = now + periodUsFilt;

// Calcul d'avance : courbe active + correction dépression + correction knock
float advBase = interpCurve(gCurves[gActiveCurve].points, 10, rpm);
// La lecture MAP en ISR est interdite (ADC2 indispo, ADC1 prend plusieurs µs).
// On utilise la dernière valeur lue par la loop().
extern volatile float gLastMAP_kPa;
float vacCorr = interpCurve(gVacuumCurve.points, 10, gLastMAP_kPa);
float knockCorr = gKnockEnable ? gKnockCorr[nextCyl] : 0.0f;

float advance = advBase + vacCorr - knockCorr;
if (advance <0> 45) advance = 45; // sécurité

// Conversion degrés → µs (120° vilebrequin par événement)
uint32_t deltaSparkUs = (uint32_t)((advance / CRANK_DEG_PER_EVENT) * (float)periodUsFilt);

uint64_t tSpark = tNextTDC - deltaSparkUs;
uint16_t dwell = gDwellUs;
if (dwell > DWELL_MAX_US) dwell = DWELL_MAX_US;
if (dwell <DWELL_MIN_US> référence physique), on tire ASAP
if (tDwellStart <= now + 100) {
tDwellStart = now + 100;
tSpark = tDwellStart + dwell;
}

enqueueEvent(tDwellStart, nextCyl, true);
enqueueEvent(tSpark, nextCyl, false);

// Multi-spark si RPM < 500 : 2 étincelles supplémentaires après la 1ère
if (rpm < RPM_MULTISPARK_MAX) {
for (uint8_t k = 1; k <= 2; ++k) {
uint64_t tDS = tSpark + k * (dwell + 500); // 500 µs entre chaque étincelle
uint64_t tSP = tDS + dwell;
enqueueEvent(tDS, nextCyl, true);
enqueueEvent(tSP, nextCyl, false);
}
}
}

// MAP rafraîchi par la loop (lecture lente)
volatile float gLastMAP_kPa = 100.0f;

// =========================================================================
// BLUETOOTH : parsing et émission
// =========================================================================
static String btRxLine;

static void sendJsonLine(JsonDocument& doc) {
serializeJson(doc, SerialBT);
SerialBT.print('\n');
}

static void sendErr(const char* msg) {
StaticJsonDocument<128> doc;
doc["r"] = "err"; doc["msg"] = msg;
sendJsonLine(doc);
}

static void sendOk(const char* cmd) {
StaticJsonDocument<96> doc;
doc["r"] = "ok"; doc["cmd"] = cmd;
sendJsonLine(doc);
}

static void sendAllConfig() {
DynamicJsonDocument doc(8192);
doc["r"] = "all";
doc["active"] = gActiveCurve;
doc["dwell"] = gDwellUs;
doc["knock"] = gKnockEnable;
JsonArray curves = doc.createNestedArray("curves");
for (uint8_t i = 0; i < NUM_CURVES; ++i) {
JsonObject c = curves.createNestedObject();
c["name"] = gCurves[i].name;
JsonArray pts = c.createNestedArray("pts");
for (uint8_t k = 0; k < 10; ++k) {
JsonArray pt = pts.createNestedArray();
pt.add(gCurves[i].points[k].rpm);
pt.add(gCurves[i].points[k].advance);
}
}
JsonObject vac = doc.createNestedObject("vacuum");
JsonArray vpts = vac.createNestedArray("pts");
for (uint8_t k = 0; k < 10; ++k) {
JsonArray pt = vpts.createNestedArray();
pt.add(gVacuumCurve.points[k].rpm);
pt.add(gVacuumCurve.points[k].advance);
}
sendJsonLine(doc);
}

static void handleBtCommand(const String& line) {
StaticJsonDocument<2048> doc;
DeserializationError err = deserializeJson(doc, line);
if (err) { sendErr("json"); return; }
const char* cmd = doc["cmd"] | "";

if (strcmp(cmd, "get_all") == 0) {
sendAllConfig();
} else if (strcmp(cmd, "set_curve") == 0) {
int idx = doc["idx"] | -1;
if (idx <0>= NUM_CURVES) { sendErr("idx"); return; }
const char* name = doc["name"] | "";
strncpy(gCurves[idx].name, name, 20);
gCurves[idx].name[20] = 0;
JsonArray pts = doc["pts"];
if (pts.size() != 10) { sendErr("pts size"); return; }
for (uint8_t k = 0; k < 10; ++k) {
gCurves[idx].points[k].rpm = pts[k][0] | 0.0f;
gCurves[idx].points[k].advance = pts[k][1] | 0.0f;
}
sendOk("set_curve");
} else if (strcmp(cmd, "set_vacuum") == 0) {
JsonArray pts = doc["pts"];
if (pts.size() != 10) { sendErr("pts size"); return; }
for (uint8_t k = 0; k < 10; ++k) {
gVacuumCurve.points[k].rpm = pts[k][0] | 0.0f;
gVacuumCurve.points[k].advance = pts[k][1] | 0.0f;
}
sendOk("set_vacuum");
} else if (strcmp(cmd, "set_active") == 0) {
int idx = doc["idx"] | -1;
if (idx <0>= NUM_CURVES) { sendErr("idx"); return; }
gActiveCurve = (uint8_t)idx;
sendOk("set_active");
} else if (strcmp(cmd, "set_dwell") == 0) {
int us = doc["us"] | -1;
if (us <DWELL_MIN_US> DWELL_MAX_US) { sendErr("dwell"); return; }
gDwellUs = (uint16_t)us;
sendOk("set_dwell");
} else if (strcmp(cmd, "set_knock") == 0) {
gKnockEnable = doc["en"] | false;
sendOk("set_knock");
} else if (strcmp(cmd, "save") == 0) {
saveAllToNvram();
sendOk("save");
} else {
sendErr("unknown cmd");
}
}

static void btPoll() {
while (SerialBT.available()) {
char c = SerialBT.read();
if (c == '\n') {
if (btRxLine.length() > 0) handleBtCommand(btRxLine);
btRxLine = "";
} else if (c != '\r' && btRxLine.length() < 4000) {
btRxLine += c;
}
}
}

static uint32_t lastTelemetryMs = 0;
static void sendTelemetry() {
StaticJsonDocument<256> doc;
doc["t"] = "tel";
float rpm = (periodUsFilt > 0) ? (20000000.0f / periodUsFilt) : 0.0f;
if (millis() - (lastTriggerUs/1000) > 1000) rpm = 0;
doc["rpm"] = (int)rpm;
float adv = interpCurve(gCurves[gActiveCurve].points, 10, rpm);
doc["adv"] = adv + interpCurve(gVacuumCurve.points, 10, gLastMAP_kPa);
doc["curve"] = gActiveCurve;
doc["vac"] = interpCurve(gVacuumCurve.points, 10, gLastMAP_kPa);
doc["knock"] = knockEnergy;
doc["map"] = gLastMAP_kPa;
sendJsonLine(doc);
}

// =========================================================================
// SETUP / LOOP
// =========================================================================
void setup() {
Serial.begin(115200);
delay(50);
Serial.println("Allumage Lucas 25D6 - démarrage");

// GPIO
for (uint8_t i = 0; i < 6; ++i) {
pinMode(PIN_HALL[i], INPUT_PULLUP);
}
pinMode(PIN_TRIGGER, INPUT_PULLUP);
pinMode(PIN_LED_SETUP, OUTPUT);
digitalWrite(PIN_LED_SETUP, LOW);

for (uint8_t c = 1; c <6> TRIGGER_TIMEOUT_US && lastTriggerUs != 0) {
// Coupe toutes les bobines, repasse en mode réglage
for (uint8_t c = 1; c <6>= 500) {
lastTelemetryMs = millis();
if (SerialBT.hasClient()) sendTelemetry();
}

// 5. Détection knock : lecture ADC périodique, simple seuil énergie
static uint32_t lastKnockSample = 0;
if (gKnockEnable && (millis() - lastKnockSample) > 5) {
lastKnockSample = millis();
int raw = analogRead(PIN_KNOCK);
// mesure relative à la moyenne (filtrage DC ultra-simple)
static float dcMean = 2048;
dcMean = dcMean * 0.99f + raw * 0.01f;
float ac = (raw - dcMean);
knockEnergy = knockEnergy * 0.95f + (ac * ac) * 0.05f;
// Si énergie dépasse seuil, retire 2° au dernier cylindre tiré
if (knockEnergy > 20000.0f && lastFiredCyl > 0) {
if (gKnockCorr[lastFiredCyl] < 6.0f) gKnockCorr[lastFiredCyl] += 0.5f;
} else {
// Récupération progressive
for (uint8_t c = 1; c <6> 0) gKnockCorr[c] -= 0.001f;
}
}
}

delay(2);
}
_________________
“Why do the Brits drink warm beer?
- Cause Lucas also makes refrigerators.”
(Private joke Yankee de très mauvais goût)
Revenir en haut de page Voir le profil de l'utilisateur Envoyer un message privé
Patrickdup
Actionnaire


Inscrit le: 02 Déc 2007
Messages: 5214
Localisation: Savoie Chambéry

"Possède une Jaguar"

MK 2 3L8 '61

"Cherche une Jaguar"

Pas encore, mais sur un malentendu...


MessagePosté le: Lun 11 Mai 2026, 20:25    Sujet du message: Répondre en citant

Je regrette de ne pas m'être intéressé à l'arduino, il y a pas mal d'année.

L'IA fait des choses formidables.................. et des moins bonnes!
Revenir en haut de page Voir le profil de l'utilisateur Envoyer un message privé
cormoran
Professeur Tournesol


Inscrit le: 06 Déc 2007
Messages: 5965
Localisation: 78 - 13

"Possède une Jaguar"

Daimler 250V8

"Cherche une Jaguar"

Non, une Daimler, évidemment !


MessagePosté le: Mar 12 Mai 2026, 09:33    Sujet du message: Répondre en citant

L'IA te met aussi les commentaires dans le code ? Icon_scratch
Et en français en plus...
C'est impressionnant. J'ai pas mal bidouillé avec des Arduino mais j'avoue ne pas avoir ce niveau en terme de programmation.
_________________
- Elle est de quelle année ?
- 69
- Aah, bien ! . . .
Revenir en haut de page Voir le profil de l'utilisateur Envoyer un message privé
nicanami
Actionnaire


Inscrit le: 15 Déc 2007
Age: 60
Messages: 3694
Localisation: Toulouse

"Possède une Jaguar"

Jaguar ? non. Daimler Saloon Mossieu, et VdP Princess

"Cherche une Jaguar"

Mark ten boite méca...


MessagePosté le: Mar 12 Mai 2026, 10:10    Sujet du message: Répondre en citant

Il manque la section Volkswagen : "Si prise OBD2 branchée, diminuer l'avance pour passer la pollution". Mais l'IA a vu juste : y'a pas de prise OBD2 sur une mk2 !

Ceci dit en regardant le code, ca ressemble beaucoup plus a de l'espionnage industriel (plus ou moins organisé) qu'a de l'intelligence...

Là ou ça va devenir cocasse, c'est quand l'IA aura vectorisé tout le forum, et diffusera comme vérité tout ce que nous considérons implicitement comme des fantaisies...
_________________
Le voyage est la seule dépense avec laquelle on s'enrichit.
Revenir en haut de page Voir le profil de l'utilisateur Envoyer un message privé
Montrer les messages depuis:   
Toutes les heures sont au format GMT
Poster un nouveau sujet Répondre au sujet
JAGUAR MK2 & Daimler V8 250 Index du Forum » Les Paddocks  
Sauter vers:  
  Vous ne pouvez pas poster de nouveaux sujets dans ce forum
Vous ne pouvez pas répondre aux sujets dans ce forum
Vous ne pouvez pas éditer vos messages dans ce forum
Vous ne pouvez pas supprimer vos messages dans ce forum
Vous ne pouvez pas voter dans les sondages de ce forum
Aller au début



Powered by phpBB © 2001, 2005 phpBB Group :: Theme & Graphics by Daz modified by phpBB-fr-thèmes