Compare commits

..

3 Commits

Author SHA1 Message Date
69801df648 InitFlash manquante : tout le firmware tournait 4x trop lentement
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>
2026-08-05 21:28:34 +02:00
b1bb0fa21a Reprise controlee apres EMUSTOP, sans lever le verrouillage des defauts
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>
2026-08-05 17:22:44 +02:00
26c1d499e0 Champ FAULT, decharge HT active, et surtemperature par voie
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>
2026-08-05 17:00:32 +02:00
18 changed files with 907 additions and 141 deletions

View File

@ -22,8 +22,8 @@
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<configuration artifactExtension="out" artifactName="Example_2802xLEDBlink" buildProperties="" description="" id="com.ti.ccstudio.buildDefinitions.C2000.Debug.1909800144" name="Debug" parent="com.ti.ccstudio.buildDefinitions.C2000.Debug">
<folderInfo id="com.ti.ccstudio.buildDefinitions.C2000.Debug.1909800144.847706334" name="/" resourcePath="">
<toolChain id="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.DebugToolchain.159661892" name="TI Build Tools" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.DebugToolchain" targetTool="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.linkerDebug.1551753352">
<option id="com.ti.ccstudio.buildDefinitions.core.OPT_TAGS.1144356066" superClass="com.ti.ccstudio.buildDefinitions.core.OPT_TAGS" valueType="stringList">
<toolChain id="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.DebugToolchain.213001512" name="TI Build Tools" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.DebugToolchain" targetTool="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.linkerDebug.813574046">
<option id="com.ti.ccstudio.buildDefinitions.core.OPT_TAGS.1793914701" superClass="com.ti.ccstudio.buildDefinitions.core.OPT_TAGS" valueType="stringList">
<listOptionValue value="DEVICE_CONFIGURATION_ID=TMS320C28XX.TMS320F28027"/>
<listOptionValue value="DEVICE_CORE_ID="/>
<listOptionValue value="DEVICE_ENDIANNESS=UNKNOWN"/>
@ -38,14 +38,14 @@
<listOptionValue value="LINK_ORDER="/>
<listOptionValue value="PRODUCTS=C2000WARE:26.1.0.00;"/>
</option>
<option id="com.ti.ccstudio.buildDefinitions.core.OPT_CODEGEN_VERSION.1164917113" superClass="com.ti.ccstudio.buildDefinitions.core.OPT_CODEGEN_VERSION" value="25.11.1.LTS" valueType="string"/>
<targetPlatform id="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.targetPlatformDebug.304590691" name="Platform" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.targetPlatformDebug"/>
<builder buildPath="${BuildDirectory}" id="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.builderDebug.1772446421" name="GNU Make.Debug" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.builderDebug"/>
<tool id="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.compilerDebug.1541428198" name="C2000 Compiler" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.compilerDebug">
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.LARGE_MEMORY_MODEL.1715790107" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.LARGE_MEMORY_MODEL" value="true" valueType="boolean"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.UNIFIED_MEMORY.1984521868" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.UNIFIED_MEMORY" value="true" valueType="boolean"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.SILICON_VERSION.2036393650" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.SILICON_VERSION" value="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.SILICON_VERSION.28" valueType="enumerated"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.INCLUDE_PATH.1849469441" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.INCLUDE_PATH" valueType="includePath">
<option id="com.ti.ccstudio.buildDefinitions.core.OPT_CODEGEN_VERSION.841378290" superClass="com.ti.ccstudio.buildDefinitions.core.OPT_CODEGEN_VERSION" value="25.11.1.LTS" valueType="string"/>
<targetPlatform id="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.targetPlatformDebug.524055294" name="Platform" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.targetPlatformDebug"/>
<builder buildPath="${BuildDirectory}" id="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.builderDebug.1769976716" name="GNU Make.Debug" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.builderDebug"/>
<tool id="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.compilerDebug.305010793" name="C2000 Compiler" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.compilerDebug">
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.LARGE_MEMORY_MODEL.1798142386" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.LARGE_MEMORY_MODEL" value="true" valueType="boolean"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.UNIFIED_MEMORY.1604870518" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.UNIFIED_MEMORY" value="true" valueType="boolean"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.SILICON_VERSION.2122353486" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.SILICON_VERSION" value="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.SILICON_VERSION.28" valueType="enumerated"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.INCLUDE_PATH.716974798" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.INCLUDE_PATH" valueType="includePath">
<listOptionValue value="${COM_TI_C2000WARE_INCLUDE_PATH}"/>
<listOptionValue value="${CG_TOOL_ROOT}/include"/>
<listOptionValue value="${INSTALLROOT_F2802x}/headers/include"/>
@ -53,35 +53,36 @@
<listOptionValue value="${INSTALLROOT_F2802x}/common/include"/>
<listOptionValue value="${INSTALLROOT_IQMATH}/include"/>
</option>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DEFINE.1614629519" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DEFINE" valueType="definedSymbols">
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DEFINE.1404568356" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DEFINE" valueType="definedSymbols">
<listOptionValue value="${COM_TI_C2000WARE_SYMBOLS}"/>
<listOptionValue value="_DEBUG"/>
<listOptionValue value="LARGE_MODEL"/>
<listOptionValue value="_FLASH"/>
</option>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DEBUGGING_MODEL.196664150" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DEBUGGING_MODEL" value="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DEBUGGING_MODEL.SYMDEBUG__DWARF" valueType="enumerated"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DIAG_SUPPRESS.689463295" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DIAG_SUPPRESS" valueType="stringList">
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DEBUGGING_MODEL.1187109720" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DEBUGGING_MODEL" value="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DEBUGGING_MODEL.SYMDEBUG__DWARF" valueType="enumerated"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DIAG_SUPPRESS.288154142" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DIAG_SUPPRESS" valueType="stringList">
<listOptionValue value="232"/>
<listOptionValue value="10063"/>
</option>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DIAG_WARNING.1851392201" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DIAG_WARNING" valueType="stringList">
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DIAG_WARNING.923695198" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DIAG_WARNING" valueType="stringList">
<listOptionValue value="225"/>
</option>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.ISSUE_REMARKS.19643992" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.ISSUE_REMARKS" value="true" valueType="boolean"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.VERBOSE_DIAGNOSTICS.1636041796" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.VERBOSE_DIAGNOSTICS" value="true" valueType="boolean"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.QUIET_LEVEL.255072012" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.QUIET_LEVEL" value="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.QUIET_LEVEL.QUIET" valueType="enumerated"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.ABI.1040365072" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.ABI" value="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.ABI.coffabi" valueType="enumerated"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.ISSUE_REMARKS.1620598451" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.ISSUE_REMARKS" value="true" valueType="boolean"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.VERBOSE_DIAGNOSTICS.537099600" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.VERBOSE_DIAGNOSTICS" value="true" valueType="boolean"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.QUIET_LEVEL.946759491" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.QUIET_LEVEL" value="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.QUIET_LEVEL.QUIET" valueType="enumerated"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.ABI.1717529760" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.ABI" value="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.ABI.coffabi" valueType="enumerated"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.OPT_LEVEL.813239093" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.OPT_LEVEL" value="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.OPT_LEVEL.2" valueType="enumerated"/>
</tool>
<tool id="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.linkerDebug.1551753352" name="C2000 Linker" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.linkerDebug">
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.STACK_SIZE.164177814" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.STACK_SIZE" value="0x400" valueType="string"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.MAP_FILE.1243476423" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.MAP_FILE" value="&quot;Example_2802xLEDBlink.map&quot;" valueType="string"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.OUTPUT_FILE.119763382" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.OUTPUT_FILE" value="Example_2802xLEDBlink.out" valueType="string"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.LIBRARY.397023344" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.LIBRARY" valueType="libs">
<tool id="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.linkerDebug.813574046" name="C2000 Linker" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.linkerDebug">
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.STACK_SIZE.1069850252" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.STACK_SIZE" value="0x400" valueType="string"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.MAP_FILE.775342445" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.MAP_FILE" value="&quot;Example_2802xLEDBlink.map&quot;" valueType="string"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.OUTPUT_FILE.1826875140" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.OUTPUT_FILE" value="Example_2802xLEDBlink.out" valueType="string"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.LIBRARY.1024492364" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.LIBRARY" valueType="libs">
<listOptionValue value="${COM_TI_C2000WARE_LIBRARIES}"/>
<listOptionValue value="rts2800_ml.lib"/>
<listOptionValue value="IQmath.lib"/>
</option>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.SEARCH_PATH.567141830" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.SEARCH_PATH" valueType="libPaths">
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.SEARCH_PATH.1469209174" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.SEARCH_PATH" valueType="libPaths">
<listOptionValue value="${COM_TI_C2000WARE_LIBRARY_PATH}"/>
<listOptionValue value="${CG_TOOL_ROOT}/lib"/>
<listOptionValue value="${CG_TOOL_ROOT}/include"/>
@ -89,12 +90,12 @@
<listOptionValue value="${INSTALLROOT_F2802x}/common/lib"/>
<listOptionValue value="${INSTALLROOT_IQMATH}/lib"/>
</option>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.ISSUE_REMARKS.1756602824" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.ISSUE_REMARKS" value="true" valueType="boolean"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.VERBOSE_DIAGNOSTICS.489766963" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.VERBOSE_DIAGNOSTICS" value="true" valueType="boolean"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.XML_LINK_INFO.667790640" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.XML_LINK_INFO" value="&quot;Example_2802xLEDBlink_linkInfo.xml&quot;" valueType="string"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.ENTRY_POINT.511960855" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.ENTRY_POINT" value="code_start" valueType="string"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.ISSUE_REMARKS.1110979405" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.ISSUE_REMARKS" value="true" valueType="boolean"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.VERBOSE_DIAGNOSTICS.113431991" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.VERBOSE_DIAGNOSTICS" value="true" valueType="boolean"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.XML_LINK_INFO.460865356" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.XML_LINK_INFO" value="&quot;Example_2802xLEDBlink_linkInfo.xml&quot;" valueType="string"/>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.ENTRY_POINT.1851948553" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.ENTRY_POINT" value="code_start" valueType="string"/>
</tool>
<tool id="com.ti.ccstudio.buildDefinitions.C2000_25.11.hex.813831478" name="C2000 Hex Utility" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.hex"/>
<tool id="com.ti.ccstudio.buildDefinitions.C2000_25.11.hex.166197469" name="C2000 Hex Utility" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.hex"/>
</toolChain>
</folderInfo>
<sourceEntries>

