Ajoute le champ FAULT au protocole UART pour le mode defaut du TMS320

Etend la trame telemetrie avec FAULT=0-5 (court-circuit, surintensite
I1/I2, temperature critique T1/T2) : le TMS320 signale le defaut apres
coup (la protection materielle elle-meme est independante de l'UART),
l'ESP32 l'affiche (DEFAUT clignotant, icone danger masquee) sans
deviner de seuils. Documente le protocole complet dans
doc/ESP32-UART.md pour le firmware TMS320.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 12:07:18 +02:00
parent d8f38e0b4b
commit ee40e500e9
5 changed files with 113 additions and 41 deletions

View File

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

View File

@ -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;
};

View File

@ -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);
}

View File

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

View File

@ -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);
}