Champ FAULT, decharge HT active, et surtemperature par voie

Circuit de decharge rapide (GPIO5, broche 40)

  Commande DISCHARGE du circuit de la carte de puissance : MOSFET en serie
  avec 47 kOhm aux bornes du condensateur de sortie, soit tau = 0,94 s et
  environ 2 s pour passer de 500 V a 50 V. Sans lui, le seul bleeder de
  1 MOhm demandait ~42 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 -> decharge INHIBEE
    niveau bas ou broche non pilotee -> grille tiree a 12 V -> decharge ACTIVE

  GPIO5 est une broche a fonction PWM : pas de pull-up au reset (PROMPT §6
  etape 3), elle flotte. La decharge est donc active au reset, pendant un
  plantage, et surtout ALIMENTATION COUPEE -- exactement le cas ou le
  firmware ne garantit plus rien et ou l'operateur croit la carte
  inoffensive. C'est precisement le cas impose par le choix de n'effacer un
  defaut que par cycle d'alimentation.

  Sequencement explicite :
   - enter_safe_state() active la decharge APRES avoir coupe HV_EN : la
     charge est isolee d'abord, le condensateur vide ensuite ;
   - le retour au nominal inhibe la decharge AVANT d'autoriser les etages,
     jamais l'inverse ;
   - bsp_gpio_control_init() la laisse ACTIVE au demarrage : au boot le
     condensateur peut encore etre charge par une session precedente.

Champ FAULT dans la trame $T

  Codes 0-5, priorite 2 > 3 > 4 > 5 > 1. EMUSTOP (code 1) 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.

  Pas de code court-circuit : il franchit le meme comparateur et le meme
  seuil qu'une surintensite, le firmware ne peut pas les distinguer.

  Aucun effacement automatique ni par commande UART : le verrouillage
  n'est leve que par un cycle d'alimentation (PROMPT §8).

Surtemperature suivie PAR VOIE

  s_overtemp_t1 / s_overtemp_t2 separes, avec hysteresis chacun. L'ancien
  maximum des deux voies ne permettait pas de distinguer les codes 4 et 5.
  Seuil ramene a 80 degC pour coller au protocole.

Divers
  UART_LINE_MAX porte a 200 (buffer ESP32 g_lineBuf[200]).
  append_field_int() pour les champs entiers, meme convention de bornage
  que append_field : un champ qui ne tient pas est abandonne entierement.

Documentation
  docs/mesure-cartepuissance.md ajoute au depot (source de tracabilite des
  coefficients de calib.h).
  docs/ESP32-UART.md mis a jour, avec deux corrections de fond :
   - la mention "remettre FAULT=0 automatiquement des que la cause a
     disparu" est remplacee par une section sur le verrouillage ; elle
     contredisait le comportement reel, le TMS320 reemettant indefiniment
     le code latche ;
   - l'icone danger est specifiee comme dependant UNIQUEMENT de VOUT et
     jamais de FAULT. Un defaut ne decharge pas le condensateur : la faire
     disparaitre reviendrait a rassurer l'operateur quand le risque est
     maximal.
  Le piege strtol / uint8_t 16 bits y est documente pour eviter qu'il soit
  reintroduit ailleurs.

A cabler cote materiel : liaison broche 40 vers la carte de puissance, et
pull-down 10 kOhm sur la base du NPN.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 17:00:32 +02:00
parent 46ddf30a64
commit 26c1d499e0
8 changed files with 487 additions and 89 deletions

View File

@ -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).