View File

@ -5,65 +5,162 @@ pour dialoguer avec l'ESP32-C3 (cote ESP32 deja fonctionnel, voir
`src/tms_link.cpp` / `include/protocol.h`). Le TMS320 envoie sa telemetrie
periodiquement, et recoit periodiquement les commandes de l'operateur.
> **Mise a jour** : coefficients de calibration remplaces par les valeurs
> mesurees sur la carte reelle (voir `mesure-cartepuissance.md`), et ajout
> du champ `FAULT`.
## Cablage
| Signal | ESP32-C3 (config.h) | TMS320 |
|---|---|---|
| ESP32 TX -> TMS320 RX | GPIO7 | RX |
| ESP32 RX <- TMS320 TX | GPIO6 | TX |
| ESP32 TX -> TMS320 RX | GPIO7 | GPIO28, broche 48 |
| ESP32 RX <- TMS320 TX | GPIO6 | GPIO29, broche 1 |
| GND commun | GND | GND |
A verifier avant de cabler : le TMS320 doit etre en logique 3,3V (comme
l'ESP32-C3). Si sa liaison serie sort en 5V, prevoir un level shifter
avant de relier les deux cartes.
Niveaux logiques 3,3 V des deux cotes -- **verifie, carte CPU validee**.
## Parametres UART
- Vitesse : **57600 bauds** (adapte cote TMS320 pour la marge de derive de
l'oscillateur interne INTOSC1 ; l'ESP32 doit etre mis a jour en
consequence, `Serial1.begin(57600, ...)`)
- Vitesse : **57600 bauds**
- Format : **8N1** (8 bits de donnees, pas de parite, 1 bit de stop)
- Cote ESP32 : `Serial1`, defini dans `TmsLink::begin()`
(`src/tms_link.cpp`)
## Format des trames
Protocole ASCII texte, inspire du format NMEA, terminé par `\n` (LF).
Protocole ASCII texte, inspire du format NMEA, termine par `\n` (LF).
Chaque trame est encadree par `$` ... `*XX`, ou `XX` est un checksum XOR
en hexadecimal (2 chiffres, majuscules).
### Telemetrie : TMS320 -> ESP32 (periodique)
```
$T,FREQ1=100000,FREQ2=100000,DUTY1=45.2,DUTY2=50.0,VIN=400.5,IIN=1.20,V1=200.3,I1=2.50,T1=45.2,VOUT=200.1,I2=2.48,T2=44.8,IOUT=1.05*7A
$T,FREQ1=200000,FREQ2=100000,DUTY1=45.2,DUTY2=50.0,VIN=400.5,IIN=1.20,V1=200.3,I1=2.50,T1=45.2,VOUT=200.1,I2=2.48,T2=44.8,IOUT=1.05,FAULT=0*XX
```
Champs (`TAG=valeur`, separes par des virgules, ordre libre) :
| Tag | Unite | Description |
|---|---|---|
| `FREQ1` | Hz | Frequence de decoupage etage 1 |
| `FREQ2` | Hz | Frequence de decoupage etage 2 |
| `FREQ1` | Hz | Frequence de decoupage etage 1 (200 kHz) |
| `FREQ2` | Hz | Frequence de decoupage etage 2 (100 kHz) |
| `DUTY1` | % | Rapport cyclique etage 1 |
| `DUTY2` | % | Rapport cyclique etage 2 |
| `VIN` | V | Tension d'entree |
| `IIN` | A | Courant d'entree |
| `V1` | V | Tension etage 1 |
| `V1` | V | Tension intermediaire (sortie etage 1) |
| `I1` | A | Courant shunt MOSFET etage 1 |
| `T1` | °C | Temperature NTC etage 1 |
| `VOUT` | V | Tension de sortie (sortie boost, etage 2) |
| `T1` | degC | Temperature NTC etage 1 |
| `VOUT` | V | Tension de sortie HT |
| `I2` | A | Courant shunt MOSFET etage 2 |
| `T2` | °C | Temperature NTC etage 2 |
| `T2` | degC | Temperature NTC etage 2 |
| `IOUT` | A | Courant de sortie |
| `FAULT` | code 0-5 | Defaut courant (voir ci-dessous) |
**Frequence d'envoi recommandee : toutes les 200-500 ms.** L'ESP32
considere la liaison perdue (`TMS:KO` affiche sur l'OLED/web) si aucune
trame `$T,...*XX` valide n'est recue depuis plus de **2 secondes**
(`TmsLink::linkOk()`).
### Codes `FAULT`
Tous les champs sont optionnels a l'envoi individuel (un champ absent
garde sa derniere valeur connue cote ESP32), mais en pratique il est plus
simple d'envoyer tous les champs a chaque trame.
| Code | Signification |
|---|---|
| `0` | Aucun defaut |
| `1` | EMUSTOP -- le debogueur a arrete le CPU |
| `2` | Surintensite I1 (etage 1) |
| `3` | Surintensite I2 (etage 2) |
| `4` | Temperature critique T1 (> 80 degC) |
| `5` | Temperature critique T2 (> 80 degC) |
**Priorite quand plusieurs defauts coexistent : 2 > 3 > 4 > 5 > 1.**
EMUSTOP 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.
**Il n'existe volontairement pas de code "court-circuit".** Un
court-circuit franchit le meme comparateur et le meme seuil qu'une
surintensite ; le firmware ne dispose d'aucune information permettant de
les distinguer. Les codes 2 et 3 couvrent les deux cas.
#### Verrouillage : pas d'effacement par le protocole
Contrairement a ce que supposait une version precedente de ce document,
**un defaut ne s'efface JAMAIS automatiquement**, ni a la disparition de
sa cause, ni par commande UART. Il n'existe aucun tag `$C` permettant de
l'acquitter.
Le seul moyen de repartir est de **couper puis retablir l'alimentation**.
C'est un choix deliberé (PROMPT §8) : un defaut de puissance doit exiger
une intervention humaine et un examen de la carte.
Consequence pour l'ESP32 : tant que le TMS320 emet un `FAULT != 0`, il
continuera de l'emettre indefiniment. L'IHM doit donc afficher ce code de
maniere persistante, sans attendre un retour spontane a 0.
> Rappel (PROMPT §2) : la protection reelle -- comparateurs + Trip Zone
> materiel -- ne depend jamais de cette liaison UART. `FAULT` n'est qu'un
> report d'information vers l'IHM, envoye apres coup ; la coupure a deja
> eu lieu en materiel avant meme que la trame ne soit construite.
#### Icone danger : pilotee par VOUT, jamais par FAULT
L'avertissement haute tension doit dependre **uniquement de `VOUT`**, et
rester affiche independamment de l'etat de `FAULT`.
Un defaut ne decharge pas le condensateur de sortie. Apres une coupure,
`HV_EN` isole la charge mais les 400 V restent presents. Faire disparaitre
l'icone danger au moment d'un defaut reviendrait a rassurer l'operateur
precisement quand le risque est maximal.
La carte de puissance comporte deux dispositifs complementaires :
- un **bleeder de 1 MOhm avec LED rouge en serie**, indication passive
qui reste valable alimentation coupee (~42 s pour passer sous 50 V) ;
- un **circuit de decharge actif** (MOSFET + 47 kOhm, ~2 s de 500 V a
50 V), commande par le TMS320 sur GPIO5 / broche 40, **a logique
inversee** : il conduit des que la commande disparait, donc au reset,
en cas de plantage et alimentation coupee.
**Frequence d'envoi : toutes les 300 ms.** L'ESP32 considere la liaison
perdue (`TMS:KO`) si aucune trame `$T` valide n'est recue depuis plus de
**2 secondes**. Le TMS320 applique le meme timeout dans l'autre sens.
Tous les champs sont optionnels individuellement (un champ absent garde
sa derniere valeur cote ESP32) ; en pratique le TMS320 les envoie tous.
Si une trame devait depasser la longueur maximale, le firmware abandonne
des champs entiers plutot que d'en tronquer un.
### Calibration des mesures analogiques (cote TMS320)
Coefficients appliques sur la lecture ADC (`Vadc`, en volts) pour obtenir
la valeur physique. **Valeurs mesurees sur la carte reelle**, sauf mention
contraire -- voir `mesure-cartepuissance.md` pour la tracabilite.
| Grandeur | Pleine echelle (3,3 V) | Relation | Statut |
|---|---|---|---|
| `VIN` | 36,7 V | `VIN = Vadc x 11,11` | mesure |
| `IIN` | 3,97 A | `IIN = Vadc x 1,2019` | mesure (1 point) |
| `V1` | 103,1 V (usage limite a 50 V) | `V1 = Vadc x 31,25` | mesure |
| `VOUT` | 600 V | `VOUT = Vadc x 181,82` | mesure |
| `IOUT` | 50 mA (a confirmer) | `IOUT = Vadc x 0,016667` | **a mesurer** |
| `I1` | 5,15 A | `I1 = (Vadc - 0,0473) / 0,631` | **provisoire (MCP6001)** |
| `I2` | 5,12 A | `I2 = (Vadc - 0,0340) / 0,637` | **provisoire (MCP6001)** |
**Notes :**
- **I1/I2** : le gain differe d'environ 1 % entre les deux voies et chaque
voie a un offset non nul a courant nul (47,3 / 34,0 mV). Ne jamais
utiliser un gain unique pour les deux. Ces valeurs sont mesurees avec
les **MCP6001 de banc** ; le composant definitif est le TLV9151. Le gain
devrait rester valable (fixe par le reseau de resistances) mais
**l'offset devra etre integralement remesure**.
- **MCP6001, limite de bande passante** : 1 MHz de produit gain-bande a un
gain de ~31 plafonne vers 32 kHz. Pour proteger un decoupage a 200 kHz,
les 30 ns du comparateur ne servent a rien si l'ampli en amont met des
dizaines de microsecondes. **La protection rapide n'est pas reellement
rapide tant que le MCP6001 est en place.**
- **NTC** : montage 3,3 V -- NTC -- R_fixe -- 0 V, mesure au point milieu.
La NTC etant cote 3,3 V, la relation est
`R_ntc = R_fixe x (VREF - Vadc) / Vadc`. Les thermistances sont a 5 mm
des MOSFET : elles mesurent le cuivre, pas la jonction, avec un ecart
statique et un retard thermique notables. Le seuil de 80 degC est donc
**provisoire**, a recaler sur la temperature de boitier en charge.
- **IOUT** : aucune mesure reelle a ce jour.
### Commande : ESP32 -> TMS320 (periodique + a chaque changement)
@ -78,61 +175,29 @@ $C,HT=1,PWM1=1,PWM2=0*3E
| `PWM2` | 0 ou 1 | PWM etage 2 activee/desactivee |
L'ESP32 envoie cette trame toutes les **500 ms**, et immediatement a
chaque changement d'etat depuis l'IHM (OLED/web). Le TMS320 doit
simplement appliquer le dernier etat recu et valide (checksum correct).
chaque changement depuis l'IHM. Le TMS320 applique le dernier etat recu et
valide. Les trois tags doivent etre presents, sinon la trame est ignoree.
## Calcul du checksum
XOR de tous les octets entre `$` et `*` (exclus), formate en hexadecimal
majuscule sur 2 chiffres (`%02X`).
XOR de tous les octets entre `$` et `*` (exclus), en hexadecimal majuscule
sur 2 chiffres (`%02X`).
Exemple en C pour une trame a construire :
Pour la reception : meme calcul, comparaison au checksum recu, trame
ignoree silencieusement si ca ne correspond pas.
```c
uint8_t checksum_of(const char *s, int len) {
uint8_t cs = 0;
for (int i = 0; i < len; i++) cs ^= (uint8_t)s[i];
return cs;
}
// Construction d'une trame telemetrie :
char body[128];
int n = sprintf(body, "T,FREQ1=%.0f,FREQ2=%.0f,DUTY1=%.1f,DUTY2=%.1f,"
"VIN=%.1f,IIN=%.2f,V1=%.1f,I1=%.2f,T1=%.1f,"
"VOUT=%.1f,I2=%.2f,T2=%.1f,IOUT=%.2f",
freq1, freq2, duty1, duty2, vin, iin, v1, i1, t1, vout, i2, t2, iout);
uint8_t cs = checksum_of(body, n);
char frame[160];
sprintf(frame, "$%s*%02X\n", body, cs);
// envoyer frame sur l'UART
```
Pour la reception des trames `$C,...*XX` : appliquer le meme calcul sur
la partie entre `$` et `*`, comparer au checksum recu, et ignorer la
trame si ca ne correspond pas (trame corrompue).
## Cote ESP32 : ce qui existe deja
- `include/protocol.h` : structures `Telemetry` et `CommandState`,
description du protocole (source de verite en cas de doute).
- `src/tms_link.cpp` : parsing des trames `$T,...`, envoi des trames
`$C,...`, gestion du timeout de liaison. Contient aussi
`updateSimulation()`, qui simule des valeurs realistes (utile comme
reference de plage de valeurs attendues : `Vin` ~400V, `Iin` ~1.2A,
`V1`/`Vout` ~200V, `I1`/`I2` ~2.5A, `T1`/`T2` ~40°C).
- `include/config.h` : `TMS_DUMMY_MODE` (actuellement `true`) fait
tourner l'ESP32 sur la simulation interne au lieu de lire l'UART reel.
**A passer a `false` une fois le firmware TMS320 pret et teste**, pour
activer la reception reelle des trames `$T,...*XX`.
> **Piege rencontre cote TMS320, a ne pas reproduire.** Decoder le
> checksum avec `strtol` sur un buffer non termine par `\0` fait lire
> au-dela de la trame. Et sur C28x, `uint8_t` fait 16 bits (pas
> d'adressage par octet) : un cast `(uint8_t)` ne tronque donc rien, et un
> `"7D"` suivi d'un residu `"7D"` donne 0x7D7D, conserve tel quel. Toute
> trame etait rejetee. Decoder **exactement deux chiffres**, de maniere
> bornee.
## Points de vigilance
- Le format hexadecimal du checksum doit etre en **majuscules** sur 2
chiffres (`%02X`), sinon la trame sera rejetee cote ESP32 (silencieux,
pas d'erreur visible la telemetrie n'avancera simplement pas).
- Terminer chaque trame par `\n` uniquement (le `\r` est tolere/ignore
cote ESP32 mais pas necessaire).
- Ne pas depasser une longueur de ligne de 160 caracteres (buffer fixe
cote ESP32, `g_lineBuf[160]` dans `tms_link.cpp`).
- Verifier au multimetre/oscilloscope le niveau logique du TMS320 avant
de le relier a l'ESP32-C3 (3,3V attendu).
- Checksum en **majuscules** sur 2 chiffres, sinon rejet silencieux cote
ESP32 -- la telemetrie n'avance simplement plus.
- Terminer par `\n` uniquement (`\r` tolere).
- Longueur de ligne maximale : **200 caracteres** (`g_lineBuf[200]` cote
ESP32, `UART_LINE_MAX` cote TMS320).

View File

@ -0,0 +1,202 @@
# Mesures carte puissance — TMS320F28027 dual boost
Suivi des mesures de calibration effectuées sur la carte élévateur (à moitié
complète : composants de puissance et drivers MOSFET pas encore montés).
Ce document est la source pour remplir `calib.h` dans le firmware. Les
valeurs numériques finales à utiliser en code sont dans
`PROMPT-claude-code-TMS320.md` (section 5) ; ce fichier-ci garde la trace
des mesures brutes et de leur contexte.
---
## 1. Chaînes de mesure de tension
Méthode : alimentation de labo réglée à une tension connue, lecture de la
tension de sortie du pont diviseur au multimètre (avant l'ADC).
| Voie | Point de mesure | Coefficient mesuré (Vadc/Vréel) | Gain (V/V) | Pleine échelle (3,3 V) | Statut |
|---|---|---|---|---|---|
| VIN (V_batt) | pin 10 | 0,09 | 11,11 | 36,7 V | ✅ mesuré |
| V1 (V_inter) | pin 16 | 0,032 | 31,25 | 103,1 V | ✅ mesuré |
| VOUT (V_HT) | pin 14 | 0,0055 | 181,82 | 600 V | ✅ mesuré |
**Remarques :**
- VIN et V1 tombent exactement sur les valeurs théoriques calculées à la
conception (11,11 et 31,25) — le pont a été monté avec les bonnes valeurs
de résistances.
- VOUT diffère de 1,8 % de la valeur théorique (178,6 → 181,82), imputable
à la tolérance des résistances réelles. Sans conséquence : la pleine
échelle réelle (600 V) reste au-dessus de l'objectif (400 V max), avec
même un peu plus de marge que prévu.
- V1 max visé = 50 V → seulement ~1,6 V sur l'ADC (48 % de la plage), pas
de saturation, résolution confirmée suffisante.
---
## 2. Chaîne de mesure de courant d'entrée (IIN)
Méthode : courant imposé au primaire, lecture de la tension de sortie de
l'ampli de mesure au multimètre.
| Point | Valeur |
|---|---|
| Mesure | 250 mA → 0,208 V en sortie ampli |
| Gain calculé | 0,832 V/A |
| Gain inverse | 1,2019 A/V |
| Pleine échelle (3,3 V) | 3,97 A |
Un seul point mesuré (pas de vérification de linéarité multi-points comme
pour les shunts MOSFET). Cohérent avec le courant d'entrée nominal attendu
(~590 mA pour ~5 W de charge côté HT), bonne marge avant saturation.
**Reste à faire** : idéalement un second point de mesure pour confirmer la
linéarité et détecter un éventuel offset (mesure faite à un seul courant,
donc le calcul suppose une caractéristique passant par zéro — à vérifier).
---
## 3. Chaînes de mesure de courant shunt MOSFET (I1, I2) — protection rapide
⚠️ **Mesures faites avec le MCP6001 monté provisoirement sur le banc de
test.** Le composant final prévu est le TLV9151 (meilleur offset, meilleure
bande passante). Le gain (fixé par le réseau de résistances RF/RG) doit
rester valable après le remplacement ; l'offset devra être entièrement
remesuré.
Méthode : courant imposé dans chaque shunt (0,02 Ω), lecture au
multimètre de la tension aux bornes du shunt (`V_Rshunt`) et de la sortie
ampli-op avant ADC (`V_shunt_adc`), à quatre points (0, 0,5, 1, 2 A).
### Données brutes
**Shunt 1 (étage 1) :**
| I (A) | V_Rshunt (mV) | V_shunt1_adc (V) |
|---|---|---|
| 0 | — | 0,0473 |
| 0,5 | 10,3 | 0,367 |
| 1 | 20,38 | 0,682 |
| 2 | 40,9 | 1,31 |
**Shunt 2 (étage 2) :**
| I (A) | V_Rshunt (mV) | V_shunt2_adc (V) |
|---|---|---|
| 0 | — | 0,0340 |
| 0,5 | 10,48 | 0,357 |
| 1 | 20,58 | 0,674 |
| 2 | 40,76 | 1,31 |
### Résultats (régression linéaire sur les 4 points)
| Voie | Offset (I=0) | Gain (V/A) | Gain équivalent shunt×ampli (V/V) | Gain inverse (A/V) | Pleine échelle (3,3 V) |
|---|---|---|---|---|---|
| I1 (étage 1) | 47,3 mV | 0,631 | ≈ 31,5 | 1,585 | 5,15 A |
| I2 (étage 2) | 34,0 mV | 0,637 | ≈ 31,8 | 1,570 | 5,12 A |
**Formules de conversion (provisoires, MCP6001) :**
```
I1 = (Vadc 0,0473) / 0,631
I2 = (Vadc 0,0340) / 0,637
```
**Remarques :**
- Très bonne linéarité sur les 4 points des deux voies.
- Écart de gain d'environ 1 % entre les deux voies — attribuable à la
tolérance des résistances de gain, propre à chaque canal. Ne pas
utiliser un gain unique moyenné : garder deux jeux de constantes séparés
dans `calib.h`.
- Le gain mesuré (~31,5-31,8) est proche de la valeur visée à la
conception (31), mais **s'écarte de l'hypothèse initiale utilisée pour
calculer le seuil DAC** (qui supposait un gain de 30 pile). Voir §5 pour
la correction.
- Les deux offsets (34-47 mV) sont nettement plus élevés que le Vos
typique du MCP6001 seul à ce gain — probablement une combinaison de
l'offset ampli réel et d'un petit décalage du pont/de la référence.
Sans conséquence pratique : l'auto-zéro logiciel prévu au boot du
firmware absorbe cet écart automatiquement, quelle que soit son origine.
---
## 4. Chaîne de mesure de courant de sortie (IOUT)
**Non mesurée.** Reste à faire — même méthode que pour IIN (courant imposé
côté charge après le switch de sortie, lecture de la tension ampli-op).
Valeur théorique provisoire dans `calib.h` : 3,0 V @ 50 mA → gain
0,016667 A/V (à confirmer/remplacer par une mesure réelle).
---
## 5. Seuil de protection — recalcul avec les gains mesurés
Le calcul initial du code DAC (`DACVAL`) pour le seuil de 3 A avait été
fait en supposant un gain exactement égal à 30 (shunt 0,02 Ω × ampli-op
×30), donnant `Vadc = 1,8 V` à 3 A. **Cette hypothèse est fausse** : le gain
réel mesuré est ~31,5-31,8, donc en réalité `1,8 V` correspondait à un
seuil réel plus proche de 3,3 A que de 3 A. Non dangereux (la protection
aurait juste déclenché un peu tard), mais corrigé avant tout flashage.
**Valeurs recalculées avec les gains et offsets mesurés (§3) :**
```
Seuil 3 A, comparateur 1 (I-shunt1) :
Vadc = 0,0473 + 3 × 0,631 = 1,940 V
DACVAL = round(1,940 × 1023 / 3,3) = 601
Seuil 3 A, comparateur 2 (I-shunt2) :
Vadc = 0,0340 + 3 × 0,637 = 1,945 V
DACVAL = round(1,945 × 1023 / 3,3) = 603
```
**⚠️ Ces deux valeurs sont provisoires (MCP6001).** Elles devront être
recalculées avec les mêmes formules dès que le gain et l'offset seront
remesurés sur le TLV9151. Le firmware ne doit jamais figer `DACVAL` en
dur : le calculer par macro à partir de `SAFETY_ISHUNT_THRESHOLD_A` et des
constantes de gain/offset mesurées, pour que ce remplacement futur ne
touche qu'aux valeurs de `calib.h`, pas au code.
---
## 6. Ce qu'il reste à faire
### Mesures à compléter
- [ ] IOUT : gain réel (un point de mesure minimum, idéalement deux)
- [ ] IIN : second point de mesure pour vérifier la linéarité/l'offset
- [ ] Chaîne NTC (Temp1, Temp2) : vérifier la valeur réelle de la
résistance fixe des ponts diviseurs (10 kΩ supposé) et, si possible,
un point de mesure à température connue pour valider la formule
Steinhart-Hart simplifiée
- [ ] I1, I2 **à refaire intégralement** une fois le TLV9151 monté (gain à
confirmer proche de ~31,5-31,8, offset à mesurer — attendu très
inférieur aux 34-47 mV actuels)
### Matériel restant à monter sur la carte élévateur
- [ ] Composants de puissance (MOSFET IPD60R360, diode SiC STPSC406,
inductances) — carte actuellement à moitié complète
- [ ] Driver de grille : **le UCC27518/19 initialement prévu ne convient
pas** (seuils d'entrée CMOS proportionnels à VDD = 12 V, un signal
3,3 V du F28027 ne les franchira jamais). Remplacer par UCC27517
(seuils CMOS/TTL indépendants de VDD, même boîtier SOT-23-5, même
brochage) — vérifier la polarité (inverseur/non-inverseur) requise
selon le sens voulu sur EN
- [ ] TLV9151 : montage définitif à la place du MCP6001 de banc
### Firmware (TMS320F28027-dualboost, côté Claude Code / CCS)
- [ ] Reporter les gains/offsets mesurés (§1-3) dans `calib.h`, avec la
structure par voie recommandée (gain, offset, seuil) plutôt que des
constantes isolées
- [ ] Vérifier que le calcul de `DACVAL` est bien fait par macro à partir
des constantes, pas en dur
- [ ] Une fois le driver de grille corrigé et les composants de puissance
montés : test matériel de l'étape 2 du firmware (sécurité) avant
toute activation du PWM — critère : `TZFRC` force les sorties à 0,
un breakpoint coupe le PWM (TZ6/OSHT6), le flag ne se réarme pas
seul
### Points de configuration matérielle en attente de confirmation
- [ ] Fréquence de découpage par étage (100 ou 200 kHz)
- [ ] Polarité réelle des LED (bleue/rouge)
- [ ] Valeur d'inductance retenue par étage (47 ou 100 µH)
- [ ] État de la liaison JTAG / UART après le dernier diagnostic (mesure
du 1,8 V sur VDD broches 32/43, recâblage croisé RX/TX) — à
confirmer que tout fonctionne avant de reprendre les tests

View File

@ -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;
}

View File

@ -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
}

View File

@ -43,12 +43,20 @@ void bsp_gpio_control_init(void)
// HV_EN = GPIO32 (broche 31)
GpioCtrlRegs.GPBMUX1.bit.GPIO32 = 0;
GpioCtrlRegs.GPBDIR.bit.GPIO32 = 1;
// DISCHARGE = GPIO5 (broche 40)
GpioCtrlRegs.GPAMUX1.bit.GPIO5 = 0;
GpioCtrlRegs.GPADIR.bit.GPIO5 = 1;
EDIS;
// Etat sur avant toute autre configuration.
// Etat sur avant toute autre configuration. La decharge est laissee
// ACTIVE : au demarrage, le condensateur de sortie peut encore etre
// charge par une session precedente, rien ne justifie de l'inhiber
// avant que la conversion ne soit reellement demandee.
stage_enable_set(STAGE_1, false);
stage_enable_set(STAGE_2, false);
hv_enable_set(false);
hv_discharge_set(true);
}
void stage_enable_set(stage_id_t stage, bool enabled)
@ -89,6 +97,20 @@ void hv_enable_set(bool enabled)
}
}
void hv_discharge_set(bool active)
{
// Niveau BAS = decharge active (NPN bloque, grille du MOSFET libre).
// Voir bsp_gpio.h pour le detail de la polarite inversee.
if (active)
{
GpioDataRegs.GPACLEAR.bit.GPIO5 = 1;
}
else
{
GpioDataRegs.GPASET.bit.GPIO5 = 1;
}
}
void bsp_gpio_stage_default_override_init(void)
{
EALLOW;

View File

@ -36,6 +36,26 @@ void stage_enable_set(stage_id_t stage, bool enabled);
// donne PAS 0 V en sortie (PROMPT §6 etape 6).
void hv_enable_set(bool enabled);
// DISCHARGE (GPIO5, broche 40) : circuit de decharge rapide du condensateur
// de sortie, sur la carte de puissance. 47 kOhm en serie avec un MOSFET,
// soit ~2 s pour passer de 500 V a 50 V (tau = 0,94 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 -> MOSFET bloque
// -> decharge INHIBEE (fonctionnement normal)
// niveau BAS, ou broche non pilotee
// -> NPN bloque -> grille tiree a 12 V (chaine 3x1 MOhm
// depuis le +500 V, ecretee par Zener) -> MOSFET conduit
// -> decharge ACTIVE
//
// GPIO5 est une broche a fonction PWM : ses pull-ups ne sont PAS actives au
// reset (PROMPT §6 etape 3), elle flotte donc. La decharge est par
// consequent active au reset, pendant un plantage, et surtout ALIMENTATION
// COUPEE -- c'est-a-dire exactement le cas ou le firmware ne peut plus rien
// garantir et ou l'operateur croit la carte inoffensive.
void hv_discharge_set(bool active);
// 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.

View File

@ -96,7 +96,7 @@
// PROVISOIRE : seuil a confirmer selon la tenue reelle du MOSFET et
// l'implantation des NTC sur la carte.
// L'hysteresis evite que l'etat oscille autour du point de bascule.
#define SAFETY_OVERTEMP_C 85.0f
#define SAFETY_OVERTEMP_C 80.0f
#define SAFETY_OVERTEMP_HYST_C 10.0f
// ---- Timeout de la liaison ESP32 (PROMPT §6 etape 7) ----------------
@ -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
@ -133,6 +140,6 @@
((uint16_t)(((UART_LSPCLK_HZ + 4UL * UART_BAUD_RATE) / (8UL * UART_BAUD_RATE)) - 1U))
#define UART_RX_RING_SIZE 64U
#define UART_LINE_MAX 160U // limite cote ESP32 (g_lineBuf[160])
#define UART_LINE_MAX 200U // limite cote ESP32 (g_lineBuf[200])
#endif

121
src/control.c Normal file
View File

@ -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]);
}
}

