DEFAUT PRINCIPAL -- etats d'attente de la flash jamais configures
Le firmware s'execute depuis la flash (_FLASH), mais InitFlash() n'etait
jamais appelee. Au reset FBANKWAIT vaut son MAXIMUM (0x0F0F, soit
RANDWAIT = PAGEWAIT = 15) : chaque acces flash coutait 16 cycles au lieu
des 3 necessaires a 60 MHz.
Ironie : le memcpy des ramfuncs en tete de main() existe PRECISEMENT pour
copier InitFlash() en RAM, puisqu'elle doit s'executer hors flash. La
recopie se faisait depuis le premier jour sans que la fonction ne soit
jamais appelee -- la section ramfuncs ne contenait que 4 mots, elle en
fait 0x21 maintenant.
Mesure au scope sur l'ISR ADC :
avant : 22,3 kHz, ISR 30 us, 88 % de charge CPU
apres : 66,85 kHz, ISR 4,9 us, 33 % de charge CPU
66,85 kHz est le maximum theorique (200 kHz / SOCAPRD=3), atteint sans
aucun declenchement manque. Recoupe au compteur : 67,5 kHz.
Optimisation compilateur
-O2 ajoute : le projet compilait sans aucune option d'optimisation.
Contribution mesuree : ISR de 41 a 30 us, mais SANS changer la cadence --
ce n'etait pas le facteur limitant. Conserve, le gain reste reel.
Discipline ISR (ni multiplication ni division en interruption)
status_led_tick() : quatre modulos (%200, %40, %100, %100) remplaces par
un compteur qui reboucle sur comparaison.
cpu_timer0_isr() : "tick % 30" remplace par un compteur dedie.
control_tick() : le tableau stages[2] etait initialise sur la pile a
chaque appel, des dizaines de milliers de fois par seconde -> static const.
pwm_set_duty_counts() : une division flottante y mettait a jour la
consigne, sur un F28027 sans FPU. Supprimee ; pwm_get_duty() relit
CMPA, la telemetrie reste juste.
control.c/h -- balayage de caracterisation (BOUCLE OUVERTE)
Ce module ne regule rien : il balaie le duty de 1 % a 50 % puis revient,
par pas de 1 LSB de CMPA a chaque conversion ADC. Appel cadence depuis
l'ISR ADC, conformement a PROMPT §6 etape 8.
API en counts ajoutee a pwm.c : le pas minimal est 1 LSB de CMPA et
DEPEND DE L'ETAGE (1/300 = 0,333 % a 200 kHz, 1/600 = 0,167 % a 100 kHz).
Une consigne flottante ne permet pas d'exprimer "le plus petit pas".
Resultat mesure a 66,7 kHz :
etage 1 : 1->50 % en 2,2 ms, cycle 4,4 ms, 22 %/ms
etage 2 : 1->50 % en 4,4 ms, cycle 8,9 ms, 11 %/ms
L'etage 2 est deux fois plus lent PARCE QUE sa resolution est deux fois
plus fine, a cadence de mise a jour identique.
La telemetrie transporte desormais le duty reel, relu depuis CMPA.
L'instrumentation de mesure a ete RETIREE : GPIO32 est rendue a HV_EN, qui
en a de nouveau l'usage exclusif (verifie sur cible, broche stable a 0).
Note : ce defaut de wait states etait present depuis le debut du projet.
Toutes les mesures de timing anterieures etaient faussees d'un facteur 4.
Les frequences PWM et les protections sont materielles et restent valides.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Circuit de decharge rapide (GPIO5, broche 40)
Commande DISCHARGE du circuit de la carte de puissance : MOSFET en serie
avec 47 kOhm aux bornes du condensateur de sortie, soit tau = 0,94 s et
environ 2 s pour passer de 500 V a 50 V. Sans lui, le seul bleeder de
1 MOhm demandait ~42 s.
POLARITE INVERSEE, et c'est le coeur du montage. Le GPIO pilote un NPN
qui court-circuite la grille du MOSFET :
niveau haut -> NPN conduit -> grille a la masse -> decharge INHIBEE
niveau bas ou broche non pilotee -> grille tiree a 12 V -> decharge ACTIVE
GPIO5 est une broche a fonction PWM : pas de pull-up au reset (PROMPT §6
etape 3), elle flotte. La decharge est donc active au reset, pendant un
plantage, et surtout ALIMENTATION COUPEE -- exactement le cas ou le
firmware ne garantit plus rien et ou l'operateur croit la carte
inoffensive. C'est precisement le cas impose par le choix de n'effacer un
defaut que par cycle d'alimentation.
Sequencement explicite :
- enter_safe_state() active la decharge APRES avoir coupe HV_EN : la
charge est isolee d'abord, le condensateur vide ensuite ;
- le retour au nominal inhibe la decharge AVANT d'autoriser les etages,
jamais l'inverse ;
- bsp_gpio_control_init() la laisse ACTIVE au demarrage : au boot le
condensateur peut encore etre charge par une session precedente.
Champ FAULT dans la trame $T
Codes 0-5, priorite 2 > 3 > 4 > 5 > 1. EMUSTOP (code 1) a donc la
priorite la PLUS BASSE malgre son numero : en developpement il se
declenche a chaque halte du debogueur et ne doit jamais masquer une
surintensite reelle.
Pas de code court-circuit : il franchit le meme comparateur et le meme
seuil qu'une surintensite, le firmware ne peut pas les distinguer.
Aucun effacement automatique ni par commande UART : le verrouillage
n'est leve que par un cycle d'alimentation (PROMPT §8).
Surtemperature suivie PAR VOIE
s_overtemp_t1 / s_overtemp_t2 separes, avec hysteresis chacun. L'ancien
maximum des deux voies ne permettait pas de distinguer les codes 4 et 5.
Seuil ramene a 80 degC pour coller au protocole.
Divers
UART_LINE_MAX porte a 200 (buffer ESP32 g_lineBuf[200]).
append_field_int() pour les champs entiers, meme convention de bornage
que append_field : un champ qui ne tient pas est abandonne entierement.
Documentation
docs/mesure-cartepuissance.md ajoute au depot (source de tracabilite des
coefficients de calib.h).
docs/ESP32-UART.md mis a jour, avec deux corrections de fond :
- la mention "remettre FAULT=0 automatiquement des que la cause a
disparu" est remplacee par une section sur le verrouillage ; elle
contredisait le comportement reel, le TMS320 reemettant indefiniment
le code latche ;
- l'icone danger est specifiee comme dependant UNIQUEMENT de VOUT et
jamais de FAULT. Un defaut ne decharge pas le condensateur : la faire
disparaitre reviendrait a rassurer l'operateur quand le risque est
maximal.
Le piege strtol / uint8_t 16 bits y est documente pour eviter qu'il soit
reintroduit ailleurs.
A cabler cote materiel : liaison broche 40 vers la carte de puissance, et
pull-down 10 kOhm sur la base du NPN.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
CORRECTIF CRITIQUE -- reception UART cassee (uart_link.c)
Le decodage du checksum utilisait strtol sur s_line, qui n'est JAMAIS
terminee par un '\0'. strtol lisait donc au-dela de la trame, dans les
residus de la ligne precedente.
Aggravant, specifique au C28x : uint8_t y fait 16 bits (pas d'adressage
par octet), donc le cast (uint8_t) ne tronquait rien. Un "7D" suivi d'un
"7D" residuel donnait 0x7D7D = 32125, conserve tel quel, et TOUTE trame
etait rejetee. Sur une architecture a octets le cast aurait masque le
probleme et le bug serait passe inapercu.
Constate sur cible : cs_calc = 0x7D (correct), cs_recv = 0x7D7D.
Consequence visible : plus aucune commande $C acceptee, donc timeout de
liaison permanent alors que l'ESP32 emettait correctement.
Remplace par parse_hex2(), borne a exactement deux chiffres, qui ne
depend d'aucune terminaison et rejette un checksum malforme. Supprime au
passage la dependance a stdlib.
status_led.c/h -- affichage d'etat
STARTUP bleu fixe (1 s minimum, sinon jamais visible)
NOMINAL bleu 0,2 / 1,8 s
LINK_LOST bleu 0,2 / 0,2 s (> 2 s sans trame $C valide)
EMUSTOP alternance bleu/rouge 0,5 / 0,5 s
OVERTEMP rouge 0,5 / 0,5 s
OVERCURRENT rouge fixe
Priorite : surintensite > surtemperature > EMUSTOP > liaison perdue.
La surintensite passe devant EMUSTOP : en developpement EMUSTOP se
declenche en permanence et ne doit jamais masquer un defaut de puissance.
Limite de principe : pendant une halte CPU, plus aucun code ne tourne,
les GPIO restent figes sur la phase courante du motif. L'alternance
EMUSTOP est donc un indicateur a posteriori, jamais un etat live.
safety.c/h -- origine du trip
TZFLG.DCAEVT1 (bit 3) est distinct de TZFLG.OST (bit 2), ce qui permet de
separer une surintensite reelle (comparateur) d'un arret du debugger.
Les ISR ne pilotent plus les LED, elles ne font que relever la cause.
Protection thermique (main.c, calib.h)
Seuil 85 degC avec hysteresis de 10 degC, coupure du PWM et de HV_EN.
Lente par nature, donc entierement logicielle -- aucun chemin materiel
requis, contrairement a la surintensite.
Pas de redemarrage automatique : rien ne reactive le PWM une fois coupe.
Seuil PROVISOIRE : les NTC sont a 5 mm des MOSFET, donc elles mesurent le
cuivre et non la jonction (ecart statique et retard thermique notables).
A recaler quand la temperature de boitier en charge sera connue.
Valide sur cible : commandes $C de nouveau acceptees, chaine NTC lue a
24,5 / 24,7 degC sur pont 10k/10k (coherent avec des resistances a 1 %).
Note : un pont 10k/10k ne discrimine PAS le sens de la formule NTC, les
deux formes coincidant au point d'equilibre. Verification a faire avec une
resistance asymetrique (4,7 kOhm -> environ 47 degC).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Report des mesures de docs/mesure-cartepuissance.md dans calib.h, avec une
structure par voie (gain, offset) plutot que des constantes isolees.
calib.h
Tensions : VIN 11,11 / V1 31,25 / VOUT 181,82 V/V (mesures)
IIN : 1,2019 A/V -- un seul point mesure, offset suppose nul
IOUT : 0,016667 A/V -- NON MESURE, valeur theorique de conception
Shunts : I1 offset 47,3 mV gain 0,631 V/A
I2 offset 34,0 mV gain 0,637 V/A
Deux jeux separes : les voies different d'environ 1 % en gain
et n'ont pas le meme offset, ne jamais moyenner.
Seuil de protection : le code DAC est maintenant calcule par macro a partir
de SAFETY_ISHUNT_THRESHOLD_A et des gain/offset mesures, et il est PROPRE A
CHAQUE ETAGE (601 et 603 au lieu d'un 558 commun).
Correction d'analyse : le 558 precedent supposait un gain de 30 pile. Le
gain reel etant plus eleve (~31,5), le seuil de 1,8 V etait atteint a
2,78 A et non a 3 A -- la protection declenchait donc TROP TOT, et non
trop tard comme indique au §5 du document de mesures. Conservatif, sans
danger, mais corrige.
measure.c/h (etape 5)
measure_vin/iin/v1/vout/iout/i1/i2/temp, signature float f(uint16_t raw).
Deux fonctions distinctes pour les shunts, puisque gain ET offset
different. measure_i1/i2 peuvent renvoyer une valeur negative a courant
nul : volontaire, une derive d'offset apres changement d'ampli devient
ainsi visible en telemetrie au lieu d'etre masquee par une saturation.
NTC : la relation du PROMPT §5 est INVERSEE. Avec la NTC cote 3,3 V c'est
R_ntc = R_fixe * (VREF - Vadc) / Vadc. Verifie sur les points de repere du
PROMPT lui-meme : 0,75 V a 0 degC donne 34,2 kOhm, ce que beta=4000
predit, alors que la forme inversee donnerait 2,94 kOhm (donc du chaud).
Telle qu'ecrite, la protection thermique aurait ete inexploitable.
La telemetrie transporte desormais de vraies grandeurs physiques.
Reste en suspens (voir docs/mesure-cartepuissance.md §6) : IOUT jamais
mesure, second point pour IIN, valeur reelle de la resistance fixe des
ponts NTC, et surtout gain/offset des shunts A REMESURER apres passage du
MCP6001 au TLV9151 -- l'offset actuel est domine par le Vos de l'ampli.
Note : l'"auto-zero logiciel au boot" evoque dans le document de mesures
n'existe pas dans le firmware et n'a pas ete ajoute ; mesurer un offset au
boot figerait une valeur fausse si du courant circule deja.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Emission $T,...*XX et reception $C,...*XX conformes a docs/ESP32-UART.md.
Trame mesuree a 129 caracteres, checksum XOR verifie independamment.
Corrections trouvees au bring-up sur materiel :
- StartCpuTimer0() manquant : ConfigCpuTimer() laisse le timer arrete, donc
le tick d'emission ne se produisait jamais et rien ne partait sur la ligne.
- Suppression de "%f" dans sprintf. Sur C28x le support flottant de printf
passe par du long double 64 bits emule (frexpl/scalbnl/L$$DIV) : le CPU
partait a PC=0 des le premier envoi. Formatage decimal manuel a la place,
comme le prevoyait deja PROMPT §7 etape 7.
- RXFFIENA n'etait pas arme dans SCIFFRX : aucune interruption de reception.
- Recuperation de SCIRXST.RXERROR. Un FE/OE/PE/BRKDT le latche et bloque le
recepteur jusqu'a un SW RESET du SCI ; sans ca une seule perturbation
arretait la reception definitivement (constate : RXERROR+FE+BRKDT latches).
- Formatage borne : clamp des valeurs (NaN et saturation) puis snprintf avec
la capacite restante. Un champ qui ne tient pas est abandonne entierement,
ce que le protocole autorise, plutot que de deborder s_tx_frame.
- Emission non bloquante : send_telemetry() ne fait que mettre en attente,
uart_link_service_tx() pousse au plus une FIFO (4 octets) puis rend la
main. L'ancienne version monopolisait 22 ms par trame, incompatible avec
la priorite donnee a la boucle de regulation.
Plan memoire (F2802x_generic_flash.cmd) :
- les 4 secteurs flash du F28027 sont declares (32K mots au lieu de 8K)
- .ebss bascule en RAML0 : il etait colle juste apres .stack, donc tout
debordement de pile ecrasait silencieusement les globales
- pile portee a 1024 mots, seule dans RAMM1
LED : polarite reelle confirmee active-haut (cathode commune a la masse,
anodes pilotees par GPIO12/GPIO33 a travers 1k) -> LED_ACTIVE_LOW = 0.
Repond au point ouvert §9.2 du PROMPT.
safety_init() reste desactive dans main.c : bring-up isole LED+UART, a
reactiver quand l'etage de puissance sera cable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous commit replaced the hardcoded path with a variable
reference to itself (${COM_TI_C2000WARE_INSTALL_DIR}), which broke
include resolution and the build entirely. Restore the absolute path
confirmed via getProjectProductReferences.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
.mcp.json and CLAUDE.md are auto-generated by CCS with machine-specific
absolute paths and shouldn't be shared via git. The .cproject change
(likely from a CCS reimport) replaces the hardcoded C2000Ware install
path with a variable reference.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>