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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
#if LED_ACTIVE_LOW
|
||||
|
||||
@ -9,6 +9,12 @@ typedef enum
|
||||
LED_RED
|
||||
} led_id_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
STAGE_1 = 0,
|
||||
STAGE_2
|
||||
} stage_id_t;
|
||||
|
||||
void bsp_gpio_leds_init(void);
|
||||
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().
|
||||
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
|
||||
|
||||
20
src/calib.h
20
src/calib.h
@ -24,6 +24,26 @@
|
||||
#define SAFETY_DAC_CODE \
|
||||
((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.
|
||||
// LSPCLK = SYSCLKOUT/4 (LOSPCP laisse a sa valeur par defaut par InitSysCtrl).
|
||||
#define UART_BAUD_RATE 57600UL
|
||||
|
||||
99
src/main.c
99
src/main.c
@ -4,6 +4,9 @@
|
||||
#include "safety.h"
|
||||
#include "uart_link.h"
|
||||
#include "protocol.h"
|
||||
#include "pwm.h"
|
||||
#include "adc.h"
|
||||
#include "calib.h"
|
||||
|
||||
extern uint16_t RamfuncsLoadStart;
|
||||
extern uint16_t RamfuncsLoadSize;
|
||||
@ -32,17 +35,46 @@ void main(void)
|
||||
InitPieVectTable();
|
||||
|
||||
bsp_gpio_analog_init();
|
||||
// safety_init() desactive temporairement : bring-up isole sur LED+UART
|
||||
// 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).
|
||||
bsp_gpio_control_init();
|
||||
|
||||
// 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();
|
||||
|
||||
// ---- 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;
|
||||
PieVectTable.TINT0 = &cpu_timer0_isr;
|
||||
EDIS;
|
||||
|
||||
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
|
||||
|
||||
IER |= M_INT1;
|
||||
@ -54,30 +86,44 @@ void main(void)
|
||||
for (;;)
|
||||
{
|
||||
command_state_t cmd;
|
||||
safety_faults_t faults;
|
||||
|
||||
if (uart_link_poll(&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)
|
||||
{
|
||||
telemetry_t t;
|
||||
|
||||
// Bring-up : valeurs figees, seul le lien TX/RX est valide ici.
|
||||
t.freq1_hz = 100000.0f;
|
||||
t.freq2_hz = 100000.0f;
|
||||
t.duty1_pct = 0.0f;
|
||||
t.duty2_pct = 0.0f;
|
||||
t.vin_v = 12.3f;
|
||||
t.iin_a = 0.5f;
|
||||
t.v1_v = 20.0f;
|
||||
t.i1_a = 0.1f;
|
||||
t.t1_c = 25.0f;
|
||||
t.vout_v = 0.0f;
|
||||
t.i2_a = 0.0f;
|
||||
t.t2_c = 25.0f;
|
||||
t.iout_a = 0.0f;
|
||||
// BRING-UP : chaque champ transporte la TENSION BRUTE de la
|
||||
// broche ADC correspondante, en volts (0..3,3) -- et non la
|
||||
// grandeur physique. Permet de verifier les 9 voies d'un coup
|
||||
// sur l'IHM ESP32 apres ressoudure. measure.c fera la vraie
|
||||
// conversion (ponts diviseurs, gain de shunt, NTC) a l'etape 5.
|
||||
t.freq1_hz = (float)PWM_STAGE1_FREQ_HZ;
|
||||
t.freq2_hz = (float)PWM_STAGE2_FREQ_HZ;
|
||||
t.duty1_pct = 50.0f;
|
||||
t.duty2_pct = 50.0f;
|
||||
t.vin_v = adc_get_volts(ADC_CH_VIN);
|
||||
t.iin_a = adc_get_volts(ADC_CH_IIN);
|
||||
t.v1_v = adc_get_volts(ADC_CH_V1);
|
||||
t.i1_a = adc_get_volts(ADC_CH_I1);
|
||||
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);
|
||||
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)
|
||||
{
|
||||
static bool blue_on = false;
|
||||
static uint16_t tick = 0;
|
||||
|
||||
CpuTimer0.InterruptCount++;
|
||||
s_send_telemetry = true;
|
||||
tick++;
|
||||
|
||||
blue_on = !blue_on;
|
||||
led_set(LED_BLUE, blue_on);
|
||||
if ((tick % 30U) == 0U)
|
||||
{
|
||||
s_send_telemetry = true; // telemetrie toutes les 300 ms
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 f;
|
||||
|
||||
10
src/safety.h
10
src/safety.h
@ -10,6 +10,16 @@ typedef struct
|
||||
} safety_faults_t;
|
||||
|
||||
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);
|
||||
|
||||
// Ecrit TZCLR[OST] sur les deux etages. Ne doit JAMAIS etre appele
|
||||
|
||||
Reference in New Issue
Block a user