32
src/control.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef CONTROL_H
#define CONTROL_H
#include <stdbool.h>
#include <stdint.h>
// 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

View File

@ -8,6 +8,7 @@
#include "adc.h"
#include "measure.h"
#include "status_led.h"
#include "control.h"
#include "calib.h"
extern uint16_t RamfuncsLoadStart;
@ -27,24 +28,80 @@ static volatile bool s_send_telemetry = false;
#define LINK_TIMEOUT_TICKS ((uint16_t)(UART_LINK_TIMEOUT_MS / TICK_MS))
static volatile uint16_t s_ticks_since_cmd = LINK_TIMEOUT_TICKS;
// Etat de surtemperature, avec hysteresis pour eviter le battement autour
// du seuil.
static bool s_overtemp = false;
// Surtemperature, suivie PAR VOIE : le protocole distingue les codes 4 (T1)
// et 5 (T2), un simple maximum ne permettrait pas de les separer.
// Hysteresis pour eviter le battement autour du seuil.
static bool s_overtemp_t1 = false;
static bool s_overtemp_t2 = false;
static bool update_one_overtemp(bool current, float temp_c)
{
if (!current)
{
return (temp_c >= SAFETY_OVERTEMP_C);
}
return (temp_c > (SAFETY_OVERTEMP_C - SAFETY_OVERTEMP_HYST_C));
}
static void update_overtemp(void)
{
float t1 = measure_temp(adc_get_raw(ADC_CH_T1));
float t2 = measure_temp(adc_get_raw(ADC_CH_T2));
float tmax = (t1 > t2) ? t1 : t2;
s_overtemp_t1 =
update_one_overtemp(s_overtemp_t1, measure_temp(adc_get_raw(ADC_CH_T1)));
s_overtemp_t2 =
update_one_overtemp(s_overtemp_t2, measure_temp(adc_get_raw(ADC_CH_T2)));
}
if (!s_overtemp)
// Sequence de demarrage de la conversion. Appelee au boot ET apres un
// EMUSTOP : dans les deux cas on repart du meme etat connu, on ne reprend
// jamais la conversion la ou elle s'etait arretee.
//
// Ordre impose : la decharge est inhibee AVANT d'autoriser les etages,
// sinon la conversion travaillerait contre le circuit de decharge.
static void start_conversion(void)
{
s_overtemp = (tmax >= SAFETY_OVERTEMP_C);
hv_discharge_set(false);
// Duty a zero d'abord : c'est le point de depart obligatoire, un
// condensateur de sortie vide provoquerait sinon un appel de courant
// destructeur (PROMPT §6 etape 6).
pwm_set_duty(STAGE_1, 0.0f);
pwm_set_duty(STAGE_2, 0.0f);
pwm_enable(STAGE_1, true);
pwm_enable(STAGE_2, true);
stage_enable_set(STAGE_1, true);
stage_enable_set(STAGE_2, true);
// Le duty est desormais pilote par control.c, qui repart du minimum.
// Les deux etages demarrent ensemble.
control_start();
}
else
// Priorite 2 > 3 > 4 > 5 > 1 : EMUSTOP est le code le moins prioritaire
// malgre son numero, pour ne jamais masquer une surintensite reelle.
static fault_code_t compute_fault_code(const safety_faults_t *f)
{
s_overtemp = (tmax > (SAFETY_OVERTEMP_C - SAFETY_OVERTEMP_HYST_C));
if (f->overcurrent && f->stage1_fault)
{
return FAULT_OVERCURRENT_I1;
}
if (f->overcurrent && f->stage2_fault)
{
return FAULT_OVERCURRENT_I2;
}
if (s_overtemp_t1)
{
return FAULT_OVERTEMP_T1;
}
if (s_overtemp_t2)
{
return FAULT_OVERTEMP_T2;
}
if (f->emustop)
{
return FAULT_EMUSTOP;
}
return FAULT_NONE;
}
// Etat sur : les deux etages inhibes ET la sortie HT coupee.
@ -55,11 +112,18 @@ static void update_overtemp(void)
// 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);
stage_enable_set(STAGE_2, false);
hv_enable_set(false);
// Decharge active. Elle vient APRES la coupure de HV_EN : la charge est
// d'abord isolee, puis le condensateur vide en ~2 s. Sans elle, les
// 400 V resteraient presents pres d'une minute sur le seul bleeder de
// 1 MOhm, sans aucune indication une fois l'alimentation coupee.
hv_discharge_set(true);
}
// Priorite : surintensite > surtemperature > EMUSTOP > liaison perdue.
@ -71,7 +135,7 @@ static led_state_t compute_led_state(const safety_faults_t *f)
{
return LED_STATE_OVERCURRENT;
}
if (s_overtemp)
if (s_overtemp_t1 || s_overtemp_t2)
{
return LED_STATE_OVERTEMP;
}
@ -121,18 +185,15 @@ 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();
// ---- 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);
// -----------------------------------------------------------------------
start_conversion();
EALLOW;
PieVectTable.TINT0 = &cpu_timer0_isr;
@ -176,14 +237,29 @@ void main(void)
// Aucun redemarrage automatique : rien ne reactive le PWM ensuite,
// meme si la temperature redescend sous l'hysteresis ou si le defaut
// disparait. Il faut un reset, ou une commande explicite (etape 8).
if (faults.stage1_fault || faults.stage2_fault || s_overtemp)
if (faults.overcurrent || s_overtemp_t1 || s_overtemp_t2)
{
// Defaut de PUISSANCE : verrouille jusqu'au cycle d'alimentation
// (PROMPT §8). Aucun acquittement, ni automatique ni par UART.
enter_safe_state();
}
else
else if (faults.emustop)
{
stage_enable_set(STAGE_1, true);
stage_enable_set(STAGE_2, true);
// EMUSTOP seul : ce n'est pas un defaut de puissance mais un
// artefact du debogueur. On autorise donc la reprise -- mais en
// repassant par la sequence de demarrage complete, jamais en
// reprenant la conversion la ou elle s'etait arretee.
//
// L'effacement ne peut aboutir qu'ici, CPU en marche : tant que
// le coeur est halte, EMUSTOP reste asserte et le drapeau se
// re-verrouille aussitot.
//
// La liaison est relancee aussi : pendant la halte l'ESP32 a
// continue d'emettre, l'anneau de reception a deborde et la
// ligne en cours est tronquee.
uart_link_restart();
safety_clear_faults();
start_conversion();
}
status_led_set_state(compute_led_state(&faults));
@ -199,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));
@ -210,6 +289,7 @@ void main(void)
t.i2_a = measure_i2(adc_get_raw(ADC_CH_I2));
t.t2_c = measure_temp(adc_get_raw(ADC_CH_T2));
t.iout_a = measure_iout(adc_get_raw(ADC_CH_IOUT));
t.fault = compute_fault_code(&faults);
uart_link_send_telemetry(&t);
s_send_telemetry = false;
@ -228,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();
@ -242,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;

