diff --git a/src/calib.h b/src/calib.h index cf1fd28..c7a9b38 100644 --- a/src/calib.h +++ b/src/calib.h @@ -74,6 +74,8 @@ // 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_DAC_VREF_V 3.3f diff --git a/src/main.c b/src/main.c index feee1c7..be266b0 100644 --- a/src/main.c +++ b/src/main.c @@ -47,10 +47,25 @@ static void update_overtemp(void) } } -// Priorite : surintensite > surtemperature > EMUSTOP > liaison perdue. -static led_state_t compute_led_state(void) +// 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) { - safety_faults_t faults = safety_get_fault_flags(); + 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) { @@ -101,13 +116,10 @@ void main(void) // que le Trip Zone n'est pas arme. pwm_init(); - // safety_init() complet volontairement NON appele : le comparateur a - // deja ete valide (trip observe en passant une entree de 0 a 3 V), et - // avec les entrees shunt flottantes il declencherait aussitot, ce qui - // masquerait le test d'EMUSTOP. On arme donc uniquement TZ6. - // A REMPLACER par safety_init() avant toute mise sous tension de la - // puissance (PROMPT §8). - safety_arm_emustop_only(); + // Protection complete : comparateurs + Digital Compare + Trip Zone, et + // TZ6/EMUSTOP. Les entrees shunt sont maintenant reellement cablees, le + // comparateur ne declenchera donc pas spontanement. + safety_init(); // ADC : declenche par ePWM1, donc apres pwm_init(). adc_init(); @@ -139,6 +151,8 @@ void main(void) for (;;) { command_state_t cmd; + safety_faults_t faults; + if (uart_link_poll(&cmd)) { g_last_cmd = cmd; @@ -147,19 +161,32 @@ void main(void) 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) + 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) { - pwm_enable(STAGE_1, false); - pwm_enable(STAGE_2, false); - hv_enable_set(false); + enter_safe_state(); + } + else + { + stage_enable_set(STAGE_1, true); + stage_enable_set(STAGE_2, true); } - status_led_set_state(compute_led_state()); + status_led_set_state(compute_led_state(&faults)); if (s_send_telemetry) { diff --git a/src/safety.c b/src/safety.c index 250e1e1..a3417de 100644 --- a/src/safety.c +++ b/src/safety.c @@ -114,9 +114,21 @@ safety_faults_t safety_get_fault_flags(void) void safety_clear_faults(void) { 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.INT = 1; + EPwm1Regs.TZEINT.bit.OST = 1; + + EPwm2Regs.TZCLR.bit.DCAEVT1 = 1; EPwm2Regs.TZCLR.bit.OST = 1; + EPwm2Regs.TZCLR.bit.INT = 1; + EPwm2Regs.TZEINT.bit.OST = 1; EDIS; + s_stage1_fault = false; s_stage2_fault = false; s_overcurrent = false; @@ -135,6 +147,24 @@ void safety_force_trip_test(void) // 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) { s_stage1_fault = true; @@ -148,6 +178,11 @@ interrupt void epwm1_tzint_isr(void) s_emustop = true; } + EALLOW; + EPwm1Regs.TZEINT.bit.OST = 0; + EPwm1Regs.TZCLR.bit.INT = 1; + EDIS; + PieCtrlRegs.PIEACK.all = PIEACK_GROUP2; } @@ -164,5 +199,10 @@ interrupt void epwm2_tzint_isr(void) s_emustop = true; } + EALLOW; + EPwm2Regs.TZEINT.bit.OST = 0; + EPwm2Regs.TZCLR.bit.INT = 1; + EDIS; + PieCtrlRegs.PIEACK.all = PIEACK_GROUP2; }