Compare commits

...

2 Commits

Author SHA1 Message Date
d26c07d3a1 Etapes 3/4/6 : GPIO de commande, PWM et ADC valides sur cible
Campagne de bring-up materiel complete, carte de conversion DECONNECTEE.

pwm.c/h (etape 6) -- valide au scope
  ePWM1 -> etage 1, EPWM1A/GPIO0/broche 29, 200 kHz (TBPRD=299)
  ePWM2 -> etage 2, EPWM2A/GPIO2/broche 37, 100 kHz (TBPRD=599)
  Mode up-count, TBCLK = SYSCLKOUT = 60 MHz, duty 0 au demarrage,
  inhibition par forcage continu AQCSFRC. Tranche le point ouvert §9.1.
  HRPWM non implemente, notes de portage consignees dans pwm.c.

adc.c/h (etape 4) -- 9 voies validees, T1/T2 comprises
  Reference interne (ADCREFSEL=0), SOC declenches par ePWM1 SOCA avec
  SOCAPRD=3 (~66 kHz), jamais en free-run. ISR ADCINT1 courte : rangement
  des valeurs brutes uniquement, aucun flottant.
  Note materielle : les 9 voies lisaient 4095 tant que VDDA (broche 11)
  n'etait pas alimentee -- y compris VREFLO converti en interne, ce qui a
  permis d'ecarter le logiciel.

bsp_gpio (etape 3)
  HV_EN (GPIO32), Stage1/2-EN (GPIO16/17) en sortie, etat sur a 0, et
  pull-ups internes de GPIO16/17 desactivees : sinon elles presentent un 1
  sur une entree de chaque porte ET avant que le firmware ne tourne.
  bsp_gpio_stage_default_override_init() : pilotage manuel de GPIO1/GPIO3
  pour valider les portes ET, exclusif avec safety_init().

safety_arm_emustop_only() -- BRING-UP
  Arme TZ6 seul, sans les comparateurs, pour valider EMUSTOP isolement
  quand les entrees shunt ne sont pas cablees.

Resultats materiels :
  - portes ET IC8/IC9 : sortie = ET logique des deux entrees, verifie au
    scope avec des cadences distinctes (default 400 ms, EN 1 s)
  - comparateur : entree shunt 0 -> 3 V declenche bien le trip
  - EMUSTOP : arret du debugger -> broches 29 et 37 a 0, TZFLG.OST latche,
    et le flag ne se rearme pas apres continue
  - un trip EMUSTOP ne peut PAS etre efface tant que le CPU est halte (la
    source reste assertee) : l'effacement devra passer par une commande
    UART en fonctionnement (etape 7)

main.c contient encore le bloc de bring-up (PWM force a 50 %, telemetrie
transportant les tensions ADC brutes en volts) : A RETIRER, et remplacer
safety_arm_emustop_only() par safety_init(), avant toute mise sous tension
de la puissance (PROMPT §8).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 20:42:14 +02:00
d185d73422 Etape 7 : liaison UART SCI-A 57600 8N1 validee sur cible
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>
2026-08-02 18:38:18 +02:00
16 changed files with 1166 additions and 48 deletions

View File

@ -22,8 +22,8 @@
<storageModule moduleId="cdtBuildSystem" version="4.0.0"> <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"> <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=""> <folderInfo id="com.ti.ccstudio.buildDefinitions.C2000.Debug.1909800144.847706334" name="/" resourcePath="">
<toolChain id="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.DebugToolchain.664403306" name="TI Build Tools" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.DebugToolchain" targetTool="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.linkerDebug.1235779316"> <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.1554114453" superClass="com.ti.ccstudio.buildDefinitions.core.OPT_TAGS" valueType="stringList"> <option id="com.ti.ccstudio.buildDefinitions.core.OPT_TAGS.1144356066" superClass="com.ti.ccstudio.buildDefinitions.core.OPT_TAGS" valueType="stringList">
<listOptionValue value="DEVICE_CONFIGURATION_ID=TMS320C28XX.TMS320F28027"/> <listOptionValue value="DEVICE_CONFIGURATION_ID=TMS320C28XX.TMS320F28027"/>
<listOptionValue value="DEVICE_CORE_ID="/> <listOptionValue value="DEVICE_CORE_ID="/>
<listOptionValue value="DEVICE_ENDIANNESS=UNKNOWN"/> <listOptionValue value="DEVICE_ENDIANNESS=UNKNOWN"/>
@ -38,14 +38,14 @@
<listOptionValue value="LINK_ORDER="/> <listOptionValue value="LINK_ORDER="/>
<listOptionValue value="PRODUCTS=C2000WARE:26.1.0.00;"/> <listOptionValue value="PRODUCTS=C2000WARE:26.1.0.00;"/>
</option> </option>
<option id="com.ti.ccstudio.buildDefinitions.core.OPT_CODEGEN_VERSION.474454367" superClass="com.ti.ccstudio.buildDefinitions.core.OPT_CODEGEN_VERSION" value="25.11.1.LTS" valueType="string"/> <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.960982070" name="Platform" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.targetPlatformDebug"/> <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.557038836" keepEnvironmentInBuildfile="false" name="GNU Make" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.builderDebug"/> <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.2141707820" name="C2000 Compiler" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.compilerDebug"> <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.400788932" 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.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.110360032" 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.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.1781053235" 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.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.563309171" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.INCLUDE_PATH" valueType="includePath"> <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">
<listOptionValue value="${COM_TI_C2000WARE_INCLUDE_PATH}"/> <listOptionValue value="${COM_TI_C2000WARE_INCLUDE_PATH}"/>
<listOptionValue value="${CG_TOOL_ROOT}/include"/> <listOptionValue value="${CG_TOOL_ROOT}/include"/>
<listOptionValue value="${INSTALLROOT_F2802x}/headers/include"/> <listOptionValue value="${INSTALLROOT_F2802x}/headers/include"/>
@ -53,35 +53,35 @@
<listOptionValue value="${INSTALLROOT_F2802x}/common/include"/> <listOptionValue value="${INSTALLROOT_F2802x}/common/include"/>
<listOptionValue value="${INSTALLROOT_IQMATH}/include"/> <listOptionValue value="${INSTALLROOT_IQMATH}/include"/>
</option> </option>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DEFINE.771081258" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DEFINE" valueType="definedSymbols"> <option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DEFINE.1614629519" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DEFINE" valueType="definedSymbols">
<listOptionValue value="${COM_TI_C2000WARE_SYMBOLS}"/> <listOptionValue value="${COM_TI_C2000WARE_SYMBOLS}"/>
<listOptionValue value="_DEBUG"/> <listOptionValue value="_DEBUG"/>
<listOptionValue value="LARGE_MODEL"/> <listOptionValue value="LARGE_MODEL"/>
<listOptionValue value="_FLASH"/> <listOptionValue value="_FLASH"/>
</option> </option>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DEBUGGING_MODEL.1668491955" 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.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.83242442" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DIAG_SUPPRESS" valueType="stringList"> <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">
<listOptionValue value="232"/> <listOptionValue value="232"/>
<listOptionValue value="10063"/> <listOptionValue value="10063"/>
</option> </option>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DIAG_WARNING.1274415402" 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.1851392201" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.DIAG_WARNING" valueType="stringList">
<listOptionValue value="225"/> <listOptionValue value="225"/>
</option> </option>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.compilerID.ISSUE_REMARKS.186583961" 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.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.318737604" 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.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.8063966" 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.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.1114196468" 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.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"/>
</tool> </tool>
<tool id="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.linkerDebug.1235779316" name="C2000 Linker" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.linkerDebug"> <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.1733044792" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.STACK_SIZE" value="0x200" valueType="string"/> <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.647012608" 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.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.813073460" 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.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.528423534" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.LIBRARY" valueType="libs"> <option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.LIBRARY.397023344" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.LIBRARY" valueType="libs">
<listOptionValue value="${COM_TI_C2000WARE_LIBRARIES}"/> <listOptionValue value="${COM_TI_C2000WARE_LIBRARIES}"/>
<listOptionValue value="rts2800_ml.lib"/> <listOptionValue value="rts2800_ml.lib"/>
<listOptionValue value="IQmath.lib"/> <listOptionValue value="IQmath.lib"/>
</option> </option>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.SEARCH_PATH.915839469" 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.567141830" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.SEARCH_PATH" valueType="libPaths">
<listOptionValue value="${COM_TI_C2000WARE_LIBRARY_PATH}"/> <listOptionValue value="${COM_TI_C2000WARE_LIBRARY_PATH}"/>
<listOptionValue value="${CG_TOOL_ROOT}/lib"/> <listOptionValue value="${CG_TOOL_ROOT}/lib"/>
<listOptionValue value="${CG_TOOL_ROOT}/include"/> <listOptionValue value="${CG_TOOL_ROOT}/include"/>
@ -89,12 +89,12 @@
<listOptionValue value="${INSTALLROOT_F2802x}/common/lib"/> <listOptionValue value="${INSTALLROOT_F2802x}/common/lib"/>
<listOptionValue value="${INSTALLROOT_IQMATH}/lib"/> <listOptionValue value="${INSTALLROOT_IQMATH}/lib"/>
</option> </option>
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.ISSUE_REMARKS.660341548" 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.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.183992281" 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.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.1900510778" 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.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.1884395562" 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.ENTRY_POINT.511960855" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.ENTRY_POINT" value="code_start" valueType="string"/>
</tool> </tool>
<tool id="com.ti.ccstudio.buildDefinitions.C2000_25.11.hex.165990339" name="C2000 Hex Utility" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.hex"/> <tool id="com.ti.ccstudio.buildDefinitions.C2000_25.11.hex.813831478" name="C2000 Hex Utility" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.hex"/>
</toolChain> </toolChain>
</folderInfo> </folderInfo>
<sourceEntries> <sourceEntries>