View File

@ -6,6 +6,26 @@
// Squelette du protocole UART TMS320<->ESP32 (voir docs/ESP32-UART.md).
// Rempli/utilise a partir de l'etape 7 (uart_link.c).
// Codes du champ FAULT de la trame $T (docs/ESP32-UART.md).
//
// Attention a la priorite d'affichage quand plusieurs defauts coexistent :
// elle est 2 > 3 > 4 > 5 > 1, donc EMUSTOP a la priorite la PLUS BASSE
// malgre son numero. C'est voulu : en developpement EMUSTOP se declenche a
// chaque halte du debogueur et ne doit jamais masquer une surintensite.
//
// Il n'existe volontairement AUCUN code de court-circuit : un court-circuit
// franchit le meme comparateur et le meme seuil qu'une surintensite, le
// firmware ne dispose d'aucune information permettant de les distinguer.
typedef enum
{
FAULT_NONE = 0,
FAULT_EMUSTOP = 1,
FAULT_OVERCURRENT_I1 = 2,
FAULT_OVERCURRENT_I2 = 3,
FAULT_OVERTEMP_T1 = 4,
FAULT_OVERTEMP_T2 = 5
} fault_code_t;
typedef struct
{
float freq1_hz;
@ -21,6 +41,7 @@ typedef struct
float i2_a;
float t2_c;
float iout_a;
fault_code_t fault;
} telemetry_t;
typedef struct

