Reprise controlee apres EMUSTOP, sans lever le verrouillage des defauts

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 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 17:22:44 +02:00
parent 26c1d499e0
commit b1bb0fa21a
4 changed files with 100 additions and 17 deletions

View File

@ -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));

View File

@ -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;

View File

@ -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)

View File

@ -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