Etape 5 : measure.c et constantes de calibration reelles

Report des mesures de docs/mesure-cartepuissance.md dans calib.h, avec une
structure par voie (gain, offset) plutot que des constantes isolees.

calib.h
  Tensions   : VIN 11,11 / V1 31,25 / VOUT 181,82 V/V (mesures)
  IIN        : 1,2019 A/V -- un seul point mesure, offset suppose nul
  IOUT       : 0,016667 A/V -- NON MESURE, valeur theorique de conception
  Shunts     : I1 offset 47,3 mV gain 0,631 V/A
               I2 offset 34,0 mV gain 0,637 V/A
               Deux jeux separes : les voies different d'environ 1 % en gain
               et n'ont pas le meme offset, ne jamais moyenner.

Seuil de protection : le code DAC est maintenant calcule par macro a partir
de SAFETY_ISHUNT_THRESHOLD_A et des gain/offset mesures, et il est PROPRE A
CHAQUE ETAGE (601 et 603 au lieu d'un 558 commun).

  Correction d'analyse : le 558 precedent supposait un gain de 30 pile. Le
  gain reel etant plus eleve (~31,5), le seuil de 1,8 V etait atteint a
  2,78 A et non a 3 A -- la protection declenchait donc TROP TOT, et non
  trop tard comme indique au §5 du document de mesures. Conservatif, sans
  danger, mais corrige.

measure.c/h (etape 5)
  measure_vin/iin/v1/vout/iout/i1/i2/temp, signature float f(uint16_t raw).
  Deux fonctions distinctes pour les shunts, puisque gain ET offset
  different. measure_i1/i2 peuvent renvoyer une valeur negative a courant
  nul : volontaire, une derive d'offset apres changement d'ampli devient
  ainsi visible en telemetrie au lieu d'etre masquee par une saturation.

  NTC : la relation du PROMPT §5 est INVERSEE. Avec la NTC cote 3,3 V c'est
  R_ntc = R_fixe * (VREF - Vadc) / Vadc. Verifie sur les points de repere du
  PROMPT lui-meme : 0,75 V a 0 degC donne 34,2 kOhm, ce que beta=4000
  predit, alors que la forme inversee donnerait 2,94 kOhm (donc du chaud).
  Telle qu'ecrite, la protection thermique aurait ete inexploitable.

La telemetrie transporte desormais de vraies grandeurs physiques.

Reste en suspens (voir docs/mesure-cartepuissance.md §6) : IOUT jamais
mesure, second point pour IIN, valeur reelle de la resistance fixe des
ponts NTC, et surtout gain/offset des shunts A REMESURER apres passage du
MCP6001 au TLV9151 -- l'offset actuel est domine par le Vos de l'ampli.

Note : l'"auto-zero logiciel au boot" evoque dans le document de mesures
n'existe pas dans le firmware et n'a pas ete ajoute ; mesurer un offset au
boot figerait une valeur fausse si du courant circule deja.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-03 18:50:21 +02:00
parent d26c07d3a1
commit 888723abc7
5 changed files with 193 additions and 20 deletions

View File

@ -17,12 +17,76 @@
// Valeur de champ AIOMUX1 pour basculer une broche AIOx en mode analogique
#define GPIO_ANALOG_MODE 2
// Seuil de protection courant shunt (identique etage 1 et 2)
// Shunt 0.02 ohm, gain x30 -> Vadc = I * 0.02 * 30 = I * 0.6
// =====================================================================
// Constantes de calibration des chaines de mesure.
// Source : docs/mesure-cartepuissance.md (mesures reelles sur la carte
// elevateur). Aucune de ces valeurs ne doit apparaitre ailleurs.
// =====================================================================
// ---- Chaines de tension (doc §1) ------------------------------------
// Gain = Vreel / Vadc, soit l'inverse du rapport du pont diviseur.
// VIN et V1 tombent sur les valeurs theoriques ; VOUT s'en ecarte de
// 1,8 % (tolerance des resistances), sans consequence : la pleine
// echelle reelle (600 V) reste au-dessus des 400 V vises.
#define MEAS_VIN_GAIN_V_PER_V 11.11f // broche 10, coef mesure 0,09
#define MEAS_V1_GAIN_V_PER_V 31.25f // broche 16, coef mesure 0,032
#define MEAS_VOUT_GAIN_V_PER_V 181.82f // broche 14, coef mesure 0,0055
// ---- Courant d'entree (doc §2) --------------------------------------
// Un seul point mesure (250 mA -> 0,208 V) : la caracteristique est
// SUPPOSEE passer par zero, offset non verifie. Un second point reste
// a faire.
#define MEAS_IIN_A_PER_V 1.2019f
// ---- Courant de sortie (doc §4) -------------------------------------
// NON MESURE : valeur theorique de conception (3,0 V @ 50 mA).
// A remplacer des qu'une mesure reelle sera disponible.
#define MEAS_IOUT_A_PER_V 0.016667f
// ---- Shunts MOSFET 0,02 ohm (doc §3) --------------------------------
// PROVISOIRE : mesures faites avec les MCP6001 de banc. Le gain, fixe
// par le reseau RF/RG, doit rester valable apres passage au TLV9151 ;
// l'OFFSET en revanche est domine par le Vos de l'ampli et devra etre
// ENTIEREMENT REMESURE. Les deux voies different d'environ 1 % :
// garder deux jeux de constantes, ne jamais moyenner.
#define MEAS_I1_OFFSET_V 0.0473f
#define MEAS_I1_GAIN_V_PER_A 0.631f
#define MEAS_I2_OFFSET_V 0.0340f
#define MEAS_I2_GAIN_V_PER_A 0.637f
// ---- NTC B57451V5103J062 (PROMPT §5) --------------------------------
// Montage : 3,3 V -- NTC -- R_fixe -- 0 V, mesure au point milieu.
// La NTC etant du cote 3,3 V, la tension MONTE avec la temperature :
// R_ntc = R_fixe * (VREF - Vadc) / Vadc
// (le PROMPT §5 donne cette relation inversee ; verifie sur ses propres
// points de repere : 0,75 V a 0 C -> 34,2 kOhm, ce que beta=4000 predit,
// alors que la forme inversee donnerait 2,94 kOhm, donc du chaud.)
// R_fixe suppose a 10 kOhm : reste a confirmer sur la carte (doc §6).
#define MEAS_NTC_R_FIXED_OHM 10000.0f
#define MEAS_NTC_R25_OHM 10000.0f
#define MEAS_NTC_BETA_K 4000.0f
#define MEAS_NTC_T25_K 298.15f
#define MEAS_KELVIN_OFFSET 273.15f
// ---- Seuil de protection rapide (doc §5) ----------------------------
// Le seuil est defini en AMPERES ; le code DAC est calcule par macro a
// partir du gain et de l'offset mesures, jamais ecrit en dur. Ainsi le
// futur passage au TLV9151 ne touchera que les constantes ci-dessus.
//
// Formule TI : V = DACVAL * (VDDA - VSSA) / 1023 -> 1023, pas 4096.
#define SAFETY_ISHUNT_THRESHOLD_A 3.0f
#define SAFETY_DAC_VREF_V 3.3f
#define SAFETY_DAC_CODE \
((uint16_t)((SAFETY_ISHUNT_THRESHOLD_A * 0.6f) / SAFETY_DAC_VREF_V * 1023.0f + 0.5f))
#define SAFETY_DAC_CODE_FROM_V(v_) \
((uint16_t)((v_) / SAFETY_DAC_VREF_V * 1023.0f + 0.5f))
// Seuil ramene a la sortie de l'ampli : Vadc = offset + I * gain.
#define SAFETY_DAC_CODE_STAGE1 \
SAFETY_DAC_CODE_FROM_V(MEAS_I1_OFFSET_V \
+ SAFETY_ISHUNT_THRESHOLD_A * MEAS_I1_GAIN_V_PER_A)
#define SAFETY_DAC_CODE_STAGE2 \
SAFETY_DAC_CODE_FROM_V(MEAS_I2_OFFSET_V \
+ SAFETY_ISHUNT_THRESHOLD_A * MEAS_I2_GAIN_V_PER_A)
// ADC : reference interne obligatoire (VREFHI partage avec ADCINA0/VIN),
// pleine echelle 3,3 V. Conversion brut -> volts : V = raw * 3.3 / 4096.

View File

@ -6,6 +6,7 @@
#include "protocol.h"
#include "pwm.h"
#include "adc.h"
#include "measure.h"
#include "calib.h"
extern uint16_t RamfuncsLoadStart;
@ -106,24 +107,24 @@ void main(void)
{
telemetry_t t;
// BRING-UP : chaque champ transporte la TENSION BRUTE de la
// broche ADC correspondante, en volts (0..3,3) -- et non la
// grandeur physique. Permet de verifier les 9 voies d'un coup
// sur l'IHM ESP32 apres ressoudure. measure.c fera la vraie
// conversion (ponts diviseurs, gain de shunt, NTC) a l'etape 5.
// Grandeurs physiques reelles (etape 5) : conversion par
// measure.c a partir des coefficients mesures sur la carte
// (docs/mesure-cartepuissance.md).
// FREQ/DUTY restent les valeurs de consigne du bloc de bring-up
// ci-dessus, pas encore une mesure.
t.freq1_hz = (float)PWM_STAGE1_FREQ_HZ;
t.freq2_hz = (float)PWM_STAGE2_FREQ_HZ;
t.duty1_pct = 50.0f;
t.duty2_pct = 50.0f;
t.vin_v = adc_get_volts(ADC_CH_VIN);
t.iin_a = adc_get_volts(ADC_CH_IIN);
t.v1_v = adc_get_volts(ADC_CH_V1);
t.i1_a = adc_get_volts(ADC_CH_I1);
t.t1_c = adc_get_volts(ADC_CH_T1);
t.vout_v = adc_get_volts(ADC_CH_VOUT);
t.i2_a = adc_get_volts(ADC_CH_I2);
t.t2_c = adc_get_volts(ADC_CH_T2);
t.iout_a = adc_get_volts(ADC_CH_IOUT);
t.vin_v = measure_vin(adc_get_raw(ADC_CH_VIN));
t.iin_a = measure_iin(adc_get_raw(ADC_CH_IIN));
t.v1_v = measure_v1(adc_get_raw(ADC_CH_V1));
t.i1_a = measure_i1(adc_get_raw(ADC_CH_I1));
t.t1_c = measure_temp(adc_get_raw(ADC_CH_T1));
t.vout_v = measure_vout(adc_get_raw(ADC_CH_VOUT));
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));
uart_link_send_telemetry(&t);
s_send_telemetry = false;