View File

@ -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);

View File

@ -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).

View File

@ -68,6 +68,22 @@ void safety_init(void)
EPwm2Regs.TZSEL.bit.OSHT6 = 1;
EPwm2Regs.TBCTL.bit.FREE_SOFT = 0;
// Efface un trip herite d'AVANT ce demarrage. TZFLG.OST est latche dans
// le materiel : il survit a un reset logiciel comme a un simple
// rechargement de programme, et sans ce clear le PWM resterait coupe
// sans raison visible.
//
// Sans danger : a ce stade pwm_init() a laisse les deux sorties forcees
// 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.
EPwm1Regs.TZCLR.bit.DCAEVT1 = 1;
EPwm1Regs.TZCLR.bit.OST = 1;
EPwm1Regs.TZCLR.bit.INT = 1;
EPwm2Regs.TZCLR.bit.DCAEVT1 = 1;
EPwm2Regs.TZCLR.bit.OST = 1;
EPwm2Regs.TZCLR.bit.INT = 1;
PieVectTable.EPWM1_TZINT = &epwm1_tzint_isr;
PieVectTable.EPWM2_TZINT = &epwm2_tzint_isr;
EDIS;

View File

@ -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:

View File

@ -151,6 +151,50 @@ static int append_field(char *out, int room, const char *tag, float value,
return n;
}
void uart_link_restart(void)
{
// Reset logiciel du SCI : vide les FIFO et efface les etats d'erreur.
SciaRegs.SCICTL1.bit.SWRESET = 0;
SciaRegs.SCICTL1.bit.SWRESET = 1;
SciaRegs.SCIFFRX.bit.RXFIFORESET = 0;
SciaRegs.SCIFFRX.bit.RXFIFORESET = 1;
SciaRegs.SCIFFRX.bit.RXFFOVRCLR = 1;
SciaRegs.SCIFFRX.bit.RXFFINTCLR = 1;
SciaRegs.SCIFFTX.bit.TXFIFOXRESET = 0;
SciaRegs.SCIFFTX.bit.TXFIFOXRESET = 1;
// On jette le contenu de l'anneau en avancant seulement la QUEUE.
// s_rx_head appartient a l'ISR : ne pas y toucher evite toute course.
s_rx_tail = s_rx_head;
s_line_len = 0;
// Trame d'emission en cours abandonnee : elle date d'avant l'arret.
s_tx_len = 0;
s_tx_pos = 0;
}
// Champ entier (FAULT). Meme convention de bornage que append_field :
// renvoie 0 et n'ecrit rien si le champ ne tient pas.
static int append_field_int(char *out, int room, const char *tag, int value)
{
int n;
if (room <= 1)
{
return 0;
}
n = snprintf(out, (size_t)room, ",%s=%d", tag, value);
if (n < 0 || n >= room)
{
out[0] = '\0';
return 0;
}
return n;
}
// Construit la trame et la met en attente. NE BLOQUE PAS : l'emission reelle
// se fait ensuite par uart_link_service_tx(), appelee quand la boucle
// principale a du temps disponible. Renvoie false si la trame precedente
@ -195,6 +239,12 @@ bool uart_link_send_telemetry(const telemetry_t *t)
TX_ADD("T2", t->t2_c, 1);
TX_ADD("IOUT", t->iout_a, 2);
{
int added_ = append_field_int(&s_tx_frame[n], TX_ROOM(), "FAULT",
(int)t->fault);
if (added_ == 0) { fields_dropped++; } else { n += added_; }
}
#undef TX_ADD
#undef TX_ROOM

View File

@ -6,6 +6,16 @@
void uart_link_init(void);
// Remet la liaison a plat sans reconfigurer le baud ni les broches :
// reset logiciel du SCI, vidage des FIFO, abandon de la ligne en cours
// d'assemblage et de la trame en attente d'emission.
//
// A appeler apres une interruption prolongee du CPU (EMUSTOP) : pendant la
// halte l'ESP32 a continue d'emettre, l'anneau de reception a deborde et
// la ligne en cours est tronquee. Repartir propre evite de traiter des
// fragments anterieurs a l'arret.
void uart_link_restart(void);
// Construit une trame $T,...*XX et la met en attente (docs/ESP32-UART.md).
// NE BLOQUE PAS : rien n'est emis ici, c'est uart_link_service_tx() qui
// alimente la ligne ensuite. Renvoie false si la trame precedente n'est pas