Etape 2 validee sur cible : trip comparateur, arret global, Stagex-EN
safety_init() complet est maintenant appele a la place de safety_arm_emustop_only() : comparateurs + Digital Compare + Trip Zone. Seuils DAC verifies sur cible a 601 / 603, conformes au calcul de docs/mesure-cartepuissance.md pour 3,0 A. CORRECTIF -- les trips ne remontaient pas au logiciel (safety.c) 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. Les ISR ne faisaient que PIEACK. Consequence : le premier trip de la session (un EMUSTOP provoque par une pause du debogueur) consommait l'unique notification, et tous les suivants coupaient bien le PWM en materiel mais en SILENCE, sans lever de drapeau logiciel. Constate au banc : surintensite reelle sur I2, EPwm2Regs.TZFLG = 0x0D (INT + OST + DCAEVT1, donc trip comparateur bien latche), PWM2 coupe, et pourtant LED au nominal. Ce defaut etait invisible en testant un seul trip a la fois. Il ne serait apparu qu'en exploitation, sous la forme "la protection coupe mais l'IHM n'affiche aucun defaut". Second piege evite : effacer INT alors que la surintensite persiste regenere l'evenement immediatement -> tempete d'interruptions qui affamerait la boucle principale, donc plus de telemetrie ni de LED, au pire moment. L'ISR desarme donc TZEINT.OST : une seule notification par episode. Le rearmement est fait par safety_clear_faults() uniquement, jamais automatiquement (PROMPT §8). safety_clear_faults() efface desormais DCAEVT1 (sinon le prochain trip serait attribue a une surintensite meme s'il vient d'EMUSTOP), INT, et rearme TZEINT.OST. Arret global sur defaut (main.c) enter_safe_state() : les deux etages inhibes (PWM + Stagex-EN) ET HV_EN coupe. Sur un boost en cascade, laisser tourner l'etage 2 pendant que l'etage 1 est coupe n'a pas de sens. Couper HV_EN n'est pas redondant : un boost a 0 % de duty ne donne PAS 0 V en sortie, le chemin Vin -> L -> diode -> Cout reste passant ; HV_EN (VOM1271) est le seul organe qui isole reellement la charge (PROMPT §6 etape 6). Les Trip Zones restent independantes en materiel : cet arret global est la couche systeme au-dessus, executee au rythme de la boucle principale. La coupure rapide (~30 ns) reste la porte ET + le Trip Zone. Stagex-EN est maintenant pilote en continu : 1 au nominal, 0 sur defaut. Inhibition SUPPLEMENTAIRE, le logiciel ne peut jamais outrepasser la coupure materielle deja faite par COMPxOUT sur l'autre entree de la porte. Valide au banc : injection de 2 A sur le shunt 2 avec seuil abaisse a 1,5 A -> les deux etages coupes, LED rouge fixe, defaut latche. ATTENTION, toujours en place dans main.c : le harnais de bring-up force le PWM a 50 % en dur, et Stagex-EN passe desormais a 1 automatiquement au demarrage. Des que les drivers UCC27517 seront montes, la carte se mettra donc a decouper A LA MISE SOUS TENSION, sans commande -- exactement ce que le PROMPT §6 etape 6 interdit (demarrage a 0 % + rampe de soft-start). Retirer ce harnais AVANT de monter les drivers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@ -74,6 +74,8 @@
|
|||||||
// futur passage au TLV9151 ne touchera que les constantes ci-dessus.
|
// futur passage au TLV9151 ne touchera que les constantes ci-dessus.
|
||||||
//
|
//
|
||||||
// Formule TI : V = DACVAL * (VDDA - VSSA) / 1023 -> 1023, pas 4096.
|
// 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
|
||||||
|
|
||||||
|
|||||||
67
src/main.c
67
src/main.c
@ -47,10 +47,25 @@ static void update_overtemp(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Priorite : surintensite > surtemperature > EMUSTOP > liaison perdue.
|
// Etat sur : les deux etages inhibes ET la sortie HT coupee.
|
||||||
static led_state_t compute_led_state(void)
|
//
|
||||||
|
// 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)
|
if (faults.overcurrent)
|
||||||
{
|
{
|
||||||
@ -101,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();
|
||||||
@ -139,6 +151,8 @@ void main(void)
|
|||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
command_state_t cmd;
|
command_state_t cmd;
|
||||||
|
safety_faults_t faults;
|
||||||
|
|
||||||
if (uart_link_poll(&cmd))
|
if (uart_link_poll(&cmd))
|
||||||
{
|
{
|
||||||
g_last_cmd = cmd;
|
g_last_cmd = cmd;
|
||||||
@ -147,19 +161,32 @@ void main(void)
|
|||||||
|
|
||||||
update_overtemp();
|
update_overtemp();
|
||||||
|
|
||||||
// Protection thermique : lente par nature, donc logicielle -- aucun
|
faults = safety_get_fault_flags();
|
||||||
// chemin materiel requis, contrairement a la surintensite.
|
|
||||||
// Pas de redemarrage automatique : rien ne reactive le PWM une fois
|
// ARRET GLOBAL : n'importe quel defaut, sur n'importe quel etage,
|
||||||
// coupe, meme si la temperature redescend sous l'hysteresis. Il faut
|
// arrete l'ensemble. Sur un boost en cascade, laisser tourner
|
||||||
// un reset (ou, a l'etape 8, une commande explicite).
|
// l'etage 2 alors que l'etage 1 est coupe n'a pas de sens : il
|
||||||
if (s_overtemp)
|
// 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);
|
enter_safe_state();
|
||||||
pwm_enable(STAGE_2, false);
|
}
|
||||||
hv_enable_set(false);
|
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)
|
if (s_send_telemetry)
|
||||||
{
|
{
|
||||||
|
|||||||
40
src/safety.c
40
src/safety.c
@ -114,9 +114,21 @@ safety_faults_t safety_get_fault_flags(void)
|
|||||||
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;
|
||||||
s_overcurrent = 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
|
// Diagnostic uniquement : la coupure est deja faite en materiel (PROMPT §6
|
||||||
// etape 2 point 7). On se contente de relever l'origine du trip.
|
// 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;
|
||||||
@ -148,6 +178,11 @@ interrupt void epwm1_tzint_isr(void)
|
|||||||
s_emustop = true;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,5 +199,10 @@ interrupt void epwm2_tzint_isr(void)
|
|||||||
s_emustop = true;
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user