diff --git a/src/calib.h b/src/calib.h index 58da0ae..cf1fd28 100644 --- a/src/calib.h +++ b/src/calib.h @@ -88,6 +88,20 @@ 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), // pleine echelle 3,3 V. Conversion brut -> volts : V = raw * 3.3 / 4096. #define ADC_VREF_V 3.3f diff --git a/src/main.c b/src/main.c index 10b5ba8..feee1c7 100644 --- a/src/main.c +++ b/src/main.c @@ -7,6 +7,7 @@ #include "pwm.h" #include "adc.h" #include "measure.h" +#include "status_led.h" #include "calib.h" extern uint16_t RamfuncsLoadStart; @@ -15,11 +16,61 @@ extern uint16_t RamfuncsRunStart; interrupt void cpu_timer0_isr(void); -// Bring-up etape 7 (UART) : dernier CommandState recu, pour inspection au -// debogueur. Valeurs de telemetrie figees tant que measure.c n'existe pas. +// Dernier CommandState recu, expose au debogueur. static volatile command_state_t g_last_cmd = {false, false, 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)); + } +} + +// Priorite : surintensite > surtemperature > EMUSTOP > liaison perdue. +static led_state_t compute_led_state(void) +{ + safety_faults_t faults = safety_get_fault_flags(); + + 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) { #ifdef _FLASH @@ -28,6 +79,7 @@ void main(void) bsp_clock_init(); bsp_gpio_leds_init(); + status_led_init(); // bleu fixe des le depart : "je suis parti" DINT; InitPieCtrl(); @@ -87,22 +139,28 @@ void main(void) for (;;) { command_state_t cmd; - safety_faults_t faults; - if (uart_link_poll(&cmd)) { g_last_cmd = cmd; + s_ticks_since_cmd = 0U; } - // Test EMUSTOP : LED rouge = defaut latche, bleue = nominal. - // Le flag ne se rearme jamais seul (PROMPT §8). - faults = safety_get_fault_flags(); + update_overtemp(); + + // Protection thermique : lente par nature, donc logicielle -- aucun + // chemin materiel requis, contrairement a la surintensite. + // Pas de redemarrage automatique : rien ne reactive le PWM une fois + // coupe, meme si la temperature redescend sous l'hysteresis. Il faut + // un reset (ou, a l'etape 8, une commande explicite). + if (s_overtemp) { - bool tripped = faults.stage1_fault || faults.stage2_fault; - led_set(LED_RED, tripped); - led_set(LED_BLUE, !tripped); + pwm_enable(STAGE_1, false); + pwm_enable(STAGE_2, false); + hv_enable_set(false); } + status_led_set_state(compute_led_state()); + if (s_send_telemetry) { telemetry_t t; @@ -150,6 +208,13 @@ interrupt void cpu_timer0_isr(void) CpuTimer0.InterruptCount++; 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) { s_send_telemetry = true; // telemetrie toutes les 300 ms diff --git a/src/safety.c b/src/safety.c index fe59cb3..250e1e1 100644 --- a/src/safety.c +++ b/src/safety.c @@ -5,6 +5,8 @@ static volatile bool s_stage1_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 epwm2_tzint_isr(void); @@ -104,6 +106,8 @@ safety_faults_t safety_get_fault_flags(void) safety_faults_t f; f.stage1_fault = s_stage1_fault; f.stage2_fault = s_stage2_fault; + f.overcurrent = s_overcurrent; + f.emustop = s_emustop; return f; } @@ -115,7 +119,10 @@ void safety_clear_faults(void) EDIS; s_stage1_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) @@ -126,16 +133,36 @@ void safety_force_trip_test(void) 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. interrupt void epwm1_tzint_isr(void) { s_stage1_fault = true; - led_set(LED_RED, true); + + if (EPwm1Regs.TZFLG.bit.DCAEVT1) + { + s_overcurrent = true; + } + else + { + s_emustop = true; + } + PieCtrlRegs.PIEACK.all = PIEACK_GROUP2; } interrupt void epwm2_tzint_isr(void) { s_stage2_fault = true; - led_set(LED_RED, true); + + if (EPwm2Regs.TZFLG.bit.DCAEVT1) + { + s_overcurrent = true; + } + else + { + s_emustop = true; + } + PieCtrlRegs.PIEACK.all = PIEACK_GROUP2; } diff --git a/src/safety.h b/src/safety.h index cd66052..fd1d959 100644 --- a/src/safety.h +++ b/src/safety.h @@ -7,6 +7,13 @@ typedef struct { bool stage1_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; void safety_init(void); diff --git a/src/status_led.c b/src/status_led.c new file mode 100644 index 0000000..15abb19 --- /dev/null +++ b/src/status_led.c @@ -0,0 +1,96 @@ +#include +#include +#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); +} diff --git a/src/status_led.h b/src/status_led.h new file mode 100644 index 0000000..f6f5137 --- /dev/null +++ b/src/status_led.h @@ -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 diff --git a/src/uart_link.c b/src/uart_link.c index c0ccd24..c6bdee0 100644 --- a/src/uart_link.c +++ b/src/uart_link.c @@ -1,6 +1,5 @@ #include #include -#include #include #include "DSP28x_Project.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; } +// 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). 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_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) { return false; // trame corrompue -> ignoree silencieusement