76
src/measure.c Normal file
View File

@ -0,0 +1,76 @@
#include <math.h>
#include "measure.h"
#include "calib.h"
// Brut -> tension a la broche. Reference interne, pleine echelle 3,3 V.
static float raw_to_volts(uint16_t raw)
{
return (float)raw * ADC_VREF_V / 4096.0f;
}
float measure_vin(uint16_t raw)
{
return raw_to_volts(raw) * MEAS_VIN_GAIN_V_PER_V;
}
float measure_iin(uint16_t raw)
{
return raw_to_volts(raw) * MEAS_IIN_A_PER_V;
}
float measure_v1(uint16_t raw)
{
return raw_to_volts(raw) * MEAS_V1_GAIN_V_PER_V;
}
float measure_vout(uint16_t raw)
{
return raw_to_volts(raw) * MEAS_VOUT_GAIN_V_PER_V;
}
float measure_iout(uint16_t raw)
{
return raw_to_volts(raw) * MEAS_IOUT_A_PER_V;
}
// Vadc = offset + I * gain -> I = (Vadc - offset) / gain.
// Le resultat peut etre legerement negatif a courant nul (bruit autour de
// l'offset) : on ne le sature pas, une valeur negative en telemetrie est un
// indice utile de derive de l'offset apres changement d'ampli.
float measure_i1(uint16_t raw)
{
return (raw_to_volts(raw) - MEAS_I1_OFFSET_V) / MEAS_I1_GAIN_V_PER_A;
}
float measure_i2(uint16_t raw)
{
return (raw_to_volts(raw) - MEAS_I2_OFFSET_V) / MEAS_I2_GAIN_V_PER_A;
}
// Pont 3,3 V -- NTC -- R_fixe -- 0 V, mesure au point milieu.
// R_ntc = R_fixe * (VREF - Vadc) / Vadc
// T(K) = 1 / ( 1/T25 + ln(R_ntc / R25) / beta )
//
// Precision attendue : la tolerance de +/-3 % sur beta donne environ
// +/-2 degC vers 100 degC sans calibration individuelle. Suffisant pour de
// la protection thermique, pas pour de la mesure fine (PROMPT §5).
float measure_temp(uint16_t raw)
{
float vadc = raw_to_volts(raw);
float r_ntc;
float inv_t;
// Bornes : hors de cette plage le pont est en butee (broche en l'air,
// NTC en court-circuit ou coupee) et le calcul n'a plus de sens.
if (vadc <= 0.01f || vadc >= (ADC_VREF_V - 0.01f))
{
return 0.0f;
}
r_ntc = MEAS_NTC_R_FIXED_OHM * (ADC_VREF_V - vadc) / vadc;
inv_t = 1.0f / MEAS_NTC_T25_K
+ logf(r_ntc / MEAS_NTC_R25_OHM) / MEAS_NTC_BETA_K;
return (1.0f / inv_t) - MEAS_KELVIN_OFFSET;
}