4
.gitignore vendored
View File

@ -9,3 +9,7 @@ Release/
# IDE-generated, machine-specific (hardcoded local tool paths) # IDE-generated, machine-specific (hardcoded local tool paths)
.mcp.json .mcp.json
CLAUDE.md CLAUDE.md
# Outillage agent (local, non partage)
.claude/
.codex/

16
.theia/launch.json Normal file
View File

@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
"version": "0.2.0",
"configurations": [
{
"name": "TMS320F28027-dualboost",
"type": "ccs-debug",
"request": "launch",
"projectInfo": {
"name": "TMS320F28027-dualboost",
"resourceId": "/TMS320F28027-dualboost"
}
}
]
}

View File

@ -99,11 +99,16 @@ PAGE 0: /* Program Memory */
RAMM0 : origin = 0x000050, length = 0x0003B0 /* on-chip RAM block M0 */ RAMM0 : origin = 0x000050, length = 0x0003B0 /* on-chip RAM block M0 */
OTP : origin = 0x3D7800, length = 0x000400 /* on-chip OTP */ OTP : origin = 0x3D7800, length = 0x000400 /* on-chip OTP */
FLASHA : origin = 0x3F7000, length = 0x000F80 /* on-chip FLASH */ /* Les 4 secteurs flash du F28027 (32K mots au total, cf. common/cmd/F28027.cmd
de C2000Ware). Le fichier "generic" d'origine ne declarait que le secteur A
(~8K), ce qui faisait deborder le link des que le code grossissait. */
FLASHD : origin = 0x3F0000, length = 0x002000 /* on-chip FLASH */
FLASHC : origin = 0x3F2000, length = 0x002000 /* on-chip FLASH */
FLASHB : origin = 0x3F4000, length = 0x002000 /* on-chip FLASH */
FLASHA : origin = 0x3F6000, length = 0x001F80 /* on-chip FLASH */
CSM_RSVD : origin = 0x3F7F80, length = 0x000076 /* Part of FLASHA. Program with all 0x0000 when CSM is in use. */ CSM_RSVD : origin = 0x3F7F80, length = 0x000076 /* Part of FLASHA. Program with all 0x0000 when CSM is in use. */
BEGIN : origin = 0x3F7FF6, length = 0x000002 /* Part of FLASHA. Used for "boot to Flash" bootloader mode. */ BEGIN : origin = 0x3F7FF6, length = 0x000002 /* Part of FLASHA. Used for "boot to Flash" bootloader mode. */
CSM_PWL_P0 : origin = 0x3F7FF8, length = 0x000008 /* Part of FLASHA. CSM password locations in FLASHA */ CSM_PWL_P0 : origin = 0x3F7FF8, length = 0x000008 /* Part of FLASHA. CSM password locations in FLASHA */
FLASHB : origin = 0x3F6000, length = 0x001000 /* on-chip FLASH */
@ -121,7 +126,7 @@ PAGE 1 : /* Data Memory */
BOOT_RSVD : origin = 0x000000, length = 0x000050 /* Part of M0, BOOT rom will use this for stack */ BOOT_RSVD : origin = 0x000000, length = 0x000050 /* Part of M0, BOOT rom will use this for stack */
RAMM1 : origin = 0x000400, length = 0x000400 /* on-chip RAM block M1 */ RAMM1 : origin = 0x000400, length = 0x000400 /* on-chip RAM block M1 */
RAML0 : origin = 0x008000, length = 0x000400 /* on-chip RAM block L0 */ RAML0 : origin = 0x008000, length = 0x001000 /* on-chip RAM block L0 (4K mots sur F28027) */
} }
@ -145,25 +150,29 @@ SECTIONS
RUN_START(_RamfuncsRunStart), RUN_START(_RamfuncsRunStart),
PAGE = 0 PAGE = 0
.cinit : > FLASHA | FLASHB, PAGE = 0 .cinit : > FLASHA | FLASHB | FLASHC | FLASHD, PAGE = 0
.pinit : > FLASHA | FLASHB, PAGE = 0 .pinit : > FLASHA | FLASHB | FLASHC | FLASHD, PAGE = 0
.text : >> FLASHA | FLASHB, PAGE = 0 .text : >> FLASHA | FLASHB | FLASHC | FLASHD, PAGE = 0
csmpasswds : > CSM_PWL_P0, PAGE = 0 csmpasswds : > CSM_PWL_P0, PAGE = 0
csm_rsvd : > CSM_RSVD, PAGE = 0 csm_rsvd : > CSM_RSVD, PAGE = 0
/* Allocate uninitalized data sections: */ /* Allocate uninitalized data sections: */
/* .ebss place en RAML0 EN PREMIER (et non RAMM1) : sinon les globales se
retrouvent collees juste apres .stack, et un debordement de pile les
ecrase silencieusement au lieu de tomber dans du vide. RAMM1 est ainsi
reserve a la pile seule. */
.stack : > RAMM1, PAGE = 1 .stack : > RAMM1, PAGE = 1
.ebss : >> RAMM1 | RAML0, PAGE = 1 .ebss : >> RAML0 | RAMM1, PAGE = 1
.esysmem : >> RAMM1 | RAML0, PAGE = 1 .esysmem : >> RAML0 | RAMM1, PAGE = 1
/* Initalized sections go in Flash */ /* Initalized sections go in Flash */
/* For SDFlash to program these, they must be allocated to page 0 */ /* For SDFlash to program these, they must be allocated to page 0 */
.econst : >> FLASHA | FLASHB, PAGE = 0 .econst : >> FLASHA | FLASHB | FLASHC | FLASHD, PAGE = 0
.switch : >> FLASHA | FLASHB, PAGE = 0 .switch : >> FLASHA | FLASHB | FLASHC | FLASHD, PAGE = 0
/* Allocate IQ math areas: */ /* Allocate IQ math areas: */
IQmath : >> FLASHA | FLASHB, PAGE = 0 /* Math Code */ IQmath : >> FLASHA | FLASHB | FLASHC | FLASHD, PAGE = 0 /* Math Code */
IQmathTables : > IQTABLES, PAGE = 0, TYPE = NOLOAD IQmathTables : > IQTABLES, PAGE = 0, TYPE = NOLOAD
/* Uncomment the section below if calling the IQNexp() or IQexp() /* Uncomment the section below if calling the IQNexp() or IQexp()

141
src/adc.c Normal file
View File

@ -0,0 +1,141 @@
#include "DSP28x_Project.h"
#include "adc.h"
#include "calib.h"
// Valeurs du champ CHSEL (TRM SPRUI09A) : groupe A = 0x0..0x7,
// groupe B = 0x8..0xF. L'ordre suit adc_channel_t / SOC0..SOC8.
static const uint16_t k_chsel[ADC_CH_COUNT] = {
0x0, // ADCINA0 - VIN
0x1, // ADCINA1 - IIN
0x2, // ADCINA2 - I1 (shunt etage 1)
0x3, // ADCINA3 - IOUT
0x4, // ADCINA4 - I2 (shunt etage 2)
0xA, // ADCINB2 - VOUT
0xC, // ADCINB4 - V1
0xE, // ADCINB6 - T1
0xF // ADCINB7 - T2
};
static volatile uint16_t s_raw[ADC_CH_COUNT];
static volatile uint32_t s_seq_count = 0;
interrupt void adc_int1_isr(void);
void adc_init(void)
{
uint16_t i;
EALLOW;
SysCtrlRegs.PCLKCR0.bit.ADCENCLK = 1;
// Sequence de mise sous tension du bloc analogique (f2802x_adc.c).
AdcRegs.ADCCTL1.bit.ADCBGPWD = 1;
AdcRegs.ADCCTL1.bit.ADCREFPWD = 1;
AdcRegs.ADCCTL1.bit.ADCPWDN = 1;
AdcRegs.ADCCTL1.bit.ADCENABLE = 1;
AdcRegs.ADCCTL1.bit.ADCREFSEL = 0; // reference interne, pleine echelle 3,3 V
EDIS;
DELAY_US(1000L); // stabilisation de la reference avant toute conversion
EALLOW;
// Impulsion d'interruption en fin de conversion, quand le resultat est
// effectivement dans ADCRESULTx (et non en debut de conversion).
AdcRegs.ADCCTL1.bit.INTPULSEPOS = 1;
// ADCCLK = SYSCLKOUT/2 = 30 MHz, et non-overlap : le mode overlap
// raccourcit la fenetre d'echantillonnage, inutile ici.
AdcRegs.ADCCTL2.bit.CLKDIV2EN = 1;
AdcRegs.ADCCTL2.bit.ADCNONOVERLAP = 1;
// Les ADCSOCxCTL sont des registres 16 bits contigus : on les parcourt
// par indexation directe de l'union.
for (i = 0U; i < (uint16_t)ADC_CH_COUNT; i++)
{
volatile union ADCSOCxCTL_REG *soc = (&AdcRegs.ADCSOC0CTL) + i;
soc->bit.CHSEL = k_chsel[i];
soc->bit.TRIGSEL = 5U; // ePWM1 SOCA
soc->bit.ACQPS = ADC_ACQPS_CYCLES; // fenetre S/H
}
// ADCINT1 declenchee par la fin du dernier SOC de la sequence.
AdcRegs.INTSEL1N2.bit.INT1SEL = (uint16_t)ADC_CH_COUNT - 1U;
AdcRegs.INTSEL1N2.bit.INT1CONT = 0;
AdcRegs.INTSEL1N2.bit.INT1E = 1;
PieVectTable.ADCINT1 = &adc_int1_isr;
EDIS;
IER |= M_INT1;
PieCtrlRegs.PIEIER1.bit.INTx1 = 1; // ADCINT1
// Declenchement par ePWM1 : SOCA sur passage a zero du compteur.
// SOCAPRD = 3 -> une sequence tous les 3 cycles PWM. A 200 kHz cela fait
// ~66 kHz, largement suffisant pour la telemetrie et la surveillance,
// sans saturer le CPU d'interruptions.
EALLOW;
EPwm1Regs.ETSEL.bit.SOCASEL = 1; // ET_CTR_ZERO
EPwm1Regs.ETPS.bit.SOCAPRD = 3;
EPwm1Regs.ETSEL.bit.SOCAEN = 1;
EDIS;
}
uint16_t adc_get_raw(adc_channel_t ch)
{
if (ch >= ADC_CH_COUNT)
{
return 0U;
}
return s_raw[ch];
}
float adc_get_volts(adc_channel_t ch)
{
if (ch >= ADC_CH_COUNT)
{
return 0.0f;
}
return (float)s_raw[ch] * ADC_VREF_V / 4096.0f;
}
bool adc_any_below(float volts)
{
uint16_t threshold = (uint16_t)(volts / ADC_VREF_V * 4096.0f + 0.5f);
uint16_t i;
for (i = 0U; i < (uint16_t)ADC_CH_COUNT; i++)
{
if (s_raw[i] < threshold)
{
return true;
}
}
return false;
}
uint32_t adc_get_sequence_count(void)
{
return s_seq_count;
}
// ISR courte : rangement des valeurs brutes, rien d'autre. Aucune conversion
// physique, aucun flottant (PROMPT §7). La protection rapide ne passe PAS
// par l'ADC, c'est le comparateur qui s'en charge.
interrupt void adc_int1_isr(void)
{
// Les resultats sont dans un bloc distinct (AdcResult), a une autre
// adresse que les registres de controle : ADCRESULT0..15 contigus.
volatile uint16_t *res = &AdcResult.ADCRESULT0;
uint16_t i;
for (i = 0U; i < (uint16_t)ADC_CH_COUNT; i++)
{
s_raw[i] = res[i];
}
s_seq_count++;
AdcRegs.ADCINTFLGCLR.bit.ADCINT1 = 1;
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}

46
src/adc.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef ADC_H
#define ADC_H
#include <stdbool.h>
#include <stdint.h>
// Ordre des voies = ordre des SOC0..SOC8. Brochage LQFP48 PT, cf. PROMPT §2.
typedef enum
{
ADC_CH_VIN = 0, // broche 10, ADCINA0 (partagee avec VREFHI)
ADC_CH_IIN, // broche 8, ADCINA1
ADC_CH_I1, // broche 9, ADCINA2 / COMP1A / AIO2
ADC_CH_IOUT, // broche 7, ADCINA3
ADC_CH_I2, // broche 5, ADCINA4 / COMP2A / AIO4
ADC_CH_VOUT, // broche 14, ADCINB2 / AIO10
ADC_CH_V1, // broche 16, ADCINB4 / AIO12
ADC_CH_T1, // broche 17, ADCINB6 / AIO14
ADC_CH_T2, // broche 18, ADCINB7
ADC_CH_COUNT
} adc_channel_t;
// Reference interne (ADCREFSEL = 0, pleine echelle 3,3 V) : imposee car
// VREFHI est partagee avec ADCINA0, utilisee pour VIN (PROMPT §2).
// Les SOC sont declenches par ePWM1, jamais en free-run, pour echantillonner
// a un instant stable loin des fronts de commutation (PROMPT §6 etape 4).
//
// A appeler APRES bsp_gpio_analog_init() (bascule des AIO en mode analogique)
// et APRES pwm_init() (c'est ePWM1 qui fournit le declenchement).
void adc_init(void);
// Derniere valeur brute (0..4095) rangee par l'ISR ADCINT1.
uint16_t adc_get_raw(adc_channel_t ch);
// Meme valeur convertie en volts a l'entree de la broche (0..3,3 V).
// Ce n'est PAS la grandeur physique : la conversion vers V/A/°C (ponts
// diviseurs, gain de shunt, NTC) sera le role de measure.c.
float adc_get_volts(adc_channel_t ch);
// true si au moins une voie est sous le seuil donne, exprime en volts.
bool adc_any_below(float volts);
// Compteur de conversions completes, pour verifier au debogueur que les SOC
// tournent bien.
uint32_t adc_get_sequence_count(void);
#endif

View File

@ -26,6 +26,111 @@ void bsp_gpio_analog_init(void)
EDIS; EDIS;
} }
void bsp_gpio_control_init(void)
{
EALLOW;
// Stage1-EN = GPIO16 (broche 27), Stage2-EN = GPIO17 (broche 26).
// GPIO16/17 ne sont pas des broches PWM : leurs pull-ups sont actives au
// reset, ce qui presente un 1 sur une entree de chaque porte ET avant que
// le firmware ne tourne. On les coupe des l'init (PROMPT §6 etape 3).
GpioCtrlRegs.GPAPUD.bit.GPIO16 = 1;
GpioCtrlRegs.GPAPUD.bit.GPIO17 = 1;
GpioCtrlRegs.GPAMUX2.bit.GPIO16 = 0;
GpioCtrlRegs.GPAMUX2.bit.GPIO17 = 0;
GpioCtrlRegs.GPADIR.bit.GPIO16 = 1;
GpioCtrlRegs.GPADIR.bit.GPIO17 = 1;
// HV_EN = GPIO32 (broche 31)
GpioCtrlRegs.GPBMUX1.bit.GPIO32 = 0;
GpioCtrlRegs.GPBDIR.bit.GPIO32 = 1;
EDIS;
// Etat sur avant toute autre configuration.
stage_enable_set(STAGE_1, false);
stage_enable_set(STAGE_2, false);
hv_enable_set(false);
}
void stage_enable_set(stage_id_t stage, bool enabled)
{
if (stage == STAGE_1)
{
if (enabled)
{
GpioDataRegs.GPASET.bit.GPIO16 = 1;
}
else
{
GpioDataRegs.GPACLEAR.bit.GPIO16 = 1;
}
}
else
{
if (enabled)
{
GpioDataRegs.GPASET.bit.GPIO17 = 1;
}
else
{
GpioDataRegs.GPACLEAR.bit.GPIO17 = 1;
}
}
}
void hv_enable_set(bool enabled)
{
if (enabled)
{
GpioDataRegs.GPBSET.bit.GPIO32 = 1;
}
else
{
GpioDataRegs.GPBCLEAR.bit.GPIO32 = 1;
}
}
void bsp_gpio_stage_default_override_init(void)
{
EALLOW;
// GPIO1 = COMP1OUT (broche 28), GPIO3 = COMP2OUT (broche 38).
// Mux 0 = GPIO : on reprend la main sur des broches normalement pilotees
// par les comparateurs. Bring-up uniquement, voir bsp_gpio.h.
GpioCtrlRegs.GPAMUX1.bit.GPIO1 = 0;
GpioCtrlRegs.GPAMUX1.bit.GPIO3 = 0;
GpioCtrlRegs.GPADIR.bit.GPIO1 = 1;
GpioCtrlRegs.GPADIR.bit.GPIO3 = 1;
EDIS;
stage_default_set(STAGE_1, false);
stage_default_set(STAGE_2, false);
}
void stage_default_set(stage_id_t stage, bool level)
{
if (stage == STAGE_1)
{
if (level)
{
GpioDataRegs.GPASET.bit.GPIO1 = 1;
}
else
{
GpioDataRegs.GPACLEAR.bit.GPIO1 = 1;
}
}
else
{
if (level)
{
GpioDataRegs.GPASET.bit.GPIO3 = 1;
}
else
{
GpioDataRegs.GPACLEAR.bit.GPIO3 = 1;
}
}
}
void led_set(led_id_t led, bool on) void led_set(led_id_t led, bool on)
{ {
#if LED_ACTIVE_LOW #if LED_ACTIVE_LOW

View File

@ -9,6 +9,12 @@ typedef enum
LED_RED LED_RED
} led_id_t; } led_id_t;
typedef enum
{
STAGE_1 = 0,
STAGE_2
} stage_id_t;
void bsp_gpio_leds_init(void); void bsp_gpio_leds_init(void);
void led_set(led_id_t led, bool on); void led_set(led_id_t led, bool on);
@ -16,4 +22,32 @@ void led_set(led_id_t led, bool on);
// COMP1A/COMP2A, VOUT, V1, NTC1). A appeler avant safety_init()/adc_init(). // COMP1A/COMP2A, VOUT, V1, NTC1). A appeler avant safety_init()/adc_init().
void bsp_gpio_analog_init(void); void bsp_gpio_analog_init(void);
// Sorties de commande dans leur etat sur : HV_EN, Stage1-EN et Stage2-EN a 0.
// Desactive aussi les pull-ups internes de GPIO16/17, qui sinon presentent un
// niveau haut sur une entree de chaque porte ET des la mise sous tension
// (PROMPT §6 etape 3).
void bsp_gpio_control_init(void);
// Stagex-EN : entree "autorisation logicielle" de la porte ET de l'etage.
void stage_enable_set(stage_id_t stage, bool enabled);
// HV_EN (GPIO32) : commande de la sortie HT via l'optocoupleur VOM1271.
// Seul organe qui isole reellement la charge -- un boost a 0 % de duty ne
// donne PAS 0 V en sortie (PROMPT §6 etape 6).
void hv_enable_set(bool enabled);
// BRING-UP UNIQUEMENT -- bascule Stage1/2-default (GPIO1/GPIO3) en sortie
// GPIO pilotee par logiciel, pour verifier le cablage des portes ET sans
// etage de puissance actif. Laisse les deux broches a 0.
//
// INCOMPATIBLE avec safety_init(), qui mux ces memes broches vers COMP1OUT /
// COMP2OUT : en fonctionnement normal ce sont les comparateurs qui les
// pilotent, et le logiciel ne doit JAMAIS pouvoir outrepasser la protection
// materielle (PROMPT §2 et §8). Ne jamais appeler les deux.
void bsp_gpio_stage_default_override_init(void);
// Pilote une broche Stagex-default apres l'override ci-dessus. N'ecrit que
// GPASET/GPACLEAR (non proteges par EALLOW) : utilisable depuis une ISR.
void stage_default_set(stage_id_t stage, bool level);
#endif #endif

View File

@ -9,7 +9,10 @@
#define CLK_SYSCLKOUT_HZ 60000000UL #define CLK_SYSCLKOUT_HZ 60000000UL
// LEDs (LQFP48 PT) : bleue = GPIO12 (broche 47), rouge = GPIO33 (broche 36) // LEDs (LQFP48 PT) : bleue = GPIO12 (broche 47), rouge = GPIO33 (broche 36)
#define LED_ACTIVE_LOW 1 // Point ouvert §9 du prompt : polarite a confirmer au premier test.
// Observation bring-up (carte 2) : GPIO33 haut = LED rouge visuellement allumee
// -> cablage actif-haut, pas actif-bas comme suppose initialement.
#define LED_ACTIVE_LOW 0
// Valeur de champ AIOMUX1 pour basculer une broche AIOx en mode analogique // Valeur de champ AIOMUX1 pour basculer une broche AIOx en mode analogique
#define GPIO_ANALOG_MODE 2 #define GPIO_ANALOG_MODE 2
@ -21,4 +24,35 @@
#define SAFETY_DAC_CODE \ #define SAFETY_DAC_CODE \
((uint16_t)((SAFETY_ISHUNT_THRESHOLD_A * 0.6f) / SAFETY_DAC_VREF_V * 1023.0f + 0.5f)) ((uint16_t)((SAFETY_ISHUNT_THRESHOLD_A * 0.6f) / SAFETY_DAC_VREF_V * 1023.0f + 0.5f))
// ADC : reference interne obligatoire (VREFHI partage avec ADCINA0/VIN),
// pleine echelle 3,3 V. Conversion brut -> volts : V = raw * 3.3 / 4096.
#define ADC_VREF_V 3.3f
// Fenetre d'echantillonnage : ACQPS + 1 cycles ADCCLK (30 MHz ici).
// 25 -> 26 cycles ~ 0,87 us, confortable meme avec une source de quelques
// kilo-ohms (ponts diviseurs, pull-down de test).
#define ADC_ACQPS_CYCLES 25
// Seuil du test de bring-up ADC : LED bleue si une voie passe SOUS cette
// valeur, rouge sinon. 300 mV = 372 LSB en pleine echelle 3,3 V.
#define ADC_TEST_THRESHOLD_V 0.3f
// Frequences de decoupage par etage (point ouvert §9.1 du PROMPT, tranche au
// bring-up). TBCLK = SYSCLKOUT = 60 MHz, TBPRD = SYSCLKOUT/Fpwm - 1 :
// etage 1 : 200 kHz -> TBPRD = 299
// etage 2 : 100 kHz -> TBPRD = 599
#define PWM_STAGE1_FREQ_HZ 200000UL
#define PWM_STAGE2_FREQ_HZ 100000UL
// Liaison UART SCI-A (voir docs/ESP32-UART.md) : 57600 8N1.
// LSPCLK = SYSCLKOUT/4 (LOSPCP laisse a sa valeur par defaut par InitSysCtrl).
#define UART_BAUD_RATE 57600UL
#define UART_LSPCLK_HZ (CLK_SYSCLKOUT_HZ / 4UL)
// BRR = round(LSPCLK / (8 * baud)) - 1 (TRM SPRUI09A, registre SCIHBAUD:SCILBAUD)
#define UART_SCIBRR \
((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])
#endif #endif

View File

@ -2,6 +2,11 @@
#include "bsp_clock.h" #include "bsp_clock.h"
#include "bsp_gpio.h" #include "bsp_gpio.h"
#include "safety.h" #include "safety.h"
#include "uart_link.h"
#include "protocol.h"
#include "pwm.h"
#include "adc.h"
#include "calib.h"
extern uint16_t RamfuncsLoadStart; extern uint16_t RamfuncsLoadStart;
extern uint16_t RamfuncsLoadSize; extern uint16_t RamfuncsLoadSize;
@ -9,6 +14,11 @@ extern uint16_t RamfuncsRunStart;
interrupt void cpu_timer0_isr(void); interrupt void cpu_timer0_isr(void);
// Bring-up etape 7 (UART) : dernier CommandState recu, pour inspection au
// debogueur. Valeurs de telemetrie figees tant que measure.c n'existe pas.
static volatile command_state_t g_last_cmd = {false, false, false};
static volatile bool s_send_telemetry = false;
void main(void) void main(void)
{ {
#ifdef _FLASH #ifdef _FLASH
@ -25,14 +35,47 @@ void main(void)
InitPieVectTable(); InitPieVectTable();
bsp_gpio_analog_init(); bsp_gpio_analog_init();
safety_init(); bsp_gpio_control_init();
// Ordre d'init (PROMPT §7) : GPIO en etat sur -> AIOMUX1 -> horloge ->
// securite -> ADC -> UART -> PWM en dernier.
//
// pwm_init() est appele AVANT safety_init() pour une raison materielle :
// il active PCLKCR1.EPWMxENCLK, sans quoi les ecritures de safety_init()
// dans les registres ePWM (TZSEL, TZCTL, DCTRIPSEL) seraient perdues.
// L'esprit de la regle est respecte : pwm_init() laisse les deux sorties
// forcees a l'etat bas (AQCSFRC) et le duty a 0, donc rien ne sort tant
// que le Trip Zone n'est pas arme.
pwm_init();
// safety_init() complet volontairement NON appele : le comparateur a
// deja ete valide (trip observe en passant une entree de 0 a 3 V), et
// avec les entrees shunt flottantes il declencherait aussitot, ce qui
// masquerait le test d'EMUSTOP. On arme donc uniquement TZ6.
// A REMPLACER par safety_init() avant toute mise sous tension de la
// puissance (PROMPT §8).
safety_arm_emustop_only();
// ADC : declenche par ePWM1, donc apres pwm_init().
adc_init();
uart_link_init();
// ---- BRING-UP : retest PWM + ADC, puissance DECONNECTEE ---------------
// A RETIRER avant toute mise sous tension de la carte de conversion.
// HV_EN et Stagex-EN restent a 0 (exclus de ce test).
pwm_set_duty(STAGE_1, 0.5f);
pwm_set_duty(STAGE_2, 0.5f);
pwm_enable(STAGE_1, true);
pwm_enable(STAGE_2, true);
// -----------------------------------------------------------------------
EALLOW; EALLOW;
PieVectTable.TINT0 = &cpu_timer0_isr; PieVectTable.TINT0 = &cpu_timer0_isr;
EDIS; EDIS;
InitCpuTimers(); InitCpuTimers();
ConfigCpuTimer(&CpuTimer0, 60, 500000); // 500 ms -> 1 Hz sur la LED ConfigCpuTimer(&CpuTimer0, 60, 10000); // base 10 ms (voir cpu_timer0_isr)
StartCpuTimer0(); // ConfigCpuTimer laisse TSS=1 (timer a l'arret) par conception
IER |= M_INT1; IER |= M_INT1;
PieCtrlRegs.PIEIER1.bit.INTx7 = 1; // TINT0 PieCtrlRegs.PIEIER1.bit.INTx7 = 1; // TINT0
@ -42,19 +85,74 @@ void main(void)
for (;;) for (;;)
{ {
// Etape 1-2 : validation clock/LED/securite. Rien d'autre pour command_state_t cmd;
// l'instant, tout se passe dans les ISR. safety_faults_t faults;
if (uart_link_poll(&cmd))
{
g_last_cmd = cmd;
}
// Test EMUSTOP : LED rouge = defaut latche, bleue = nominal.
// Le flag ne se rearme jamais seul (PROMPT §8).
faults = safety_get_fault_flags();
{
bool tripped = faults.stage1_fault || faults.stage2_fault;
led_set(LED_RED, tripped);
led_set(LED_BLUE, !tripped);
}
if (s_send_telemetry)
{
telemetry_t t;
// BRING-UP : chaque champ transporte la TENSION BRUTE de la
// broche ADC correspondante, en volts (0..3,3) -- et non la
// grandeur physique. Permet de verifier les 9 voies d'un coup
// sur l'IHM ESP32 apres ressoudure. measure.c fera la vraie
// conversion (ponts diviseurs, gain de shunt, NTC) a l'etape 5.
t.freq1_hz = (float)PWM_STAGE1_FREQ_HZ;
t.freq2_hz = (float)PWM_STAGE2_FREQ_HZ;
t.duty1_pct = 50.0f;
t.duty2_pct = 50.0f;
t.vin_v = adc_get_volts(ADC_CH_VIN);
t.iin_a = adc_get_volts(ADC_CH_IIN);
t.v1_v = adc_get_volts(ADC_CH_V1);
t.i1_a = adc_get_volts(ADC_CH_I1);
t.t1_c = adc_get_volts(ADC_CH_T1);
t.vout_v = adc_get_volts(ADC_CH_VOUT);
t.i2_a = adc_get_volts(ADC_CH_I2);
t.t2_c = adc_get_volts(ADC_CH_T2);
t.iout_a = adc_get_volts(ADC_CH_IOUT);
uart_link_send_telemetry(&t);
s_send_telemetry = false;
}
// Emission en tache de fond : pousse au plus 4 octets (profondeur de
// la FIFO) puis rend la main. Ne doit jamais retarder la regulation.
uart_link_service_tx();
} }
} }
// Base de temps 10 ms. Le clignotement des signaux d'etage a ete retire :
// GPIO1/GPIO3 (Stagex-default) appartiennent maintenant aux comparateurs, et
// Stagex-EN est fige a 1 pour rendre le declenchement observable sur les
// sorties des portes ET. HV_EN reste a 0.
//
// Les LED n'affichent plus l'etat de l'ADC mais celui des defauts, pilotees
// depuis la boucle principale.
interrupt void cpu_timer0_isr(void) interrupt void cpu_timer0_isr(void)
{ {
static bool blue_on = false; static uint16_t tick = 0;
CpuTimer0.InterruptCount++; CpuTimer0.InterruptCount++;
tick++;
blue_on = !blue_on; if ((tick % 30U) == 0U)
led_set(LED_BLUE, blue_on); {
s_send_telemetry = true; // telemetrie toutes les 300 ms
}
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
} }

149
src/pwm.c Normal file
View File

@ -0,0 +1,149 @@
#include "DSP28x_Project.h"
#include "pwm.h"
#include "calib.h"
// Mode up-count, TBCLK = SYSCLKOUT = 60 MHz (HSPCLKDIV = CLKDIV = /1).
// TBPRD = SYSCLKOUT / Fpwm - 1 -> 299 a 200 kHz, 599 a 100 kHz.
//
// HRPWM : volontairement NON implemente a ce stade (PROMPT §6 etape 6), mais
// le routage et la structure s'y pretent. Notes pour l'ajout ulterieur :
// - HRPWM n'existe que sur les sorties EPWMxA : les deux etages sont donc
// compatibles tels que routes (GPIO0 et GPIO2).
// - SYSCLKOUT >= 50 MHz requis (60 MHz ici), MEP 150-310 ps, SFO obligatoire.
// - Limitation de 3 cycles SYSCLK sur le rapport cyclique : a 200 kHz cela
// donne un duty min ~0,67 % et max ~99 %, a confronter aux points de
// fonctionnement de l'etage 1 (D ~ 0,8).
// - A 100 kHz sans HRPWM : 600 pas ~ 9,2 bits, risque de limit cycling en
// boucle fermee. C'est l'etage 2 (D ~ 0,875) qui est le plus concerne.
typedef struct
{
uint32_t freq_hz;
float duty;
bool enabled;
} pwm_stage_t;
static pwm_stage_t s_stage[2];
static volatile struct EPWM_REGS *pwm_regs(stage_id_t stage)
{
return (stage == STAGE_1) ? &EPwm1Regs : &EPwm2Regs;
}
static uint16_t stage_index(stage_id_t stage)
{
return (stage == STAGE_1) ? 0U : 1U;
}
// Recalcule CMPA a partir du duty et du TBPRD courants.
static void pwm_apply_duty(stage_id_t stage)
{
volatile struct EPWM_REGS *p = pwm_regs(stage);
float duty = s_stage[stage_index(stage)].duty;
uint16_t prd = p->TBPRD;
// En up-count avec AQ_SET a zero et AQ_CLEAR sur CMPA, la sortie est
// haute pendant CMPA cycles : duty = CMPA / (TBPRD + 1).
p->CMPA.half.CMPA = (uint16_t)(duty * (float)(prd + 1U) + 0.5f);
}
void pwm_init(void)
{
EALLOW;
SysCtrlRegs.PCLKCR1.bit.EPWM1ENCLK = 1;
SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 1;
// Les bases de temps sont figees pendant la configuration puis relachees
// ensemble, pour que les deux etages demarrent leur compteur en phase.
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0;
GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 1; // EPWM1A
GpioCtrlRegs.GPAMUX1.bit.GPIO2 = 1; // EPWM2A
EDIS;
s_stage[0].freq_hz = PWM_STAGE1_FREQ_HZ;
s_stage[1].freq_hz = PWM_STAGE2_FREQ_HZ;
{
stage_id_t stages[2] = {STAGE_1, STAGE_2};
uint16_t i;
for (i = 0U; i < 2U; i++)
{
stage_id_t s = stages[i];
volatile struct EPWM_REGS *p = pwm_regs(s);
p->TBCTL.bit.CTRMODE = TB_COUNT_UP;
p->TBCTL.bit.PHSEN = TB_DISABLE;
p->TBCTL.bit.PRDLD = TB_SHADOW;
p->TBCTL.bit.HSPCLKDIV = TB_DIV1;
p->TBCTL.bit.CLKDIV = TB_DIV1;
// FREE_SOFT = 0 : le compteur gele quand le CPU s'arrete. Ne
// suffit PAS a mettre la sortie a l'etat bas (l'Action Qualifier
// a deja positionne la broche, qui reste figee) -- c'est TZ6 =
// EMUSTOP dans safety.c qui garantit la mise a zero.
p->TBCTL.bit.FREE_SOFT = 0;
p->TBCTR = 0;
p->TBPHS.half.TBPHS = 0;
p->CMPCTL.bit.SHDWAMODE = CC_SHADOW;
p->CMPCTL.bit.LOADAMODE = CC_CTR_ZERO;
// Haut a zero, bas sur CMPA -> impulsion en debut de periode.
p->AQCTLA.bit.ZRO = AQ_SET;
p->AQCTLA.bit.CAU = AQ_CLEAR;
s_stage[i].duty = 0.0f;
s_stage[i].enabled = false;
pwm_set_freq(s, s_stage[i].freq_hz);
pwm_enable(s, false);
}
}
EALLOW;
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1;
EDIS;
}
void pwm_set_freq(stage_id_t stage, uint32_t hz)
{
volatile struct EPWM_REGS *p = pwm_regs(stage);
if (hz == 0UL)
{
return;
}
s_stage[stage_index(stage)].freq_hz = hz;
p->TBPRD = (uint16_t)((CLK_SYSCLKOUT_HZ / hz) - 1UL);
// TBPRD change -> CMPA doit suivre pour conserver le meme duty.
pwm_apply_duty(stage);
}
void pwm_set_duty(stage_id_t stage, float duty_0_1)
{
if (duty_0_1 < 0.0f)
{
duty_0_1 = 0.0f;
}
else if (duty_0_1 > 1.0f)
{
duty_0_1 = 1.0f;
}
s_stage[stage_index(stage)].duty = duty_0_1;
pwm_apply_duty(stage);
}
void pwm_enable(stage_id_t stage, bool enabled)
{
volatile struct EPWM_REGS *p = pwm_regs(stage);
s_stage[stage_index(stage)].enabled = enabled;
// CSFA : 1 = forcage continu a l'etat bas, 0 = action normale de l'AQ.
p->AQCSFRC.bit.CSFA = enabled ? 0U : 1U;
}

28
src/pwm.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef PWM_H
#define PWM_H
#include <stdbool.h>
#include <stdint.h>
#include "bsp_gpio.h" // stage_id_t
// ePWM1 -> etage 1 (EPWM1A, GPIO0, broche 29)
// ePWM2 -> etage 2 (EPWM2A, GPIO2, broche 37)
//
// Initialise les deux etages a leur frequence nominale (calib.h), duty a 0
// et sorties inhibees. PROMPT §6 etape 6 : ne jamais demarrer au duty
// nominal, le condensateur de sortie vide provoquerait un appel de courant
// destructeur. La rampe de demarrage (soft-start) est du ressort de
// l'appelant, pas de ce module.
void pwm_init(void);
void pwm_set_freq(stage_id_t stage, uint32_t hz);
// duty exprime en 0..1. Sature hors bornes.
void pwm_set_duty(stage_id_t stage, float duty_0_1);
// Inhibition logicielle par forcage continu de la sortie a l'etat bas
// (AQCSFRC). N'a rien a voir avec la protection materielle : le Trip Zone
// reste le seul mecanisme de securite (PROMPT §8).
void pwm_enable(stage_id_t stage, bool enabled);
#endif

View File

@ -73,6 +73,30 @@ void safety_init(void)
PieCtrlRegs.PIEIER2.bit.INTx2 = 1; // EPWM2_TZINT PieCtrlRegs.PIEIER2.bit.INTx2 = 1; // EPWM2_TZINT
} }
void safety_arm_emustop_only(void)
{
EALLOW;
// TZ6 = EMUSTOP, cable en dur depuis le CPU (TRM SPRUI09A section 3.2.7).
// One-shot : le flag latche, il ne se rearme pas seul.
EPwm1Regs.TZSEL.bit.OSHT6 = 1;
EPwm1Regs.TZCTL.bit.TZA = TZ_FORCE_LO;
EPwm1Regs.TZEINT.bit.OST = 1;
EPwm1Regs.TBCTL.bit.FREE_SOFT = 0;
EPwm2Regs.TZSEL.bit.OSHT6 = 1;
EPwm2Regs.TZCTL.bit.TZA = TZ_FORCE_LO;
EPwm2Regs.TZEINT.bit.OST = 1;
EPwm2Regs.TBCTL.bit.FREE_SOFT = 0;
PieVectTable.EPWM1_TZINT = &epwm1_tzint_isr;
PieVectTable.EPWM2_TZINT = &epwm2_tzint_isr;
EDIS;
IER |= M_INT2;
PieCtrlRegs.PIEIER2.bit.INTx1 = 1; // EPWM1_TZINT
PieCtrlRegs.PIEIER2.bit.INTx2 = 1; // EPWM2_TZINT
}
safety_faults_t safety_get_fault_flags(void) safety_faults_t safety_get_fault_flags(void)
{ {
safety_faults_t f; safety_faults_t f;

View File

@ -10,6 +10,16 @@ typedef struct
} safety_faults_t; } safety_faults_t;
void safety_init(void); void safety_init(void);
// BRING-UP UNIQUEMENT -- arme seulement TZ6 (EMUSTOP) et l'interruption de
// diagnostic, SANS les comparateurs ni le Digital Compare. Permet de valider
// isolement la coupure du PWM a l'arret du debugger, tant que les entrees
// shunt ne sont pas cablees : avec safety_init() complet, elles flottent
// au-dessus du seuil et le trip comparateur masquerait le test.
//
// safety_init() arme deja TZ6 : ces deux fonctions sont exclusives.
void safety_arm_emustop_only(void);
safety_faults_t safety_get_fault_flags(void); safety_faults_t safety_get_fault_flags(void);
// Ecrit TZCLR[OST] sur les deux etages. Ne doit JAMAIS etre appele // Ecrit TZCLR[OST] sur les deux etages. Ne doit JAMAIS etre appele

391
src/uart_link.c Normal file
View File

@ -0,0 +1,391 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "DSP28x_Project.h"
#include "uart_link.h"
#include "calib.h"
static volatile uint8_t s_rx_ring[UART_RX_RING_SIZE];
static volatile uint16_t s_rx_head = 0; // ecrit par l'ISR uniquement
static uint16_t s_rx_tail = 0; // ecrit par uart_link_poll() uniquement
static char s_line[UART_LINE_MAX];
static uint16_t s_line_len = 0;
interrupt void scia_rx_isr(void);
// Profondeur de la FIFO d'emission du SCI (TRM SPRUI09A) : on ne pousse
// jamais au-dela, ce qui garantit que uart_link_service_tx() rend la main
// immediatement au lieu d'attendre le debit de la ligne.
#define SCI_TX_FIFO_DEPTH 4U
static uint8_t checksum_of(const char *s, int len)
{
uint8_t cs = 0;
int i;
for (i = 0; i < len; i++)
{
cs ^= (uint8_t)s[i];
}
return cs;
}
void uart_link_init(void)
{
EALLOW;
GpioCtrlRegs.GPAPUD.bit.GPIO28 = 0; // pull-up sur RX
GpioCtrlRegs.GPAPUD.bit.GPIO29 = 1; // pas de pull-up sur TX (sortie)
GpioCtrlRegs.GPAQSEL2.bit.GPIO28 = 3; // RX asynchrone, pas de qualification
GpioCtrlRegs.GPAMUX2.bit.GPIO28 = 1; // GPIO28 -> SCIRXDA
GpioCtrlRegs.GPAMUX2.bit.GPIO29 = 1; // GPIO29 -> SCITXDA
EDIS;
// 1 stop bit, pas de parite, 8 bits, mode async, protocole idle-line
SciaRegs.SCICCR.all = 0x0007;
// TX/RX actives, horloge SCI interne
SciaRegs.SCICTL1.all = 0x0003;
SciaRegs.SCICTL2.bit.TXINTENA = 0; // TX en polling, pas d'interruption
SciaRegs.SCICTL2.bit.RXBKINTENA = 1;
SciaRegs.SCIHBAUD = (UART_SCIBRR >> 8) & 0xFF;
SciaRegs.SCILBAUD = UART_SCIBRR & 0xFF;
SciaRegs.SCIFFTX.all = 0xE040; // FIFO active, TX en polling (pas d'IT FIFO)
SciaRegs.SCIFFRX.all = 0x2021; // FIFO active, RXFFIENA=1, seuil=1 octet
SciaRegs.SCIFFCT.all = 0x0;
SciaRegs.SCICTL1.all = 0x0023; // sort le SCI de son reset local
EALLOW;
PieVectTable.SCIRXINTA = &scia_rx_isr;
EDIS;
IER |= M_INT9;
PieCtrlRegs.PIEIER9.bit.INTx1 = 1; // SCIRXINTA
}
// Buffer d'emission statique et non local : evite 160 mots de pile a chaque
// envoi (la pile ne fait que 1024 mots et sprintf en consomme deja beaucoup).
static char s_tx_frame[UART_LINE_MAX];
static uint16_t s_tx_len = 0; // longueur de la trame en attente d'emission
static uint16_t s_tx_pos = 0; // prochain octet a pousser dans la FIFO
static uint16_t s_tx_dropped = 0; // trames abandonnees (ligne trop lente)
static uint16_t s_tx_truncated = 0; // trames ayant perdu des champs (borne)
// Place a reserver en fin de buffer : "*XX\n" + le NUL ecrit par snprintf.
#define UART_TX_TAIL_LEN 5
// Garde-fou sur les valeurs formatees : un flottant aberrant (capteur non
// encore cable, ADC non initialise, NaN) rendrait le cast en int32
// indefini, donc un champ de longueur arbitraire.
#define UART_FIELD_ABS_MAX 999999.0f
// Formatage decimal a la main, SANS "%f". Sur C28x le support flottant de
// printf passe par du long double 64 bits emule (frexpl/scalbnl/L$$DIV) :
// tres couteux en pile et en temps, et constate au bring-up comme faisant
// partir le CPU dans le decor (PC=0) des le premier envoi de telemetrie.
// Voir aussi PROMPT §7 etape 7, qui prevoyait deja ce repli.
//
// `room` = octets disponibles pour ce champ, NUL compris. Renvoie 0 (champ
// entierement abandonne, rien d'ecrit) s'il ne tient pas : le protocole
// autorise les champs manquants, l'ESP32 garde alors sa derniere valeur
// (docs/ESP32-UART.md). Mieux vaut une trame courte mais valide qu'un
// debordement de s_tx_frame.
static int append_field(char *out, int room, const char *tag, float value,
uint16_t decimals)
{
static const int32_t k_pow10[3] = {1L, 10L, 100L};
const char *sign = "";
int32_t scaled;
int32_t ip;
int32_t fp;
int n;
if (room <= 1)
{
return 0;
}
// value != value n'est vrai que pour un NaN.
if (value != value)
{
value = 0.0f;
}
else if (value < -UART_FIELD_ABS_MAX)
{
value = -UART_FIELD_ABS_MAX;
}
else if (value > UART_FIELD_ABS_MAX)
{
value = UART_FIELD_ABS_MAX;
}
scaled = (int32_t)(value * (float)k_pow10[decimals]
+ ((value >= 0.0f) ? 0.5f : -0.5f));
if (scaled < 0)
{
scaled = -scaled;
sign = "-";
}
ip = scaled / k_pow10[decimals];
fp = scaled % k_pow10[decimals];
if (decimals == 0)
{
n = snprintf(out, (size_t)room, ",%s=%s%ld", tag, sign, ip);
}
else if (decimals == 1)
{
n = snprintf(out, (size_t)room, ",%s=%s%ld.%01ld", tag, sign, ip, fp);
}
else
{
n = snprintf(out, (size_t)room, ",%s=%s%ld.%02ld", tag, sign, ip, fp);
}
if (n < 0 || n >= room)
{
out[0] = '\0'; // tronque : on annule le champ au lieu de le laisser coupe
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
// n'est pas encore partie (nouvelle trame abandonnee, la telemetrie est par
// nature perissable : mieux vaut la suivante que du retard accumule).
bool uart_link_send_telemetry(const telemetry_t *t)
{
int n;
int fields_dropped;
uint8_t cs;
if (uart_link_tx_busy())
{
s_tx_dropped++;
return false;
}
s_tx_frame[0] = '$';
s_tx_frame[1] = 'T';
n = 2;
fields_dropped = 0;
#define TX_ROOM() ((int)UART_LINE_MAX - UART_TX_TAIL_LEN - n)
#define TX_ADD(tag_, val_, dec_) \
do { \
int added_ = append_field(&s_tx_frame[n], TX_ROOM(), (tag_), (val_), \
(dec_)); \
if (added_ == 0) { fields_dropped++; } else { n += added_; } \
} while (0)
TX_ADD("FREQ1", t->freq1_hz, 0);
TX_ADD("FREQ2", t->freq2_hz, 0);
TX_ADD("DUTY1", t->duty1_pct, 1);
TX_ADD("DUTY2", t->duty2_pct, 1);
TX_ADD("VIN", t->vin_v, 1);
TX_ADD("IIN", t->iin_a, 2);
TX_ADD("V1", t->v1_v, 1);
TX_ADD("I1", t->i1_a, 2);
TX_ADD("T1", t->t1_c, 1);
TX_ADD("VOUT", t->vout_v, 1);
TX_ADD("I2", t->i2_a, 2);
TX_ADD("T2", t->t2_c, 1);
TX_ADD("IOUT", t->iout_a, 2);
#undef TX_ADD
#undef TX_ROOM
if (fields_dropped != 0)
{
s_tx_truncated++;
}
// Checksum XOR sur le corps seul, entre '$' et '*' (docs/ESP32-UART.md).
// La place a ete reservee par UART_TX_TAIL_LEN, l'ecriture tient toujours.
cs = checksum_of(&s_tx_frame[1], n - 1);
n += snprintf(&s_tx_frame[n], (size_t)((int)UART_LINE_MAX - n),
"*%02X\n", cs);
s_tx_len = (uint16_t)n;
s_tx_pos = 0;
return true;
}
bool uart_link_tx_busy(void)
{
return (s_tx_pos < s_tx_len);
}
void uart_link_service_tx(void)
{
while ((s_tx_pos < s_tx_len)
&& (SciaRegs.SCIFFTX.bit.TXFFST < SCI_TX_FIFO_DEPTH))
{
SciaRegs.SCITXBUF = (uint16_t)(uint8_t)s_tx_frame[s_tx_pos];
s_tx_pos++;
}
}
// body pointe juste apres "C," ; len = nb de caracteres avant le '*'.
static bool parse_command(const char *body, command_state_t *cmd)
{
const char *p;
bool ht_found = false, pwm1_found = false, pwm2_found = false;
p = strstr(body, "HT=");
if (p != NULL)
{
cmd->ht_enabled = (p[3] == '1');
ht_found = true;
}
p = strstr(body, "PWM1=");
if (p != NULL)
{
cmd->pwm1_enabled = (p[5] == '1');
pwm1_found = true;
}
p = strstr(body, "PWM2=");
if (p != NULL)
{
cmd->pwm2_enabled = (p[5] == '1');
pwm2_found = true;
}
return ht_found && pwm1_found && pwm2_found;
}
// s_line contient une ligne complete (sans le '\n' terminal).
static bool process_line(command_state_t *cmd)
{
int len = (int)s_line_len;
int i;
int star = -1;
uint8_t cs_calc, cs_recv;
if (len > 0 && s_line[len - 1] == '\r')
{
len--;
}
if (len < 4 || s_line[0] != '$')
{
return false;
}
for (i = 1; i < len; i++)
{
if (s_line[i] == '*')
{
star = i;
break;
}
}
if (star < 0 || (len - star) < 3)
{
return false; // pas de checksum complet
}
cs_calc = checksum_of(&s_line[1], star - 1);
cs_recv = (uint8_t)strtol(&s_line[star + 1], NULL, 16);
if (cs_calc != cs_recv)
{
return false; // trame corrompue -> ignoree silencieusement
}
if (s_line[1] != 'C' || s_line[2] != ',')
{
return false; // pas une trame de commande
}
s_line[star] = '\0';
return parse_command(&s_line[3], cmd);
}
// Compteur de recuperations, expose au debogueur : une valeur qui grimpe
// signale un probleme physique sur la ligne (baud, masse, niveaux), pas un
// simple alea.
static uint16_t s_rx_error_recoveries = 0;
// Sur C28x, un FE / OE / PE / BRKDT positionne SCIRXST.RXERROR, qui est
// LATCHE : le recepteur reste bloque tant qu'on ne fait pas un SW RESET du
// SCI (TRM SPRUI09A, SCICTL1.SWRESET) ou un reset systeme. Sans ce
// traitement, une seule perturbation sur la ligne arrete definitivement la
// reception -- constate au bring-up (RXERROR+FE+BRKDT latches, plus aucune
// trame $C recue ensuite).
static void scia_recover_if_rx_error(void)
{
if (SciaRegs.SCIRXST.bit.RXERROR == 0)
{
return;
}
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;
// La ligne en cours d'assemblage est forcement tronquee : on repart propre.
s_line_len = 0;
s_rx_error_recoveries++;
}
bool uart_link_poll(command_state_t *cmd)
{
bool got_command = false;
scia_recover_if_rx_error();
while (s_rx_tail != s_rx_head)
{
uint8_t c = s_rx_ring[s_rx_tail];
s_rx_tail = (uint16_t)((s_rx_tail + 1U) % UART_RX_RING_SIZE);
if (c == '\n')
{
if (process_line(cmd))
{
got_command = true;
}
s_line_len = 0;
}
else if (s_line_len < (UART_LINE_MAX - 1U))
{
s_line[s_line_len++] = (char)c;
}
else
{
// Ligne trop longue (> limite cote ESP32) : abandonnee.
s_line_len = 0;
}
}
return got_command;
}
interrupt void scia_rx_isr(void)
{
uint16_t next_head = (uint16_t)((s_rx_head + 1U) % UART_RX_RING_SIZE);
if (next_head != s_rx_tail)
{
s_rx_ring[s_rx_head] = (uint8_t)SciaRegs.SCIRXBUF.bit.RXDT;
s_rx_head = next_head;
}
else
{
(void)SciaRegs.SCIRXBUF.bit.RXDT; // anneau plein : octet jete
}
SciaRegs.SCIFFRX.bit.RXFFOVRCLR = 1;
SciaRegs.SCIFFRX.bit.RXFFINTCLR = 1;
PieCtrlRegs.PIEACK.all = PIEACK_GROUP9;
}

29
src/uart_link.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef UART_LINK_H
#define UART_LINK_H
#include <stdbool.h>
#include "protocol.h"
void uart_link_init(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
// encore partie -- la nouvelle est alors abandonnee.
bool uart_link_send_telemetry(const telemetry_t *t);
// Pousse dans la FIFO du SCI ce qui y rentre, puis rend la main
// immediatement. A appeler depuis la boucle principale, en tache de fond :
// l'emission ne doit jamais passer avant la regulation.
void uart_link_service_tx(void);
// true tant qu'une trame reste a emettre.
bool uart_link_tx_busy(void);
// A appeler dans la boucle principale (jamais en ISR). Si une trame
// $C,...*XX complete et de checksum valide a ete recue depuis le dernier
// appel, decode son contenu dans *cmd et renvoie true. Les trames
// corrompues sont ignorees silencieusement (voir docs/ESP32-UART.md).
bool uart_link_poll(command_state_t *cmd);
#endif