Etapes 3/4/6 : GPIO de commande, PWM et ADC valides sur cible
Campagne de bring-up materiel complete, carte de conversion DECONNECTEE.
pwm.c/h (etape 6) -- valide au scope
ePWM1 -> etage 1, EPWM1A/GPIO0/broche 29, 200 kHz (TBPRD=299)
ePWM2 -> etage 2, EPWM2A/GPIO2/broche 37, 100 kHz (TBPRD=599)
Mode up-count, TBCLK = SYSCLKOUT = 60 MHz, duty 0 au demarrage,
inhibition par forcage continu AQCSFRC. Tranche le point ouvert §9.1.
HRPWM non implemente, notes de portage consignees dans pwm.c.
adc.c/h (etape 4) -- 9 voies validees, T1/T2 comprises
Reference interne (ADCREFSEL=0), SOC declenches par ePWM1 SOCA avec
SOCAPRD=3 (~66 kHz), jamais en free-run. ISR ADCINT1 courte : rangement
des valeurs brutes uniquement, aucun flottant.
Note materielle : les 9 voies lisaient 4095 tant que VDDA (broche 11)
n'etait pas alimentee -- y compris VREFLO converti en interne, ce qui a
permis d'ecarter le logiciel.
bsp_gpio (etape 3)
HV_EN (GPIO32), Stage1/2-EN (GPIO16/17) en sortie, etat sur a 0, et
pull-ups internes de GPIO16/17 desactivees : sinon elles presentent un 1
sur une entree de chaque porte ET avant que le firmware ne tourne.
bsp_gpio_stage_default_override_init() : pilotage manuel de GPIO1/GPIO3
pour valider les portes ET, exclusif avec safety_init().
safety_arm_emustop_only() -- BRING-UP
Arme TZ6 seul, sans les comparateurs, pour valider EMUSTOP isolement
quand les entrees shunt ne sont pas cablees.
Resultats materiels :
- portes ET IC8/IC9 : sortie = ET logique des deux entrees, verifie au
scope avec des cadences distinctes (default 400 ms, EN 1 s)
- comparateur : entree shunt 0 -> 3 V declenche bien le trip
- EMUSTOP : arret du debugger -> broches 29 et 37 a 0, TZFLG.OST latche,
et le flag ne se rearme pas apres continue
- un trip EMUSTOP ne peut PAS etre efface tant que le CPU est halte (la
source reste assertee) : l'effacement devra passer par une commande
UART en fonctionnement (etape 7)
main.c contient encore le bloc de bring-up (PWM force a 50 %, telemetrie
transportant les tensions ADC brutes en volts) : A RETIRER, et remplacer
safety_arm_emustop_only() par safety_init(), avant toute mise sous tension
de la puissance (PROMPT §8).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
141
src/adc.c
Normal file
141
src/adc.c
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
#include "DSP28x_Project.h"
|
||||||
|
#include "adc.h"
|
||||||
|
#include "calib.h"
|
||||||
|
|
||||||
|
// Valeurs du champ CHSEL (TRM SPRUI09A) : groupe A = 0x0..0x7,
|
||||||
|
// groupe B = 0x8..0xF. L'ordre suit adc_channel_t / SOC0..SOC8.
|
||||||
|
static const uint16_t k_chsel[ADC_CH_COUNT] = {
|
||||||
|
0x0, // ADCINA0 - VIN
|
||||||
|
0x1, // ADCINA1 - IIN
|
||||||
|
0x2, // ADCINA2 - I1 (shunt etage 1)
|
||||||
|
0x3, // ADCINA3 - IOUT
|
||||||
|
0x4, // ADCINA4 - I2 (shunt etage 2)
|
||||||
|
0xA, // ADCINB2 - VOUT
|
||||||
|
0xC, // ADCINB4 - V1
|
||||||
|
0xE, // ADCINB6 - T1
|
||||||
|
0xF // ADCINB7 - T2
|
||||||
|
};
|
||||||
|
|
||||||
|
static volatile uint16_t s_raw[ADC_CH_COUNT];
|
||||||
|
static volatile uint32_t s_seq_count = 0;
|
||||||
|
|
||||||
|
interrupt void adc_int1_isr(void);
|
||||||
|
|
||||||
|
void adc_init(void)
|
||||||
|
{
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
EALLOW;
|
||||||
|
SysCtrlRegs.PCLKCR0.bit.ADCENCLK = 1;
|
||||||
|
|
||||||
|
// Sequence de mise sous tension du bloc analogique (f2802x_adc.c).
|
||||||
|
AdcRegs.ADCCTL1.bit.ADCBGPWD = 1;
|
||||||
|
AdcRegs.ADCCTL1.bit.ADCREFPWD = 1;
|
||||||
|
AdcRegs.ADCCTL1.bit.ADCPWDN = 1;
|
||||||
|
AdcRegs.ADCCTL1.bit.ADCENABLE = 1;
|
||||||
|
AdcRegs.ADCCTL1.bit.ADCREFSEL = 0; // reference interne, pleine echelle 3,3 V
|
||||||
|
EDIS;
|
||||||
|
|
||||||
|
DELAY_US(1000L); // stabilisation de la reference avant toute conversion
|
||||||
|
|
||||||
|
EALLOW;
|
||||||
|
// Impulsion d'interruption en fin de conversion, quand le resultat est
|
||||||
|
// effectivement dans ADCRESULTx (et non en debut de conversion).
|
||||||
|
AdcRegs.ADCCTL1.bit.INTPULSEPOS = 1;
|
||||||
|
|
||||||
|
// ADCCLK = SYSCLKOUT/2 = 30 MHz, et non-overlap : le mode overlap
|
||||||
|
// raccourcit la fenetre d'echantillonnage, inutile ici.
|
||||||
|
AdcRegs.ADCCTL2.bit.CLKDIV2EN = 1;
|
||||||
|
AdcRegs.ADCCTL2.bit.ADCNONOVERLAP = 1;
|
||||||
|
|
||||||
|
// Les ADCSOCxCTL sont des registres 16 bits contigus : on les parcourt
|
||||||
|
// par indexation directe de l'union.
|
||||||
|
for (i = 0U; i < (uint16_t)ADC_CH_COUNT; i++)
|
||||||
|
{
|
||||||
|
volatile union ADCSOCxCTL_REG *soc = (&AdcRegs.ADCSOC0CTL) + i;
|
||||||
|
|
||||||
|
soc->bit.CHSEL = k_chsel[i];
|
||||||
|
soc->bit.TRIGSEL = 5U; // ePWM1 SOCA
|
||||||
|
soc->bit.ACQPS = ADC_ACQPS_CYCLES; // fenetre S/H
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADCINT1 declenchee par la fin du dernier SOC de la sequence.
|
||||||
|
AdcRegs.INTSEL1N2.bit.INT1SEL = (uint16_t)ADC_CH_COUNT - 1U;
|
||||||
|
AdcRegs.INTSEL1N2.bit.INT1CONT = 0;
|
||||||
|
AdcRegs.INTSEL1N2.bit.INT1E = 1;
|
||||||
|
|
||||||
|
PieVectTable.ADCINT1 = &adc_int1_isr;
|
||||||
|
EDIS;
|
||||||
|
|
||||||
|
IER |= M_INT1;
|
||||||
|
PieCtrlRegs.PIEIER1.bit.INTx1 = 1; // ADCINT1
|
||||||
|
|
||||||
|
// Declenchement par ePWM1 : SOCA sur passage a zero du compteur.
|
||||||
|
// SOCAPRD = 3 -> une sequence tous les 3 cycles PWM. A 200 kHz cela fait
|
||||||
|
// ~66 kHz, largement suffisant pour la telemetrie et la surveillance,
|
||||||
|
// sans saturer le CPU d'interruptions.
|
||||||
|
EALLOW;
|
||||||
|
EPwm1Regs.ETSEL.bit.SOCASEL = 1; // ET_CTR_ZERO
|
||||||
|
EPwm1Regs.ETPS.bit.SOCAPRD = 3;
|
||||||
|
EPwm1Regs.ETSEL.bit.SOCAEN = 1;
|
||||||
|
EDIS;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t adc_get_raw(adc_channel_t ch)
|
||||||
|
{
|
||||||
|
if (ch >= ADC_CH_COUNT)
|
||||||
|
{
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
return s_raw[ch];
|
||||||
|
}
|
||||||
|
|
||||||
|
float adc_get_volts(adc_channel_t ch)
|
||||||
|
{
|
||||||
|
if (ch >= ADC_CH_COUNT)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
return (float)s_raw[ch] * ADC_VREF_V / 4096.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool adc_any_below(float volts)
|
||||||
|
{
|
||||||
|
uint16_t threshold = (uint16_t)(volts / ADC_VREF_V * 4096.0f + 0.5f);
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
for (i = 0U; i < (uint16_t)ADC_CH_COUNT; i++)
|
||||||
|
{
|
||||||
|
if (s_raw[i] < threshold)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t adc_get_sequence_count(void)
|
||||||
|
{
|
||||||
|
return s_seq_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ISR courte : rangement des valeurs brutes, rien d'autre. Aucune conversion
|
||||||
|
// physique, aucun flottant (PROMPT §7). La protection rapide ne passe PAS
|
||||||
|
// par l'ADC, c'est le comparateur qui s'en charge.
|
||||||
|
interrupt void adc_int1_isr(void)
|
||||||
|
{
|
||||||
|
// Les resultats sont dans un bloc distinct (AdcResult), a une autre
|
||||||
|
// adresse que les registres de controle : ADCRESULT0..15 contigus.
|
||||||
|
volatile uint16_t *res = &AdcResult.ADCRESULT0;
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
for (i = 0U; i < (uint16_t)ADC_CH_COUNT; i++)
|
||||||
|
{
|
||||||
|
s_raw[i] = res[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
s_seq_count++;
|
||||||
|
|
||||||
|
AdcRegs.ADCINTFLGCLR.bit.ADCINT1 = 1;
|
||||||
|
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
|
||||||
|
}
|
||||||
46
src/adc.h
Normal file
46
src/adc.h
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#ifndef ADC_H
|
||||||
|
#define ADC_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Ordre des voies = ordre des SOC0..SOC8. Brochage LQFP48 PT, cf. PROMPT §2.
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
ADC_CH_VIN = 0, // broche 10, ADCINA0 (partagee avec VREFHI)
|
||||||
|
ADC_CH_IIN, // broche 8, ADCINA1
|
||||||
|
ADC_CH_I1, // broche 9, ADCINA2 / COMP1A / AIO2
|
||||||
|
ADC_CH_IOUT, // broche 7, ADCINA3
|
||||||
|
ADC_CH_I2, // broche 5, ADCINA4 / COMP2A / AIO4
|
||||||
|
ADC_CH_VOUT, // broche 14, ADCINB2 / AIO10
|
||||||
|
ADC_CH_V1, // broche 16, ADCINB4 / AIO12
|
||||||
|
ADC_CH_T1, // broche 17, ADCINB6 / AIO14
|
||||||
|
ADC_CH_T2, // broche 18, ADCINB7
|
||||||
|
ADC_CH_COUNT
|
||||||
|
} adc_channel_t;
|
||||||
|
|
||||||
|
// Reference interne (ADCREFSEL = 0, pleine echelle 3,3 V) : imposee car
|
||||||
|
// VREFHI est partagee avec ADCINA0, utilisee pour VIN (PROMPT §2).
|
||||||
|
// Les SOC sont declenches par ePWM1, jamais en free-run, pour echantillonner
|
||||||
|
// a un instant stable loin des fronts de commutation (PROMPT §6 etape 4).
|
||||||
|
//
|
||||||
|
// A appeler APRES bsp_gpio_analog_init() (bascule des AIO en mode analogique)
|
||||||
|
// et APRES pwm_init() (c'est ePWM1 qui fournit le declenchement).
|
||||||
|
void adc_init(void);
|
||||||
|
|
||||||
|
// Derniere valeur brute (0..4095) rangee par l'ISR ADCINT1.
|
||||||
|
uint16_t adc_get_raw(adc_channel_t ch);
|
||||||
|
|
||||||
|
// Meme valeur convertie en volts a l'entree de la broche (0..3,3 V).
|
||||||
|
// Ce n'est PAS la grandeur physique : la conversion vers V/A/°C (ponts
|
||||||
|
// diviseurs, gain de shunt, NTC) sera le role de measure.c.
|
||||||
|
float adc_get_volts(adc_channel_t ch);
|
||||||
|
|
||||||
|
// true si au moins une voie est sous le seuil donne, exprime en volts.
|
||||||
|
bool adc_any_below(float volts);
|
||||||
|
|
||||||
|
// Compteur de conversions completes, pour verifier au debogueur que les SOC
|
||||||
|
// tournent bien.
|
||||||
|
uint32_t adc_get_sequence_count(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
105
src/bsp_gpio.c
105
src/bsp_gpio.c
@ -26,6 +26,111 @@ void bsp_gpio_analog_init(void)
|
|||||||
EDIS;
|
EDIS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void bsp_gpio_control_init(void)
|
||||||
|
{
|
||||||
|
EALLOW;
|
||||||
|
// Stage1-EN = GPIO16 (broche 27), Stage2-EN = GPIO17 (broche 26).
|
||||||
|
// GPIO16/17 ne sont pas des broches PWM : leurs pull-ups sont actives au
|
||||||
|
// reset, ce qui presente un 1 sur une entree de chaque porte ET avant que
|
||||||
|
// le firmware ne tourne. On les coupe des l'init (PROMPT §6 etape 3).
|
||||||
|
GpioCtrlRegs.GPAPUD.bit.GPIO16 = 1;
|
||||||
|
GpioCtrlRegs.GPAPUD.bit.GPIO17 = 1;
|
||||||
|
GpioCtrlRegs.GPAMUX2.bit.GPIO16 = 0;
|
||||||
|
GpioCtrlRegs.GPAMUX2.bit.GPIO17 = 0;
|
||||||
|
GpioCtrlRegs.GPADIR.bit.GPIO16 = 1;
|
||||||
|
GpioCtrlRegs.GPADIR.bit.GPIO17 = 1;
|
||||||
|
|
||||||
|
// HV_EN = GPIO32 (broche 31)
|
||||||
|
GpioCtrlRegs.GPBMUX1.bit.GPIO32 = 0;
|
||||||
|
GpioCtrlRegs.GPBDIR.bit.GPIO32 = 1;
|
||||||
|
EDIS;
|
||||||
|
|
||||||
|
// Etat sur avant toute autre configuration.
|
||||||
|
stage_enable_set(STAGE_1, false);
|
||||||
|
stage_enable_set(STAGE_2, false);
|
||||||
|
hv_enable_set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stage_enable_set(stage_id_t stage, bool enabled)
|
||||||
|
{
|
||||||
|
if (stage == STAGE_1)
|
||||||
|
{
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
GpioDataRegs.GPASET.bit.GPIO16 = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GpioDataRegs.GPACLEAR.bit.GPIO16 = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
GpioDataRegs.GPASET.bit.GPIO17 = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GpioDataRegs.GPACLEAR.bit.GPIO17 = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hv_enable_set(bool enabled)
|
||||||
|
{
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
GpioDataRegs.GPBSET.bit.GPIO32 = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GpioDataRegs.GPBCLEAR.bit.GPIO32 = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void bsp_gpio_stage_default_override_init(void)
|
||||||
|
{
|
||||||
|
EALLOW;
|
||||||
|
// GPIO1 = COMP1OUT (broche 28), GPIO3 = COMP2OUT (broche 38).
|
||||||
|
// Mux 0 = GPIO : on reprend la main sur des broches normalement pilotees
|
||||||
|
// par les comparateurs. Bring-up uniquement, voir bsp_gpio.h.
|
||||||
|
GpioCtrlRegs.GPAMUX1.bit.GPIO1 = 0;
|
||||||
|
GpioCtrlRegs.GPAMUX1.bit.GPIO3 = 0;
|
||||||
|
GpioCtrlRegs.GPADIR.bit.GPIO1 = 1;
|
||||||
|
GpioCtrlRegs.GPADIR.bit.GPIO3 = 1;
|
||||||
|
EDIS;
|
||||||
|
|
||||||
|
stage_default_set(STAGE_1, false);
|
||||||
|
stage_default_set(STAGE_2, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stage_default_set(stage_id_t stage, bool level)
|
||||||
|
{
|
||||||
|
if (stage == STAGE_1)
|
||||||
|
{
|
||||||
|
if (level)
|
||||||
|
{
|
||||||
|
GpioDataRegs.GPASET.bit.GPIO1 = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GpioDataRegs.GPACLEAR.bit.GPIO1 = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (level)
|
||||||
|
{
|
||||||
|
GpioDataRegs.GPASET.bit.GPIO3 = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GpioDataRegs.GPACLEAR.bit.GPIO3 = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void led_set(led_id_t led, bool on)
|
void led_set(led_id_t led, bool on)
|
||||||
{
|
{
|
||||||
#if LED_ACTIVE_LOW
|
#if LED_ACTIVE_LOW
|
||||||
|
|||||||
@ -9,6 +9,12 @@ typedef enum
|
|||||||
LED_RED
|
LED_RED
|
||||||
} led_id_t;
|
} led_id_t;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
STAGE_1 = 0,
|
||||||
|
STAGE_2
|
||||||
|
} stage_id_t;
|
||||||
|
|
||||||
void bsp_gpio_leds_init(void);
|
void bsp_gpio_leds_init(void);
|
||||||
void led_set(led_id_t led, bool on);
|
void led_set(led_id_t led, bool on);
|
||||||
|
|
||||||
@ -16,4 +22,32 @@ void led_set(led_id_t led, bool on);
|
|||||||
// COMP1A/COMP2A, VOUT, V1, NTC1). A appeler avant safety_init()/adc_init().
|
// COMP1A/COMP2A, VOUT, V1, NTC1). A appeler avant safety_init()/adc_init().
|
||||||
void bsp_gpio_analog_init(void);
|
void bsp_gpio_analog_init(void);
|
||||||
|
|
||||||
|
// Sorties de commande dans leur etat sur : HV_EN, Stage1-EN et Stage2-EN a 0.
|
||||||
|
// Desactive aussi les pull-ups internes de GPIO16/17, qui sinon presentent un
|
||||||
|
// niveau haut sur une entree de chaque porte ET des la mise sous tension
|
||||||
|
// (PROMPT §6 etape 3).
|
||||||
|
void bsp_gpio_control_init(void);
|
||||||
|
|
||||||
|
// Stagex-EN : entree "autorisation logicielle" de la porte ET de l'etage.
|
||||||
|
void stage_enable_set(stage_id_t stage, bool enabled);
|
||||||
|
|
||||||
|
// HV_EN (GPIO32) : commande de la sortie HT via l'optocoupleur VOM1271.
|
||||||
|
// Seul organe qui isole reellement la charge -- un boost a 0 % de duty ne
|
||||||
|
// donne PAS 0 V en sortie (PROMPT §6 etape 6).
|
||||||
|
void hv_enable_set(bool enabled);
|
||||||
|
|
||||||
|
// BRING-UP UNIQUEMENT -- bascule Stage1/2-default (GPIO1/GPIO3) en sortie
|
||||||
|
// GPIO pilotee par logiciel, pour verifier le cablage des portes ET sans
|
||||||
|
// etage de puissance actif. Laisse les deux broches a 0.
|
||||||
|
//
|
||||||
|
// INCOMPATIBLE avec safety_init(), qui mux ces memes broches vers COMP1OUT /
|
||||||
|
// COMP2OUT : en fonctionnement normal ce sont les comparateurs qui les
|
||||||
|
// pilotent, et le logiciel ne doit JAMAIS pouvoir outrepasser la protection
|
||||||
|
// materielle (PROMPT §2 et §8). Ne jamais appeler les deux.
|
||||||
|
void bsp_gpio_stage_default_override_init(void);
|
||||||
|
|
||||||
|
// Pilote une broche Stagex-default apres l'override ci-dessus. N'ecrit que
|
||||||
|
// GPASET/GPACLEAR (non proteges par EALLOW) : utilisable depuis une ISR.
|
||||||
|
void stage_default_set(stage_id_t stage, bool level);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
20
src/calib.h
20
src/calib.h
@ -24,6 +24,26 @@
|
|||||||
#define SAFETY_DAC_CODE \
|
#define SAFETY_DAC_CODE \
|
||||||
((uint16_t)((SAFETY_ISHUNT_THRESHOLD_A * 0.6f) / SAFETY_DAC_VREF_V * 1023.0f + 0.5f))
|
((uint16_t)((SAFETY_ISHUNT_THRESHOLD_A * 0.6f) / SAFETY_DAC_VREF_V * 1023.0f + 0.5f))
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// Fenetre d'echantillonnage : ACQPS + 1 cycles ADCCLK (30 MHz ici).
|
||||||
|
// 25 -> 26 cycles ~ 0,87 us, confortable meme avec une source de quelques
|
||||||
|
// kilo-ohms (ponts diviseurs, pull-down de test).
|
||||||
|
#define ADC_ACQPS_CYCLES 25
|
||||||
|
|
||||||
|
// Seuil du test de bring-up ADC : LED bleue si une voie passe SOUS cette
|
||||||
|
// valeur, rouge sinon. 300 mV = 372 LSB en pleine echelle 3,3 V.
|
||||||
|
#define ADC_TEST_THRESHOLD_V 0.3f
|
||||||
|
|
||||||
|
// Frequences de decoupage par etage (point ouvert §9.1 du PROMPT, tranche au
|
||||||
|
// bring-up). TBCLK = SYSCLKOUT = 60 MHz, TBPRD = SYSCLKOUT/Fpwm - 1 :
|
||||||
|
// etage 1 : 200 kHz -> TBPRD = 299
|
||||||
|
// etage 2 : 100 kHz -> TBPRD = 599
|
||||||
|
#define PWM_STAGE1_FREQ_HZ 200000UL
|
||||||
|
#define PWM_STAGE2_FREQ_HZ 100000UL
|
||||||
|
|
||||||
// Liaison UART SCI-A (voir docs/ESP32-UART.md) : 57600 8N1.
|
// Liaison UART SCI-A (voir docs/ESP32-UART.md) : 57600 8N1.
|
||||||
// LSPCLK = SYSCLKOUT/4 (LOSPCP laisse a sa valeur par defaut par InitSysCtrl).
|
// LSPCLK = SYSCLKOUT/4 (LOSPCP laisse a sa valeur par defaut par InitSysCtrl).
|
||||||
#define UART_BAUD_RATE 57600UL
|
#define UART_BAUD_RATE 57600UL
|
||||||
|
|||||||
99
src/main.c
99
src/main.c
@ -4,6 +4,9 @@
|
|||||||
#include "safety.h"
|
#include "safety.h"
|
||||||
#include "uart_link.h"
|
#include "uart_link.h"
|
||||||
#include "protocol.h"
|
#include "protocol.h"
|
||||||
|
#include "pwm.h"
|
||||||
|
#include "adc.h"
|
||||||
|
#include "calib.h"
|
||||||
|
|
||||||
extern uint16_t RamfuncsLoadStart;
|
extern uint16_t RamfuncsLoadStart;
|
||||||
extern uint16_t RamfuncsLoadSize;
|
extern uint16_t RamfuncsLoadSize;
|
||||||
@ -32,17 +35,46 @@ void main(void)
|
|||||||
InitPieVectTable();
|
InitPieVectTable();
|
||||||
|
|
||||||
bsp_gpio_analog_init();
|
bsp_gpio_analog_init();
|
||||||
// safety_init() desactive temporairement : bring-up isole sur LED+UART
|
bsp_gpio_control_init();
|
||||||
// uniquement (voir §8 du prompt -- sans risque ici, pwm.c n'existe pas
|
|
||||||
// encore, donc aucun PWM ne peut sortir avec ou sans TZ arme).
|
// Ordre d'init (PROMPT §7) : GPIO en etat sur -> AIOMUX1 -> horloge ->
|
||||||
|
// securite -> ADC -> UART -> PWM en dernier.
|
||||||
|
//
|
||||||
|
// pwm_init() est appele AVANT safety_init() pour une raison materielle :
|
||||||
|
// il active PCLKCR1.EPWMxENCLK, sans quoi les ecritures de safety_init()
|
||||||
|
// dans les registres ePWM (TZSEL, TZCTL, DCTRIPSEL) seraient perdues.
|
||||||
|
// L'esprit de la regle est respecte : pwm_init() laisse les deux sorties
|
||||||
|
// forcees a l'etat bas (AQCSFRC) et le duty a 0, donc rien ne sort tant
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// ADC : declenche par ePWM1, donc apres pwm_init().
|
||||||
|
adc_init();
|
||||||
uart_link_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);
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
EALLOW;
|
EALLOW;
|
||||||
PieVectTable.TINT0 = &cpu_timer0_isr;
|
PieVectTable.TINT0 = &cpu_timer0_isr;
|
||||||
EDIS;
|
EDIS;
|
||||||
|
|
||||||
InitCpuTimers();
|
InitCpuTimers();
|
||||||
ConfigCpuTimer(&CpuTimer0, 60, 300000); // 300 ms -> rythme d'envoi bring-up UART
|
ConfigCpuTimer(&CpuTimer0, 60, 10000); // base 10 ms (voir cpu_timer0_isr)
|
||||||
StartCpuTimer0(); // ConfigCpuTimer laisse TSS=1 (timer a l'arret) par conception
|
StartCpuTimer0(); // ConfigCpuTimer laisse TSS=1 (timer a l'arret) par conception
|
||||||
|
|
||||||
IER |= M_INT1;
|
IER |= M_INT1;
|
||||||
@ -54,30 +86,44 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test EMUSTOP : LED rouge = defaut latche, bleue = nominal.
|
||||||
|
// Le flag ne se rearme jamais seul (PROMPT §8).
|
||||||
|
faults = safety_get_fault_flags();
|
||||||
|
{
|
||||||
|
bool tripped = faults.stage1_fault || faults.stage2_fault;
|
||||||
|
led_set(LED_RED, tripped);
|
||||||
|
led_set(LED_BLUE, !tripped);
|
||||||
|
}
|
||||||
|
|
||||||
if (s_send_telemetry)
|
if (s_send_telemetry)
|
||||||
{
|
{
|
||||||
telemetry_t t;
|
telemetry_t t;
|
||||||
|
|
||||||
// Bring-up : valeurs figees, seul le lien TX/RX est valide ici.
|
// BRING-UP : chaque champ transporte la TENSION BRUTE de la
|
||||||
t.freq1_hz = 100000.0f;
|
// broche ADC correspondante, en volts (0..3,3) -- et non la
|
||||||
t.freq2_hz = 100000.0f;
|
// grandeur physique. Permet de verifier les 9 voies d'un coup
|
||||||
t.duty1_pct = 0.0f;
|
// sur l'IHM ESP32 apres ressoudure. measure.c fera la vraie
|
||||||
t.duty2_pct = 0.0f;
|
// conversion (ponts diviseurs, gain de shunt, NTC) a l'etape 5.
|
||||||
t.vin_v = 12.3f;
|
t.freq1_hz = (float)PWM_STAGE1_FREQ_HZ;
|
||||||
t.iin_a = 0.5f;
|
t.freq2_hz = (float)PWM_STAGE2_FREQ_HZ;
|
||||||
t.v1_v = 20.0f;
|
t.duty1_pct = 50.0f;
|
||||||
t.i1_a = 0.1f;
|
t.duty2_pct = 50.0f;
|
||||||
t.t1_c = 25.0f;
|
t.vin_v = adc_get_volts(ADC_CH_VIN);
|
||||||
t.vout_v = 0.0f;
|
t.iin_a = adc_get_volts(ADC_CH_IIN);
|
||||||
t.i2_a = 0.0f;
|
t.v1_v = adc_get_volts(ADC_CH_V1);
|
||||||
t.t2_c = 25.0f;
|
t.i1_a = adc_get_volts(ADC_CH_I1);
|
||||||
t.iout_a = 0.0f;
|
t.t1_c = adc_get_volts(ADC_CH_T1);
|
||||||
|
t.vout_v = adc_get_volts(ADC_CH_VOUT);
|
||||||
|
t.i2_a = adc_get_volts(ADC_CH_I2);
|
||||||
|
t.t2_c = adc_get_volts(ADC_CH_T2);
|
||||||
|
t.iout_a = adc_get_volts(ADC_CH_IOUT);
|
||||||
|
|
||||||
uart_link_send_telemetry(&t);
|
uart_link_send_telemetry(&t);
|
||||||
s_send_telemetry = false;
|
s_send_telemetry = false;
|
||||||
@ -89,15 +135,24 @@ void main(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Base de temps 10 ms. Le clignotement des signaux d'etage a ete retire :
|
||||||
|
// GPIO1/GPIO3 (Stagex-default) appartiennent maintenant aux comparateurs, et
|
||||||
|
// Stagex-EN est fige a 1 pour rendre le declenchement observable sur les
|
||||||
|
// sorties des portes ET. HV_EN reste a 0.
|
||||||
|
//
|
||||||
|
// Les LED n'affichent plus l'etat de l'ADC mais celui des defauts, pilotees
|
||||||
|
// depuis la boucle principale.
|
||||||
interrupt void cpu_timer0_isr(void)
|
interrupt void cpu_timer0_isr(void)
|
||||||
{
|
{
|
||||||
static bool blue_on = false;
|
static uint16_t tick = 0;
|
||||||
|
|
||||||
CpuTimer0.InterruptCount++;
|
CpuTimer0.InterruptCount++;
|
||||||
s_send_telemetry = true;
|
tick++;
|
||||||
|
|
||||||
blue_on = !blue_on;
|
if ((tick % 30U) == 0U)
|
||||||
led_set(LED_BLUE, blue_on);
|
{
|
||||||
|
s_send_telemetry = true; // telemetrie toutes les 300 ms
|
||||||
|
}
|
||||||
|
|
||||||
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
|
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
|
||||||
}
|
}
|
||||||
|
|||||||
149
src/pwm.c
Normal file
149
src/pwm.c
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
#include "DSP28x_Project.h"
|
||||||
|
#include "pwm.h"
|
||||||
|
#include "calib.h"
|
||||||
|
|
||||||
|
// Mode up-count, TBCLK = SYSCLKOUT = 60 MHz (HSPCLKDIV = CLKDIV = /1).
|
||||||
|
// TBPRD = SYSCLKOUT / Fpwm - 1 -> 299 a 200 kHz, 599 a 100 kHz.
|
||||||
|
//
|
||||||
|
// HRPWM : volontairement NON implemente a ce stade (PROMPT §6 etape 6), mais
|
||||||
|
// le routage et la structure s'y pretent. Notes pour l'ajout ulterieur :
|
||||||
|
// - HRPWM n'existe que sur les sorties EPWMxA : les deux etages sont donc
|
||||||
|
// compatibles tels que routes (GPIO0 et GPIO2).
|
||||||
|
// - SYSCLKOUT >= 50 MHz requis (60 MHz ici), MEP 150-310 ps, SFO obligatoire.
|
||||||
|
// - Limitation de 3 cycles SYSCLK sur le rapport cyclique : a 200 kHz cela
|
||||||
|
// donne un duty min ~0,67 % et max ~99 %, a confronter aux points de
|
||||||
|
// fonctionnement de l'etage 1 (D ~ 0,8).
|
||||||
|
// - A 100 kHz sans HRPWM : 600 pas ~ 9,2 bits, risque de limit cycling en
|
||||||
|
// boucle fermee. C'est l'etage 2 (D ~ 0,875) qui est le plus concerne.
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t freq_hz;
|
||||||
|
float duty;
|
||||||
|
bool enabled;
|
||||||
|
} pwm_stage_t;
|
||||||
|
|
||||||
|
static pwm_stage_t s_stage[2];
|
||||||
|
|
||||||
|
static volatile struct EPWM_REGS *pwm_regs(stage_id_t stage)
|
||||||
|
{
|
||||||
|
return (stage == STAGE_1) ? &EPwm1Regs : &EPwm2Regs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t stage_index(stage_id_t stage)
|
||||||
|
{
|
||||||
|
return (stage == STAGE_1) ? 0U : 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recalcule CMPA a partir du duty et du TBPRD courants.
|
||||||
|
static void pwm_apply_duty(stage_id_t stage)
|
||||||
|
{
|
||||||
|
volatile struct EPWM_REGS *p = pwm_regs(stage);
|
||||||
|
float duty = s_stage[stage_index(stage)].duty;
|
||||||
|
uint16_t prd = p->TBPRD;
|
||||||
|
|
||||||
|
// En up-count avec AQ_SET a zero et AQ_CLEAR sur CMPA, la sortie est
|
||||||
|
// haute pendant CMPA cycles : duty = CMPA / (TBPRD + 1).
|
||||||
|
p->CMPA.half.CMPA = (uint16_t)(duty * (float)(prd + 1U) + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pwm_init(void)
|
||||||
|
{
|
||||||
|
EALLOW;
|
||||||
|
SysCtrlRegs.PCLKCR1.bit.EPWM1ENCLK = 1;
|
||||||
|
SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 1;
|
||||||
|
|
||||||
|
// Les bases de temps sont figees pendant la configuration puis relachees
|
||||||
|
// ensemble, pour que les deux etages demarrent leur compteur en phase.
|
||||||
|
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0;
|
||||||
|
|
||||||
|
GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 1; // EPWM1A
|
||||||
|
GpioCtrlRegs.GPAMUX1.bit.GPIO2 = 1; // EPWM2A
|
||||||
|
EDIS;
|
||||||
|
|
||||||
|
s_stage[0].freq_hz = PWM_STAGE1_FREQ_HZ;
|
||||||
|
s_stage[1].freq_hz = PWM_STAGE2_FREQ_HZ;
|
||||||
|
|
||||||
|
{
|
||||||
|
stage_id_t stages[2] = {STAGE_1, STAGE_2};
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
for (i = 0U; i < 2U; i++)
|
||||||
|
{
|
||||||
|
stage_id_t s = stages[i];
|
||||||
|
volatile struct EPWM_REGS *p = pwm_regs(s);
|
||||||
|
|
||||||
|
p->TBCTL.bit.CTRMODE = TB_COUNT_UP;
|
||||||
|
p->TBCTL.bit.PHSEN = TB_DISABLE;
|
||||||
|
p->TBCTL.bit.PRDLD = TB_SHADOW;
|
||||||
|
p->TBCTL.bit.HSPCLKDIV = TB_DIV1;
|
||||||
|
p->TBCTL.bit.CLKDIV = TB_DIV1;
|
||||||
|
// FREE_SOFT = 0 : le compteur gele quand le CPU s'arrete. Ne
|
||||||
|
// suffit PAS a mettre la sortie a l'etat bas (l'Action Qualifier
|
||||||
|
// a deja positionne la broche, qui reste figee) -- c'est TZ6 =
|
||||||
|
// EMUSTOP dans safety.c qui garantit la mise a zero.
|
||||||
|
p->TBCTL.bit.FREE_SOFT = 0;
|
||||||
|
|
||||||
|
p->TBCTR = 0;
|
||||||
|
p->TBPHS.half.TBPHS = 0;
|
||||||
|
|
||||||
|
p->CMPCTL.bit.SHDWAMODE = CC_SHADOW;
|
||||||
|
p->CMPCTL.bit.LOADAMODE = CC_CTR_ZERO;
|
||||||
|
|
||||||
|
// Haut a zero, bas sur CMPA -> impulsion en debut de periode.
|
||||||
|
p->AQCTLA.bit.ZRO = AQ_SET;
|
||||||
|
p->AQCTLA.bit.CAU = AQ_CLEAR;
|
||||||
|
|
||||||
|
s_stage[i].duty = 0.0f;
|
||||||
|
s_stage[i].enabled = false;
|
||||||
|
|
||||||
|
pwm_set_freq(s, s_stage[i].freq_hz);
|
||||||
|
pwm_enable(s, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EALLOW;
|
||||||
|
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1;
|
||||||
|
EDIS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pwm_set_freq(stage_id_t stage, uint32_t hz)
|
||||||
|
{
|
||||||
|
volatile struct EPWM_REGS *p = pwm_regs(stage);
|
||||||
|
|
||||||
|
if (hz == 0UL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_stage[stage_index(stage)].freq_hz = hz;
|
||||||
|
p->TBPRD = (uint16_t)((CLK_SYSCLKOUT_HZ / hz) - 1UL);
|
||||||
|
|
||||||
|
// TBPRD change -> CMPA doit suivre pour conserver le meme duty.
|
||||||
|
pwm_apply_duty(stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pwm_set_duty(stage_id_t stage, float duty_0_1)
|
||||||
|
{
|
||||||
|
if (duty_0_1 < 0.0f)
|
||||||
|
{
|
||||||
|
duty_0_1 = 0.0f;
|
||||||
|
}
|
||||||
|
else if (duty_0_1 > 1.0f)
|
||||||
|
{
|
||||||
|
duty_0_1 = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_stage[stage_index(stage)].duty = duty_0_1;
|
||||||
|
pwm_apply_duty(stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pwm_enable(stage_id_t stage, bool enabled)
|
||||||
|
{
|
||||||
|
volatile struct EPWM_REGS *p = pwm_regs(stage);
|
||||||
|
|
||||||
|
s_stage[stage_index(stage)].enabled = enabled;
|
||||||
|
|
||||||
|
// CSFA : 1 = forcage continu a l'etat bas, 0 = action normale de l'AQ.
|
||||||
|
p->AQCSFRC.bit.CSFA = enabled ? 0U : 1U;
|
||||||
|
}
|
||||||
28
src/pwm.h
Normal file
28
src/pwm.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#ifndef PWM_H
|
||||||
|
#define PWM_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "bsp_gpio.h" // stage_id_t
|
||||||
|
|
||||||
|
// ePWM1 -> etage 1 (EPWM1A, GPIO0, broche 29)
|
||||||
|
// ePWM2 -> etage 2 (EPWM2A, GPIO2, broche 37)
|
||||||
|
//
|
||||||
|
// Initialise les deux etages a leur frequence nominale (calib.h), duty a 0
|
||||||
|
// et sorties inhibees. PROMPT §6 etape 6 : ne jamais demarrer au duty
|
||||||
|
// nominal, le condensateur de sortie vide provoquerait un appel de courant
|
||||||
|
// destructeur. La rampe de demarrage (soft-start) est du ressort de
|
||||||
|
// l'appelant, pas de ce module.
|
||||||
|
void pwm_init(void);
|
||||||
|
|
||||||
|
void pwm_set_freq(stage_id_t stage, uint32_t hz);
|
||||||
|
|
||||||
|
// duty exprime en 0..1. Sature hors bornes.
|
||||||
|
void pwm_set_duty(stage_id_t stage, float duty_0_1);
|
||||||
|
|
||||||
|
// Inhibition logicielle par forcage continu de la sortie a l'etat bas
|
||||||
|
// (AQCSFRC). N'a rien a voir avec la protection materielle : le Trip Zone
|
||||||
|
// reste le seul mecanisme de securite (PROMPT §8).
|
||||||
|
void pwm_enable(stage_id_t stage, bool enabled);
|
||||||
|
|
||||||
|
#endif
|
||||||
24
src/safety.c
24
src/safety.c
@ -73,6 +73,30 @@ void safety_init(void)
|
|||||||
PieCtrlRegs.PIEIER2.bit.INTx2 = 1; // EPWM2_TZINT
|
PieCtrlRegs.PIEIER2.bit.INTx2 = 1; // EPWM2_TZINT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void safety_arm_emustop_only(void)
|
||||||
|
{
|
||||||
|
EALLOW;
|
||||||
|
// TZ6 = EMUSTOP, cable en dur depuis le CPU (TRM SPRUI09A section 3.2.7).
|
||||||
|
// One-shot : le flag latche, il ne se rearme pas seul.
|
||||||
|
EPwm1Regs.TZSEL.bit.OSHT6 = 1;
|
||||||
|
EPwm1Regs.TZCTL.bit.TZA = TZ_FORCE_LO;
|
||||||
|
EPwm1Regs.TZEINT.bit.OST = 1;
|
||||||
|
EPwm1Regs.TBCTL.bit.FREE_SOFT = 0;
|
||||||
|
|
||||||
|
EPwm2Regs.TZSEL.bit.OSHT6 = 1;
|
||||||
|
EPwm2Regs.TZCTL.bit.TZA = TZ_FORCE_LO;
|
||||||
|
EPwm2Regs.TZEINT.bit.OST = 1;
|
||||||
|
EPwm2Regs.TBCTL.bit.FREE_SOFT = 0;
|
||||||
|
|
||||||
|
PieVectTable.EPWM1_TZINT = &epwm1_tzint_isr;
|
||||||
|
PieVectTable.EPWM2_TZINT = &epwm2_tzint_isr;
|
||||||
|
EDIS;
|
||||||
|
|
||||||
|
IER |= M_INT2;
|
||||||
|
PieCtrlRegs.PIEIER2.bit.INTx1 = 1; // EPWM1_TZINT
|
||||||
|
PieCtrlRegs.PIEIER2.bit.INTx2 = 1; // EPWM2_TZINT
|
||||||
|
}
|
||||||
|
|
||||||
safety_faults_t safety_get_fault_flags(void)
|
safety_faults_t safety_get_fault_flags(void)
|
||||||
{
|
{
|
||||||
safety_faults_t f;
|
safety_faults_t f;
|
||||||
|
|||||||
10
src/safety.h
10
src/safety.h
@ -10,6 +10,16 @@ typedef struct
|
|||||||
} safety_faults_t;
|
} safety_faults_t;
|
||||||
|
|
||||||
void safety_init(void);
|
void safety_init(void);
|
||||||
|
|
||||||
|
// BRING-UP UNIQUEMENT -- arme seulement TZ6 (EMUSTOP) et l'interruption de
|
||||||
|
// diagnostic, SANS les comparateurs ni le Digital Compare. Permet de valider
|
||||||
|
// isolement la coupure du PWM a l'arret du debugger, tant que les entrees
|
||||||
|
// shunt ne sont pas cablees : avec safety_init() complet, elles flottent
|
||||||
|
// au-dessus du seuil et le trip comparateur masquerait le test.
|
||||||
|
//
|
||||||
|
// safety_init() arme deja TZ6 : ces deux fonctions sont exclusives.
|
||||||
|
void safety_arm_emustop_only(void);
|
||||||
|
|
||||||
safety_faults_t safety_get_fault_flags(void);
|
safety_faults_t safety_get_fault_flags(void);
|
||||||
|
|
||||||
// Ecrit TZCLR[OST] sur les deux etages. Ne doit JAMAIS etre appele
|
// Ecrit TZCLR[OST] sur les deux etages. Ne doit JAMAIS etre appele
|
||||||
|
|||||||
Reference in New Issue
Block a user