30
src/measure.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef MEASURE_H
#define MEASURE_H
#include <stdint.h>
// Conversion des valeurs brutes de l'ADC (0..4095) vers les grandeurs
// physiques, a partir des seules constantes de calib.h (elles-memes issues
// de docs/mesure-cartepuissance.md).
//
// Ces fonctions utilisent du flottant et, pour les NTC, un logarithme :
// a n'appeler QUE depuis la boucle principale, jamais depuis une ISR
// (PROMPT §7). La protection rapide ne passe pas par ici.
float measure_vin(uint16_t raw); // V - tension d'entree batterie
float measure_iin(uint16_t raw); // A - courant d'entree
float measure_v1(uint16_t raw); // V - tension intermediaire (etage 1)
float measure_vout(uint16_t raw); // V - tension de sortie HT
float measure_iout(uint16_t raw); // A - courant de sortie HT
// Courant shunt MOSFET. Deux fonctions distinctes et non une seule
// parametree : les deux voies ont leur propre gain ET leur propre offset,
// mesures separement.
float measure_i1(uint16_t raw); // A - shunt etage 1
float measure_i2(uint16_t raw); // A - shunt etage 2
// Temperature NTC, en degres Celsius. Meme chaine de mesure sur les deux
// etages, donc une seule fonction.
float measure_temp(uint16_t raw); // degC
#endif

