diff --git a/doc/ESP32-UART.md b/doc/ESP32-UART.md index 5f52703..3af5a29 100644 --- a/doc/ESP32-UART.md +++ b/doc/ESP32-UART.md @@ -5,6 +5,12 @@ pour dialoguer avec l'ESP32-C3 (cote ESP32 deja fonctionnel, voir `src/tms_link.cpp` / `include/protocol.h`). Le TMS320 envoie sa telemetrie periodiquement, et recoit periodiquement les commandes de l'operateur. +> **Mise a jour** : coefficients de calibration remplaces par les valeurs +> mesurees sur la carte reelle (voir `mesure-cartepuissance.md` pour le +> detail des mesures et ce qui reste a faire). La remarque precedente sur +> une incoherence de `V1` etait un malentendu de formulation, pas une +> erreur de calcul — voir note dans le tableau ci-dessous. + ## Cablage | Signal | ESP32-C3 (config.h) | TMS320 | @@ -33,7 +39,7 @@ en hexadecimal (2 chiffres, majuscules). ### Telemetrie : TMS320 -> ESP32 (periodique) ``` -$T,FREQ1=100000,FREQ2=100000,DUTY1=45.2,DUTY2=50.0,VIN=400.5,IIN=1.20,V1=200.3,I1=2.50,T1=45.2,VOUT=200.1,I2=2.48,T2=44.8,IOUT=1.05*7A +$T,FREQ1=100000,FREQ2=100000,DUTY1=45.2,DUTY2=50.0,VIN=400.5,IIN=1.20,V1=200.3,I1=2.50,T1=45.2,VOUT=200.1,I2=2.48,T2=44.8,IOUT=1.05,FAULT=0*XX ``` Champs (`TAG=valeur`, separes par des virgules, ordre libre) : @@ -53,6 +59,37 @@ Champs (`TAG=valeur`, separes par des virgules, ordre libre) : | `I2` | A | Courant shunt MOSFET etage 2 | | `T2` | °C | Temperature NTC etage 2 | | `IOUT` | A | Courant de sortie | +| `FAULT` | code 0-5 | Defaut courant (voir tableau ci-dessous) | + +### Codes `FAULT` + +Transmis par le TMS320 des qu'il coupe lui-meme les sorties PWM par +securite (protection integree au TMS320, l'ESP32 ne fait que l'afficher) : + +| Code | Signification | +|---|---| +| `0` | Aucun defaut (fonctionnement normal) | +| `1` | Court-circuit | +| `2` | Surintensite I1 | +| `3` | Surintensite I2 | +| `4` | Temperature critique T1 (>80°C) | +| `5` | Temperature critique T2 (>80°C) | + +Remettre `FAULT=0` automatiquement des que la cause a disparu (pas de +"latch" gere par le protocole — un verrouillage manuel eventuel doit +etre implemente cote TMS320, l'ESP32 se contente d'afficher le dernier +code recu). Cote ESP32, `FAULT` pilote directement l'affichage +`DEFAUT` (clignotant, colonne 3 de l'OLED, remplace `HT`/`OFF`) et +l'icone danger (n'apparait jamais tant que `FAULT != 0`, meme si HT est +commande et Vout > 50V) -- voir `OledUi::drawDashboard()` +(`src/oled_ui.cpp`) et l'enum `TmsFault` (`include/protocol.h`). + +> Rappel important cote firmware TMS320 (voir +> `PROMPT-claude-code-TMS320.md` §2) : la protection reelle (comparateurs +> + Trip Zone matériel) ne depend jamais de cette liaison UART. `FAULT` +> n'est qu'un report d'information vers l'IHM, envoye apres coup ; la +> coupure elle-meme a deja eu lieu en matériel avant meme que la trame ne +> soit construite. **Frequence d'envoi recommandee : toutes les 200-500 ms.** L'ESP32 considere la liaison perdue (`TMS:KO` affiche sur l'OLED/web) si aucune @@ -67,20 +104,42 @@ simple d'envoyer tous les champs a chaque trame. Coefficients de mise a l'echelle a appliquer sur la lecture ADC (`Vadc`, en volts) pour obtenir la valeur physique a inserer dans la trame -telemetrie : +telemetrie. **Valeurs mesurees sur la carte reelle**, sauf mention +contraire — voir `mesure-cartepuissance.md` pour le detail des mesures et +la tracabilite complete. -| Grandeur | Point de mesure | Pleine echelle / max | Relation | -|---|---|---|---| -| `VIN` | 1,8 V @ 20 V | 30 V max | `VIN = Vadc x 11,111` | -| `IIN` | 2,4 V @ 2,7 A | 3 A max | `IIN = Vadc x 1,125` | -| `V1` (inter) | 3,2 V @ 100 V | 50 V max | `V1 = Vadc x 31,25` | -| `VOUT` | 2,8 V @ 500 V | 450 V max | `VOUT = Vadc x 178,571` | -| `IOUT` | 3,0 V @ 50 mA | 50 mA max | `IOUT = Vadc x 0,016667` (A) | -| `I1`, `I2` | shunt 0,02 Ω, gain x30 | 3 A max | `I = Vadc / 0,6 = Vadc x 1,6667` | +| Grandeur | Point de mesure | Pleine echelle reelle (3,3V) | Relation | Statut | +|---|---|---|---|---| +| `VIN` | mesure : coef 0,09 | 36,7 V | `VIN = Vadc x 11,11` | ✅ mesure | +| `IIN` | mesure : 0,208V @ 250mA | 3,97 A | `IIN = Vadc x 1,2019` | ✅ mesure (1 point) | +| `V1` (inter) | mesure : coef 0,032 | 103,1 V (usage limite a 50V max) | `V1 = Vadc x 31,25` | ✅ mesure | +| `VOUT` | mesure : coef 0,0055 | 600 V | `VOUT = Vadc x 181,82` | ✅ mesure | +| `IOUT` | valeur theorique non verifiee | 50 mA (a confirmer) | `IOUT = Vadc x 0,016667` (A) | ⚠️ a mesurer | +| `I1` (shunt etage 1) | mesure sur banc, **ampli MCP6001 provisoire** | 5,15 A | `I1 = (Vadc - 0,0473) / 0,631` | ⚠️ provisoire, a refaire avec TLV9151 | +| `I2` (shunt etage 2) | mesure sur banc, **ampli MCP6001 provisoire** | 5,12 A | `I2 = (Vadc - 0,0340) / 0,637` | ⚠️ provisoire, a refaire avec TLV9151 | -Note sur `V1` : le point de calibration (3,2V @ 100V) et le max indique -(50V) sont incoherents en l'etat — a reverifier avec l'auteur des -mesures avant implementation. +**Notes :** + +- **V1** : la remarque precedente sur une incoherence entre le point de + calibration (3,2V @ 100V) et le "max 50V" indique etait un malentendu + de formulation, pas une erreur de calcul. Le pont diviseur donne + effectivement une pleine echelle reelle de 103 V (coef 0,032 mesure, + confirme identique a la valeur theorique de conception) ; la carte + n'est simplement utilisee que jusqu'a 50V en usage normal (etage 1 = + 10V -> 50V), ce qui laisse une bonne marge sur l'ADC. Le calcul + `V1 = Vadc x 31,25` est correct sur toute la plage 0-103V. +- **I1/I2** : contrairement aux autres voies, le gain differe legerement + entre les deux etages (0,631 vs 0,637 V/A, ~1% d'ecart du a la + tolerance des resistances) et chaque voie a un offset non negligeable + (47,3 mV / 34,0 mV) qui n'est pas nul a courant nul. Ne pas utiliser un + gain unique pour les deux voies. **Ces deux formules sont mesurees avec + le MCP6001 monte provisoirement sur le banc de test** ; le composant + definitif sera le TLV9151 (meilleure precision, meilleure bande + passante). Le gain devrait rester valable au remplacement (fixe par le + reseau de resistances), mais l'offset devra etre integralement + remesure. Ne pas figer ces constantes comme definitives. +- **IOUT** : aucune mesure reelle a ce jour, valeur theorique de + conception conservee par defaut. A remplacer des que mesure. ### Commande : ESP32 -> TMS320 (periodique + a chaque changement) @@ -116,8 +175,8 @@ uint8_t checksum_of(const char *s, int len) { char body[128]; int n = sprintf(body, "T,FREQ1=%.0f,FREQ2=%.0f,DUTY1=%.1f,DUTY2=%.1f," "VIN=%.1f,IIN=%.2f,V1=%.1f,I1=%.2f,T1=%.1f," - "VOUT=%.1f,I2=%.2f,T2=%.1f,IOUT=%.2f", - freq1, freq2, duty1, duty2, vin, iin, v1, i1, t1, vout, i2, t2, iout); + "VOUT=%.1f,I2=%.2f,T2=%.1f,IOUT=%.2f,FAULT=%d", + freq1, freq2, duty1, duty2, vin, iin, v1, i1, t1, vout, i2, t2, iout, fault_code); uint8_t cs = checksum_of(body, n); char frame[160]; sprintf(frame, "$%s*%02X\n", body, cs); @@ -137,23 +196,18 @@ trame si ca ne correspond pas (trame corrompue). `updateSimulation()`, qui simule des valeurs realistes (utile comme reference de plage de valeurs attendues : `Vin` ~400V, `Iin` ~1.2A, `V1`/`Vout` ~200V, `I1`/`I2` ~2.5A, `T1`/`T2` ~40°C). + + ⚠️ **A revoir** : ces plages de simulation ne correspondent plus aux + valeurs reelles mesurees. `V1` ne depassera jamais 50V en usage normal + (pas 200V), `VIN` plafonne a 36,7V (pas 400V — c'est `VOUT` qui monte a + 400V), et `IIN`/`I1`/`I2` sont a confirmer avec les gains mesures + ci-dessus. A mettre a jour pour que les jauges OLED/web soient + coherentes au moment du passage en liaison reelle. - `include/config.h` : `TMS_DUMMY_MODE` (actuellement `false`, liaison reelle active) fait tourner l'ESP32 sur la simulation interne au lieu de lire l'UART reel quand mis a `true` (utile pour developper l'IHM sans hardware TMS320 branche). -## A faire cote protocole (pas encore implemente) - -- **Trame de defaut** : le TMS doit signaler lui-meme une coupure de - securite des sorties PWM (court-circuit, surintensite I1/I2, - temperature critique >80°C) — pas de champ dedie actuellement, la - telemetrie ne donne que les mesures brutes. Cote ESP32, - `OledUi::drawDashboard()` (`src/oled_ui.cpp`) affiche pour l'instant - DEFAUT simplement quand HT n'est pas commande (`!cmd.ht`), en attendant - cette trame. Quand elle existera, ajouter le champ correspondant dans - `Telemetry` (`include/protocol.h`) et brancher la vraie condition dans - `drawDashboard()`. - ## Points de vigilance - Le format hexadecimal du checksum doit etre en **majuscules** sur 2 @@ -164,4 +218,4 @@ trame si ca ne correspond pas (trame corrompue). - Ne pas depasser une longueur de ligne de 200 caracteres (buffer fixe cote ESP32, `g_lineBuf[200]` dans `tms_link.cpp`). - Verifier au multimetre/oscilloscope le niveau logique du TMS320 avant - de le relier a l'ESP32-C3 (3,3V attendu). + de le relier a l'ESP32-C3 (3,3V attendu) — **fait, carte CPU validee**. diff --git a/include/protocol.h b/include/protocol.h index 97dde94..ae2044f 100644 --- a/include/protocol.h +++ b/include/protocol.h @@ -6,13 +6,29 @@ // // Telemetrie TMS320 -> ESP32, periodique : // $T,FREQ1=100000,FREQ2=100000,DUTY1=45.2,DUTY2=50.0,VIN=400.5,IIN=1.20, -// V1=200.3,I1=2.50,T1=45.2,VOUT=200.1,I2=2.48,T2=44.8,IOUT=1.05*XX\n +// V1=200.3,I1=2.50,T1=45.2,VOUT=200.1,I2=2.48,T2=44.8,IOUT=1.05, +// FAULT=0*XX\n // // Commande ESP32 -> TMS320, a chaque changement (et rafraichissement // periodique) : // $C,HT=1,PWM1=1,PWM2=0*XX\n // // XX = XOR de tous les octets entre '$' et '*', en hexadecimal majuscule. +// +// FAULT (entier, 0-5) : code de defaut courant, transmis par le TMS320 +// des qu'il coupe lui-meme les sorties PWM par securite. Rester a +// FAULT=0 tant qu'aucun defaut n'est actif -- le TMS320 doit remettre +// 0 automatiquement une fois la cause du defaut disparue (pas de "latch" +// cote protocole ; si un verrouillage manuel est necessaire, il doit +// etre gere cote TMS320, pas suppose par l'ESP32). +enum TmsFault : uint8_t { + FAULT_NONE = 0, + FAULT_SHORT_CIRCUIT = 1, + FAULT_OVERCURRENT_I1 = 2, + FAULT_OVERCURRENT_I2 = 3, + FAULT_OVERTEMP_T1 = 4, // T1 > 80 C + FAULT_OVERTEMP_T2 = 5, // T2 > 80 C +}; struct Telemetry { float freq1 = 0; // Hz, frequence etage 1 @@ -28,6 +44,7 @@ struct Telemetry { float i2 = 0; // A, courant shunt mosfet 2 float t2 = 0; // C, temperature NTC etage 2 float iout = 0; // A, courant sortie + uint8_t fault = FAULT_NONE; // voir enum TmsFault uint32_t lastUpdateMs = 0; }; diff --git a/src/oled_ui.cpp b/src/oled_ui.cpp index a732db0..7bff8ff 100644 --- a/src/oled_ui.cpp +++ b/src/oled_ui.cpp @@ -460,15 +460,10 @@ void drawDashboard(const Telemetry &t, bool linkOk) { // (clignotant) si un probleme est detecte. HT/OFF restent fixes, // seul DEFAUT clignote (Blink1Hz). // - // TODO(protocole) : la detection de probleme n'existe pas encore -- - // problemDetected reste a false pour l'instant. Le TMS doit signaler - // lui-meme le defaut (court-circuit, surintensite I1/I2, temperature - // critique >80C) via une trame dediee, pas encore implementee cote TMS - // (cf. discussion). Quand cette trame existera (ajouter un champ - // correspondant dans Telemetry/protocol.h), c'est ici qu'il faudra - // brancher la vraie condition, plutot que de deviner des seuils cote - // ESP32 sans donnee du TMS pour les confirmer. - bool problemDetected = false; + // t.fault (voir enum TmsFault, protocol.h) est signale par le TMS320 + // lui-meme via le champ FAULT de la trame telemetrie -- pas de seuils + // devines cote ESP32. + bool problemDetected = (t.fault != FAULT_NONE); if (problemDetected) { if (Blink1Hz()) u8g2.drawStr(COL3_X + 2, 53, "DEFAUT"); } else { @@ -479,7 +474,7 @@ void drawDashboard(const Telemetry &t, bool linkOk) { // Regle reelle : HT actif (cmd.ht) ET Vout > 50V -- jamais avec DEFAUT, // meme si Vout mesure encore > 50V par inertie a ce moment-la. // Calee tout en bas a droite de la colonne 3 (= bord ecran). - bool dangerActive = cmd.ht && voutDisp > VOUT_DANGER_THRESHOLD; + bool dangerActive = cmd.ht && !problemDetected && voutDisp > VOUT_DANGER_THRESHOLD; if ((DEBUG_FORCE_ALL_ICONS || dangerActive) && Blink1Hz()) { drawWarningIconHalf(SCREEN_W - WARNING_ICON_WIDTH / 2, 64 - WARNING_ICON_HEIGHT / 2); } diff --git a/src/tms_link.cpp b/src/tms_link.cpp index bd8dac5..645f023 100644 --- a/src/tms_link.cpp +++ b/src/tms_link.cpp @@ -48,6 +48,12 @@ void applyField(const char *tag, float value) { else if (!strcmp(tag, "I2")) g_telemetry.i2 = clampNonNegative(value); else if (!strcmp(tag, "T2")) g_telemetry.t2 = value; else if (!strcmp(tag, "IOUT")) g_telemetry.iout = clampNonNegative(value); + else if (!strcmp(tag, "FAULT")) { + int code = (int)value; + if (code < 0) code = 0; + if (code > 5) code = 0; // code inconnu -> traite comme "pas de defaut" + g_telemetry.fault = (uint8_t)code; + } } // Parse une ligne "$T,TAG=val,TAG=val,...*XX". Retourne true si le diff --git a/src/web_ui.cpp b/src/web_ui.cpp index dd7e2b9..fb5b9c5 100644 --- a/src/web_ui.cpp +++ b/src/web_ui.cpp @@ -155,10 +155,10 @@ void handleState() { "{\"link\":%s,\"vin\":%.2f,\"iin\":%.2f,\"iout\":%.2f," "\"v1\":%.2f,\"i1\":%.2f,\"t1\":%.2f," "\"vout\":%.2f,\"i2\":%.2f,\"t2\":%.2f," - "\"ht\":%s,\"pwm1\":%s,\"pwm2\":%s,\"brightness\":%u}", + "\"ht\":%s,\"pwm1\":%s,\"pwm2\":%s,\"brightness\":%u,\"fault\":%u}", TmsLink::linkOk() ? "true" : "false", t.vin, t.iin, t.iout, t.v1, t.i1, t.t1, t.vout, t.i2, t.t2, cmd.ht ? "true" : "false", cmd.pwm1 ? "true" : "false", - cmd.pwm2 ? "true" : "false", OledUi::brightness()); + cmd.pwm2 ? "true" : "false", OledUi::brightness(), t.fault); server.send(200, "application/json", buf); }