From b1bb0fa21adbe807d3f86959a542d4c16a47abb2 Mon Sep 17 00:00:00 2001 From: nicoboy Date: Wed, 5 Aug 2026 17:22:44 +0200 Subject: [PATCH] Reprise controlee apres EMUSTOP, sans lever le verrouillage des defauts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le verrouillage total decide precedemment (aucun effacement, cycle d'alimentation obligatoire) rendait le developpement intenable : chaque breakpoint tuait le PWM jusqu'a debranchement. EMUSTOP n'est pourtant pas un defaut de puissance mais un artefact du debogueur. La distinction est maintenant explicite dans la boucle principale : overcurrent / overtemp -> enter_safe_state(), VERROUILLE jusqu'au cycle d'alimentation (PROMPT §8), aucun acquittement emustop seul -> reprise controlee La reprise ne redemarre PAS la conversion la ou elle s'etait arretee : elle repasse par start_conversion(), le meme chemin qu'au boot, duty remis a zero d'abord. Quand la rampe de soft-start existera, la reprise apres breakpoint en beneficiera automatiquement. uart_link_restart() Reset logiciel du SCI, vidage des FIFO, abandon de la ligne en cours et de la trame en attente. Pendant la halte l'ESP32 a continue d'emettre : l'anneau de reception a deborde et la ligne assemblee est tronquee. Le vidage avance seulement la QUEUE de l'anneau ; s_rx_head appartient a l'ISR et n'est pas touche, ce qui exclut toute course. TZCLR au demarrage dans safety_init() TZFLG.OST est latche dans le materiel : il survit a un reset logiciel comme a un rechargement de programme, et sans ce clear le PWM restait coupe sans raison visible. Sans danger, pour une raison precise : a ce stade pwm_init() a deja force les deux sorties a l'etat bas (AQCSFRC), donc rien ne demarre ; et si la condition de defaut est toujours presente le trip se re-verrouille immediatement. On n'efface qu'un etat perime, jamais un defaut reel. start_conversion() Sequence de demarrage factorisee, appelee au boot et a la reprise. Inhibe la decharge AVANT d'autoriser les etages -- jamais l'inverse, sinon la conversion travaillerait contre le circuit de decharge. Valide sur cible apres cycle pause/reprise/pause : TZEINT.OST rearme a 1 (l'ISR l'avait mis a 0), AQCSFRC.CSFA a 0, GPIO5 a 1 (decharge inhibee), GPIO16 a 1. LIMITE CONNUE, non resolue ici : a la reprise l'effacement est immediat alors que la telemetrie ne part que toutes les 300 ms. FAULT=1 ne sera donc quasiment jamais emis, et l'ESP32 ignorera qu'un EMUSTOP a eu lieu (sans meme voir de timeout si la halte a dure moins de 2 s). Transmettre FAULT=1 AVANT la halte est structurellement impossible : la halte est asynchrone, imposee par la logique de debug, sans point d'accroche prealable -- c'est justement pourquoi TZ6 est un chemin materiel. La solution sera un drapeau collant maintenu quelques secondes apres la reprise. Co-Authored-By: Claude Opus 5 --- src/main.c | 67 ++++++++++++++++++++++++++++++++++++------------- src/safety.c | 16 ++++++++++++ src/uart_link.c | 24 ++++++++++++++++++ src/uart_link.h | 10 ++++++++ 4 files changed, 100 insertions(+), 17 deletions(-) diff --git a/src/main.c b/src/main.c index 8a10337..7e82c68 100644 --- a/src/main.c +++ b/src/main.c @@ -50,6 +50,36 @@ static void update_overtemp(void) update_one_overtemp(s_overtemp_t2, measure_temp(adc_get_raw(ADC_CH_T2))); } +// Sequence de demarrage de la conversion. Appelee au boot ET apres un +// EMUSTOP : dans les deux cas on repart du meme etat connu, on ne reprend +// jamais la conversion la ou elle s'etait arretee. +// +// Ordre impose : la decharge est inhibee AVANT d'autoriser les etages, +// sinon la conversion travaillerait contre le circuit de decharge. +static void start_conversion(void) +{ + hv_discharge_set(false); + + // Duty a zero d'abord : c'est le point de depart obligatoire, un + // condensateur de sortie vide provoquerait sinon un appel de courant + // destructeur (PROMPT §6 etape 6). + pwm_set_duty(STAGE_1, 0.0f); + pwm_set_duty(STAGE_2, 0.0f); + + pwm_enable(STAGE_1, true); + pwm_enable(STAGE_2, true); + stage_enable_set(STAGE_1, true); + stage_enable_set(STAGE_2, true); + + // ---- BRING-UP : duty applique d'un coup, SANS rampe de soft-start. -- + // A REMPLACER par la rampe avant toute mise sous tension de la + // puissance, et a supprimer entierement quand control.c pilotera le + // duty (etape 8). + pwm_set_duty(STAGE_1, 0.5f); + pwm_set_duty(STAGE_2, 0.5f); + // --------------------------------------------------------------------- +} + // Priorite 2 > 3 > 4 > 5 > 1 : EMUSTOP est le code le moins prioritaire // malgre son numero, pour ne jamais masquer une surintensite reelle. static fault_code_t compute_fault_code(const safety_faults_t *f) @@ -161,14 +191,7 @@ void main(void) adc_init(); uart_link_init(); - // ---- BRING-UP : retest PWM + ADC, puissance DECONNECTEE --------------- - // A RETIRER avant toute mise sous tension de la carte de conversion. - // HV_EN et Stagex-EN restent a 0 (exclus de ce test). - pwm_set_duty(STAGE_1, 0.5f); - pwm_set_duty(STAGE_2, 0.5f); - pwm_enable(STAGE_1, true); - pwm_enable(STAGE_2, true); - // ----------------------------------------------------------------------- + start_conversion(); EALLOW; PieVectTable.TINT0 = &cpu_timer0_isr; @@ -212,19 +235,29 @@ void main(void) // 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_t1 || s_overtemp_t2) + if (faults.overcurrent || s_overtemp_t1 || s_overtemp_t2) { + // Defaut de PUISSANCE : verrouille jusqu'au cycle d'alimentation + // (PROMPT §8). Aucun acquittement, ni automatique ni par UART. enter_safe_state(); } - else + else if (faults.emustop) { - // Inhiber la decharge AVANT d'autoriser les etages : jamais - // l'inverse, sinon on ferait travailler la conversion contre le - // circuit de decharge. - hv_discharge_set(false); - stage_enable_set(STAGE_1, true); - stage_enable_set(STAGE_2, true); + // EMUSTOP seul : ce n'est pas un defaut de puissance mais un + // artefact du debogueur. On autorise donc la reprise -- mais en + // repassant par la sequence de demarrage complete, jamais en + // reprenant la conversion la ou elle s'etait arretee. + // + // L'effacement ne peut aboutir qu'ici, CPU en marche : tant que + // le coeur est halte, EMUSTOP reste asserte et le drapeau se + // re-verrouille aussitot. + // + // La liaison est relancee aussi : pendant la halte l'ESP32 a + // continue d'emettre, l'anneau de reception a deborde et la + // ligne en cours est tronquee. + uart_link_restart(); + safety_clear_faults(); + start_conversion(); } status_led_set_state(compute_led_state(&faults)); diff --git a/src/safety.c b/src/safety.c index a3417de..71a9f40 100644 --- a/src/safety.c +++ b/src/safety.c @@ -68,6 +68,22 @@ void safety_init(void) EPwm2Regs.TZSEL.bit.OSHT6 = 1; EPwm2Regs.TBCTL.bit.FREE_SOFT = 0; + // Efface un trip herite d'AVANT ce demarrage. TZFLG.OST est latche dans + // le materiel : il survit a un reset logiciel comme a un simple + // rechargement de programme, et sans ce clear le PWM resterait coupe + // sans raison visible. + // + // Sans danger : a ce stade pwm_init() a laisse les deux sorties forcees + // a l'etat bas (AQCSFRC), donc rien ne demarre. Et si la condition de + // defaut est toujours presente, le trip se re-verrouille immediatement + // -- on n'efface qu'un etat perime, jamais un defaut reel. + EPwm1Regs.TZCLR.bit.DCAEVT1 = 1; + EPwm1Regs.TZCLR.bit.OST = 1; + EPwm1Regs.TZCLR.bit.INT = 1; + EPwm2Regs.TZCLR.bit.DCAEVT1 = 1; + EPwm2Regs.TZCLR.bit.OST = 1; + EPwm2Regs.TZCLR.bit.INT = 1; + PieVectTable.EPWM1_TZINT = &epwm1_tzint_isr; PieVectTable.EPWM2_TZINT = &epwm2_tzint_isr; EDIS; diff --git a/src/uart_link.c b/src/uart_link.c index c005be2..d55e28e 100644 --- a/src/uart_link.c +++ b/src/uart_link.c @@ -151,6 +151,30 @@ static int append_field(char *out, int room, const char *tag, float value, return n; } +void uart_link_restart(void) +{ + // Reset logiciel du SCI : vide les FIFO et efface les etats d'erreur. + SciaRegs.SCICTL1.bit.SWRESET = 0; + SciaRegs.SCICTL1.bit.SWRESET = 1; + + SciaRegs.SCIFFRX.bit.RXFIFORESET = 0; + SciaRegs.SCIFFRX.bit.RXFIFORESET = 1; + SciaRegs.SCIFFRX.bit.RXFFOVRCLR = 1; + SciaRegs.SCIFFRX.bit.RXFFINTCLR = 1; + + SciaRegs.SCIFFTX.bit.TXFIFOXRESET = 0; + SciaRegs.SCIFFTX.bit.TXFIFOXRESET = 1; + + // On jette le contenu de l'anneau en avancant seulement la QUEUE. + // s_rx_head appartient a l'ISR : ne pas y toucher evite toute course. + s_rx_tail = s_rx_head; + s_line_len = 0; + + // Trame d'emission en cours abandonnee : elle date d'avant l'arret. + s_tx_len = 0; + s_tx_pos = 0; +} + // Champ entier (FAULT). Meme convention de bornage que append_field : // renvoie 0 et n'ecrit rien si le champ ne tient pas. static int append_field_int(char *out, int room, const char *tag, int value) diff --git a/src/uart_link.h b/src/uart_link.h index 1dc3296..edebf54 100644 --- a/src/uart_link.h +++ b/src/uart_link.h @@ -6,6 +6,16 @@ void uart_link_init(void); +// Remet la liaison a plat sans reconfigurer le baud ni les broches : +// reset logiciel du SCI, vidage des FIFO, abandon de la ligne en cours +// d'assemblage et de la trame en attente d'emission. +// +// A appeler apres une interruption prolongee du CPU (EMUSTOP) : pendant la +// halte l'ESP32 a continue d'emettre, l'anneau de reception a deborde et +// la ligne en cours est tronquee. Repartir propre evite de traiter des +// fragments anterieurs a l'arret. +void uart_link_restart(void); + // Construit une trame $T,...*XX et la met en attente (docs/ESP32-UART.md). // NE BLOQUE PAS : rien n'est emis ici, c'est uart_link_service_tx() qui // alimente la ligne ensuite. Renvoie false si la trame precedente n'est pas