View File

@ -31,7 +31,9 @@ void safety_init(void)
Comp1Regs.COMPCTL.bit.SYNCSEL = 0; // asynchrone -> ~30 ns vers le Trip Zone
Comp1Regs.COMPCTL.bit.CMPINV = 1; // driver actif haut : 1=OK, 0=defaut
Comp1Regs.COMPCTL.bit.QUALSEL = 0; // pas de qualification pour l'instant
Comp1Regs.DACVAL.bit.DACVAL = SAFETY_DAC_CODE;
// Seuil propre a l'etage 1 : les deux chaines de mesure different d'environ
// 1 % en gain et n'ont pas le meme offset (docs/mesure-cartepuissance.md).
Comp1Regs.DACVAL.bit.DACVAL = SAFETY_DAC_CODE_STAGE1;
GpioCtrlRegs.GPAMUX1.bit.GPIO1 = 3; // COMP1OUT
// Comparateur 2 : idem, shunt etage 2 sur COMP2A.
@ -40,7 +42,7 @@ void safety_init(void)
Comp2Regs.COMPCTL.bit.SYNCSEL = 0;
Comp2Regs.COMPCTL.bit.CMPINV = 1;
Comp2Regs.COMPCTL.bit.QUALSEL = 0;
Comp2Regs.DACVAL.bit.DACVAL = SAFETY_DAC_CODE;
Comp2Regs.DACVAL.bit.DACVAL = SAFETY_DAC_CODE_STAGE2;
GpioCtrlRegs.GPAMUX1.bit.GPIO3 = 3; // COMP2OUT
// Digital Compare : COMPxOUT -> DCAEVT1 -> Trip Zone one-shot (latche)