diff --git a/docs/ESP32-UART.md b/docs/ESP32-UART.md index 6b43054..a6f6b22 100644 --- a/docs/ESP32-UART.md +++ b/docs/ESP32-UART.md @@ -5,65 +5,162 @@ 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`), et ajout +> du champ `FAULT`. + ## Cablage | Signal | ESP32-C3 (config.h) | TMS320 | |---|---|---| -| ESP32 TX -> TMS320 RX | GPIO7 | RX | -| ESP32 RX <- TMS320 TX | GPIO6 | TX | +| ESP32 TX -> TMS320 RX | GPIO7 | GPIO28, broche 48 | +| ESP32 RX <- TMS320 TX | GPIO6 | GPIO29, broche 1 | | GND commun | GND | GND | -A verifier avant de cabler : le TMS320 doit etre en logique 3,3V (comme -l'ESP32-C3). Si sa liaison serie sort en 5V, prevoir un level shifter -avant de relier les deux cartes. +Niveaux logiques 3,3 V des deux cotes -- **verifie, carte CPU validee**. ## Parametres UART -- Vitesse : **57600 bauds** (adapte cote TMS320 pour la marge de derive de - l'oscillateur interne INTOSC1 ; l'ESP32 doit etre mis a jour en - consequence, `Serial1.begin(57600, ...)`) +- Vitesse : **57600 bauds** - Format : **8N1** (8 bits de donnees, pas de parite, 1 bit de stop) - Cote ESP32 : `Serial1`, defini dans `TmsLink::begin()` - (`src/tms_link.cpp`) ## Format des trames -Protocole ASCII texte, inspire du format NMEA, terminé par `\n` (LF). +Protocole ASCII texte, inspire du format NMEA, termine par `\n` (LF). Chaque trame est encadree par `$` ... `*XX`, ou `XX` est un checksum XOR 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=200000,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) : | Tag | Unite | Description | |---|---|---| -| `FREQ1` | Hz | Frequence de decoupage etage 1 | -| `FREQ2` | Hz | Frequence de decoupage etage 2 | +| `FREQ1` | Hz | Frequence de decoupage etage 1 (200 kHz) | +| `FREQ2` | Hz | Frequence de decoupage etage 2 (100 kHz) | | `DUTY1` | % | Rapport cyclique etage 1 | | `DUTY2` | % | Rapport cyclique etage 2 | | `VIN` | V | Tension d'entree | | `IIN` | A | Courant d'entree | -| `V1` | V | Tension etage 1 | +| `V1` | V | Tension intermediaire (sortie etage 1) | | `I1` | A | Courant shunt MOSFET etage 1 | -| `T1` | °C | Temperature NTC etage 1 | -| `VOUT` | V | Tension de sortie (sortie boost, etage 2) | +| `T1` | degC | Temperature NTC etage 1 | +| `VOUT` | V | Tension de sortie HT | | `I2` | A | Courant shunt MOSFET etage 2 | -| `T2` | °C | Temperature NTC etage 2 | +| `T2` | degC | Temperature NTC etage 2 | | `IOUT` | A | Courant de sortie | +| `FAULT` | code 0-5 | Defaut courant (voir ci-dessous) | -**Frequence d'envoi recommandee : toutes les 200-500 ms.** L'ESP32 -considere la liaison perdue (`TMS:KO` affiche sur l'OLED/web) si aucune -trame `$T,...*XX` valide n'est recue depuis plus de **2 secondes** -(`TmsLink::linkOk()`). +### Codes `FAULT` -Tous les champs sont optionnels a l'envoi individuel (un champ absent -garde sa derniere valeur connue cote ESP32), mais en pratique il est plus -simple d'envoyer tous les champs a chaque trame. +| Code | Signification | +|---|---| +| `0` | Aucun defaut | +| `1` | EMUSTOP -- le debogueur a arrete le CPU | +| `2` | Surintensite I1 (etage 1) | +| `3` | Surintensite I2 (etage 2) | +| `4` | Temperature critique T1 (> 80 degC) | +| `5` | Temperature critique T2 (> 80 degC) | + +**Priorite quand plusieurs defauts coexistent : 2 > 3 > 4 > 5 > 1.** +EMUSTOP a donc la priorite la plus BASSE malgre son numero : en +developpement il se declenche a chaque halte du debogueur et ne doit +jamais masquer une surintensite reelle. + +**Il n'existe volontairement pas de code "court-circuit".** Un +court-circuit franchit le meme comparateur et le meme seuil qu'une +surintensite ; le firmware ne dispose d'aucune information permettant de +les distinguer. Les codes 2 et 3 couvrent les deux cas. + +#### Verrouillage : pas d'effacement par le protocole + +Contrairement a ce que supposait une version precedente de ce document, +**un defaut ne s'efface JAMAIS automatiquement**, ni a la disparition de +sa cause, ni par commande UART. Il n'existe aucun tag `$C` permettant de +l'acquitter. + +Le seul moyen de repartir est de **couper puis retablir l'alimentation**. +C'est un choix deliberé (PROMPT §8) : un defaut de puissance doit exiger +une intervention humaine et un examen de la carte. + +Consequence pour l'ESP32 : tant que le TMS320 emet un `FAULT != 0`, il +continuera de l'emettre indefiniment. L'IHM doit donc afficher ce code de +maniere persistante, sans attendre un retour spontane a 0. + +> Rappel (PROMPT §2) : la protection reelle -- comparateurs + Trip Zone +> materiel -- ne depend jamais de cette liaison UART. `FAULT` n'est qu'un +> report d'information vers l'IHM, envoye apres coup ; la coupure a deja +> eu lieu en materiel avant meme que la trame ne soit construite. + +#### Icone danger : pilotee par VOUT, jamais par FAULT + +L'avertissement haute tension doit dependre **uniquement de `VOUT`**, et +rester affiche independamment de l'etat de `FAULT`. + +Un defaut ne decharge pas le condensateur de sortie. Apres une coupure, +`HV_EN` isole la charge mais les 400 V restent presents. Faire disparaitre +l'icone danger au moment d'un defaut reviendrait a rassurer l'operateur +precisement quand le risque est maximal. + +La carte de puissance comporte deux dispositifs complementaires : + +- un **bleeder de 1 MOhm avec LED rouge en serie**, indication passive + qui reste valable alimentation coupee (~42 s pour passer sous 50 V) ; +- un **circuit de decharge actif** (MOSFET + 47 kOhm, ~2 s de 500 V a + 50 V), commande par le TMS320 sur GPIO5 / broche 40, **a logique + inversee** : il conduit des que la commande disparait, donc au reset, + en cas de plantage et alimentation coupee. + +**Frequence d'envoi : toutes les 300 ms.** L'ESP32 considere la liaison +perdue (`TMS:KO`) si aucune trame `$T` valide n'est recue depuis plus de +**2 secondes**. Le TMS320 applique le meme timeout dans l'autre sens. + +Tous les champs sont optionnels individuellement (un champ absent garde +sa derniere valeur cote ESP32) ; en pratique le TMS320 les envoie tous. +Si une trame devait depasser la longueur maximale, le firmware abandonne +des champs entiers plutot que d'en tronquer un. + +### Calibration des mesures analogiques (cote TMS320) + +Coefficients appliques sur la lecture ADC (`Vadc`, en volts) pour obtenir +la valeur physique. **Valeurs mesurees sur la carte reelle**, sauf mention +contraire -- voir `mesure-cartepuissance.md` pour la tracabilite. + +| Grandeur | Pleine echelle (3,3 V) | Relation | Statut | +|---|---|---|---| +| `VIN` | 36,7 V | `VIN = Vadc x 11,11` | mesure | +| `IIN` | 3,97 A | `IIN = Vadc x 1,2019` | mesure (1 point) | +| `V1` | 103,1 V (usage limite a 50 V) | `V1 = Vadc x 31,25` | mesure | +| `VOUT` | 600 V | `VOUT = Vadc x 181,82` | mesure | +| `IOUT` | 50 mA (a confirmer) | `IOUT = Vadc x 0,016667` | **a mesurer** | +| `I1` | 5,15 A | `I1 = (Vadc - 0,0473) / 0,631` | **provisoire (MCP6001)** | +| `I2` | 5,12 A | `I2 = (Vadc - 0,0340) / 0,637` | **provisoire (MCP6001)** | + +**Notes :** + +- **I1/I2** : le gain differe d'environ 1 % entre les deux voies et chaque + voie a un offset non nul a courant nul (47,3 / 34,0 mV). Ne jamais + utiliser un gain unique pour les deux. Ces valeurs sont mesurees avec + les **MCP6001 de banc** ; le composant definitif est le TLV9151. Le gain + devrait rester valable (fixe par le reseau de resistances) mais + **l'offset devra etre integralement remesure**. +- **MCP6001, limite de bande passante** : 1 MHz de produit gain-bande a un + gain de ~31 plafonne vers 32 kHz. Pour proteger un decoupage a 200 kHz, + les 30 ns du comparateur ne servent a rien si l'ampli en amont met des + dizaines de microsecondes. **La protection rapide n'est pas reellement + rapide tant que le MCP6001 est en place.** +- **NTC** : montage 3,3 V -- NTC -- R_fixe -- 0 V, mesure au point milieu. + La NTC etant cote 3,3 V, la relation est + `R_ntc = R_fixe x (VREF - Vadc) / Vadc`. Les thermistances sont a 5 mm + des MOSFET : elles mesurent le cuivre, pas la jonction, avec un ecart + statique et un retard thermique notables. Le seuil de 80 degC est donc + **provisoire**, a recaler sur la temperature de boitier en charge. +- **IOUT** : aucune mesure reelle a ce jour. ### Commande : ESP32 -> TMS320 (periodique + a chaque changement) @@ -78,61 +175,29 @@ $C,HT=1,PWM1=1,PWM2=0*3E | `PWM2` | 0 ou 1 | PWM etage 2 activee/desactivee | L'ESP32 envoie cette trame toutes les **500 ms**, et immediatement a -chaque changement d'etat depuis l'IHM (OLED/web). Le TMS320 doit -simplement appliquer le dernier etat recu et valide (checksum correct). +chaque changement depuis l'IHM. Le TMS320 applique le dernier etat recu et +valide. Les trois tags doivent etre presents, sinon la trame est ignoree. ## Calcul du checksum -XOR de tous les octets entre `$` et `*` (exclus), formate en hexadecimal -majuscule sur 2 chiffres (`%02X`). +XOR de tous les octets entre `$` et `*` (exclus), en hexadecimal majuscule +sur 2 chiffres (`%02X`). -Exemple en C pour une trame a construire : +Pour la reception : meme calcul, comparaison au checksum recu, trame +ignoree silencieusement si ca ne correspond pas. -```c -uint8_t checksum_of(const char *s, int len) { - uint8_t cs = 0; - for (int i = 0; i < len; i++) cs ^= (uint8_t)s[i]; - return cs; -} - -// Construction d'une trame telemetrie : -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); -uint8_t cs = checksum_of(body, n); -char frame[160]; -sprintf(frame, "$%s*%02X\n", body, cs); -// envoyer frame sur l'UART -``` - -Pour la reception des trames `$C,...*XX` : appliquer le meme calcul sur -la partie entre `$` et `*`, comparer au checksum recu, et ignorer la -trame si ca ne correspond pas (trame corrompue). - -## Cote ESP32 : ce qui existe deja - -- `include/protocol.h` : structures `Telemetry` et `CommandState`, - description du protocole (source de verite en cas de doute). -- `src/tms_link.cpp` : parsing des trames `$T,...`, envoi des trames - `$C,...`, gestion du timeout de liaison. Contient aussi - `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). -- `include/config.h` : `TMS_DUMMY_MODE` (actuellement `true`) fait - tourner l'ESP32 sur la simulation interne au lieu de lire l'UART reel. - **A passer a `false` une fois le firmware TMS320 pret et teste**, pour - activer la reception reelle des trames `$T,...*XX`. +> **Piege rencontre cote TMS320, a ne pas reproduire.** Decoder le +> checksum avec `strtol` sur un buffer non termine par `\0` fait lire +> au-dela de la trame. Et sur C28x, `uint8_t` fait 16 bits (pas +> d'adressage par octet) : un cast `(uint8_t)` ne tronque donc rien, et un +> `"7D"` suivi d'un residu `"7D"` donne 0x7D7D, conserve tel quel. Toute +> trame etait rejetee. Decoder **exactement deux chiffres**, de maniere +> bornee. ## Points de vigilance -- Le format hexadecimal du checksum doit etre en **majuscules** sur 2 - chiffres (`%02X`), sinon la trame sera rejetee cote ESP32 (silencieux, - pas d'erreur visible — la telemetrie n'avancera simplement pas). -- Terminer chaque trame par `\n` uniquement (le `\r` est tolere/ignore - cote ESP32 mais pas necessaire). -- Ne pas depasser une longueur de ligne de 160 caracteres (buffer fixe - cote ESP32, `g_lineBuf[160]` dans `tms_link.cpp`). -- Verifier au multimetre/oscilloscope le niveau logique du TMS320 avant - de le relier a l'ESP32-C3 (3,3V attendu). +- Checksum en **majuscules** sur 2 chiffres, sinon rejet silencieux cote + ESP32 -- la telemetrie n'avance simplement plus. +- Terminer par `\n` uniquement (`\r` tolere). +- Longueur de ligne maximale : **200 caracteres** (`g_lineBuf[200]` cote + ESP32, `UART_LINE_MAX` cote TMS320). diff --git a/docs/mesure-cartepuissance.md b/docs/mesure-cartepuissance.md new file mode 100644 index 0000000..02684e2 --- /dev/null +++ b/docs/mesure-cartepuissance.md @@ -0,0 +1,202 @@ +# Mesures carte puissance — TMS320F28027 dual boost + +Suivi des mesures de calibration effectuées sur la carte élévateur (à moitié +complète : composants de puissance et drivers MOSFET pas encore montés). +Ce document est la source pour remplir `calib.h` dans le firmware. Les +valeurs numériques finales à utiliser en code sont dans +`PROMPT-claude-code-TMS320.md` (section 5) ; ce fichier-ci garde la trace +des mesures brutes et de leur contexte. + +--- + +## 1. Chaînes de mesure de tension + +Méthode : alimentation de labo réglée à une tension connue, lecture de la +tension de sortie du pont diviseur au multimètre (avant l'ADC). + +| Voie | Point de mesure | Coefficient mesuré (Vadc/Vréel) | Gain (V/V) | Pleine échelle (3,3 V) | Statut | +|---|---|---|---|---|---| +| VIN (V_batt) | pin 10 | 0,09 | 11,11 | 36,7 V | ✅ mesuré | +| V1 (V_inter) | pin 16 | 0,032 | 31,25 | 103,1 V | ✅ mesuré | +| VOUT (V_HT) | pin 14 | 0,0055 | 181,82 | 600 V | ✅ mesuré | + +**Remarques :** +- VIN et V1 tombent exactement sur les valeurs théoriques calculées à la + conception (11,11 et 31,25) — le pont a été monté avec les bonnes valeurs + de résistances. +- VOUT diffère de 1,8 % de la valeur théorique (178,6 → 181,82), imputable + à la tolérance des résistances réelles. Sans conséquence : la pleine + échelle réelle (600 V) reste au-dessus de l'objectif (400 V max), avec + même un peu plus de marge que prévu. +- V1 max visé = 50 V → seulement ~1,6 V sur l'ADC (48 % de la plage), pas + de saturation, résolution confirmée suffisante. + +--- + +## 2. Chaîne de mesure de courant d'entrée (IIN) + +Méthode : courant imposé au primaire, lecture de la tension de sortie de +l'ampli de mesure au multimètre. + +| Point | Valeur | +|---|---| +| Mesure | 250 mA → 0,208 V en sortie ampli | +| Gain calculé | 0,832 V/A | +| Gain inverse | 1,2019 A/V | +| Pleine échelle (3,3 V) | 3,97 A | + +Un seul point mesuré (pas de vérification de linéarité multi-points comme +pour les shunts MOSFET). Cohérent avec le courant d'entrée nominal attendu +(~590 mA pour ~5 W de charge côté HT), bonne marge avant saturation. + +**Reste à faire** : idéalement un second point de mesure pour confirmer la +linéarité et détecter un éventuel offset (mesure faite à un seul courant, +donc le calcul suppose une caractéristique passant par zéro — à vérifier). + +--- + +## 3. Chaînes de mesure de courant shunt MOSFET (I1, I2) — protection rapide + +⚠️ **Mesures faites avec le MCP6001 monté provisoirement sur le banc de +test.** Le composant final prévu est le TLV9151 (meilleur offset, meilleure +bande passante). Le gain (fixé par le réseau de résistances RF/RG) doit +rester valable après le remplacement ; l'offset devra être entièrement +remesuré. + +Méthode : courant imposé dans chaque shunt (0,02 Ω), lecture au +multimètre de la tension aux bornes du shunt (`V_Rshunt`) et de la sortie +ampli-op avant ADC (`V_shunt_adc`), à quatre points (0, 0,5, 1, 2 A). + +### Données brutes + +**Shunt 1 (étage 1) :** + +| I (A) | V_Rshunt (mV) | V_shunt1_adc (V) | +|---|---|---| +| 0 | — | 0,0473 | +| 0,5 | 10,3 | 0,367 | +| 1 | 20,38 | 0,682 | +| 2 | 40,9 | 1,31 | + +**Shunt 2 (étage 2) :** + +| I (A) | V_Rshunt (mV) | V_shunt2_adc (V) | +|---|---|---| +| 0 | — | 0,0340 | +| 0,5 | 10,48 | 0,357 | +| 1 | 20,58 | 0,674 | +| 2 | 40,76 | 1,31 | + +### Résultats (régression linéaire sur les 4 points) + +| Voie | Offset (I=0) | Gain (V/A) | Gain équivalent shunt×ampli (V/V) | Gain inverse (A/V) | Pleine échelle (3,3 V) | +|---|---|---|---|---|---| +| I1 (étage 1) | 47,3 mV | 0,631 | ≈ 31,5 | 1,585 | 5,15 A | +| I2 (étage 2) | 34,0 mV | 0,637 | ≈ 31,8 | 1,570 | 5,12 A | + +**Formules de conversion (provisoires, MCP6001) :** +``` +I1 = (Vadc − 0,0473) / 0,631 +I2 = (Vadc − 0,0340) / 0,637 +``` + +**Remarques :** +- Très bonne linéarité sur les 4 points des deux voies. +- Écart de gain d'environ 1 % entre les deux voies — attribuable à la + tolérance des résistances de gain, propre à chaque canal. Ne pas + utiliser un gain unique moyenné : garder deux jeux de constantes séparés + dans `calib.h`. +- Le gain mesuré (~31,5-31,8) est proche de la valeur visée à la + conception (31), mais **s'écarte de l'hypothèse initiale utilisée pour + calculer le seuil DAC** (qui supposait un gain de 30 pile). Voir §5 pour + la correction. +- Les deux offsets (34-47 mV) sont nettement plus élevés que le Vos + typique du MCP6001 seul à ce gain — probablement une combinaison de + l'offset ampli réel et d'un petit décalage du pont/de la référence. + Sans conséquence pratique : l'auto-zéro logiciel prévu au boot du + firmware absorbe cet écart automatiquement, quelle que soit son origine. + +--- + +## 4. Chaîne de mesure de courant de sortie (IOUT) + +**Non mesurée.** Reste à faire — même méthode que pour IIN (courant imposé +côté charge après le switch de sortie, lecture de la tension ampli-op). +Valeur théorique provisoire dans `calib.h` : 3,0 V @ 50 mA → gain +0,016667 A/V (à confirmer/remplacer par une mesure réelle). + +--- + +## 5. Seuil de protection — recalcul avec les gains mesurés + +Le calcul initial du code DAC (`DACVAL`) pour le seuil de 3 A avait été +fait en supposant un gain exactement égal à 30 (shunt 0,02 Ω × ampli-op +×30), donnant `Vadc = 1,8 V` à 3 A. **Cette hypothèse est fausse** : le gain +réel mesuré est ~31,5-31,8, donc en réalité `1,8 V` correspondait à un +seuil réel plus proche de 3,3 A que de 3 A. Non dangereux (la protection +aurait juste déclenché un peu tard), mais corrigé avant tout flashage. + +**Valeurs recalculées avec les gains et offsets mesurés (§3) :** + +``` +Seuil 3 A, comparateur 1 (I-shunt1) : + Vadc = 0,0473 + 3 × 0,631 = 1,940 V + DACVAL = round(1,940 × 1023 / 3,3) = 601 + +Seuil 3 A, comparateur 2 (I-shunt2) : + Vadc = 0,0340 + 3 × 0,637 = 1,945 V + DACVAL = round(1,945 × 1023 / 3,3) = 603 +``` + +**⚠️ Ces deux valeurs sont provisoires (MCP6001).** Elles devront être +recalculées avec les mêmes formules dès que le gain et l'offset seront +remesurés sur le TLV9151. Le firmware ne doit jamais figer `DACVAL` en +dur : le calculer par macro à partir de `SAFETY_ISHUNT_THRESHOLD_A` et des +constantes de gain/offset mesurées, pour que ce remplacement futur ne +touche qu'aux valeurs de `calib.h`, pas au code. + +--- + +## 6. Ce qu'il reste à faire + +### Mesures à compléter +- [ ] IOUT : gain réel (un point de mesure minimum, idéalement deux) +- [ ] IIN : second point de mesure pour vérifier la linéarité/l'offset +- [ ] Chaîne NTC (Temp1, Temp2) : vérifier la valeur réelle de la + résistance fixe des ponts diviseurs (10 kΩ supposé) et, si possible, + un point de mesure à température connue pour valider la formule + Steinhart-Hart simplifiée +- [ ] I1, I2 **à refaire intégralement** une fois le TLV9151 monté (gain à + confirmer proche de ~31,5-31,8, offset à mesurer — attendu très + inférieur aux 34-47 mV actuels) + +### Matériel restant à monter sur la carte élévateur +- [ ] Composants de puissance (MOSFET IPD60R360, diode SiC STPSC406, + inductances) — carte actuellement à moitié complète +- [ ] Driver de grille : **le UCC27518/19 initialement prévu ne convient + pas** (seuils d'entrée CMOS proportionnels à VDD = 12 V, un signal + 3,3 V du F28027 ne les franchira jamais). Remplacer par UCC27517 + (seuils CMOS/TTL indépendants de VDD, même boîtier SOT-23-5, même + brochage) — vérifier la polarité (inverseur/non-inverseur) requise + selon le sens voulu sur EN +- [ ] TLV9151 : montage définitif à la place du MCP6001 de banc + +### Firmware (TMS320F28027-dualboost, côté Claude Code / CCS) +- [ ] Reporter les gains/offsets mesurés (§1-3) dans `calib.h`, avec la + structure par voie recommandée (gain, offset, seuil) plutôt que des + constantes isolées +- [ ] Vérifier que le calcul de `DACVAL` est bien fait par macro à partir + des constantes, pas en dur +- [ ] Une fois le driver de grille corrigé et les composants de puissance + montés : test matériel de l'étape 2 du firmware (sécurité) avant + toute activation du PWM — critère : `TZFRC` force les sorties à 0, + un breakpoint coupe le PWM (TZ6/OSHT6), le flag ne se réarme pas + seul + +### Points de configuration matérielle en attente de confirmation +- [ ] Fréquence de découpage par étage (100 ou 200 kHz) +- [ ] Polarité réelle des LED (bleue/rouge) +- [ ] Valeur d'inductance retenue par étage (47 ou 100 µH) +- [ ] État de la liaison JTAG / UART après le dernier diagnostic (mesure + du 1,8 V sur VDD broches 32/43, recâblage croisé RX/TX) — à + confirmer que tout fonctionne avant de reprendre les tests diff --git a/src/bsp_gpio.c b/src/bsp_gpio.c index 9ba9afd..50a6500 100644 --- a/src/bsp_gpio.c +++ b/src/bsp_gpio.c @@ -43,12 +43,20 @@ void bsp_gpio_control_init(void) // HV_EN = GPIO32 (broche 31) GpioCtrlRegs.GPBMUX1.bit.GPIO32 = 0; GpioCtrlRegs.GPBDIR.bit.GPIO32 = 1; + + // DISCHARGE = GPIO5 (broche 40) + GpioCtrlRegs.GPAMUX1.bit.GPIO5 = 0; + GpioCtrlRegs.GPADIR.bit.GPIO5 = 1; EDIS; - // Etat sur avant toute autre configuration. + // Etat sur avant toute autre configuration. La decharge est laissee + // ACTIVE : au demarrage, le condensateur de sortie peut encore etre + // charge par une session precedente, rien ne justifie de l'inhiber + // avant que la conversion ne soit reellement demandee. stage_enable_set(STAGE_1, false); stage_enable_set(STAGE_2, false); hv_enable_set(false); + hv_discharge_set(true); } void stage_enable_set(stage_id_t stage, bool enabled) @@ -89,6 +97,20 @@ void hv_enable_set(bool enabled) } } +void hv_discharge_set(bool active) +{ + // Niveau BAS = decharge active (NPN bloque, grille du MOSFET libre). + // Voir bsp_gpio.h pour le detail de la polarite inversee. + if (active) + { + GpioDataRegs.GPACLEAR.bit.GPIO5 = 1; + } + else + { + GpioDataRegs.GPASET.bit.GPIO5 = 1; + } +} + void bsp_gpio_stage_default_override_init(void) { EALLOW; diff --git a/src/bsp_gpio.h b/src/bsp_gpio.h index 158682e..25b4ec4 100644 --- a/src/bsp_gpio.h +++ b/src/bsp_gpio.h @@ -36,6 +36,26 @@ void stage_enable_set(stage_id_t stage, bool enabled); // donne PAS 0 V en sortie (PROMPT §6 etape 6). void hv_enable_set(bool enabled); +// DISCHARGE (GPIO5, broche 40) : circuit de decharge rapide du condensateur +// de sortie, sur la carte de puissance. 47 kOhm en serie avec un MOSFET, +// soit ~2 s pour passer de 500 V a 50 V (tau = 0,94 s). +// +// POLARITE INVERSEE, et c'est le coeur du montage. Le GPIO pilote un NPN +// qui court-circuite la grille du MOSFET : +// niveau HAUT -> NPN conduit -> grille a la masse -> MOSFET bloque +// -> decharge INHIBEE (fonctionnement normal) +// niveau BAS, ou broche non pilotee +// -> NPN bloque -> grille tiree a 12 V (chaine 3x1 MOhm +// depuis le +500 V, ecretee par Zener) -> MOSFET conduit +// -> decharge ACTIVE +// +// GPIO5 est une broche a fonction PWM : ses pull-ups ne sont PAS actives au +// reset (PROMPT §6 etape 3), elle flotte donc. La decharge est par +// consequent active au reset, pendant un plantage, et surtout ALIMENTATION +// COUPEE -- c'est-a-dire exactement le cas ou le firmware ne peut plus rien +// garantir et ou l'operateur croit la carte inoffensive. +void hv_discharge_set(bool active); + // BRING-UP UNIQUEMENT -- bascule Stage1/2-default (GPIO1/GPIO3) en sortie // GPIO pilotee par logiciel, pour verifier le cablage des portes ET sans // etage de puissance actif. Laisse les deux broches a 0. diff --git a/src/calib.h b/src/calib.h index c7a9b38..9ae0c12 100644 --- a/src/calib.h +++ b/src/calib.h @@ -96,7 +96,7 @@ // PROVISOIRE : seuil a confirmer selon la tenue reelle du MOSFET et // l'implantation des NTC sur la carte. // L'hysteresis evite que l'etat oscille autour du point de bascule. -#define SAFETY_OVERTEMP_C 85.0f +#define SAFETY_OVERTEMP_C 80.0f #define SAFETY_OVERTEMP_HYST_C 10.0f // ---- Timeout de la liaison ESP32 (PROMPT §6 etape 7) ---------------- @@ -133,6 +133,6 @@ ((uint16_t)(((UART_LSPCLK_HZ + 4UL * UART_BAUD_RATE) / (8UL * UART_BAUD_RATE)) - 1U)) #define UART_RX_RING_SIZE 64U -#define UART_LINE_MAX 160U // limite cote ESP32 (g_lineBuf[160]) +#define UART_LINE_MAX 200U // limite cote ESP32 (g_lineBuf[200]) #endif diff --git a/src/main.c b/src/main.c index be266b0..8a10337 100644 --- a/src/main.c +++ b/src/main.c @@ -27,24 +27,54 @@ static volatile bool s_send_telemetry = false; #define LINK_TIMEOUT_TICKS ((uint16_t)(UART_LINK_TIMEOUT_MS / TICK_MS)) static volatile uint16_t s_ticks_since_cmd = LINK_TIMEOUT_TICKS; -// Etat de surtemperature, avec hysteresis pour eviter le battement autour -// du seuil. -static bool s_overtemp = false; +// Surtemperature, suivie PAR VOIE : le protocole distingue les codes 4 (T1) +// et 5 (T2), un simple maximum ne permettrait pas de les separer. +// Hysteresis pour eviter le battement autour du seuil. +static bool s_overtemp_t1 = false; +static bool s_overtemp_t2 = false; + +static bool update_one_overtemp(bool current, float temp_c) +{ + if (!current) + { + return (temp_c >= SAFETY_OVERTEMP_C); + } + return (temp_c > (SAFETY_OVERTEMP_C - SAFETY_OVERTEMP_HYST_C)); +} static void update_overtemp(void) { - float t1 = measure_temp(adc_get_raw(ADC_CH_T1)); - float t2 = measure_temp(adc_get_raw(ADC_CH_T2)); - float tmax = (t1 > t2) ? t1 : t2; + s_overtemp_t1 = + update_one_overtemp(s_overtemp_t1, measure_temp(adc_get_raw(ADC_CH_T1))); + s_overtemp_t2 = + update_one_overtemp(s_overtemp_t2, measure_temp(adc_get_raw(ADC_CH_T2))); +} - if (!s_overtemp) +// Priorite 2 > 3 > 4 > 5 > 1 : EMUSTOP est le code le moins prioritaire +// malgre son numero, pour ne jamais masquer une surintensite reelle. +static fault_code_t compute_fault_code(const safety_faults_t *f) +{ + if (f->overcurrent && f->stage1_fault) { - s_overtemp = (tmax >= SAFETY_OVERTEMP_C); + return FAULT_OVERCURRENT_I1; } - else + if (f->overcurrent && f->stage2_fault) { - s_overtemp = (tmax > (SAFETY_OVERTEMP_C - SAFETY_OVERTEMP_HYST_C)); + return FAULT_OVERCURRENT_I2; } + if (s_overtemp_t1) + { + return FAULT_OVERTEMP_T1; + } + if (s_overtemp_t2) + { + return FAULT_OVERTEMP_T2; + } + if (f->emustop) + { + return FAULT_EMUSTOP; + } + return FAULT_NONE; } // Etat sur : les deux etages inhibes ET la sortie HT coupee. @@ -60,6 +90,12 @@ static void enter_safe_state(void) stage_enable_set(STAGE_1, false); stage_enable_set(STAGE_2, false); hv_enable_set(false); + + // Decharge active. Elle vient APRES la coupure de HV_EN : la charge est + // d'abord isolee, puis le condensateur vide en ~2 s. Sans elle, les + // 400 V resteraient presents pres d'une minute sur le seul bleeder de + // 1 MOhm, sans aucune indication une fois l'alimentation coupee. + hv_discharge_set(true); } // Priorite : surintensite > surtemperature > EMUSTOP > liaison perdue. @@ -71,7 +107,7 @@ static led_state_t compute_led_state(const safety_faults_t *f) { return LED_STATE_OVERCURRENT; } - if (s_overtemp) + if (s_overtemp_t1 || s_overtemp_t2) { return LED_STATE_OVERTEMP; } @@ -176,12 +212,17 @@ void main(void) // Aucun redemarrage automatique : rien ne reactive le PWM ensuite, // meme si la temperature redescend sous l'hysteresis ou si le defaut // disparait. Il faut un reset, ou une commande explicite (etape 8). - if (faults.stage1_fault || faults.stage2_fault || s_overtemp) + if (faults.stage1_fault || faults.stage2_fault + || s_overtemp_t1 || s_overtemp_t2) { enter_safe_state(); } else { + // Inhiber la decharge AVANT d'autoriser les etages : jamais + // l'inverse, sinon on ferait travailler la conversion contre le + // circuit de decharge. + hv_discharge_set(false); stage_enable_set(STAGE_1, true); stage_enable_set(STAGE_2, true); } @@ -210,6 +251,7 @@ void main(void) t.i2_a = measure_i2(adc_get_raw(ADC_CH_I2)); t.t2_c = measure_temp(adc_get_raw(ADC_CH_T2)); t.iout_a = measure_iout(adc_get_raw(ADC_CH_IOUT)); + t.fault = compute_fault_code(&faults); uart_link_send_telemetry(&t); s_send_telemetry = false; diff --git a/src/protocol.h b/src/protocol.h index ed3350b..d612269 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -6,6 +6,26 @@ // Squelette du protocole UART TMS320<->ESP32 (voir docs/ESP32-UART.md). // Rempli/utilise a partir de l'etape 7 (uart_link.c). +// Codes du champ FAULT de la trame $T (docs/ESP32-UART.md). +// +// Attention a la priorite d'affichage quand plusieurs defauts coexistent : +// elle est 2 > 3 > 4 > 5 > 1, donc EMUSTOP a la priorite la PLUS BASSE +// malgre son numero. C'est voulu : en developpement EMUSTOP se declenche a +// chaque halte du debogueur et ne doit jamais masquer une surintensite. +// +// Il n'existe volontairement AUCUN code de court-circuit : un court-circuit +// franchit le meme comparateur et le meme seuil qu'une surintensite, le +// firmware ne dispose d'aucune information permettant de les distinguer. +typedef enum +{ + FAULT_NONE = 0, + FAULT_EMUSTOP = 1, + FAULT_OVERCURRENT_I1 = 2, + FAULT_OVERCURRENT_I2 = 3, + FAULT_OVERTEMP_T1 = 4, + FAULT_OVERTEMP_T2 = 5 +} fault_code_t; + typedef struct { float freq1_hz; @@ -21,6 +41,7 @@ typedef struct float i2_a; float t2_c; float iout_a; + fault_code_t fault; } telemetry_t; typedef struct diff --git a/src/uart_link.c b/src/uart_link.c index c6bdee0..c005be2 100644 --- a/src/uart_link.c +++ b/src/uart_link.c @@ -151,6 +151,26 @@ static int append_field(char *out, int room, const char *tag, float value, return n; } +// Champ entier (FAULT). Meme convention de bornage que append_field : +// renvoie 0 et n'ecrit rien si le champ ne tient pas. +static int append_field_int(char *out, int room, const char *tag, int value) +{ + int n; + + if (room <= 1) + { + return 0; + } + + n = snprintf(out, (size_t)room, ",%s=%d", tag, value); + if (n < 0 || n >= room) + { + out[0] = '\0'; + return 0; + } + return n; +} + // Construit la trame et la met en attente. NE BLOQUE PAS : l'emission reelle // se fait ensuite par uart_link_service_tx(), appelee quand la boucle // principale a du temps disponible. Renvoie false si la trame precedente @@ -195,6 +215,12 @@ bool uart_link_send_telemetry(const telemetry_t *t) TX_ADD("T2", t->t2_c, 1); TX_ADD("IOUT", t->iout_a, 2); + { + int added_ = append_field_int(&s_tx_frame[n], TX_ROOM(), "FAULT", + (int)t->fault); + if (added_ == 0) { fields_dropped++; } else { n += added_; } + } + #undef TX_ADD #undef TX_ROOM