diff --git a/.cproject b/.cproject index 989af13..d484673 100644 --- a/.cproject +++ b/.cproject @@ -22,8 +22,8 @@ - - diff --git a/src/adc.c b/src/adc.c index 23b977b..43a6f0b 100644 --- a/src/adc.c +++ b/src/adc.c @@ -1,5 +1,6 @@ #include "DSP28x_Project.h" #include "adc.h" +#include "control.h" #include "calib.h" // Valeurs du champ CHSEL (TRM SPRUI09A) : groupe A = 0x0..0x7, @@ -19,6 +20,13 @@ static const uint16_t k_chsel[ADC_CH_COUNT] = { static volatile uint16_t s_raw[ADC_CH_COUNT]; static volatile uint32_t s_seq_count = 0; +// Note de mesure (instrumentation retiree) : la duree de cette ISR a ete +// mesuree au scope en basculant une broche a l'entree et a la sortie. +// Resultat apres correction des etats d'attente de la flash : 4,9 us pour +// une periode de 15 us, soit 33 % de charge CPU a 66,85 kHz. +// Ne PAS refaire cette mesure sur GPIO32 : c'est HV_EN, la commande de +// l'optocoupleur VOM1271. Utiliser une broche libre (GPIO4, 6 ou 7). + interrupt void adc_int1_isr(void); void adc_init(void) @@ -136,6 +144,11 @@ interrupt void adc_int1_isr(void) s_seq_count++; + // Point d'appel cadence de la commande (PROMPT §6 etape 8 : dt constant, + // dans l'ISR ADC ou PWM). Une sequence ADC complete = une decision. + // Doit rester tres court : ni flottant, ni division, ni attente. + control_tick(); + AdcRegs.ADCINTFLGCLR.bit.ADCINT1 = 1; PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; } diff --git a/src/bsp_clock.c b/src/bsp_clock.c index e2b5c71..6fe5252 100644 --- a/src/bsp_clock.c +++ b/src/bsp_clock.c @@ -9,4 +9,20 @@ void bsp_clock_init(void) EDIS; InitSysCtrl(); + +#ifdef _FLASH + // Etats d'attente de la flash. Au reset ils valent leur MAXIMUM + // (FBANKWAIT = 0x0F0F, soit RANDWAIT = PAGEWAIT = 15) : chaque acces + // coute alors 16 cycles au lieu des 3 necessaires a 60 MHz, et TOUT le + // code resident en flash tourne environ 4 fois trop lentement. + // + // Mesure avant/apres sur l'ISR ADC : c'etait le facteur dominant, bien + // devant l'optimisation du compilateur. + // + // InitFlash() DOIT s'executer depuis la RAM (elle reconfigure la flash + // sous ses propres pieds) : c'est tout l'objet du memcpy des ramfuncs + // fait en tete de main(), qui existait deja mais ne servait a rien + // puisque la fonction n'etait jamais appelee. + InitFlash(); +#endif } diff --git a/src/calib.h b/src/calib.h index 9ae0c12..f69a6a1 100644 --- a/src/calib.h +++ b/src/calib.h @@ -117,6 +117,13 @@ // valeur, rouge sinon. 300 mV = 372 LSB en pleine echelle 3,3 V. #define ADC_TEST_THRESHOLD_V 0.3f +// ---- Balayage de caracterisation (BRING-UP, boucle ouverte) --------- +// Rampe montante puis descendante entre ces deux bornes, par pas de 1 LSB +// de CMPA a chaque conversion ADC. Sert a mesurer la vitesse de correction +// atteignable, pas a reguler. A supprimer avec le reste du harnais. +#define CONTROL_SWEEP_MIN_PCT 1.0f +#define CONTROL_SWEEP_MAX_PCT 50.0f + // 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 diff --git a/src/control.c b/src/control.c new file mode 100644 index 0000000..1509232 --- /dev/null +++ b/src/control.c @@ -0,0 +1,121 @@ +#include "control.h" +#include "pwm.h" +#include "bsp_gpio.h" +#include "calib.h" + +// Bornes du balayage, en counts, calculees une fois a l'init depuis la +// periode reelle de chaque etage. Elles different d'un etage a l'autre : +// 300 counts a 200 kHz contre 600 a 100 kHz. +static uint16_t s_min_counts[2]; +static uint16_t s_max_counts[2]; +static uint16_t s_counts[2]; +static bool s_rising[2]; +static volatile bool s_armed = false; + +// Compteur d'allers-retours complets, lisible au debogueur : permet de +// verifier que le balayage tourne sans avoir a sonder les broches. +static volatile uint32_t s_sweep_cycles = 0; + +static uint16_t pct_to_counts(uint16_t period, float pct) +{ + uint16_t c = (uint16_t)((pct / 100.0f) * (float)period + 0.5f); + + // Au moins 1 LSB : un duty de 0 ne serait pas un balayage mais un arret. + if (c == 0U) + { + c = 1U; + } + if (c > period) + { + c = period; + } + return c; +} + +void control_init(void) +{ + uint16_t i; + stage_id_t stages[2] = {STAGE_1, STAGE_2}; + + for (i = 0U; i < 2U; i++) + { + uint16_t period = pwm_get_period_counts(stages[i]); + + s_min_counts[i] = pct_to_counts(period, CONTROL_SWEEP_MIN_PCT); + s_max_counts[i] = pct_to_counts(period, CONTROL_SWEEP_MAX_PCT); + s_counts[i] = s_min_counts[i]; + s_rising[i] = true; + } + + s_armed = false; + s_sweep_cycles = 0; +} + +void control_start(void) +{ + uint16_t i; + stage_id_t stages[2] = {STAGE_1, STAGE_2}; + + // Les deux etages repartent ensemble du minimum. + for (i = 0U; i < 2U; i++) + { + s_counts[i] = s_min_counts[i]; + s_rising[i] = true; + pwm_set_duty_counts(stages[i], s_counts[i]); + } + + s_armed = true; +} + +void control_stop(void) +{ + s_armed = false; +} + +// Appelee en ISR : que des comparaisons, incrementations et decrementations. +// Le tableau des identifiants est en static const, pas en local : une +// initialisation locale serait recopiee sur la pile a chaque appel, soit +// plusieurs milliers de fois par seconde pour rien. +static const stage_id_t k_stages[2] = {STAGE_1, STAGE_2}; + +void control_tick(void) +{ + uint16_t i; + + if (!s_armed) + { + return; + } + + for (i = 0U; i < 2U; i++) + { + if (s_rising[i]) + { + if (s_counts[i] < s_max_counts[i]) + { + s_counts[i]++; + } + else + { + s_rising[i] = false; + } + } + else + { + if (s_counts[i] > s_min_counts[i]) + { + s_counts[i]--; + } + else + { + s_rising[i] = true; + if (i == 0U) + { + s_sweep_cycles++; // compte les allers-retours de l'etage 1 + } + } + } + + pwm_set_duty_counts(k_stages[i], s_counts[i]); + } +} diff --git a/src/control.h b/src/control.h new file mode 100644 index 0000000..ce64272 --- /dev/null +++ b/src/control.h @@ -0,0 +1,32 @@ +#ifndef CONTROL_H +#define CONTROL_H + +#include +#include + +// Emplacement de la future regulation (PROMPT §6 etape 8). Pour l'instant +// ce module ne contient PAS de regulation : il execute un balayage de duty +// en boucle OUVERTE, destine a caracteriser la vitesse de correction +// atteignable. Aucune mesure n'influence encore le duty. +// +// Le balayage va de CONTROL_SWEEP_MIN_PCT a CONTROL_SWEEP_MAX_PCT puis +// revient, par pas de 1 LSB de CMPA a chaque conversion ADC -- c'est-a-dire +// le plus petit increment realisable, et la cadence la plus rapide dont on +// dispose sans ajouter d'interruption. + +void control_init(void); + +// Arme le balayage et repositionne les deux etages au duty minimal. +void control_start(void); + +// Fige le balayage. Le duty n'est pas modifie : c'est pwm_enable() et le +// Trip Zone qui coupent reellement les sorties. +void control_stop(void); + +// A appeler a CADENCE FIXE depuis l'ISR ADC (PROMPT §6 etape 8 : "prevoir +// l'emplacement d'appel cadence, dt constant"). Ne fait rien si le balayage +// n'est pas arme. Sans flottant ni division : un increment et une ecriture +// de registre par etage. +void control_tick(void); + +#endif diff --git a/src/main.c b/src/main.c index 7e82c68..f503120 100644 --- a/src/main.c +++ b/src/main.c @@ -8,6 +8,7 @@ #include "adc.h" #include "measure.h" #include "status_led.h" +#include "control.h" #include "calib.h" extern uint16_t RamfuncsLoadStart; @@ -71,13 +72,9 @@ static void start_conversion(void) 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); - // --------------------------------------------------------------------- + // Le duty est desormais pilote par control.c, qui repart du minimum. + // Les deux etages demarrent ensemble. + control_start(); } // Priorite 2 > 3 > 4 > 5 > 1 : EMUSTOP est le code le moins prioritaire @@ -115,6 +112,7 @@ static fault_code_t compute_fault_code(const safety_faults_t *f) // reellement la charge (PROMPT §6 etape 6). static void enter_safe_state(void) { + control_stop(); pwm_enable(STAGE_1, false); pwm_enable(STAGE_2, false); stage_enable_set(STAGE_1, false); @@ -187,6 +185,10 @@ void main(void) // comparateur ne declenchera donc pas spontanement. safety_init(); + // control_init() lit les periodes ePWM, donc apres pwm_init(), et avant + // adc_init() dont l'ISR appellera control_tick(). + control_init(); + // ADC : declenche par ePWM1, donc apres pwm_init(). adc_init(); uart_link_init(); @@ -273,8 +275,11 @@ void main(void) // ci-dessus, pas encore une mesure. 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; + // Duty reellement en sortie, relu depuis CMPA. Pendant le + // balayage la valeur change bien plus vite que la cadence de + // telemetrie : c'est un echantillon, pas un suivi. + t.duty1_pct = pwm_get_duty(STAGE_1) * 100.0f; + t.duty2_pct = pwm_get_duty(STAGE_2) * 100.0f; t.vin_v = measure_vin(adc_get_raw(ADC_CH_VIN)); t.iin_a = measure_iin(adc_get_raw(ADC_CH_IIN)); t.v1_v = measure_v1(adc_get_raw(ADC_CH_V1)); @@ -303,12 +308,16 @@ void main(void) // // Les LED n'affichent plus l'etat de l'ADC mais celui des defauts, pilotees // depuis la boucle principale. +// Aucune multiplication ni division ici : le rythme de la telemetrie est +// obtenu par un compteur qui reboucle sur comparaison, pas par un modulo +// (division logicielle sur C28x). +#define TELEMETRY_PERIOD_TICKS 30U // 300 ms + interrupt void cpu_timer0_isr(void) { - static uint16_t tick = 0; + static uint16_t telemetry_tick = 0; CpuTimer0.InterruptCount++; - tick++; status_led_tick(); @@ -317,9 +326,11 @@ interrupt void cpu_timer0_isr(void) s_ticks_since_cmd++; // sature au seuil, pas de rebouclage } - if ((tick % 30U) == 0U) + telemetry_tick++; + if (telemetry_tick >= TELEMETRY_PERIOD_TICKS) { - s_send_telemetry = true; // telemetrie toutes les 300 ms + telemetry_tick = 0U; + s_send_telemetry = true; } PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; diff --git a/src/pwm.c b/src/pwm.c index 8f89e7f..51ee21a 100644 --- a/src/pwm.c +++ b/src/pwm.c @@ -138,6 +138,42 @@ void pwm_set_duty(stage_id_t stage, float duty_0_1) pwm_apply_duty(stage); } +float pwm_get_duty(stage_id_t stage) +{ + volatile struct EPWM_REGS *p = pwm_regs(stage); + uint16_t period = (uint16_t)(p->TBPRD + 1U); + + if (period == 0U) + { + return 0.0f; + } + return (float)p->CMPA.half.CMPA / (float)period; +} + +uint16_t pwm_get_period_counts(stage_id_t stage) +{ + return (uint16_t)(pwm_regs(stage)->TBPRD + 1U); +} + +void pwm_set_duty_counts(stage_id_t stage, uint16_t counts) +{ + volatile struct EPWM_REGS *p = pwm_regs(stage); + uint16_t period = (uint16_t)(p->TBPRD + 1U); + + if (counts > period) + { + counts = period; + } + + p->CMPA.half.CMPA = counts; + + // PAS de mise a jour de la consigne flottante ici : cette fonction est + // appelee depuis l'ISR ADC a plusieurs dizaines de kHz, et le F28027 + // n'a pas d'unite flottante -- une division y coutait plusieurs + // centaines de cycles et effondrait la cadence de l'ISR. + // pwm_get_duty() relit CMPA, la telemetrie reste donc juste. +} + void pwm_enable(stage_id_t stage, bool enabled) { volatile struct EPWM_REGS *p = pwm_regs(stage); diff --git a/src/pwm.h b/src/pwm.h index 6cd583b..7f46358 100644 --- a/src/pwm.h +++ b/src/pwm.h @@ -20,6 +20,18 @@ 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); +// Duty courant (0..1), reconstruit depuis CMPA : reflete donc ce qui sort +// reellement, y compris apres un reglage en counts. +float pwm_get_duty(stage_id_t stage); + +// ---- Reglage en pas de compteur -------------------------------------- +// Le pas minimal realisable est 1 LSB de CMPA, et il DEPEND DE L'ETAGE : +// 1/300 = 0,333 % a 200 kHz, 1/600 = 0,167 % a 100 kHz. Une consigne en +// flottant ne permet pas d'exprimer "le plus petit pas possible" ; ces +// deux fonctions si. +uint16_t pwm_get_period_counts(stage_id_t stage); // TBPRD + 1 +void pwm_set_duty_counts(stage_id_t stage, uint16_t counts); + // 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). diff --git a/src/status_led.c b/src/status_led.c index 15abb19..d1f385b 100644 --- a/src/status_led.c +++ b/src/status_led.c @@ -50,8 +50,14 @@ void status_led_set_state(led_state_t state) } } +// Appelee en ISR : aucune multiplication ni division. Le compteur de phase +// reboucle par COMPARAISON, jamais par modulo -- sur C28x un "%" par une +// valeur qui n'est pas une puissance de deux est une division logicielle, +// donc plusieurs dizaines de cycles. void status_led_tick(void) { + uint16_t period; + uint16_t on_time; bool blue = false; bool red = false; @@ -60,25 +66,55 @@ void status_led_tick(void) s_startup_hold--; } + // Periode et duree du niveau actif du motif courant. + switch (s_state) + { + case LED_STATE_NOMINAL: + period = NOMINAL_PERIOD; + on_time = NOMINAL_ON; + break; + + case LED_STATE_LINK_LOST: + period = LINK_LOST_PERIOD; + on_time = LINK_LOST_ON; + break; + + case LED_STATE_EMUSTOP: + period = EMUSTOP_PERIOD; + on_time = EMUSTOP_HALF; + break; + + case LED_STATE_OVERTEMP: + period = OVERTEMP_PERIOD; + on_time = OVERTEMP_ON; + break; + + default: // STARTUP et OVERCURRENT : niveau fixe, pas de motif + period = 1U; + on_time = 1U; + break; + } + s_phase++; + if (s_phase >= period) + { + s_phase = 0U; + } switch (s_state) { case LED_STATE_NOMINAL: - blue = ((s_phase % NOMINAL_PERIOD) < NOMINAL_ON); - break; - case LED_STATE_LINK_LOST: - blue = ((s_phase % LINK_LOST_PERIOD) < LINK_LOST_ON); + blue = (s_phase < on_time); break; case LED_STATE_EMUSTOP: - blue = ((s_phase % EMUSTOP_PERIOD) < EMUSTOP_HALF); + blue = (s_phase < on_time); red = !blue; break; case LED_STATE_OVERTEMP: - red = ((s_phase % OVERTEMP_PERIOD) < OVERTEMP_ON); + red = (s_phase < on_time); break; case LED_STATE_OVERCURRENT: