Compare commits
3 Commits
d26c07d3a1
...
46ddf30a64
| Author | SHA1 | Date | |
|---|---|---|---|
| 46ddf30a64 | |||
| a05c1e8124 | |||
| 888723abc7 |
88
src/calib.h
88
src/calib.h
@ -17,12 +17,92 @@
|
|||||||
// Valeur de champ AIOMUX1 pour basculer une broche AIOx en mode analogique
|
// Valeur de champ AIOMUX1 pour basculer une broche AIOx en mode analogique
|
||||||
#define GPIO_ANALOG_MODE 2
|
#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.
|
||||||
|
// Seuil nominal (PROMPT §5). Le declenchement a ete valide au banc a 1,5 A
|
||||||
|
// (courant injectable), puis le seuil remis a sa valeur de service.
|
||||||
#define SAFETY_ISHUNT_THRESHOLD_A 3.0f
|
#define SAFETY_ISHUNT_THRESHOLD_A 3.0f
|
||||||
#define SAFETY_DAC_VREF_V 3.3f
|
#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)
|
||||||
|
|
||||||
|
// ---- Protection thermique (logicielle, PROMPT §6 etape 4) -----------
|
||||||
|
// Contrairement a la surintensite, la thermique est lente : le logiciel
|
||||||
|
// suffit, aucun chemin materiel n'est requis.
|
||||||
|
// 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_HYST_C 10.0f
|
||||||
|
|
||||||
|
// ---- Timeout de la liaison ESP32 (PROMPT §6 etape 7) ----------------
|
||||||
|
// Sans trame $C valide au-dela de ce delai, la liaison est declaree
|
||||||
|
// perdue. La securite ne depend jamais de l'ESP32.
|
||||||
|
#define UART_LINK_TIMEOUT_MS 2000U
|
||||||
|
|
||||||
// ADC : reference interne obligatoire (VREFHI partage avec ADCINA0/VIN),
|
// ADC : reference interne obligatoire (VREFHI partage avec ADCINA0/VIN),
|
||||||
// pleine echelle 3,3 V. Conversion brut -> volts : V = raw * 3.3 / 4096.
|
// pleine echelle 3,3 V. Conversion brut -> volts : V = raw * 3.3 / 4096.
|
||||||
|
|||||||
149
src/main.c
149
src/main.c
@ -6,6 +6,8 @@
|
|||||||
#include "protocol.h"
|
#include "protocol.h"
|
||||||
#include "pwm.h"
|
#include "pwm.h"
|
||||||
#include "adc.h"
|
#include "adc.h"
|
||||||
|
#include "measure.h"
|
||||||
|
#include "status_led.h"
|
||||||
#include "calib.h"
|
#include "calib.h"
|
||||||
|
|
||||||
extern uint16_t RamfuncsLoadStart;
|
extern uint16_t RamfuncsLoadStart;
|
||||||
@ -14,11 +16,76 @@ extern uint16_t RamfuncsRunStart;
|
|||||||
|
|
||||||
interrupt void cpu_timer0_isr(void);
|
interrupt void cpu_timer0_isr(void);
|
||||||
|
|
||||||
// Bring-up etape 7 (UART) : dernier CommandState recu, pour inspection au
|
// Dernier CommandState recu, expose au debogueur.
|
||||||
// debogueur. Valeurs de telemetrie figees tant que measure.c n'existe pas.
|
|
||||||
static volatile command_state_t g_last_cmd = {false, false, false};
|
static volatile command_state_t g_last_cmd = {false, false, false};
|
||||||
static volatile bool s_send_telemetry = false;
|
static volatile bool s_send_telemetry = false;
|
||||||
|
|
||||||
|
// Age de la derniere trame $C valide, en ticks de 10 ms. Incremente par
|
||||||
|
// l'ISR, remis a zero par la boucle principale a chaque commande valide.
|
||||||
|
// Sature pour ne jamais reboucler.
|
||||||
|
#define TICK_MS 10U
|
||||||
|
#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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (!s_overtemp)
|
||||||
|
{
|
||||||
|
s_overtemp = (tmax >= SAFETY_OVERTEMP_C);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s_overtemp = (tmax > (SAFETY_OVERTEMP_C - SAFETY_OVERTEMP_HYST_C));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Etat sur : les deux etages inhibes ET la sortie HT coupee.
|
||||||
|
//
|
||||||
|
// Couper HV_EN est indispensable et n'a rien de redondant : un boost a 0 %
|
||||||
|
// de duty ne donne PAS 0 V en sortie, le chemin Vin -> L -> diode -> Cout
|
||||||
|
// reste passant en permanence. HV_EN (VOM1271) est le seul organe qui isole
|
||||||
|
// reellement la charge (PROMPT §6 etape 6).
|
||||||
|
static void enter_safe_state(void)
|
||||||
|
{
|
||||||
|
pwm_enable(STAGE_1, false);
|
||||||
|
pwm_enable(STAGE_2, false);
|
||||||
|
stage_enable_set(STAGE_1, false);
|
||||||
|
stage_enable_set(STAGE_2, false);
|
||||||
|
hv_enable_set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priorite : surintensite > surtemperature > EMUSTOP > liaison perdue.
|
||||||
|
static led_state_t compute_led_state(const safety_faults_t *f)
|
||||||
|
{
|
||||||
|
safety_faults_t faults = *f;
|
||||||
|
|
||||||
|
if (faults.overcurrent)
|
||||||
|
{
|
||||||
|
return LED_STATE_OVERCURRENT;
|
||||||
|
}
|
||||||
|
if (s_overtemp)
|
||||||
|
{
|
||||||
|
return LED_STATE_OVERTEMP;
|
||||||
|
}
|
||||||
|
if (faults.emustop)
|
||||||
|
{
|
||||||
|
return LED_STATE_EMUSTOP;
|
||||||
|
}
|
||||||
|
if (s_ticks_since_cmd >= LINK_TIMEOUT_TICKS)
|
||||||
|
{
|
||||||
|
return LED_STATE_LINK_LOST;
|
||||||
|
}
|
||||||
|
return LED_STATE_NOMINAL;
|
||||||
|
}
|
||||||
|
|
||||||
void main(void)
|
void main(void)
|
||||||
{
|
{
|
||||||
#ifdef _FLASH
|
#ifdef _FLASH
|
||||||
@ -27,6 +94,7 @@ void main(void)
|
|||||||
|
|
||||||
bsp_clock_init();
|
bsp_clock_init();
|
||||||
bsp_gpio_leds_init();
|
bsp_gpio_leds_init();
|
||||||
|
status_led_init(); // bleu fixe des le depart : "je suis parti"
|
||||||
|
|
||||||
DINT;
|
DINT;
|
||||||
InitPieCtrl();
|
InitPieCtrl();
|
||||||
@ -48,13 +116,10 @@ void main(void)
|
|||||||
// que le Trip Zone n'est pas arme.
|
// que le Trip Zone n'est pas arme.
|
||||||
pwm_init();
|
pwm_init();
|
||||||
|
|
||||||
// safety_init() complet volontairement NON appele : le comparateur a
|
// Protection complete : comparateurs + Digital Compare + Trip Zone, et
|
||||||
// deja ete valide (trip observe en passant une entree de 0 a 3 V), et
|
// TZ6/EMUSTOP. Les entrees shunt sont maintenant reellement cablees, le
|
||||||
// avec les entrees shunt flottantes il declencherait aussitot, ce qui
|
// comparateur ne declenchera donc pas spontanement.
|
||||||
// masquerait le test d'EMUSTOP. On arme donc uniquement TZ6.
|
safety_init();
|
||||||
// A REMPLACER par safety_init() avant toute mise sous tension de la
|
|
||||||
// puissance (PROMPT §8).
|
|
||||||
safety_arm_emustop_only();
|
|
||||||
|
|
||||||
// ADC : declenche par ePWM1, donc apres pwm_init().
|
// ADC : declenche par ePWM1, donc apres pwm_init().
|
||||||
adc_init();
|
adc_init();
|
||||||
@ -91,39 +156,60 @@ void main(void)
|
|||||||
if (uart_link_poll(&cmd))
|
if (uart_link_poll(&cmd))
|
||||||
{
|
{
|
||||||
g_last_cmd = cmd;
|
g_last_cmd = cmd;
|
||||||
|
s_ticks_since_cmd = 0U;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test EMUSTOP : LED rouge = defaut latche, bleue = nominal.
|
update_overtemp();
|
||||||
// Le flag ne se rearme jamais seul (PROMPT §8).
|
|
||||||
faults = safety_get_fault_flags();
|
faults = safety_get_fault_flags();
|
||||||
|
|
||||||
|
// ARRET GLOBAL : n'importe quel defaut, sur n'importe quel etage,
|
||||||
|
// arrete l'ensemble. Sur un boost en cascade, laisser tourner
|
||||||
|
// l'etage 2 alors que l'etage 1 est coupe n'a pas de sens : il
|
||||||
|
// travaillerait sans tension d'entree.
|
||||||
|
//
|
||||||
|
// Les Trip Zones restent independantes en materiel (une surintensite
|
||||||
|
// etage 2 ne coupe que EPWM2A) : cet arret global est la couche
|
||||||
|
// systeme qui vient PAR-DESSUS. Le logiciel peut inhiber en plus,
|
||||||
|
// jamais outrepasser la protection materielle (PROMPT §2).
|
||||||
|
//
|
||||||
|
// 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)
|
||||||
{
|
{
|
||||||
bool tripped = faults.stage1_fault || faults.stage2_fault;
|
enter_safe_state();
|
||||||
led_set(LED_RED, tripped);
|
|
||||||
led_set(LED_BLUE, !tripped);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stage_enable_set(STAGE_1, true);
|
||||||
|
stage_enable_set(STAGE_2, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
status_led_set_state(compute_led_state(&faults));
|
||||||
|
|
||||||
if (s_send_telemetry)
|
if (s_send_telemetry)
|
||||||
{
|
{
|
||||||
telemetry_t t;
|
telemetry_t t;
|
||||||
|
|
||||||
// BRING-UP : chaque champ transporte la TENSION BRUTE de la
|
// Grandeurs physiques reelles (etape 5) : conversion par
|
||||||
// broche ADC correspondante, en volts (0..3,3) -- et non la
|
// measure.c a partir des coefficients mesures sur la carte
|
||||||
// grandeur physique. Permet de verifier les 9 voies d'un coup
|
// (docs/mesure-cartepuissance.md).
|
||||||
// sur l'IHM ESP32 apres ressoudure. measure.c fera la vraie
|
// FREQ/DUTY restent les valeurs de consigne du bloc de bring-up
|
||||||
// conversion (ponts diviseurs, gain de shunt, NTC) a l'etape 5.
|
// ci-dessus, pas encore une mesure.
|
||||||
t.freq1_hz = (float)PWM_STAGE1_FREQ_HZ;
|
t.freq1_hz = (float)PWM_STAGE1_FREQ_HZ;
|
||||||
t.freq2_hz = (float)PWM_STAGE2_FREQ_HZ;
|
t.freq2_hz = (float)PWM_STAGE2_FREQ_HZ;
|
||||||
t.duty1_pct = 50.0f;
|
t.duty1_pct = 50.0f;
|
||||||
t.duty2_pct = 50.0f;
|
t.duty2_pct = 50.0f;
|
||||||
t.vin_v = adc_get_volts(ADC_CH_VIN);
|
t.vin_v = measure_vin(adc_get_raw(ADC_CH_VIN));
|
||||||
t.iin_a = adc_get_volts(ADC_CH_IIN);
|
t.iin_a = measure_iin(adc_get_raw(ADC_CH_IIN));
|
||||||
t.v1_v = adc_get_volts(ADC_CH_V1);
|
t.v1_v = measure_v1(adc_get_raw(ADC_CH_V1));
|
||||||
t.i1_a = adc_get_volts(ADC_CH_I1);
|
t.i1_a = measure_i1(adc_get_raw(ADC_CH_I1));
|
||||||
t.t1_c = adc_get_volts(ADC_CH_T1);
|
t.t1_c = measure_temp(adc_get_raw(ADC_CH_T1));
|
||||||
t.vout_v = adc_get_volts(ADC_CH_VOUT);
|
t.vout_v = measure_vout(adc_get_raw(ADC_CH_VOUT));
|
||||||
t.i2_a = adc_get_volts(ADC_CH_I2);
|
t.i2_a = measure_i2(adc_get_raw(ADC_CH_I2));
|
||||||
t.t2_c = adc_get_volts(ADC_CH_T2);
|
t.t2_c = measure_temp(adc_get_raw(ADC_CH_T2));
|
||||||
t.iout_a = adc_get_volts(ADC_CH_IOUT);
|
t.iout_a = measure_iout(adc_get_raw(ADC_CH_IOUT));
|
||||||
|
|
||||||
uart_link_send_telemetry(&t);
|
uart_link_send_telemetry(&t);
|
||||||
s_send_telemetry = false;
|
s_send_telemetry = false;
|
||||||
@ -149,6 +235,13 @@ interrupt void cpu_timer0_isr(void)
|
|||||||
CpuTimer0.InterruptCount++;
|
CpuTimer0.InterruptCount++;
|
||||||
tick++;
|
tick++;
|
||||||
|
|
||||||
|
status_led_tick();
|
||||||
|
|
||||||
|
if (s_ticks_since_cmd < LINK_TIMEOUT_TICKS)
|
||||||
|
{
|
||||||
|
s_ticks_since_cmd++; // sature au seuil, pas de rebouclage
|
||||||
|
}
|
||||||
|
|
||||||
if ((tick % 30U) == 0U)
|
if ((tick % 30U) == 0U)
|
||||||
{
|
{
|
||||||
s_send_telemetry = true; // telemetrie toutes les 300 ms
|
s_send_telemetry = true; // telemetrie toutes les 300 ms
|
||||||
|
|||||||
76
src/measure.c
Normal file
76
src/measure.c
Normal 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
30
src/measure.h
Normal 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
|
||||||
79
src/safety.c
79
src/safety.c
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
static volatile bool s_stage1_fault = false;
|
static volatile bool s_stage1_fault = false;
|
||||||
static volatile bool s_stage2_fault = false;
|
static volatile bool s_stage2_fault = false;
|
||||||
|
static volatile bool s_overcurrent = false;
|
||||||
|
static volatile bool s_emustop = false;
|
||||||
|
|
||||||
interrupt void epwm1_tzint_isr(void);
|
interrupt void epwm1_tzint_isr(void);
|
||||||
interrupt void epwm2_tzint_isr(void);
|
interrupt void epwm2_tzint_isr(void);
|
||||||
@ -31,7 +33,9 @@ void safety_init(void)
|
|||||||
Comp1Regs.COMPCTL.bit.SYNCSEL = 0; // asynchrone -> ~30 ns vers le Trip Zone
|
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.CMPINV = 1; // driver actif haut : 1=OK, 0=defaut
|
||||||
Comp1Regs.COMPCTL.bit.QUALSEL = 0; // pas de qualification pour l'instant
|
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
|
GpioCtrlRegs.GPAMUX1.bit.GPIO1 = 3; // COMP1OUT
|
||||||
|
|
||||||
// Comparateur 2 : idem, shunt etage 2 sur COMP2A.
|
// Comparateur 2 : idem, shunt etage 2 sur COMP2A.
|
||||||
@ -40,7 +44,7 @@ void safety_init(void)
|
|||||||
Comp2Regs.COMPCTL.bit.SYNCSEL = 0;
|
Comp2Regs.COMPCTL.bit.SYNCSEL = 0;
|
||||||
Comp2Regs.COMPCTL.bit.CMPINV = 1;
|
Comp2Regs.COMPCTL.bit.CMPINV = 1;
|
||||||
Comp2Regs.COMPCTL.bit.QUALSEL = 0;
|
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
|
GpioCtrlRegs.GPAMUX1.bit.GPIO3 = 3; // COMP2OUT
|
||||||
|
|
||||||
// Digital Compare : COMPxOUT -> DCAEVT1 -> Trip Zone one-shot (latche)
|
// Digital Compare : COMPxOUT -> DCAEVT1 -> Trip Zone one-shot (latche)
|
||||||
@ -102,18 +106,35 @@ safety_faults_t safety_get_fault_flags(void)
|
|||||||
safety_faults_t f;
|
safety_faults_t f;
|
||||||
f.stage1_fault = s_stage1_fault;
|
f.stage1_fault = s_stage1_fault;
|
||||||
f.stage2_fault = s_stage2_fault;
|
f.stage2_fault = s_stage2_fault;
|
||||||
|
f.overcurrent = s_overcurrent;
|
||||||
|
f.emustop = s_emustop;
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void safety_clear_faults(void)
|
void safety_clear_faults(void)
|
||||||
{
|
{
|
||||||
EALLOW;
|
EALLOW;
|
||||||
|
// OST libere la sortie PWM, DCAEVT1 remet a zero la cause (sinon le
|
||||||
|
// prochain trip serait attribue a une surintensite meme s'il vient
|
||||||
|
// d'EMUSTOP), INT autorise a nouveau la generation d'interruptions, et
|
||||||
|
// TZEINT.OST rearme la notification desarmee par l'ISR.
|
||||||
|
EPwm1Regs.TZCLR.bit.DCAEVT1 = 1;
|
||||||
EPwm1Regs.TZCLR.bit.OST = 1;
|
EPwm1Regs.TZCLR.bit.OST = 1;
|
||||||
|
EPwm1Regs.TZCLR.bit.INT = 1;
|
||||||
|
EPwm1Regs.TZEINT.bit.OST = 1;
|
||||||
|
|
||||||
|
EPwm2Regs.TZCLR.bit.DCAEVT1 = 1;
|
||||||
EPwm2Regs.TZCLR.bit.OST = 1;
|
EPwm2Regs.TZCLR.bit.OST = 1;
|
||||||
|
EPwm2Regs.TZCLR.bit.INT = 1;
|
||||||
|
EPwm2Regs.TZEINT.bit.OST = 1;
|
||||||
EDIS;
|
EDIS;
|
||||||
|
|
||||||
s_stage1_fault = false;
|
s_stage1_fault = false;
|
||||||
s_stage2_fault = false;
|
s_stage2_fault = false;
|
||||||
led_set(LED_RED, false);
|
s_overcurrent = false;
|
||||||
|
s_emustop = false;
|
||||||
|
// Les LED ne sont plus pilotees ici : c'est status_led.c qui detient
|
||||||
|
// l'affichage, a partir de ces drapeaux.
|
||||||
}
|
}
|
||||||
|
|
||||||
void safety_force_trip_test(void)
|
void safety_force_trip_test(void)
|
||||||
@ -124,16 +145,64 @@ void safety_force_trip_test(void)
|
|||||||
EDIS;
|
EDIS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Diagnostic uniquement : la coupure est deja faite en materiel (PROMPT §6
|
||||||
|
// etape 2 point 7). On se contente de relever l'origine du trip.
|
||||||
|
//
|
||||||
|
// Deux subtilites, decouvertes au banc :
|
||||||
|
//
|
||||||
|
// 1. TZFLG.INT est le drapeau GLOBAL d'interruption Trip Zone. Tant qu'il
|
||||||
|
// n'est pas efface via TZCLR.INT, plus AUCUNE interruption TZ n'est
|
||||||
|
// generee. Sans ce clear, seul le tout premier trip de la session etait
|
||||||
|
// signale au logiciel : les suivants coupaient bien le PWM en materiel,
|
||||||
|
// mais en silence, sans lever de drapeau.
|
||||||
|
//
|
||||||
|
// 2. Effacer INT alors que la condition de trip persiste (surintensite
|
||||||
|
// toujours presente) regenere l'evenement immediatement -> tempete
|
||||||
|
// d'interruptions qui affamerait la boucle principale. On desarme donc
|
||||||
|
// TZEINT.OST ici : une seule notification par episode de defaut. Le
|
||||||
|
// rearmement est fait par safety_clear_faults(), c'est-a-dire jamais
|
||||||
|
// automatiquement (PROMPT §8).
|
||||||
|
//
|
||||||
|
// Le drapeau DCAEVT1 n'est volontairement PAS efface ici : il porte la
|
||||||
|
// cause du defaut et doit rester lisible jusqu'a l'effacement explicite.
|
||||||
interrupt void epwm1_tzint_isr(void)
|
interrupt void epwm1_tzint_isr(void)
|
||||||
{
|
{
|
||||||
s_stage1_fault = true;
|
s_stage1_fault = true;
|
||||||
led_set(LED_RED, true);
|
|
||||||
|
if (EPwm1Regs.TZFLG.bit.DCAEVT1)
|
||||||
|
{
|
||||||
|
s_overcurrent = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s_emustop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
EALLOW;
|
||||||
|
EPwm1Regs.TZEINT.bit.OST = 0;
|
||||||
|
EPwm1Regs.TZCLR.bit.INT = 1;
|
||||||
|
EDIS;
|
||||||
|
|
||||||
PieCtrlRegs.PIEACK.all = PIEACK_GROUP2;
|
PieCtrlRegs.PIEACK.all = PIEACK_GROUP2;
|
||||||
}
|
}
|
||||||
|
|
||||||
interrupt void epwm2_tzint_isr(void)
|
interrupt void epwm2_tzint_isr(void)
|
||||||
{
|
{
|
||||||
s_stage2_fault = true;
|
s_stage2_fault = true;
|
||||||
led_set(LED_RED, true);
|
|
||||||
|
if (EPwm2Regs.TZFLG.bit.DCAEVT1)
|
||||||
|
{
|
||||||
|
s_overcurrent = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s_emustop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
EALLOW;
|
||||||
|
EPwm2Regs.TZEINT.bit.OST = 0;
|
||||||
|
EPwm2Regs.TZCLR.bit.INT = 1;
|
||||||
|
EDIS;
|
||||||
|
|
||||||
PieCtrlRegs.PIEACK.all = PIEACK_GROUP2;
|
PieCtrlRegs.PIEACK.all = PIEACK_GROUP2;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,13 @@ typedef struct
|
|||||||
{
|
{
|
||||||
bool stage1_fault;
|
bool stage1_fault;
|
||||||
bool stage2_fault;
|
bool stage2_fault;
|
||||||
|
|
||||||
|
// Origine du trip, relevee dans TZFLG au moment ou l'ISR s'execute.
|
||||||
|
// TZFLG.DCAEVT1 (bit 3) est distinct de TZFLG.OST (bit 2), ce qui permet
|
||||||
|
// de separer une vraie surintensite d'un arret du debugger -- les deux
|
||||||
|
// n'appellent pas du tout la meme reaction.
|
||||||
|
bool overcurrent; // DCAEVT1 : comparateur, surintensite reelle
|
||||||
|
bool emustop; // OST sans DCAEVT1 : EMUSTOP (ou TZFRC de test)
|
||||||
} safety_faults_t;
|
} safety_faults_t;
|
||||||
|
|
||||||
void safety_init(void);
|
void safety_init(void);
|
||||||
|
|||||||
96
src/status_led.c
Normal file
96
src/status_led.c
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "status_led.h"
|
||||||
|
#include "bsp_gpio.h"
|
||||||
|
|
||||||
|
// Durees exprimees en ticks de 10 ms. Ce sont des constantes de
|
||||||
|
// presentation, pas de calibration materielle : elles restent ici plutot
|
||||||
|
// que dans calib.h, qui decrit la carte.
|
||||||
|
#define TICKS_PER_100MS 10U
|
||||||
|
|
||||||
|
#define STARTUP_MIN_TICKS 100U // 1 s : rend l'etat de demarrage visible
|
||||||
|
|
||||||
|
#define NOMINAL_PERIOD 200U // 2,0 s
|
||||||
|
#define NOMINAL_ON 20U // 0,2 s
|
||||||
|
|
||||||
|
#define LINK_LOST_PERIOD 40U // 0,4 s
|
||||||
|
#define LINK_LOST_ON 20U // 0,2 s
|
||||||
|
|
||||||
|
#define EMUSTOP_PERIOD 100U // 1,0 s
|
||||||
|
#define EMUSTOP_HALF 50U // 0,5 s bleu puis 0,5 s rouge
|
||||||
|
|
||||||
|
#define OVERTEMP_PERIOD 100U // 1,0 s
|
||||||
|
#define OVERTEMP_ON 50U // 0,5 s
|
||||||
|
|
||||||
|
static led_state_t s_state = LED_STATE_STARTUP;
|
||||||
|
static uint16_t s_phase = 0; // position dans le motif courant
|
||||||
|
static uint16_t s_startup_hold = STARTUP_MIN_TICKS;
|
||||||
|
|
||||||
|
void status_led_init(void)
|
||||||
|
{
|
||||||
|
s_state = LED_STATE_STARTUP;
|
||||||
|
s_phase = 0;
|
||||||
|
s_startup_hold = STARTUP_MIN_TICKS;
|
||||||
|
|
||||||
|
led_set(LED_BLUE, true);
|
||||||
|
led_set(LED_RED, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void status_led_set_state(led_state_t state)
|
||||||
|
{
|
||||||
|
if (s_startup_hold != 0U)
|
||||||
|
{
|
||||||
|
return; // duree minimale de l'etat de demarrage non ecoulee
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state != s_state)
|
||||||
|
{
|
||||||
|
s_state = state;
|
||||||
|
s_phase = 0; // le motif repart proprement a son debut
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void status_led_tick(void)
|
||||||
|
{
|
||||||
|
bool blue = false;
|
||||||
|
bool red = false;
|
||||||
|
|
||||||
|
if (s_startup_hold != 0U)
|
||||||
|
{
|
||||||
|
s_startup_hold--;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_phase++;
|
||||||
|
|
||||||
|
switch (s_state)
|
||||||
|
{
|
||||||
|
case LED_STATE_NOMINAL:
|
||||||
|
blue = ((s_phase % NOMINAL_PERIOD) < NOMINAL_ON);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LED_STATE_LINK_LOST:
|
||||||
|
blue = ((s_phase % LINK_LOST_PERIOD) < LINK_LOST_ON);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LED_STATE_EMUSTOP:
|
||||||
|
blue = ((s_phase % EMUSTOP_PERIOD) < EMUSTOP_HALF);
|
||||||
|
red = !blue;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LED_STATE_OVERTEMP:
|
||||||
|
red = ((s_phase % OVERTEMP_PERIOD) < OVERTEMP_ON);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LED_STATE_OVERCURRENT:
|
||||||
|
red = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LED_STATE_STARTUP:
|
||||||
|
default:
|
||||||
|
blue = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
led_set(LED_BLUE, blue);
|
||||||
|
led_set(LED_RED, red);
|
||||||
|
}
|
||||||
41
src/status_led.h
Normal file
41
src/status_led.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#ifndef STATUS_LED_H
|
||||||
|
#define STATUS_LED_H
|
||||||
|
|
||||||
|
// Affichage d'etat sur la LED bicolore (bleue = GPIO12, rouge = GPIO33).
|
||||||
|
// Les motifs sont volontairement tous distinguables a l'oeil :
|
||||||
|
//
|
||||||
|
// STARTUP bleu fixe
|
||||||
|
// NOMINAL bleu 0,2 s ON / 1,8 s OFF (battement discret)
|
||||||
|
// LINK_LOST bleu 0,2 s ON / 0,2 s OFF (clignotement rapide)
|
||||||
|
// EMUSTOP alternance bleu/rouge 0,5 s / 0,5 s
|
||||||
|
// OVERTEMP rouge 0,5 s ON / 0,5 s OFF
|
||||||
|
// OVERCURRENT rouge fixe
|
||||||
|
//
|
||||||
|
// L'ordre de l'enumeration EST l'ordre de priorite croissante : quand
|
||||||
|
// plusieurs conditions sont vraies, on affiche la plus grande valeur.
|
||||||
|
// La surintensite passe donc devant EMUSTOP -- pendant le developpement
|
||||||
|
// EMUSTOP se declenche en permanence et ne doit jamais masquer un vrai
|
||||||
|
// defaut de puissance.
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
LED_STATE_STARTUP = 0,
|
||||||
|
LED_STATE_NOMINAL,
|
||||||
|
LED_STATE_LINK_LOST,
|
||||||
|
LED_STATE_EMUSTOP,
|
||||||
|
LED_STATE_OVERTEMP,
|
||||||
|
LED_STATE_OVERCURRENT
|
||||||
|
} led_state_t;
|
||||||
|
|
||||||
|
void status_led_init(void);
|
||||||
|
|
||||||
|
// Etat souhaite, calcule dans la boucle principale. Tout changement
|
||||||
|
// redemarre le motif a zero. Les demandes de sortie de STARTUP sont
|
||||||
|
// ignorees tant que la duree minimale d'affichage n'est pas ecoulee,
|
||||||
|
// sans quoi cet etat ne serait jamais visible (l'init dure quelques ms).
|
||||||
|
void status_led_set_state(led_state_t state);
|
||||||
|
|
||||||
|
// A appeler a chaque tick de 10 ms. Ne fait qu'ecrire GPASET/GPACLEAR,
|
||||||
|
// donc utilisable depuis une ISR.
|
||||||
|
void status_led_tick(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,6 +1,5 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "DSP28x_Project.h"
|
#include "DSP28x_Project.h"
|
||||||
#include "uart_link.h"
|
#include "uart_link.h"
|
||||||
@ -260,6 +259,50 @@ static bool parse_command(const char *body, command_state_t *cmd)
|
|||||||
return ht_found && pwm1_found && pwm2_found;
|
return ht_found && pwm1_found && pwm2_found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode EXACTEMENT deux chiffres hexadecimaux. Renvoie false si l'un des
|
||||||
|
// deux n'en est pas un.
|
||||||
|
//
|
||||||
|
// Remplace strtol, qui etait doublement piegeux ici :
|
||||||
|
// - s_line n'est jamais terminee par un '\0', donc strtol lisait au-dela
|
||||||
|
// de la trame, dans les residus de la ligne precedente ;
|
||||||
|
// - sur C28x, uint8_t fait 16 bits (pas d'adressage par octet), donc le
|
||||||
|
// cast (uint8_t) ne tronquait pas le resultat. Un "7D" suivi d'un "7D"
|
||||||
|
// residuel donnait 0x7D7D, retenu tel quel, et toute trame etait
|
||||||
|
// rejetee. Sur une architecture a octets le bug serait passe inapercu.
|
||||||
|
static bool parse_hex2(const char *s, uint8_t *out)
|
||||||
|
{
|
||||||
|
uint16_t value = 0U;
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
for (i = 0U; i < 2U; i++)
|
||||||
|
{
|
||||||
|
char c = s[i];
|
||||||
|
uint16_t digit;
|
||||||
|
|
||||||
|
if ((c >= '0') && (c <= '9'))
|
||||||
|
{
|
||||||
|
digit = (uint16_t)(c - '0');
|
||||||
|
}
|
||||||
|
else if ((c >= 'A') && (c <= 'F'))
|
||||||
|
{
|
||||||
|
digit = (uint16_t)(c - 'A' + 10);
|
||||||
|
}
|
||||||
|
else if ((c >= 'a') && (c <= 'f'))
|
||||||
|
{
|
||||||
|
digit = (uint16_t)(c - 'a' + 10);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = (uint16_t)((value << 4) | digit);
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = (uint8_t)(value & 0xFFU);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// s_line contient une ligne complete (sans le '\n' terminal).
|
// s_line contient une ligne complete (sans le '\n' terminal).
|
||||||
static bool process_line(command_state_t *cmd)
|
static bool process_line(command_state_t *cmd)
|
||||||
{
|
{
|
||||||
@ -292,7 +335,10 @@ static bool process_line(command_state_t *cmd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
cs_calc = checksum_of(&s_line[1], star - 1);
|
cs_calc = checksum_of(&s_line[1], star - 1);
|
||||||
cs_recv = (uint8_t)strtol(&s_line[star + 1], NULL, 16);
|
if (!parse_hex2(&s_line[star + 1], &cs_recv))
|
||||||
|
{
|
||||||
|
return false; // checksum malforme -> trame ignoree
|
||||||
|
}
|
||||||
if (cs_calc != cs_recv)
|
if (cs_calc != cs_recv)
|
||||||
{
|
{
|
||||||
return false; // trame corrompue -> ignoree silencieusement
|
return false; // trame corrompue -> ignoree silencieusement
|
||||||
|
|||||||
Reference in New Issue
Block a user