Compare commits
13 Commits
ad51f1d9c6
...
bringup
| Author | SHA1 | Date | |
|---|---|---|---|
| 69801df648 | |||
| b1bb0fa21a | |||
| 26c1d499e0 | |||
| 46ddf30a64 | |||
| a05c1e8124 | |||
| 888723abc7 | |||
| d26c07d3a1 | |||
| d185d73422 | |||
| e717d0deec | |||
| e0efeebe94 | |||
| f9f6909a56 | |||
| 616ae539cc | |||
| 8d71489f48 |
69
.cproject
69
.cproject
@ -22,30 +22,30 @@
|
||||
<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.715961659" name="TI Build Tools" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.DebugToolchain" targetTool="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.linkerDebug.1498851383">
|
||||
<option id="com.ti.ccstudio.buildDefinitions.core.OPT_TAGS.496542915" 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"/>
|
||||
<listOptionValue value="OUTPUT_FORMAT=COFF"/>
|
||||
<listOptionValue value="RUNTIME_SUPPORT_LIBRARY="/>
|
||||
<listOptionValue value="CCS_MBS_VERSION=70.0.0"/>
|
||||
<listOptionValue value="IS_ASSEMBLY_ONLY=false"/>
|
||||
<listOptionValue value="PROJECT_KIND=com.ti.ccstudio.managedbuild.core.ProjectKind_Executable"/>
|
||||
<listOptionValue value="LINK_ORDER="/>
|
||||
<listOptionValue value="PRODUCTS=C2000WARE:26.1.0.00;"/>
|
||||
<listOptionValue value="LINKER_COMMAND_FILE="/>
|
||||
<listOptionValue value="PRODUCT_MACRO_IMPORTS={"C2000WARE":["${COM_TI_C2000WARE_INCLUDE_PATH}","${COM_TI_C2000WARE_LIBRARY_PATH}","${COM_TI_C2000WARE_LIBRARIES}","${COM_TI_C2000WARE_SYMBOLS}","${COM_TI_C2000WARE_SYSCONFIG_MANIFEST}"]}"/>
|
||||
<listOptionValue value="OUTPUT_TYPE=executable"/>
|
||||
<listOptionValue value="CCS_MBS_VERSION=70.0.0"/>
|
||||
<listOptionValue value="LINK_ORDER="/>
|
||||
<listOptionValue value="PRODUCTS=C2000WARE:26.1.0.00;"/>
|
||||
</option>
|
||||
<option id="com.ti.ccstudio.buildDefinitions.core.OPT_CODEGEN_VERSION.1720423258" 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.636408046" 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.52666393" keepEnvironmentInBuildfile="false" name="GNU Make" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.builderDebug"/>
|
||||
<tool id="com.ti.ccstudio.buildDefinitions.C2000_25.11.exe.compilerDebug.49305770" 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.753719686" 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.408372431" 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.2006190902" 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.101870543" 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,34 +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.1030597368" 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.851670006" 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.1263395190" 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.191571893" 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.1262034113" 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.1947015882" 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.920546019" 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.155698373" 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.1498851383" 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.671635529" 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.MAP_FILE.1089801316" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.MAP_FILE" value=""Example_2802xLEDBlink.map"" valueType="string"/>
|
||||
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.OUTPUT_FILE.376320651" 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.1484799295" 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=""Example_2802xLEDBlink.map"" 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.1742958036" 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"/>
|
||||
@ -88,14 +90,17 @@
|
||||
<listOptionValue value="${INSTALLROOT_F2802x}/common/lib"/>
|
||||
<listOptionValue value="${INSTALLROOT_IQMATH}/lib"/>
|
||||
</option>
|
||||
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.ISSUE_REMARKS.948575730" 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.534455233" 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.2111771756" superClass="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.XML_LINK_INFO" value=""Example_2802xLEDBlink_linkInfo.xml"" valueType="string"/>
|
||||
<option id="com.ti.ccstudio.buildDefinitions.C2000_25.11.linkerID.ENTRY_POINT.286189493" 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=""Example_2802xLEDBlink_linkInfo.xml"" 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.280593134" 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>
|
||||
<entry excluding="F2802x_generic_ram.cmd" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
|
||||
</sourceEntries>
|
||||
</configuration>
|
||||
</storageModule>
|
||||
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@ -5,3 +5,11 @@ Release/
|
||||
*.d_raw
|
||||
*.out
|
||||
*.map
|
||||
|
||||
# IDE-generated, machine-specific (hardcoded local tool paths)
|
||||
.mcp.json
|
||||
CLAUDE.md
|
||||
|
||||
# Outillage agent (local, non partage)
|
||||
.claude/
|
||||
.codex/
|
||||
|
||||
@ -2,5 +2,7 @@ eclipse.preferences.version=1
|
||||
encoding//Debug/makefile=UTF-8
|
||||
encoding//Debug/objects.mk=UTF-8
|
||||
encoding//Debug/sources.mk=UTF-8
|
||||
encoding//Debug/src/subdir_rules.mk=UTF-8
|
||||
encoding//Debug/src/subdir_vars.mk=UTF-8
|
||||
encoding//Debug/subdir_rules.mk=UTF-8
|
||||
encoding//Debug/subdir_vars.mk=UTF-8
|
||||
|
||||
16
.theia/launch.json
Normal file
16
.theia/launch.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,265 +0,0 @@
|
||||
//###########################################################################
|
||||
//
|
||||
// FILE: Example_2802xLedBlink.c
|
||||
//
|
||||
// TITLE: f2802x LED Blink Getting Started Program.
|
||||
//
|
||||
// ASSUMPTIONS:
|
||||
//
|
||||
// This program requires the f2802x header files.
|
||||
// As supplied, this project is configured for "boot to SARAM"
|
||||
// operation. The 2802x Boot Mode table is shown below.
|
||||
//
|
||||
// $Boot_Table
|
||||
// While an emulator is connected to your device, the TRSTn pin = 1,
|
||||
// which sets the device into EMU_BOOT boot mode. In this mode, the
|
||||
// peripheral boot modes are as follows:
|
||||
//
|
||||
// Boot Mode: EMU_KEY EMU_BMODE
|
||||
// (0xD00) (0xD01)
|
||||
// ---------------------------------------
|
||||
// Wait !=0x55AA X
|
||||
// I/O 0x55AA 0x0000
|
||||
// SCI 0x55AA 0x0001
|
||||
// Wait 0x55AA 0x0002
|
||||
// Get_Mode 0x55AA 0x0003
|
||||
// SPI 0x55AA 0x0004
|
||||
// I2C 0x55AA 0x0005
|
||||
// OTP 0x55AA 0x0006
|
||||
// Wait 0x55AA 0x0007
|
||||
// Wait 0x55AA 0x0008
|
||||
// SARAM 0x55AA 0x000A <-- "Boot to SARAM"
|
||||
// Flash 0x55AA 0x000B
|
||||
// Wait 0x55AA Other
|
||||
//
|
||||
// Write EMU_KEY to 0xD00 and EMU_BMODE to 0xD01 via the debugger
|
||||
// according to the Boot Mode Table above. Build/Load project,
|
||||
// Reset the device, and Run example
|
||||
//
|
||||
// $End_Boot_Table
|
||||
//
|
||||
// DESCRIPTION:
|
||||
//
|
||||
// This example configures CPU Timer0 for a 500 msec period, and toggles the
|
||||
// GPIO34 LED once per interrupt. For testing purposes, this example
|
||||
// also increments a counter each time the timer asserts an interrupt.
|
||||
//
|
||||
// Watch Variables:
|
||||
// CpuTimer0.InterruptCount
|
||||
//
|
||||
// Monitor the GPIO34 LED blink on (for 500 msec) and off (for 500 msec)
|
||||
// on the 2802x control card.
|
||||
//
|
||||
//###########################################################################
|
||||
// $TI Release: $
|
||||
// $Release Date: $
|
||||
// $Copyright:
|
||||
// Copyright (C) 2009-2026 Texas Instruments Incorporated - http://www.ti.com/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
//
|
||||
// Neither the name of Texas Instruments Incorporated nor the names of
|
||||
// its contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// $
|
||||
//###########################################################################
|
||||
|
||||
//
|
||||
// Included Files
|
||||
//
|
||||
#include "DSP28x_Project.h" // Device Headerfile and Examples Include File
|
||||
|
||||
//
|
||||
// Function Prototypes
|
||||
//
|
||||
__interrupt void cpu_timer0_isr(void);
|
||||
|
||||
//
|
||||
// Main
|
||||
//
|
||||
void main(void)
|
||||
{
|
||||
//
|
||||
// WARNING: Always ensure you call memcpy before running any functions from
|
||||
// RAM InitSysCtrl includes a call to a RAM based function and without a
|
||||
// call to memcpy first, the processor will go "into the weeds"
|
||||
//
|
||||
#ifdef _FLASH
|
||||
memcpy(&RamfuncsRunStart, &RamfuncsLoadStart, (size_t)&RamfuncsLoadSize);
|
||||
#endif
|
||||
|
||||
//
|
||||
// Step 1. Initialize System Control:
|
||||
// PLL, WatchDog, enable Peripheral Clocks
|
||||
// This example function is found in the f2802x_SysCtrl.c file.
|
||||
//
|
||||
InitSysCtrl();
|
||||
|
||||
//
|
||||
// Step 2. Initialize GPIO:
|
||||
// This example function is found in the f2802x_Gpio.c file and
|
||||
// illustrates how to set the GPIO to it's default state.
|
||||
//
|
||||
//InitGpio(); // Skipped for this example
|
||||
|
||||
//
|
||||
// Step 3. Clear all interrupts and initialize PIE vector table:
|
||||
// Disable CPU interrupts
|
||||
//
|
||||
DINT;
|
||||
|
||||
//
|
||||
// Initialize the PIE control registers to their default state.
|
||||
// The default state is all PIE interrupts disabled and flags
|
||||
// are cleared.
|
||||
// This function is found in the f2802x_PieCtrl.c file.
|
||||
//
|
||||
InitPieCtrl();
|
||||
|
||||
//
|
||||
// Disable CPU interrupts and clear all CPU interrupt flags
|
||||
//
|
||||
IER = 0x0000;
|
||||
IFR = 0x0000;
|
||||
|
||||
//
|
||||
// Initialize the PIE vector table with pointers to the shell Interrupt
|
||||
// Service Routines (ISR).
|
||||
// This will populate the entire table, even if the interrupt
|
||||
// is not used in this example. This is useful for debug purposes.
|
||||
// The shell ISR routines are found in f2802x_DefaultIsr.c.
|
||||
// This function is found in f2802x_PieVect.c.
|
||||
//
|
||||
InitPieVectTable();
|
||||
|
||||
//
|
||||
// Interrupts that are used in this example are re-mapped to
|
||||
// ISR functions found within this file.
|
||||
//
|
||||
EALLOW; // This is needed to write to EALLOW protected registers
|
||||
PieVectTable.TINT0 = &cpu_timer0_isr;
|
||||
EDIS; // This is needed to disable write to EALLOW protected registers
|
||||
|
||||
//
|
||||
// Step 4. Initialize the Device Peripheral. This function can be
|
||||
// found in f2802x_CpuTimers.c
|
||||
//
|
||||
|
||||
//
|
||||
// For this example, only initialize the Cpu Timers
|
||||
//
|
||||
InitCpuTimers();
|
||||
|
||||
//
|
||||
// Configure CPU-Timer 0 to interrupt every 500 milliseconds:
|
||||
// 60MHz CPU Freq, 50 millisecond Period (in uSeconds)
|
||||
//
|
||||
ConfigCpuTimer(&CpuTimer0, 60, 500000);
|
||||
|
||||
//
|
||||
// To ensure precise timing, use write-only instructions to write to the
|
||||
// entire register. Therefore, if any of the configuration bits are changed
|
||||
// in ConfigCpuTimer and InitCpuTimers (in f2802x_CpuTimers.h), the
|
||||
// below settings must also be updated.
|
||||
//
|
||||
|
||||
//
|
||||
// Use write-only instruction to set TSS bit = 0
|
||||
//
|
||||
CpuTimer0Regs.TCR.all = 0x4001;
|
||||
|
||||
//
|
||||
// Step 5. User specific code, enable interrupts
|
||||
//
|
||||
|
||||
//
|
||||
// Configure GPIO12 (blue LED) and GPIO33 (red LED) as GPIO output pins
|
||||
//
|
||||
EALLOW;
|
||||
GpioCtrlRegs.GPAMUX1.bit.GPIO12 = 0;
|
||||
GpioCtrlRegs.GPADIR.bit.GPIO12 = 1;
|
||||
GpioCtrlRegs.GPBMUX1.bit.GPIO33 = 0;
|
||||
GpioCtrlRegs.GPBDIR.bit.GPIO33 = 1;
|
||||
GpioDataRegs.GPACLEAR.bit.GPIO12 = 1;
|
||||
GpioDataRegs.GPBCLEAR.bit.GPIO33 = 1;
|
||||
EDIS;
|
||||
|
||||
//
|
||||
// Enable CPU INT1 which is connected to CPU-Timer 0
|
||||
//
|
||||
IER |= M_INT1;
|
||||
|
||||
//
|
||||
// Enable TINT0 in the PIE: Group 1 interrupt 7
|
||||
//
|
||||
PieCtrlRegs.PIEIER1.bit.INTx7 = 1;
|
||||
|
||||
//
|
||||
// Enable global Interrupts and higher priority real-time debug events
|
||||
//
|
||||
EINT; // Enable Global interrupt INTM
|
||||
ERTM; // Enable Global realtime interrupt DBGM
|
||||
|
||||
//
|
||||
// Step 6. IDLE loop. Just sit and loop forever (optional)
|
||||
//
|
||||
for(;;);
|
||||
}
|
||||
|
||||
//
|
||||
// cpu_timer0_isr -
|
||||
//
|
||||
__interrupt void
|
||||
cpu_timer0_isr(void)
|
||||
{
|
||||
static Uint16 ledState = 0;
|
||||
|
||||
CpuTimer0.InterruptCount++;
|
||||
|
||||
//
|
||||
// Alternate between the blue LED (GPIO12) and the red LED (GPIO33) every 500 milliseconds
|
||||
//
|
||||
if (ledState == 0)
|
||||
{
|
||||
GpioDataRegs.GPASET.bit.GPIO12 = 1;
|
||||
GpioDataRegs.GPBCLEAR.bit.GPIO33 = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
GpioDataRegs.GPACLEAR.bit.GPIO12 = 1;
|
||||
GpioDataRegs.GPBSET.bit.GPIO33 = 1;
|
||||
}
|
||||
ledState = (ledState + 1) % 2;
|
||||
|
||||
//
|
||||
// Acknowledge this interrupt to receive more interrupts from group 1
|
||||
//
|
||||
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
|
||||
}
|
||||
|
||||
//
|
||||
// End of File
|
||||
//
|
||||
|
||||
223
F2802x_generic_flash.cmd
Normal file
223
F2802x_generic_flash.cmd
Normal file
@ -0,0 +1,223 @@
|
||||
/*
|
||||
//###########################################################################
|
||||
//
|
||||
// FILE: F2802x_generic_flash.cmd
|
||||
//
|
||||
// TITLE: Generic Linker Command File for f2802x devices
|
||||
//
|
||||
//###########################################################################
|
||||
// $TI Release: $
|
||||
// $Release Date: $
|
||||
// $Copyright:
|
||||
// Copyright (C) 2009-2026 Texas Instruments Incorporated - http://www.ti.com/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
//
|
||||
// Neither the name of Texas Instruments Incorporated nor the names of
|
||||
// its contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// $
|
||||
//###########################################################################
|
||||
*/
|
||||
|
||||
/* ======================================================
|
||||
// For Code Composer Studio V2.2 and later
|
||||
// ---------------------------------------
|
||||
// In addition to this memory linker command file,
|
||||
// add the header linker command file directly to the project.
|
||||
// The header linker command file is required to link the
|
||||
// peripheral structures to the proper locations within
|
||||
// the memory map.
|
||||
//
|
||||
// The header linker files are found in <base>\headers\cmd
|
||||
//
|
||||
// For BIOS applications add: F2802x_Headers_BIOS.cmd
|
||||
// For nonBIOS applications add: F2802x_Headers_nonBIOS.cmd
|
||||
========================================================= */
|
||||
|
||||
/* ======================================================
|
||||
// For Code Composer Studio prior to V2.2
|
||||
// --------------------------------------
|
||||
// 1) Use one of the following -l statements to include the
|
||||
// header linker command file in the project. The header linker
|
||||
// file is required to link the peripheral structures to the proper
|
||||
// locations within the memory map */
|
||||
|
||||
/* Uncomment this line to include file only for non-BIOS applications */
|
||||
/* -l F2802x_Headers_nonBIOS.cmd */
|
||||
|
||||
/* Uncomment this line to include file only for BIOS applications */
|
||||
/* -l F2802x_Headers_BIOS.cmd */
|
||||
|
||||
/* 2) In your project add the path to <base>\headers\cmd to the
|
||||
library search path under project->build options, linker tab,
|
||||
library search path (-i).
|
||||
/*========================================================= */
|
||||
|
||||
/* Define the memory block start/length for the F2802x
|
||||
PAGE 0 will be used to organize program sections
|
||||
PAGE 1 will be used to organize data sections
|
||||
|
||||
Notes:
|
||||
Memory blocks on F2802x are uniform (ie same
|
||||
physical memory) in both PAGE 0 and PAGE 1.
|
||||
That is the same memory region should not be
|
||||
defined for both PAGE 0 and PAGE 1.
|
||||
Doing so will result in corruption of program
|
||||
and/or data.
|
||||
|
||||
Contiguous SARAM memory blocks or flash sectors can be
|
||||
be combined if required to create a larger memory block.
|
||||
*/
|
||||
|
||||
MEMORY
|
||||
{
|
||||
PAGE 0: /* Program Memory */
|
||||
/* Memory (RAM/FLASH/OTP) blocks can be moved to PAGE1 for data allocation */
|
||||
|
||||
RAMM0 : origin = 0x000050, length = 0x0003B0 /* on-chip RAM block M0 */
|
||||
OTP : origin = 0x3D7800, length = 0x000400 /* on-chip OTP */
|
||||
/* 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. */
|
||||
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 */
|
||||
|
||||
|
||||
|
||||
IQTABLES : origin = 0x3FE000, length = 0x000B50 /* IQ Math Tables in Boot ROM */
|
||||
IQTABLES2 : origin = 0x3FEB50, length = 0x00008C /* IQ Math Tables in Boot ROM */
|
||||
IQTABLES3 : origin = 0x3FEBDC, length = 0x0000AA /* IQ Math Tables in Boot ROM */
|
||||
|
||||
ROM : origin = 0x3FF27C, length = 0x000D44 /* Boot ROM */
|
||||
RESET : origin = 0x3FFFC0, length = 0x000002 /* part of boot ROM */
|
||||
VECTORS : origin = 0x3FFFC2, length = 0x00003E /* part of boot ROM */
|
||||
|
||||
PAGE 1 : /* Data Memory */
|
||||
/* Memory (RAM/FLASH/OTP) blocks can be moved to PAGE0 for program allocation */
|
||||
/* Registers remain on PAGE1 */
|
||||
|
||||
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 */
|
||||
RAML0 : origin = 0x008000, length = 0x001000 /* on-chip RAM block L0 (4K mots sur F28027) */
|
||||
|
||||
}
|
||||
|
||||
/* Allocate sections to memory blocks.
|
||||
Note:
|
||||
codestart user defined section in DSP28_CodeStartBranch.asm used to redirect code
|
||||
execution when booting to flash
|
||||
ramfuncs user defined section to store functions that will be copied from Flash into RAM
|
||||
*/
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
|
||||
/* Allocate program areas: */
|
||||
codestart : > BEGIN PAGE = 0
|
||||
|
||||
ramfuncs : LOAD = FLASHA,
|
||||
RUN = RAMM0,
|
||||
LOAD_START(_RamfuncsLoadStart),
|
||||
LOAD_SIZE(_RamfuncsLoadSize),
|
||||
RUN_START(_RamfuncsRunStart),
|
||||
PAGE = 0
|
||||
|
||||
.cinit : > FLASHA | FLASHB | FLASHC | FLASHD, PAGE = 0
|
||||
.pinit : > FLASHA | FLASHB | FLASHC | FLASHD, PAGE = 0
|
||||
.text : >> FLASHA | FLASHB | FLASHC | FLASHD, PAGE = 0
|
||||
|
||||
csmpasswds : > CSM_PWL_P0, PAGE = 0
|
||||
csm_rsvd : > CSM_RSVD, PAGE = 0
|
||||
|
||||
/* 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
|
||||
.ebss : >> RAML0 | RAMM1, PAGE = 1
|
||||
.esysmem : >> RAML0 | RAMM1, PAGE = 1
|
||||
|
||||
/* Initalized sections go in Flash */
|
||||
/* For SDFlash to program these, they must be allocated to page 0 */
|
||||
.econst : >> FLASHA | FLASHB | FLASHC | FLASHD, PAGE = 0
|
||||
.switch : >> FLASHA | FLASHB | FLASHC | FLASHD, PAGE = 0
|
||||
|
||||
/* Allocate IQ math areas: */
|
||||
IQmath : >> FLASHA | FLASHB | FLASHC | FLASHD, PAGE = 0 /* Math Code */
|
||||
IQmathTables : > IQTABLES, PAGE = 0, TYPE = NOLOAD
|
||||
|
||||
/* Uncomment the section below if calling the IQNexp() or IQexp()
|
||||
functions from the IQMath.lib library in order to utilize the
|
||||
relevant IQ Math table in Boot ROM (This saves space and Boot ROM
|
||||
is 1 wait-state). If this section is not uncommented, IQmathTables2
|
||||
will be loaded into other memory (SARAM, Flash, etc.) and will take
|
||||
up space, but 0 wait-state is possible.
|
||||
*/
|
||||
/*
|
||||
IQmathTables2 : > IQTABLES2, PAGE = 0, TYPE = NOLOAD
|
||||
{
|
||||
|
||||
IQmath.lib<IQNexpTable.obj> (IQmathTablesRam)
|
||||
|
||||
}
|
||||
*/
|
||||
/* Uncomment the section below if calling the IQNasin() or IQasin()
|
||||
functions from the IQMath.lib library in order to utilize the
|
||||
relevant IQ Math table in Boot ROM (This saves space and Boot ROM
|
||||
is 1 wait-state). If this section is not uncommented, IQmathTables2
|
||||
will be loaded into other memory (SARAM, Flash, etc.) and will take
|
||||
up space, but 0 wait-state is possible.
|
||||
*/
|
||||
/*
|
||||
IQmathTables3 : > IQTABLES3, PAGE = 0, TYPE = NOLOAD
|
||||
{
|
||||
|
||||
IQmath.lib<IQNasinTable.obj> (IQmathTablesRam)
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
/* .reset is a standard section used by the compiler. It contains the */
|
||||
/* the address of the start of _c_int00 for C Code. /*
|
||||
/* When using the boot ROM this section and the CPU vector */
|
||||
/* table is not needed. Thus the default type is set here to */
|
||||
/* DSECT */
|
||||
.reset : > RESET, PAGE = 0, TYPE = DSECT
|
||||
vectors : > VECTORS PAGE = 0, TYPE = DSECT
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
//===========================================================================
|
||||
// End of file.
|
||||
//===========================================================================
|
||||
*/
|
||||
203
docs/ESP32-UART.md
Normal file
203
docs/ESP32-UART.md
Normal file
@ -0,0 +1,203 @@
|
||||
# Liaison UART ESP32-C3 <-> TMS320
|
||||
|
||||
Ce document decrit le protocole que le firmware TMS320 doit implementer
|
||||
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 | GPIO28, broche 48 |
|
||||
| ESP32 RX <- TMS320 TX | GPIO6 | GPIO29, broche 1 |
|
||||
| GND commun | GND | GND |
|
||||
|
||||
Niveaux logiques 3,3 V des deux cotes -- **verifie, carte CPU validee**.
|
||||
|
||||
## Parametres UART
|
||||
|
||||
- Vitesse : **57600 bauds**
|
||||
- Format : **8N1** (8 bits de donnees, pas de parite, 1 bit de stop)
|
||||
- Cote ESP32 : `Serial1`, defini dans `TmsLink::begin()`
|
||||
|
||||
## Format des trames
|
||||
|
||||
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=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 (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 intermediaire (sortie etage 1) |
|
||||
| `I1` | A | Courant shunt MOSFET etage 1 |
|
||||
| `T1` | degC | Temperature NTC etage 1 |
|
||||
| `VOUT` | V | Tension de sortie HT |
|
||||
| `I2` | A | Courant shunt MOSFET etage 2 |
|
||||
| `T2` | degC | Temperature NTC etage 2 |
|
||||
| `IOUT` | A | Courant de sortie |
|
||||
| `FAULT` | code 0-5 | Defaut courant (voir ci-dessous) |
|
||||
|
||||
### Codes `FAULT`
|
||||
|
||||
| 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)
|
||||
|
||||
```
|
||||
$C,HT=1,PWM1=1,PWM2=0*3E
|
||||
```
|
||||
|
||||
| Tag | Valeurs | Description |
|
||||
|---|---|---|
|
||||
| `HT` | 0 ou 1 | Sortie HT activee/desactivee |
|
||||
| `PWM1` | 0 ou 1 | PWM etage 1 activee/desactivee |
|
||||
| `PWM2` | 0 ou 1 | PWM etage 2 activee/desactivee |
|
||||
|
||||
L'ESP32 envoie cette trame toutes les **500 ms**, et immediatement a
|
||||
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), en hexadecimal majuscule
|
||||
sur 2 chiffres (`%02X`).
|
||||
|
||||
Pour la reception : meme calcul, comparaison au checksum recu, trame
|
||||
ignoree silencieusement si ca ne correspond pas.
|
||||
|
||||
> **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
|
||||
|
||||
- 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).
|
||||
423
docs/PROMPT-claude-code-TMS320.md
Normal file
423
docs/PROMPT-claude-code-TMS320.md
Normal file
@ -0,0 +1,423 @@
|
||||
# Prompt pour Claude Code — Firmware TMS320F28027 « dual boost »
|
||||
|
||||
> Version 2 — brochage corrigé, constantes de calibration réelles.
|
||||
> À coller dans Claude Code (CCS v21) après avoir ouvert le dépôt
|
||||
> `TMS320F28027-dualboost`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Contexte
|
||||
|
||||
Firmware d'une alimentation haute tension à deux étages boost en cascade,
|
||||
pour préamplis à tubes (charge ~10-15 mA, modulée à fréquence audio).
|
||||
|
||||
- **MCU** : TMS320F28027, LQFP48 (PT), carte sur mesure
|
||||
- **IDE** : Code Composer Studio v21 (Theia), compilateur C2000
|
||||
- **Bibliothèque** : C2000Ware, headers **bitfield** (`DSP28x_Project.h`,
|
||||
`F2802x_Device.h`) — **pas driverlib**
|
||||
- **Sonde** : Olimex TMS320-XDS100-V3
|
||||
- **Horloge** : **oscillateur interne INTOSC1 (10 MHz)**, PLL → SYSCLKOUT = 60 MHz
|
||||
- **Dépôt** : `git@git.syoul.fr:alim-High-Volt-boost/TMS320F28027-dualboost.git`
|
||||
|
||||
Un ESP32-C3 (dépôt `ESP32-C3-dualboost`, déjà fonctionnel : OLED SSD1322
|
||||
256x64 + serveur web) gère l'IHM. Protocole dans `docs/ESP32-UART.md` —
|
||||
**source de vérité, ne rien inventer**.
|
||||
|
||||
Puissance par étage : MOSFET IPD60R360 (600 V), diode SiC STPSC406 (600 V/4 A),
|
||||
inductance 47-100 µH. Sortie HT commutée par MOSFET N piloté par optocoupleur
|
||||
photovoltaïque VOM1271 (~265-325 µs — coupure de sécurité lente, **pas** une
|
||||
protection rapide).
|
||||
|
||||
---
|
||||
|
||||
## 2. Brochage définitif (LQFP48 PT)
|
||||
|
||||
### Entrées analogiques
|
||||
|
||||
| Signal | Broche | Fonction MCU | Rôle | Échelle |
|
||||
|---|---|---|---|---|
|
||||
| `V-Batt-adc` | 10 | ADCINA0 / **VREFHI** | VIN | 11,11 V/V (PE 36,7 V) |
|
||||
| `I-Batt-adc` | 8 | ADCINA1 | IIN | 1,125 A/V (PE 3,71 A) |
|
||||
| `I-shunt1-adc` | **9** | ADCINA2 / **COMP1A** / AIO2 | I1 + **protection étage 1** | 0,02 Ω × 30 → 1,667 A/V (PE 5,5 A) |
|
||||
| `I-HV-adc` | 7 | ADCINA3 | IOUT | 16,67 mA/V (PE 55 mA) |
|
||||
| `I-shunt2-adc` | **5** | ADCINA4 / **COMP2A** / AIO4 | I2 + **protection étage 2** | idem |
|
||||
| `V-HV-adc` | **14** | ADCINB2 / AIO10 | VOUT | 178,6 V/V (PE 589 V) |
|
||||
| `V-inter-adc` | **16** | ADCINB4 / AIO12 | V1 | 31,25 V/V (PE 103 V, V1max = 50 V → 1,6 V) |
|
||||
| `Temp1` | 17 | ADCINB6 / AIO14 | NTC étage 1 | voir §5 |
|
||||
| `Temp2` | 18 | ADCINB7 | NTC étage 2 | voir §5 |
|
||||
| *libre* | 13 | ADCINB1 | — | |
|
||||
| *libre* | 15 | ADCINB3 | — | |
|
||||
|
||||
**Points d'attention obligatoires :**
|
||||
|
||||
1. Les broches 5, 9, 14, 16, 17 sont des **AIO** (AIO4, AIO2, AIO10, AIO12,
|
||||
AIO14) et sont en **mode numérique par défaut au reset**. Sans écriture
|
||||
explicite dans `AIOMUX1` (valeur du champ = **2**), ni l'ADC ni les
|
||||
comparateurs ne voient le signal. **À faire en tout début d'init.**
|
||||
2. **Les shunts sont obligatoirement sur les entrées `COMPxA` (pins 9 et 5).**
|
||||
Contrainte de silicium : `COMPxA` est câblée en dur sur l'entrée
|
||||
non-inverseuse (+) du comparateur ; `COMPxB` n'existe que comme
|
||||
alternative au DAC sur l'entrée inverseuse (−). Il n'y a aucun mux
|
||||
permettant de les échanger. Seules les entrées A sont donc utilisées,
|
||||
avec `COMPSOURCE = 0` (référence = DAC interne).
|
||||
3. Les broches 9 et 5 servent **simultanément** d'entrée ADC et d'entrée
|
||||
comparateur — c'est la même broche physique, le signal alimente les deux
|
||||
chemins en parallèle. Pas de conflit, `I1`/`I2` restent disponibles en
|
||||
télémétrie.
|
||||
4. `VREFHI` est partagé avec `ADCINA0`, utilisé pour VIN → **référence ADC
|
||||
interne obligatoire** (`ADCCTL1.bit.ADCREFSEL = 0`, pleine échelle 3,3 V).
|
||||
Conséquences documentées : erreur de gain ±60 LSB, tempco −50 ppm/°C.
|
||||
Le signal sur ADCINA0 ne doit jamais dépasser VDDA.
|
||||
5. `V-inter` : pleine échelle 103 V pour une tension intermédiaire maximale
|
||||
de **50 V** → ~48 % de la plage ADC utilisée, ~2000 codes sur la plage
|
||||
utile. Pas de saturation, diviseur conservé tel quel.
|
||||
|
||||
### Répartition des tensions
|
||||
|
||||
- **Étage 1** : 10 V → **50 V max** (D ≈ 0,8)
|
||||
- **Étage 2** : 50 V → **400 V max** (D ≈ 0,875), consigne programmable
|
||||
200 / 250 / … / 400 V
|
||||
|
||||
### Entrées / sorties numériques
|
||||
|
||||
| Signal | Broche | Fonction MCU | Sens |
|
||||
|---|---|---|---|
|
||||
| `UART-RX` | 48 | GPIO28 / SCIRXDA | Entrée (← ESP32 GPIO7) |
|
||||
| `UART-TX` | 1 | GPIO29 / SCITXDA | Sortie (→ ESP32 GPIO6) |
|
||||
| `Stage1-PWM` | 29 | GPIO0 / **EPWM1A** | Sortie PWM étage 1 (HRPWM possible) |
|
||||
| `Stage1-default` | 28 | GPIO1 / **COMP1OUT** | Sortie comparateur 1 |
|
||||
| `Stage2-PWM` | 37 | GPIO2 / **EPWM2A** | Sortie PWM étage 2 (HRPWM possible) |
|
||||
| `Stage2-default` | 38 | GPIO3 / **COMP2OUT** | Sortie comparateur 2 |
|
||||
| `Stage1-EN` | 27 | GPIO16 | Sortie — enable logiciel étage 1 |
|
||||
| `Stage2-EN` | 26 | GPIO17 | Sortie — enable logiciel étage 2 |
|
||||
| `HV_EN` | 31 | GPIO32 | Sortie — commande sortie HT (VOM1271) |
|
||||
| LED bleue | 47 | GPIO12 | Sortie |
|
||||
| LED rouge | 35 | GPIO33 | Sortie |
|
||||
| JTAG | 20-23 | TDI/TMS/TDO/TCK | **Ne pas reconfigurer en GPIO** |
|
||||
|
||||
Les LEDs sont câblées avec une résistance de 1 kΩ vers le 3,3 V → **logique
|
||||
probablement inversée** (LED allumée quand le GPIO est bas) : à vérifier au
|
||||
premier test et à encapsuler dans `led_set()` pour ne pas polluer le reste
|
||||
du code.
|
||||
|
||||
**Aucune broche TZ externe n'est disponible** (GPIO12 = TZ1 → LED, GPIO16/17
|
||||
= TZ2/TZ3 → Stage-EN). Tous les trips passent par le Digital Compare interne
|
||||
depuis `COMPxOUT`. Conséquence assumée : pas de possibilité d'ajouter un trip
|
||||
externe (arrêt d'urgence, thermostat matériel) sans reprendre le routage.
|
||||
|
||||
### Logique de sécurité câblée (hors MCU)
|
||||
|
||||
Deux portes ET 74HVC1G08 (IC8, IC9), une par étage :
|
||||
|
||||
```
|
||||
COMPxOUT (Stagex-default, CMPINV=1 : 1=OK, 0=défaut) ──┐
|
||||
├─ AND ─→ PWMx-EN (driver)
|
||||
GPIO Stagex-EN (logiciel : 1=autorisé, 0=inhibé) ──┘
|
||||
```
|
||||
|
||||
Les drivers sont **actifs à l'état haut**, d'où `CMPINV = 1` sur les deux
|
||||
comparateurs. Le logiciel peut inhiber en plus, **jamais** outrepasser la
|
||||
protection matérielle.
|
||||
|
||||
---
|
||||
|
||||
## 3. Horloge — contraintes liées à l'oscillateur interne
|
||||
|
||||
Pas de quartz : INTOSC1 (10 MHz nominal). PLL pour 60 MHz :
|
||||
`PLLCR = 12`, `DIVSEL = /2` → 10 × 12 / 2 = 60 MHz.
|
||||
|
||||
**Obligatoire** : quand la source est l'oscillateur interne, `PLLLOCKPRD`
|
||||
doit être écrit avec **10000 minimum** (sinon le verrouillage PLL est mal
|
||||
temporisé).
|
||||
|
||||
**Dérive à documenter et à gérer** : 3,03 à 4,85 kHz/°C, plage 9,6-10,6 MHz
|
||||
sur −40/+125 °C, soit ~2 % sur 40 °C d'échauffement. Impacts :
|
||||
|
||||
- **UART fixé à 57600 bauds** (au lieu de 115200) pour élargir la marge de
|
||||
tolérance sur le timing des trames. Côté ESP32, `Serial1.begin(57600, ...)`
|
||||
doit être mis à jour en conséquence.
|
||||
- Fréquence de découpage réelle à ±2 % → `FREQ1`/`FREQ2` en télémétrie sont
|
||||
indicatifs, à annoter comme tels.
|
||||
- Prévoir un point d'accroche (fonction vide + commentaire) pour ajouter plus
|
||||
tard la compensation d'oscillateur via le capteur de température interne
|
||||
(voir *Oscillator Compensation Guide* + code C2000Ware).
|
||||
|
||||
---
|
||||
|
||||
## 4. Architecture logicielle demandée
|
||||
|
||||
```
|
||||
src/
|
||||
main.c boucle principale, ordonnancement
|
||||
bsp_clock.c/h InitSysCtrl, INTOSC1 + PLL 60 MHz, PLLLOCKPRD, watchdog
|
||||
bsp_gpio.c/h mux GPIO, AIOMUX1, LEDs, HV_EN, StageX-EN
|
||||
safety.c/h comparateurs + DAC, Digital Compare, Trip Zone, EMUSTOP
|
||||
pwm.c/h init ePWM1/ePWM2, duty, fréquence (HRPWM plus tard)
|
||||
adc.c/h init ADC (réf interne), SOC déclenché par ePWM, lecture brute
|
||||
measure.c/h conversion brut -> unités physiques (V, A, °C)
|
||||
control.c/h régulation — SQUELETTE UNIQUEMENT pour l'instant
|
||||
uart_link.c/h SCI-A 57600 8N1, trames $T,...*XX et $C,...*XX
|
||||
calib.h TOUTES les constantes matérielles, rien ailleurs
|
||||
protocol.h tags, structures Telemetry / CommandState
|
||||
docs/
|
||||
ESP32-UART.md protocole (copié depuis le dépôt ESP32)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Constantes de calibration (`calib.h`)
|
||||
|
||||
Aucune de ces valeurs ne doit apparaître ailleurs que dans ce header.
|
||||
|
||||
### Tensions et courants
|
||||
|
||||
| Grandeur | Relation | Coefficient |
|
||||
|---|---|---|
|
||||
| VIN | 1,8 V @ 20 V | `VIN = Vadc × 11,111` |
|
||||
| IIN | 2,4 V @ 2,7 A | `IIN = Vadc × 1,125` |
|
||||
| V1 (inter) | 3,2 V @ 100 V | `V1 = Vadc × 31,25` |
|
||||
| VOUT | 2,8 V @ 500 V | `VOUT = Vadc × 178,571` |
|
||||
| IOUT | 3,0 V @ 50 mA | `IOUT = Vadc × 0,016667` (A) |
|
||||
| I1, I2 | shunt 0,02 Ω, gain ×30 | `I = Vadc / 0,6 = Vadc × 1,6667` |
|
||||
|
||||
Conversion brut → volts : `Vadc = raw × 3.3f / 4096.0f` (référence interne).
|
||||
|
||||
### Seuil de protection (identique sur les deux étages)
|
||||
|
||||
- Seuil : **3 A** → `Vadc = 3 × 0,02 × 30 = 1,8 V`
|
||||
- Code DAC 10 bits : `round(1.8 / 3.3 × 1023)` = **558**
|
||||
- Marges connues : gain DAC −1,5 %, offset DAC 10 mV, offset comparateur
|
||||
±5 mV, hystérésis 35 mV (≈ 58 mA ramenés au courant) → précision réelle du
|
||||
seuil de l'ordre de ±2 %, largement suffisant face à la marge des composants.
|
||||
- Temps de réponse comparateur → Trip Zone : 30 ns (asynchrone).
|
||||
- Définir le seuil en **ampères** dans `calib.h`, et calculer le code DAC par
|
||||
macro — ne jamais écrire 558 en dur.
|
||||
|
||||
### NTC (B57451V5103J062, boîtier 0805)
|
||||
|
||||
Montage : `3,3 V — NTC 10 k — R 10 k — 0 V`, mesure au point milieu.
|
||||
R25 = 10 kΩ, B25/100 = 4000 K ±3 %.
|
||||
|
||||
```
|
||||
R_ntc = R_fixe × Vadc / (3.3 - Vadc)
|
||||
T(K) = 1 / ( 1/298.15 + ln(R_ntc / 10000) / 4000 )
|
||||
```
|
||||
|
||||
Points de repère attendus : 0,75 V à 0 °C · 1,65 V à 25 °C · 2,94 V à 80 °C ·
|
||||
3,09 V à 100 °C. La tension **monte** avec la température. Sensibilité vers
|
||||
100 °C : ~7,7 mV/°C, soit ~10 LSB/°C — suffisant pour de la surveillance.
|
||||
La tolérance ±3 % sur B donne ~±2 °C d'erreur vers 100 °C sans calibration
|
||||
individuelle : acceptable pour de la protection thermique, pas pour de la
|
||||
mesure de précision. À documenter.
|
||||
|
||||
Résistance fixe confirmée à **10 kΩ** (pont 10 k NTC / 10 k).
|
||||
|
||||
---
|
||||
|
||||
## 6. Étapes d'implémentation (un commit par étape)
|
||||
|
||||
### Étape 1 — Squelette + « hello LED »
|
||||
|
||||
- Projet CCS F28027, fichier `.cmd` Flash
|
||||
- `InitSysCtrl()` adapté : INTOSC1, `PLLLOCKPRD = 10000`, PLLCR 12, DIVSEL /2
|
||||
- Watchdog désactivé au début (à réactiver plus tard)
|
||||
- Clignotement LED pour valider compilation + flash + XDS100v3
|
||||
|
||||
**Critère** : LED à 1 Hz, compilation sans warning, SYSCLKOUT vérifié à 60 MHz
|
||||
(sortie XCLKOUT ou mesure d'un timer).
|
||||
|
||||
### Étape 2 — Sécurité (AVANT toute génération PWM)
|
||||
|
||||
Aucun PWM ne doit sortir tant que les protections ne sont pas armées **et
|
||||
testées**.
|
||||
|
||||
1. `AIOMUX1` : basculer AIO2, AIO4, AIO10, AIO12, AIO14 en mode analogique
|
||||
(valeur du champ = **2**).
|
||||
2. **Horloges de module** : `PCLKCR3.bit.COMP1ENCLK = 1` et `COMP2ENCLK = 1`,
|
||||
sinon `COMPSTS` ne se met jamais à jour.
|
||||
3. **Comparateurs COMP1 / COMP2** (shunt sur l'entrée A, DAC en référence) :
|
||||
- `COMPCTL.bit.COMPDACEN = 1` — **sans ça le bloc reste éteint**
|
||||
- `COMPCTL.bit.COMPSOURCE = 0` (entrée inverseuse = DAC interne)
|
||||
- `DACVAL.bit.DACVAL` = code du seuil 3 A. Formule TI :
|
||||
`V = DACVAL × (VDDA − VSSA) / 1023` → **1023, pas 4096**.
|
||||
Calculé par macro depuis `SAFETY_ISHUNT_THRESHOLD_A`, jamais en dur.
|
||||
- `COMPCTL.bit.CMPINV = 1` (driver actif haut : sortie 1 = OK, 0 = défaut)
|
||||
- `COMPCTL.bit.SYNCSEL = 0` (**asynchrone**) — c'est ce qui donne les 30 ns
|
||||
de temps de réponse vers le Trip Zone. `QUALSEL` n'a d'effet qu'en mode
|
||||
synchrone, qui ajoute de la latence : n'activer la qualification qu'après
|
||||
avoir observé le bruit réel au scope, jamais par précaution a priori.
|
||||
- Hystérésis d'entrée activée par défaut (~100 kΩ de contre-réaction,
|
||||
35 mV) : compatible avec la sortie basse impédance de l'ampli.
|
||||
`COMPHYSTCTL` (module ADC) permet de la désactiver si besoin.
|
||||
- Sorties routées : GPIO1 (COMP1OUT, mux 3) et GPIO3 (COMP2OUT, mux 3)
|
||||
4. **Trip Zone via Digital Compare**
|
||||
- `DCTRIPSEL.bit.DCAHCOMPSEL` = COMP1OUT (code `1000b`) pour ePWM1,
|
||||
COMP2OUT (`1001b`) pour ePWM2
|
||||
- `TZDCSEL.bit.DCAEVT1` : configurer l'événement sur DCAH bas
|
||||
- `TZSEL.bit.DCAEVT1 = 1` → **one-shot** (latché), pas cycle-by-cycle
|
||||
- `TZCTL.bit.TZA = TZ_FORCE_LO`
|
||||
5. **TZ6 = EMUSTOP** sur ePWM1 et ePWM2 :
|
||||
```c
|
||||
EPwm1Regs.TZSEL.bit.OSHT6 = 1;
|
||||
EPwm1Regs.TZCTL.bit.TZA = TZ_FORCE_LO;
|
||||
```
|
||||
Le signal `EMUSTOP` du CPU **existe bien** et est câblé en dur sur TZ6 sur
|
||||
cette famille (TRM SPRUI09A §3.2.7) ; il n'y a simplement pas de macro
|
||||
nommée `EMUSTOP` dans les headers, on écrit directement `OSHT6`.
|
||||
Indispensable : sans ça, un breakpoint posé pendant que le MOSFET conduit
|
||||
laisse l'inductance se charger sans limite.
|
||||
6. **`TBCTL.bit.FREE_SOFT = 0`** sur les deux ePWM, **en plus et non à la
|
||||
place** du point 5. Attention : `FREE_SOFT` gèle seulement le compteur de
|
||||
base de temps — l'Action Qualifier a déjà positionné la sortie, qui reste
|
||||
figée dans son dernier état. Si le CPU s'arrête pendant le temps de
|
||||
conduction, **la broche reste haute**. Seul TZ6 + `TZ_FORCE_LO` garantit
|
||||
la mise à l'état bas.
|
||||
7. **`EPWMx_TZINT`** : diagnostic logiciel uniquement (log, LED rouge,
|
||||
notification ESP32). La coupure est déjà faite en matériel.
|
||||
8. API : `safety_get_fault_flags()`, `safety_clear_faults()` (écriture
|
||||
`TZCLR[OST]`, **jamais appelée automatiquement**).
|
||||
9. **Test** : `TZFRC[OSHT]` pour forcer un trip logiciel, sans provoquer de
|
||||
vrai court-circuit.
|
||||
|
||||
**Critère** : `TZFRC` force les deux sorties à 0 et lève le flag ; un
|
||||
breakpoint coupe le PWM ; le flag ne se réarme pas seul.
|
||||
|
||||
### Étape 3 — GPIO de commande, état sûr au reset
|
||||
|
||||
- `HV_EN`, `Stage1-EN`, `Stage2-EN` à 0 **avant toute autre configuration**
|
||||
- ⚠️ **L'état au reset n'est PAS fail-safe** : GPIO1 et GPIO3 (COMPxOUT) sont
|
||||
des broches à fonction PWM, dont **les pull-ups ne sont pas activées au
|
||||
reset** → elles flottent. GPIO16/17 (Stage-EN) ne sont pas des broches PWM
|
||||
→ **pull-ups activées** → état haut. Résultat : à la mise sous tension, une
|
||||
entrée de chaque porte ET flotte et l'autre est à 1 → **sortie
|
||||
indéterminée, le driver peut être validé avant que le firmware ne tourne**.
|
||||
- Correctif **matériel obligatoire** : pull-down 10 kΩ sur les deux entrées
|
||||
de chaque porte ET, et sur les lignes PWM vers les drivers. Le firmware
|
||||
ne peut rien pour la fenêtre pré-boot.
|
||||
- Correctif logiciel complémentaire : désactiver les pull-ups internes de
|
||||
GPIO16/17 via `GPAPUD` dès l'init.
|
||||
- LED rouge = défaut latché, LED bleue = fonctionnement nominal
|
||||
- Encapsuler la polarité des LED dans `led_set()`
|
||||
|
||||
### Étape 4 — ADC
|
||||
|
||||
- Référence **interne** (`ADCREFSEL = 0`), `Device_cal()` appelé
|
||||
- 9 canaux (§2), **SOC déclenché par ePWM**, jamais en free-run :
|
||||
échantillonner à un instant stable, loin des fronts de commutation
|
||||
- ISR `ADCINT1` courte : stockage des valeurs brutes, rien de plus
|
||||
- Moyenne glissante côté `measure.c` pour la télémétrie. La protection rapide
|
||||
ne passe **pas** par l'ADC.
|
||||
|
||||
### Étape 5 — Conversion physique (`measure.c`)
|
||||
|
||||
Implémenter les relations du §5. Signature du type
|
||||
`float measure_vin(uint16_t raw)`, unités documentées dans les prototypes.
|
||||
|
||||
### Étape 6 — PWM
|
||||
|
||||
- **ePWM1** → étage 1 (EPWM1A, GPIO0) · **ePWM2** → étage 2 (EPWM2A, GPIO2)
|
||||
- Mode up-count, TBCLK = SYSCLKOUT = 60 MHz
|
||||
- `TBPRD = 60e6 / Fpwm - 1` (599 à 100 kHz, 299 à 200 kHz)
|
||||
- **Fréquences par étage : À CONFIRMER**
|
||||
- **Duty de départ = 0 %**, puis rampe logicielle montante (soft-start),
|
||||
indépendante de la régulation. Ne jamais démarrer au duty nominal : le
|
||||
condensateur de sortie vide provoquerait un appel de courant destructeur.
|
||||
|
||||
> **Important — un boost à 0 % de duty ne donne pas 0 V en sortie.** Le chemin
|
||||
> `Vin → L → diode → Cout` reste passant en permanence. Avec les deux étages à
|
||||
> 0 %, la sortie HT est déjà à ~10 V ; si l'étage 1 régule à 50 V pendant que
|
||||
> l'étage 2 est à 0 %, la sortie est à **50 V**. Le seul organe qui isole
|
||||
> réellement la charge est `HV_EN` (VOM1271). Conséquence pour le firmware :
|
||||
> toute séquence d'arrêt, de mise en sécurité ou de timeout UART doit couper
|
||||
> `HV_EN` **en plus** d'inhiber les PWM, jamais seulement les PWM.
|
||||
- API : `pwm_set_duty(stage, float duty_0_1)`, `pwm_set_freq(stage, hz)`,
|
||||
`pwm_enable(stage, bool)`
|
||||
- **HRPWM : ne pas l'implémenter maintenant**, mais structurer `pwm.c` pour
|
||||
l'accueillir. Notes à consigner en commentaire :
|
||||
- disponible uniquement sur les sorties **EPWMxA** → les deux étages sont
|
||||
compatibles tels que routés
|
||||
- SYSCLKOUT ≥ 50 MHz requis (OK à 60), MEP 150-310 ps, SFO obligatoire
|
||||
- **limitation de 3 cycles SYSCLK** sur le rapport cyclique : à 200 kHz,
|
||||
duty min 0,67 % et max 99 % — à confronter aux points de fonctionnement
|
||||
- à 100 kHz sans HRPWM : 600 pas ≈ 9,2 bits, risque de limit cycling en
|
||||
boucle fermée
|
||||
|
||||
### Étape 7 — Liaison UART
|
||||
|
||||
Conforme à `docs/ESP32-UART.md`, avec **57600 bauds 8N1** (et non 115200 —
|
||||
mettre à jour le document et le firmware ESP32).
|
||||
|
||||
- SCI-A, GPIO28 (RX) / GPIO29 (TX)
|
||||
- Émission `$T,...*XX` toutes les **200-500 ms** (l'ESP32 déclare la liaison
|
||||
perdue au-delà de 2 s)
|
||||
- Réception `$C,HT=x,PWM1=x,PWM2=x*XX` toutes les 500 ms
|
||||
- Checksum **XOR** entre `$` et `*`, format `%02X` **majuscules** (sinon rejet
|
||||
silencieux côté ESP32), terminaison `\n`, ligne ≤ 160 caractères
|
||||
- Réception par ISR + buffer circulaire, **parsing dans la boucle principale**
|
||||
- Trame au checksum invalide : ignorée silencieusement
|
||||
- **Timeout côté TMS320** : sans trame `$C` valide depuis > 2 s → retour en
|
||||
état sûr (PWM inhibés, HT coupée). La sécurité ne dépend jamais de l'ESP32.
|
||||
- Vérifier le support `%f` du compilateur C28x dans `sprintf` ; si coût trop
|
||||
élevé, formater les flottants manuellement (entier + décimales)
|
||||
|
||||
Champs : `FREQ1 FREQ2 DUTY1 DUTY2 VIN IIN V1 I1 T1 VOUT I2 T2 IOUT`
|
||||
|
||||
### Étape 8 — Régulation : squelette seulement
|
||||
|
||||
**Aucune régulation active à ce stade.** Le PWM reste à sa valeur de départ.
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
float setpoint; // consigne (V)
|
||||
float measured;
|
||||
float duty; // 0..1
|
||||
float duty_min, duty_max;
|
||||
bool enabled;
|
||||
} control_stage_t;
|
||||
|
||||
void control_init(void);
|
||||
void control_set_setpoint(int stage, float volts);
|
||||
float control_update(int stage, float measured, float dt); // renvoie duty inchangé
|
||||
```
|
||||
|
||||
Prévoir l'emplacement d'appel cadencé (`dt` constant) dans l'ISR ADC ou PWM.
|
||||
Documenter en commentaire : le boost à fort gain présente un **zéro dans le
|
||||
demi-plan droit** qui limite la bande passante atteignable ; la charge
|
||||
(~10-15 mA, modulée à fréquence audio) impose surtout un bon **PSRR en bande
|
||||
audio** ; une structure ADRC ou un PI cascadé sera évalué ensuite.
|
||||
|
||||
---
|
||||
|
||||
## 7. Règles
|
||||
|
||||
- **Headers bitfield uniquement**, pas de driverlib, pas de mélange sur un
|
||||
même périphérique
|
||||
- `EALLOW`/`EDIS` autour de tous les registres protégés (TZSEL, TZCTL, TZEINT,
|
||||
TZCLR, TZFRC, HRCNFG, GPxMUX, AIOMUX1, INTOSCnTRIM, table des vecteurs PIE…)
|
||||
- Aucune valeur magique : tout dans `calib.h`, commenté avec son origine
|
||||
- ISR courtes : pas de `sprintf`, pas de flottant lourd, pas d'attente
|
||||
- Ordre d'init strict : **GPIO en état sûr → AIOMUX1 → horloge → sécurité →
|
||||
ADC → UART → PWM en dernier**
|
||||
- Commits atomiques, un par étape
|
||||
|
||||
## 8. Interdits
|
||||
|
||||
- Activer le PWM avant que l'étape 2 soit testée
|
||||
- Effacer automatiquement un flag de défaut latché
|
||||
- Faire dépendre une protection du CPU, de l'ADC ou de l'UART
|
||||
- Implémenter la régulation en boucle fermée maintenant
|
||||
- Toucher aux broches JTAG (20-23)
|
||||
- Inventer le protocole UART
|
||||
|
||||
## 9. Points encore ouverts
|
||||
|
||||
1. Fréquence de découpage par étage (100 ou 200 kHz)
|
||||
2. Polarité réelle des LED (à confirmer au premier test)
|
||||
3. Valeur d'inductance retenue par étage (47 ou 100 µH) — influe sur le mode
|
||||
CCM/DCM et sur la vitesse de montée d'un courant de défaut
|
||||
|
||||
## 10. Première action attendue
|
||||
|
||||
Avant d'écrire du code : lire le dépôt, lister l'existant, **poser les
|
||||
questions sur les points du §9**, puis proposer un plan et attendre validation
|
||||
avant de commencer l'étape 1.
|
||||
194
docs/TMS-pin-v2.txt
Normal file
194
docs/TMS-pin-v2.txt
Normal file
@ -0,0 +1,194 @@
|
||||
TMS320F28027 — LQFP48 (PT) — Brochage
|
||||
Reference : SPRS523P, tableau 7.2.1 "Signal Descriptions" (faisant foi)
|
||||
|
||||
================================================================
|
||||
V0.01 — ETAT ACTUEL, ANNOTE
|
||||
================================================================
|
||||
|
||||
Pin Signal actuel Fonction reelle du silicium Verdict
|
||||
--- ----------------- ------------------------------ ----------------
|
||||
01 UART-TX GPIO29/SCITXDA/SCLA/TZ3 OK
|
||||
02 JTAG-TRST TRST OK
|
||||
03 RST XRS OK
|
||||
04 NC ADCINA6/AIO6 OK
|
||||
05 I-shunt1-adc ADCINA4/COMP2A/AIO4 DOUBLON (cf. 13)
|
||||
06 NC ADCINA7 OK
|
||||
07 I-HV-adc ADCINA3 OK
|
||||
08 I-Batt-adc ADCINA1 OK
|
||||
09 V-HV-adc ADCINA2/COMP1A/AIO2 A DEPLACER (E1)
|
||||
10 V-Batt-adc ADCINA0/VREFHI OK
|
||||
11 10uF VDDA + 3.3V (E3)
|
||||
12 GND VSSA/VREFLO OK
|
||||
13 I-shunt1-adc ADCINB1 DOUBLON (cf. 05)
|
||||
14 V-inter-adc ADCINB2/COMP1B/AIO10 A DEPLACER (E1)
|
||||
15 I-shunt2-adc ADCINB3 DOUBLON (cf. 16)
|
||||
16 I-shunt2-adc ADCINB4/COMP2B/AIO12 DOUBLON (cf. 15)
|
||||
17 Temp1 ADCINB6/AIO14 OK
|
||||
18 Temp2 ADCINB7 OK
|
||||
19 NC GPIO34/COMP2OUT OK
|
||||
20 JTAG-TDI GPIO35 (TDI) OK
|
||||
21 JTAG-TMS GPIO36 (TMS) OK
|
||||
22 JTAG-TDO GPIO37 (TDO) OK
|
||||
23 JTAG-CK GPIO38/XCLKIN (TCK) OK
|
||||
24 NC GPIO18 OK
|
||||
25 NC GPIO19 OK
|
||||
26 Stage2-En GPIO17/SPISOMIA/TZ3 OK
|
||||
27 Stage1-En GPIO16/SPISIMOA/TZ2 OK
|
||||
28 Stage1-default GPIO1/EPWM1B/COMP1OUT OK
|
||||
29 Stage1-PWM GPIO0/EPWM1A OK
|
||||
30 NC TEST OK
|
||||
31 Stage2-PWM GPIO32/SDAA/EPWMSYNCI ERREUR (E2)
|
||||
32 10uF VDD OK
|
||||
33 GND VSS OK
|
||||
34 GND VREGENZ OK
|
||||
35 3.3V VDDIO + 10uF
|
||||
36 RED LED GPIO33/SCLA/EPWMSYNCO OK
|
||||
37 HV-EN GPIO2/EPWM2A ERREUR (E2)
|
||||
38 Stage2-default GPIO3/EPWM2B/COMP2OUT OK
|
||||
39 NC GPIO4/EPWM3A OK
|
||||
40 NC GPIO5/EPWM3B/ECAP1 OK
|
||||
41 NC GPIO6/EPWM4A OK
|
||||
42 NC GPIO7/EPWM4B/SCIRXDA OK
|
||||
43 10uF VDD OK
|
||||
44 GND VSS OK
|
||||
45 GND X1 OK
|
||||
46 NC X2 OK
|
||||
47 BLUE LED GPIO12/TZ1/SCITXDA OK
|
||||
48 UART-RX GPIO28/SCIRXDA/SDAA/TZ2 OK
|
||||
|
||||
|
||||
----------------------------------------------------------------
|
||||
ERREURS RESTANTES
|
||||
----------------------------------------------------------------
|
||||
|
||||
[E1] SHUNTS SUR LES MAUVAISES ENTREES COMPARATEUR --- BLOQUANT ---
|
||||
COMPxA est cablee en dur sur l'entree NON INVERSEUSE (+) du
|
||||
comparateur. COMPxB n'existe que comme alternative au DAC sur
|
||||
l'entree inverseuse (-). Aucun mux ne permet de les echanger.
|
||||
Pour garder un seuil programmable par le DAC interne, les shunts
|
||||
doivent etre sur les entrees A :
|
||||
COMP1A = pin 09 -> sortie COMP1OUT = pin 28 (Stage1-default)
|
||||
COMP2A = pin 05 -> sortie COMP2OUT = pin 38 (Stage2-default)
|
||||
V-HV-adc et V-inter-adc migrent vers 14 et 16 (entrees ADC simples).
|
||||
|
||||
[E2] STAGE2-PWM ET HV-EN INVERSES --- BLOQUANT ---
|
||||
31 = GPIO32, ce n'est pas une sortie ePWM -> HV-EN
|
||||
37 = GPIO2/EPWM2A, sortie PWM etage 2 (et canal HRPWM) -> Stage2-PWM
|
||||
|
||||
[E3] ALIMENTATIONS A COMPLETER
|
||||
11 = VDDA : 3,3 V + 2,2 uF au plus pres de la broche
|
||||
35 = VDDIO : 3,3 V + condensateur de decouplage (actuellement absent)
|
||||
32 et 43 = VDD : 1,2 uF minimum par broche (VREG interne actif)
|
||||
|
||||
[E4] DOUBLONS DE SIGNAUX
|
||||
I-shunt1-adc present sur 05 et 13
|
||||
I-shunt2-adc present sur 15 et 16
|
||||
Le fichier melange l'affectation d'origine et une correction partielle.
|
||||
|
||||
[E5] TYPO
|
||||
22 : "JTAG-TMO" -> TDO
|
||||
|
||||
[E6] PULLDOWN TRST MANQUANT
|
||||
02 = TRST, actif haut, doit rester bas en fonctionnement normal.
|
||||
Resistance de pulldown externe obligatoire, 2,2 kohm recommandee.
|
||||
|
||||
|
||||
================================================================
|
||||
V0.2 — BROCHAGE CORRIGE
|
||||
================================================================
|
||||
|
||||
Pin Signal Fonction MCU Note
|
||||
--- ------------------ ------------------------------ ------------------
|
||||
01 UART-TX GPIO29 / SCITXDA vers ESP32 GPIO6
|
||||
02 JTAG-TRST TRST + pulldown 2,2k
|
||||
03 RST XRS
|
||||
04 NC ADCINA6 / AIO6
|
||||
05 I-shunt2-adc ADCINA4 / COMP2A / AIO4 protection etage 2
|
||||
06 NC ADCINA7
|
||||
07 I-HV-adc ADCINA3 IOUT
|
||||
08 I-Batt-adc ADCINA1 IIN
|
||||
09 I-shunt1-adc ADCINA2 / COMP1A / AIO2 protection etage 1
|
||||
10 V-Batt-adc ADCINA0 / VREFHI VIN
|
||||
11 3.3V + 2.2uF VDDA alim analogique
|
||||
12 GND VSSA / VREFLO
|
||||
13 NC ADCINB1 libre
|
||||
14 V-HV-adc ADCINB2 / AIO10 VOUT
|
||||
15 NC ADCINB3 libre
|
||||
16 V-inter-adc ADCINB4 / AIO12 V1
|
||||
17 Temp1 ADCINB6 / AIO14 NTC etage 1
|
||||
18 Temp2 ADCINB7 NTC etage 2
|
||||
19 NC GPIO34 / COMP2OUT
|
||||
20 JTAG-TDI GPIO35
|
||||
21 JTAG-TMS GPIO36
|
||||
22 JTAG-TDO GPIO37 etait "TMO"
|
||||
23 JTAG-TCK GPIO38 / XCLKIN
|
||||
24 NC GPIO18
|
||||
25 NC GPIO19
|
||||
26 Stage2-EN GPIO17 vers porte ET IC9
|
||||
27 Stage1-EN GPIO16 vers porte ET IC8
|
||||
28 Stage1-default GPIO1 / COMP1OUT vers porte ET IC8
|
||||
29 Stage1-PWM GPIO0 / EPWM1A HRPWM possible
|
||||
30 NC TEST LAISSER NON CONNECTE
|
||||
31 HV-EN GPIO32 etait Stage2-PWM
|
||||
32 VDD + 1.2uF VDD
|
||||
33 GND VSS
|
||||
34 GND VREGENZ active le VREG 1,8V
|
||||
35 3.3V + decouplage VDDIO condo a ajouter
|
||||
36 RED LED GPIO33
|
||||
37 Stage2-PWM GPIO2 / EPWM2A etait HV-EN
|
||||
38 Stage2-default GPIO3 / COMP2OUT vers porte ET IC9
|
||||
39 NC GPIO4 / EPWM3A
|
||||
40 NC GPIO5 / EPWM3B
|
||||
41 NC GPIO6 / EPWM4A
|
||||
42 NC GPIO7 / EPWM4B
|
||||
43 VDD + 1.2uF VDD
|
||||
44 GND VSS
|
||||
45 GND X1 oscillateur interne
|
||||
46 NC X2 oscillateur interne
|
||||
47 BLUE LED GPIO12 TZ1 non disponible
|
||||
48 UART-RX GPIO28 / SCIRXDA depuis ESP32 GPIO7
|
||||
|
||||
|
||||
----------------------------------------------------------------
|
||||
MODIFICATIONS V0.01 -> V0.2
|
||||
----------------------------------------------------------------
|
||||
|
||||
05 I-shunt1-adc -> I-shunt2-adc (vers COMP2A)
|
||||
09 V-HV-adc -> I-shunt1-adc (vers COMP1A)
|
||||
13 I-shunt1-adc -> NC
|
||||
14 V-inter-adc -> V-HV-adc
|
||||
15 I-shunt2-adc -> NC
|
||||
16 I-shunt2-adc -> V-inter-adc
|
||||
31 Stage2-PWM -> HV-EN
|
||||
35 ajout d'un condensateur de decouplage 10uF
|
||||
37 HV-EN -> Stage2-PWM
|
||||
|
||||
Broches 34, 36, 43, 44 : inchangees, la V0.01 etait correcte.
|
||||
|
||||
|
||||
----------------------------------------------------------------
|
||||
POINTS MATERIELS HORS BROCHAGE
|
||||
----------------------------------------------------------------
|
||||
|
||||
1. PULLDOWN SUR LES PORTES ET --- IMPORTANT ---
|
||||
GPIO1 et GPIO3 (COMPxOUT) sont des broches a fonction PWM : leurs
|
||||
pullups internes ne sont PAS activees au reset, elles flottent.
|
||||
GPIO16/17 ne sont pas des broches PWM : pullups activees -> etat haut.
|
||||
A la mise sous tension, une entree de chaque porte ET flotte et l'autre
|
||||
est a 1 -> sortie indeterminee, le driver peut etre valide avant que le
|
||||
firmware ne demarre.
|
||||
-> Pulldown 10 kohm sur les DEUX entrees de chaque porte ET (IC8, IC9)
|
||||
-> Pulldown 10 kohm sur les lignes PWM vers les drivers
|
||||
Le firmware ne peut rien pour la fenetre pre-boot.
|
||||
|
||||
2. Sequencement d'alimentation : alimenter VDD avant ou avec VDDIO
|
||||
(VDD doit atteindre 0,7 V avant VDDIO) pour eviter un glitch sur les
|
||||
sorties au demarrage.
|
||||
|
||||
3. Oscillateur interne : X1 (45) a la masse, X2 (46) non connecte.
|
||||
Derive de 3,03 a 4,85 kHz/degC, d'ou le choix de 57600 bauds sur l'UART
|
||||
au lieu de 115200.
|
||||
|
||||
4. TEST (30) : imperativement laisse non connecte (reserve TI).
|
||||
|
||||
5. Broches 13 et 15 libres (ADCINB1, ADCINB3) pour une mesure future.
|
||||
202
docs/mesure-cartepuissance.md
Normal file
202
docs/mesure-cartepuissance.md
Normal 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
|
||||
BIN
docs/sprui09a.pdf
Normal file
BIN
docs/sprui09a.pdf
Normal file
Binary file not shown.
BIN
docs/tms320f28027.pdf
Normal file
BIN
docs/tms320f28027.pdf
Normal file
Binary file not shown.
154
src/adc.c
Normal file
154
src/adc.c
Normal file
@ -0,0 +1,154 @@
|
||||
#include "DSP28x_Project.h"
|
||||
#include "adc.h"
|
||||
#include "control.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;
|
||||
|
||||
// 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)
|
||||
{
|
||||
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++;
|
||||
|
||||
// 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;
|
||||
}
|
||||
46
src/adc.h
Normal file
46
src/adc.h
Normal file
@ -0,0 +1,46 @@
|
||||
#ifndef ADC_H
|
||||
#define ADC_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Ordre des voies = ordre des SOC0..SOC8. Brochage LQFP48 PT, cf. PROMPT §2.
|
||||
typedef enum
|
||||
{
|
||||
ADC_CH_VIN = 0, // broche 10, ADCINA0 (partagee avec VREFHI)
|
||||
ADC_CH_IIN, // broche 8, ADCINA1
|
||||
ADC_CH_I1, // broche 9, ADCINA2 / COMP1A / AIO2
|
||||
ADC_CH_IOUT, // broche 7, ADCINA3
|
||||
ADC_CH_I2, // broche 5, ADCINA4 / COMP2A / AIO4
|
||||
ADC_CH_VOUT, // broche 14, ADCINB2 / AIO10
|
||||
ADC_CH_V1, // broche 16, ADCINB4 / AIO12
|
||||
ADC_CH_T1, // broche 17, ADCINB6 / AIO14
|
||||
ADC_CH_T2, // broche 18, ADCINB7
|
||||
ADC_CH_COUNT
|
||||
} adc_channel_t;
|
||||
|
||||
// Reference interne (ADCREFSEL = 0, pleine echelle 3,3 V) : imposee car
|
||||
// VREFHI est partagee avec ADCINA0, utilisee pour VIN (PROMPT §2).
|
||||
// Les SOC sont declenches par ePWM1, jamais en free-run, pour echantillonner
|
||||
// a un instant stable loin des fronts de commutation (PROMPT §6 etape 4).
|
||||
//
|
||||
// A appeler APRES bsp_gpio_analog_init() (bascule des AIO en mode analogique)
|
||||
// et APRES pwm_init() (c'est ePWM1 qui fournit le declenchement).
|
||||
void adc_init(void);
|
||||
|
||||
// Derniere valeur brute (0..4095) rangee par l'ISR ADCINT1.
|
||||
uint16_t adc_get_raw(adc_channel_t ch);
|
||||
|
||||
// Meme valeur convertie en volts a l'entree de la broche (0..3,3 V).
|
||||
// Ce n'est PAS la grandeur physique : la conversion vers V/A/°C (ponts
|
||||
// diviseurs, gain de shunt, NTC) sera le role de measure.c.
|
||||
float adc_get_volts(adc_channel_t ch);
|
||||
|
||||
// true si au moins une voie est sous le seuil donne, exprime en volts.
|
||||
bool adc_any_below(float volts);
|
||||
|
||||
// Compteur de conversions completes, pour verifier au debogueur que les SOC
|
||||
// tournent bien.
|
||||
uint32_t adc_get_sequence_count(void);
|
||||
|
||||
#endif
|
||||
28
src/bsp_clock.c
Normal file
28
src/bsp_clock.c
Normal file
@ -0,0 +1,28 @@
|
||||
#include "DSP28x_Project.h"
|
||||
#include "bsp_clock.h"
|
||||
#include "calib.h"
|
||||
|
||||
void bsp_clock_init(void)
|
||||
{
|
||||
EALLOW;
|
||||
SysCtrlRegs.PLLLOCKPRD = CLK_PLLLOCKPRD;
|
||||
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
|
||||
}
|
||||
6
src/bsp_clock.h
Normal file
6
src/bsp_clock.h
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef BSP_CLOCK_H
|
||||
#define BSP_CLOCK_H
|
||||
|
||||
void bsp_clock_init(void);
|
||||
|
||||
#endif
|
||||
186
src/bsp_gpio.c
Normal file
186
src/bsp_gpio.c
Normal file
@ -0,0 +1,186 @@
|
||||
#include "DSP28x_Project.h"
|
||||
#include "bsp_gpio.h"
|
||||
#include "calib.h"
|
||||
|
||||
void bsp_gpio_leds_init(void)
|
||||
{
|
||||
EALLOW;
|
||||
GpioCtrlRegs.GPAMUX1.bit.GPIO12 = 0;
|
||||
GpioCtrlRegs.GPADIR.bit.GPIO12 = 1;
|
||||
GpioCtrlRegs.GPBMUX1.bit.GPIO33 = 0;
|
||||
GpioCtrlRegs.GPBDIR.bit.GPIO33 = 1;
|
||||
EDIS;
|
||||
|
||||
led_set(LED_BLUE, false);
|
||||
led_set(LED_RED, false);
|
||||
}
|
||||
|
||||
void bsp_gpio_analog_init(void)
|
||||
{
|
||||
EALLOW;
|
||||
GpioCtrlRegs.AIOMUX1.bit.AIO2 = GPIO_ANALOG_MODE;
|
||||
GpioCtrlRegs.AIOMUX1.bit.AIO4 = GPIO_ANALOG_MODE;
|
||||
GpioCtrlRegs.AIOMUX1.bit.AIO10 = GPIO_ANALOG_MODE;
|
||||
GpioCtrlRegs.AIOMUX1.bit.AIO12 = GPIO_ANALOG_MODE;
|
||||
GpioCtrlRegs.AIOMUX1.bit.AIO14 = GPIO_ANALOG_MODE;
|
||||
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;
|
||||
|
||||
// DISCHARGE = GPIO5 (broche 40)
|
||||
GpioCtrlRegs.GPAMUX1.bit.GPIO5 = 0;
|
||||
GpioCtrlRegs.GPADIR.bit.GPIO5 = 1;
|
||||
EDIS;
|
||||
|
||||
// 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)
|
||||
{
|
||||
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 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;
|
||||
// GPIO1 = COMP1OUT (broche 28), GPIO3 = COMP2OUT (broche 38).
|
||||
// Mux 0 = GPIO : on reprend la main sur des broches normalement pilotees
|
||||
// par les comparateurs. Bring-up uniquement, voir bsp_gpio.h.
|
||||
GpioCtrlRegs.GPAMUX1.bit.GPIO1 = 0;
|
||||
GpioCtrlRegs.GPAMUX1.bit.GPIO3 = 0;
|
||||
GpioCtrlRegs.GPADIR.bit.GPIO1 = 1;
|
||||
GpioCtrlRegs.GPADIR.bit.GPIO3 = 1;
|
||||
EDIS;
|
||||
|
||||
stage_default_set(STAGE_1, false);
|
||||
stage_default_set(STAGE_2, false);
|
||||
}
|
||||
|
||||
void stage_default_set(stage_id_t stage, bool level)
|
||||
{
|
||||
if (stage == STAGE_1)
|
||||
{
|
||||
if (level)
|
||||
{
|
||||
GpioDataRegs.GPASET.bit.GPIO1 = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
GpioDataRegs.GPACLEAR.bit.GPIO1 = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (level)
|
||||
{
|
||||
GpioDataRegs.GPASET.bit.GPIO3 = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
GpioDataRegs.GPACLEAR.bit.GPIO3 = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void led_set(led_id_t led, bool on)
|
||||
{
|
||||
#if LED_ACTIVE_LOW
|
||||
bool pin_high = !on;
|
||||
#else
|
||||
bool pin_high = on;
|
||||
#endif
|
||||
|
||||
if (led == LED_BLUE)
|
||||
{
|
||||
if (pin_high)
|
||||
{
|
||||
GpioDataRegs.GPASET.bit.GPIO12 = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
GpioDataRegs.GPACLEAR.bit.GPIO12 = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pin_high)
|
||||
{
|
||||
GpioDataRegs.GPBSET.bit.GPIO33 = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
GpioDataRegs.GPBCLEAR.bit.GPIO33 = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/bsp_gpio.h
Normal file
73
src/bsp_gpio.h
Normal file
@ -0,0 +1,73 @@
|
||||
#ifndef BSP_GPIO_H
|
||||
#define BSP_GPIO_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum
|
||||
{
|
||||
LED_BLUE = 0,
|
||||
LED_RED
|
||||
} led_id_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
STAGE_1 = 0,
|
||||
STAGE_2
|
||||
} stage_id_t;
|
||||
|
||||
void bsp_gpio_leds_init(void);
|
||||
void led_set(led_id_t led, bool on);
|
||||
|
||||
// Bascule AIO2, AIO4, AIO10, AIO12, AIO14 en mode analogique (shunts,
|
||||
// COMP1A/COMP2A, VOUT, V1, NTC1). A appeler avant safety_init()/adc_init().
|
||||
void bsp_gpio_analog_init(void);
|
||||
|
||||
// Sorties de commande dans leur etat sur : HV_EN, Stage1-EN et Stage2-EN a 0.
|
||||
// Desactive aussi les pull-ups internes de GPIO16/17, qui sinon presentent un
|
||||
// niveau haut sur une entree de chaque porte ET des la mise sous tension
|
||||
// (PROMPT §6 etape 3).
|
||||
void bsp_gpio_control_init(void);
|
||||
|
||||
// Stagex-EN : entree "autorisation logicielle" de la porte ET de l'etage.
|
||||
void stage_enable_set(stage_id_t stage, bool enabled);
|
||||
|
||||
// HV_EN (GPIO32) : commande de la sortie HT via l'optocoupleur VOM1271.
|
||||
// Seul organe qui isole reellement la charge -- un boost a 0 % de duty ne
|
||||
// donne PAS 0 V en sortie (PROMPT §6 etape 6).
|
||||
void hv_enable_set(bool enabled);
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
145
src/calib.h
Normal file
145
src/calib.h
Normal file
@ -0,0 +1,145 @@
|
||||
#ifndef CALIB_H
|
||||
#define CALIB_H
|
||||
|
||||
// Horloge : INTOSC1 (10 MHz nominal) + PLL (DSP28_PLLCR=12, DSP28_DIVSEL=2,
|
||||
// deja les valeurs par defaut de f2802x_examples.h pour ce device) -> 60 MHz.
|
||||
// PLLLOCKPRD doit etre ecrit AVANT InitSysCtrl() (qui attend le verrouillage
|
||||
// PLL en interne) car l'oscillateur interne impose un minimum de 10000.
|
||||
#define CLK_PLLLOCKPRD 10000
|
||||
#define CLK_SYSCLKOUT_HZ 60000000UL
|
||||
|
||||
// LEDs (LQFP48 PT) : bleue = GPIO12 (broche 47), rouge = GPIO33 (broche 36)
|
||||
// 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
|
||||
#define GPIO_ANALOG_MODE 2
|
||||
|
||||
// =====================================================================
|
||||
// Constantes de calibration des chaines de mesure.
|
||||
// Source : docs/mesure-cartepuissance.md (mesures reelles sur la carte
|
||||
// elevateur). Aucune de ces valeurs ne doit apparaitre ailleurs.
|
||||
// =====================================================================
|
||||
|
||||
// ---- Chaines de tension (doc §1) ------------------------------------
|
||||
// Gain = Vreel / Vadc, soit l'inverse du rapport du pont diviseur.
|
||||
// VIN et V1 tombent sur les valeurs theoriques ; VOUT s'en ecarte de
|
||||
// 1,8 % (tolerance des resistances), sans consequence : la pleine
|
||||
// echelle reelle (600 V) reste au-dessus des 400 V vises.
|
||||
#define MEAS_VIN_GAIN_V_PER_V 11.11f // broche 10, coef mesure 0,09
|
||||
#define MEAS_V1_GAIN_V_PER_V 31.25f // broche 16, coef mesure 0,032
|
||||
#define MEAS_VOUT_GAIN_V_PER_V 181.82f // broche 14, coef mesure 0,0055
|
||||
|
||||
// ---- Courant d'entree (doc §2) --------------------------------------
|
||||
// Un seul point mesure (250 mA -> 0,208 V) : la caracteristique est
|
||||
// SUPPOSEE passer par zero, offset non verifie. Un second point reste
|
||||
// a faire.
|
||||
#define MEAS_IIN_A_PER_V 1.2019f
|
||||
|
||||
// ---- Courant de sortie (doc §4) -------------------------------------
|
||||
// NON MESURE : valeur theorique de conception (3,0 V @ 50 mA).
|
||||
// A remplacer des qu'une mesure reelle sera disponible.
|
||||
#define MEAS_IOUT_A_PER_V 0.016667f
|
||||
|
||||
// ---- Shunts MOSFET 0,02 ohm (doc §3) --------------------------------
|
||||
// PROVISOIRE : mesures faites avec les MCP6001 de banc. Le gain, fixe
|
||||
// par le reseau RF/RG, doit rester valable apres passage au TLV9151 ;
|
||||
// l'OFFSET en revanche est domine par le Vos de l'ampli et devra etre
|
||||
// ENTIEREMENT REMESURE. Les deux voies different d'environ 1 % :
|
||||
// garder deux jeux de constantes, ne jamais moyenner.
|
||||
#define MEAS_I1_OFFSET_V 0.0473f
|
||||
#define MEAS_I1_GAIN_V_PER_A 0.631f
|
||||
#define MEAS_I2_OFFSET_V 0.0340f
|
||||
#define MEAS_I2_GAIN_V_PER_A 0.637f
|
||||
|
||||
// ---- NTC B57451V5103J062 (PROMPT §5) --------------------------------
|
||||
// Montage : 3,3 V -- NTC -- R_fixe -- 0 V, mesure au point milieu.
|
||||
// La NTC etant du cote 3,3 V, la tension MONTE avec la temperature :
|
||||
// R_ntc = R_fixe * (VREF - Vadc) / Vadc
|
||||
// (le PROMPT §5 donne cette relation inversee ; verifie sur ses propres
|
||||
// points de repere : 0,75 V a 0 C -> 34,2 kOhm, ce que beta=4000 predit,
|
||||
// alors que la forme inversee donnerait 2,94 kOhm, donc du chaud.)
|
||||
// R_fixe suppose a 10 kOhm : reste a confirmer sur la carte (doc §6).
|
||||
#define MEAS_NTC_R_FIXED_OHM 10000.0f
|
||||
#define MEAS_NTC_R25_OHM 10000.0f
|
||||
#define MEAS_NTC_BETA_K 4000.0f
|
||||
#define MEAS_NTC_T25_K 298.15f
|
||||
#define MEAS_KELVIN_OFFSET 273.15f
|
||||
|
||||
// ---- Seuil de protection rapide (doc §5) ----------------------------
|
||||
// Le seuil est defini en AMPERES ; le code DAC est calcule par macro a
|
||||
// partir du gain et de l'offset mesures, jamais ecrit en dur. Ainsi le
|
||||
// futur passage au TLV9151 ne touchera que les constantes ci-dessus.
|
||||
//
|
||||
// Formule TI : V = DACVAL * (VDDA - VSSA) / 1023 -> 1023, pas 4096.
|
||||
// Seuil nominal (PROMPT §5). Le declenchement a ete valide au banc a 1,5 A
|
||||
// (courant injectable), puis le seuil remis a sa valeur de service.
|
||||
#define SAFETY_ISHUNT_THRESHOLD_A 3.0f
|
||||
#define SAFETY_DAC_VREF_V 3.3f
|
||||
|
||||
#define SAFETY_DAC_CODE_FROM_V(v_) \
|
||||
((uint16_t)((v_) / SAFETY_DAC_VREF_V * 1023.0f + 0.5f))
|
||||
|
||||
// Seuil ramene a la sortie de l'ampli : Vadc = offset + I * gain.
|
||||
#define SAFETY_DAC_CODE_STAGE1 \
|
||||
SAFETY_DAC_CODE_FROM_V(MEAS_I1_OFFSET_V \
|
||||
+ SAFETY_ISHUNT_THRESHOLD_A * MEAS_I1_GAIN_V_PER_A)
|
||||
#define SAFETY_DAC_CODE_STAGE2 \
|
||||
SAFETY_DAC_CODE_FROM_V(MEAS_I2_OFFSET_V \
|
||||
+ SAFETY_ISHUNT_THRESHOLD_A * MEAS_I2_GAIN_V_PER_A)
|
||||
|
||||
// ---- Protection thermique (logicielle, PROMPT §6 etape 4) -----------
|
||||
// Contrairement a la surintensite, la thermique est lente : le logiciel
|
||||
// suffit, aucun chemin materiel n'est requis.
|
||||
// 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 80.0f
|
||||
#define SAFETY_OVERTEMP_HYST_C 10.0f
|
||||
|
||||
// ---- Timeout de la liaison ESP32 (PROMPT §6 etape 7) ----------------
|
||||
// Sans trame $C valide au-dela de ce delai, la liaison est declaree
|
||||
// perdue. La securite ne depend jamais de l'ESP32.
|
||||
#define UART_LINK_TIMEOUT_MS 2000U
|
||||
|
||||
// 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
|
||||
|
||||
// ---- 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
|
||||
// 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 200U // limite cote ESP32 (g_lineBuf[200])
|
||||
|
||||
#endif
|
||||
121
src/control.c
Normal file
121
src/control.c
Normal 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
32
src/control.h
Normal 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
|
||||
337
src/main.c
Normal file
337
src/main.c
Normal file
@ -0,0 +1,337 @@
|
||||
#include "DSP28x_Project.h"
|
||||
#include "bsp_clock.h"
|
||||
#include "bsp_gpio.h"
|
||||
#include "safety.h"
|
||||
#include "uart_link.h"
|
||||
#include "protocol.h"
|
||||
#include "pwm.h"
|
||||
#include "adc.h"
|
||||
#include "measure.h"
|
||||
#include "status_led.h"
|
||||
#include "control.h"
|
||||
#include "calib.h"
|
||||
|
||||
extern uint16_t RamfuncsLoadStart;
|
||||
extern uint16_t RamfuncsLoadSize;
|
||||
extern uint16_t RamfuncsRunStart;
|
||||
|
||||
interrupt void cpu_timer0_isr(void);
|
||||
|
||||
// Dernier CommandState recu, expose au debogueur.
|
||||
static volatile command_state_t g_last_cmd = {false, false, false};
|
||||
static volatile bool s_send_telemetry = false;
|
||||
|
||||
// Age de la derniere trame $C valide, en ticks de 10 ms. Incremente par
|
||||
// l'ISR, remis a zero par la boucle principale a chaque commande valide.
|
||||
// Sature pour ne jamais reboucler.
|
||||
#define TICK_MS 10U
|
||||
#define LINK_TIMEOUT_TICKS ((uint16_t)(UART_LINK_TIMEOUT_MS / TICK_MS))
|
||||
static volatile uint16_t s_ticks_since_cmd = LINK_TIMEOUT_TICKS;
|
||||
|
||||
// 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)
|
||||
{
|
||||
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)));
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
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.
|
||||
//
|
||||
// Couper HV_EN est indispensable et n'a rien de redondant : un boost a 0 %
|
||||
// de duty ne donne PAS 0 V en sortie, le chemin Vin -> L -> diode -> Cout
|
||||
// reste passant en permanence. HV_EN (VOM1271) est le seul organe qui isole
|
||||
// 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.
|
||||
static led_state_t compute_led_state(const safety_faults_t *f)
|
||||
{
|
||||
safety_faults_t faults = *f;
|
||||
|
||||
if (faults.overcurrent)
|
||||
{
|
||||
return LED_STATE_OVERCURRENT;
|
||||
}
|
||||
if (s_overtemp_t1 || s_overtemp_t2)
|
||||
{
|
||||
return LED_STATE_OVERTEMP;
|
||||
}
|
||||
if (faults.emustop)
|
||||
{
|
||||
return LED_STATE_EMUSTOP;
|
||||
}
|
||||
if (s_ticks_since_cmd >= LINK_TIMEOUT_TICKS)
|
||||
{
|
||||
return LED_STATE_LINK_LOST;
|
||||
}
|
||||
return LED_STATE_NOMINAL;
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
#ifdef _FLASH
|
||||
memcpy(&RamfuncsRunStart, &RamfuncsLoadStart, (size_t)&RamfuncsLoadSize);
|
||||
#endif
|
||||
|
||||
bsp_clock_init();
|
||||
bsp_gpio_leds_init();
|
||||
status_led_init(); // bleu fixe des le depart : "je suis parti"
|
||||
|
||||
DINT;
|
||||
InitPieCtrl();
|
||||
IER = 0x0000;
|
||||
IFR = 0x0000;
|
||||
InitPieVectTable();
|
||||
|
||||
bsp_gpio_analog_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();
|
||||
|
||||
// Protection complete : comparateurs + Digital Compare + Trip Zone, et
|
||||
// TZ6/EMUSTOP. Les entrees shunt sont maintenant reellement cablees, le
|
||||
// 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();
|
||||
|
||||
start_conversion();
|
||||
|
||||
EALLOW;
|
||||
PieVectTable.TINT0 = &cpu_timer0_isr;
|
||||
EDIS;
|
||||
|
||||
InitCpuTimers();
|
||||
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;
|
||||
PieCtrlRegs.PIEIER1.bit.INTx7 = 1; // TINT0
|
||||
|
||||
EINT;
|
||||
ERTM;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
command_state_t cmd;
|
||||
safety_faults_t faults;
|
||||
|
||||
if (uart_link_poll(&cmd))
|
||||
{
|
||||
g_last_cmd = cmd;
|
||||
s_ticks_since_cmd = 0U;
|
||||
}
|
||||
|
||||
update_overtemp();
|
||||
|
||||
faults = safety_get_fault_flags();
|
||||
|
||||
// ARRET GLOBAL : n'importe quel defaut, sur n'importe quel etage,
|
||||
// arrete l'ensemble. Sur un boost en cascade, laisser tourner
|
||||
// l'etage 2 alors que l'etage 1 est coupe n'a pas de sens : il
|
||||
// travaillerait sans tension d'entree.
|
||||
//
|
||||
// Les Trip Zones restent independantes en materiel (une surintensite
|
||||
// etage 2 ne coupe que EPWM2A) : cet arret global est la couche
|
||||
// systeme qui vient PAR-DESSUS. Le logiciel peut inhiber en plus,
|
||||
// jamais outrepasser la protection materielle (PROMPT §2).
|
||||
//
|
||||
// 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.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 if (faults.emustop)
|
||||
{
|
||||
// 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));
|
||||
|
||||
if (s_send_telemetry)
|
||||
{
|
||||
telemetry_t t;
|
||||
|
||||
// Grandeurs physiques reelles (etape 5) : conversion par
|
||||
// measure.c a partir des coefficients mesures sur la carte
|
||||
// (docs/mesure-cartepuissance.md).
|
||||
// FREQ/DUTY restent les valeurs de consigne du bloc de bring-up
|
||||
// ci-dessus, pas encore une mesure.
|
||||
t.freq1_hz = (float)PWM_STAGE1_FREQ_HZ;
|
||||
t.freq2_hz = (float)PWM_STAGE2_FREQ_HZ;
|
||||
// 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));
|
||||
t.i1_a = measure_i1(adc_get_raw(ADC_CH_I1));
|
||||
t.t1_c = measure_temp(adc_get_raw(ADC_CH_T1));
|
||||
t.vout_v = measure_vout(adc_get_raw(ADC_CH_VOUT));
|
||||
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;
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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 telemetry_tick = 0;
|
||||
|
||||
CpuTimer0.InterruptCount++;
|
||||
|
||||
status_led_tick();
|
||||
|
||||
if (s_ticks_since_cmd < LINK_TIMEOUT_TICKS)
|
||||
{
|
||||
s_ticks_since_cmd++; // sature au seuil, pas de rebouclage
|
||||
}
|
||||
|
||||
telemetry_tick++;
|
||||
if (telemetry_tick >= TELEMETRY_PERIOD_TICKS)
|
||||
{
|
||||
telemetry_tick = 0U;
|
||||
s_send_telemetry = true;
|
||||
}
|
||||
|
||||
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
|
||||
}
|
||||
76
src/measure.c
Normal file
76
src/measure.c
Normal file
@ -0,0 +1,76 @@
|
||||
#include <math.h>
|
||||
#include "measure.h"
|
||||
#include "calib.h"
|
||||
|
||||
// Brut -> tension a la broche. Reference interne, pleine echelle 3,3 V.
|
||||
static float raw_to_volts(uint16_t raw)
|
||||
{
|
||||
return (float)raw * ADC_VREF_V / 4096.0f;
|
||||
}
|
||||
|
||||
float measure_vin(uint16_t raw)
|
||||
{
|
||||
return raw_to_volts(raw) * MEAS_VIN_GAIN_V_PER_V;
|
||||
}
|
||||
|
||||
float measure_iin(uint16_t raw)
|
||||
{
|
||||
return raw_to_volts(raw) * MEAS_IIN_A_PER_V;
|
||||
}
|
||||
|
||||
float measure_v1(uint16_t raw)
|
||||
{
|
||||
return raw_to_volts(raw) * MEAS_V1_GAIN_V_PER_V;
|
||||
}
|
||||
|
||||
float measure_vout(uint16_t raw)
|
||||
{
|
||||
return raw_to_volts(raw) * MEAS_VOUT_GAIN_V_PER_V;
|
||||
}
|
||||
|
||||
float measure_iout(uint16_t raw)
|
||||
{
|
||||
return raw_to_volts(raw) * MEAS_IOUT_A_PER_V;
|
||||
}
|
||||
|
||||
// Vadc = offset + I * gain -> I = (Vadc - offset) / gain.
|
||||
// Le resultat peut etre legerement negatif a courant nul (bruit autour de
|
||||
// l'offset) : on ne le sature pas, une valeur negative en telemetrie est un
|
||||
// indice utile de derive de l'offset apres changement d'ampli.
|
||||
float measure_i1(uint16_t raw)
|
||||
{
|
||||
return (raw_to_volts(raw) - MEAS_I1_OFFSET_V) / MEAS_I1_GAIN_V_PER_A;
|
||||
}
|
||||
|
||||
float measure_i2(uint16_t raw)
|
||||
{
|
||||
return (raw_to_volts(raw) - MEAS_I2_OFFSET_V) / MEAS_I2_GAIN_V_PER_A;
|
||||
}
|
||||
|
||||
// Pont 3,3 V -- NTC -- R_fixe -- 0 V, mesure au point milieu.
|
||||
// R_ntc = R_fixe * (VREF - Vadc) / Vadc
|
||||
// T(K) = 1 / ( 1/T25 + ln(R_ntc / R25) / beta )
|
||||
//
|
||||
// Precision attendue : la tolerance de +/-3 % sur beta donne environ
|
||||
// +/-2 degC vers 100 degC sans calibration individuelle. Suffisant pour de
|
||||
// la protection thermique, pas pour de la mesure fine (PROMPT §5).
|
||||
float measure_temp(uint16_t raw)
|
||||
{
|
||||
float vadc = raw_to_volts(raw);
|
||||
float r_ntc;
|
||||
float inv_t;
|
||||
|
||||
// Bornes : hors de cette plage le pont est en butee (broche en l'air,
|
||||
// NTC en court-circuit ou coupee) et le calcul n'a plus de sens.
|
||||
if (vadc <= 0.01f || vadc >= (ADC_VREF_V - 0.01f))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
r_ntc = MEAS_NTC_R_FIXED_OHM * (ADC_VREF_V - vadc) / vadc;
|
||||
|
||||
inv_t = 1.0f / MEAS_NTC_T25_K
|
||||
+ logf(r_ntc / MEAS_NTC_R25_OHM) / MEAS_NTC_BETA_K;
|
||||
|
||||
return (1.0f / inv_t) - MEAS_KELVIN_OFFSET;
|
||||
}
|
||||
30
src/measure.h
Normal file
30
src/measure.h
Normal file
@ -0,0 +1,30 @@
|
||||
#ifndef MEASURE_H
|
||||
#define MEASURE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Conversion des valeurs brutes de l'ADC (0..4095) vers les grandeurs
|
||||
// physiques, a partir des seules constantes de calib.h (elles-memes issues
|
||||
// de docs/mesure-cartepuissance.md).
|
||||
//
|
||||
// Ces fonctions utilisent du flottant et, pour les NTC, un logarithme :
|
||||
// a n'appeler QUE depuis la boucle principale, jamais depuis une ISR
|
||||
// (PROMPT §7). La protection rapide ne passe pas par ici.
|
||||
|
||||
float measure_vin(uint16_t raw); // V - tension d'entree batterie
|
||||
float measure_iin(uint16_t raw); // A - courant d'entree
|
||||
float measure_v1(uint16_t raw); // V - tension intermediaire (etage 1)
|
||||
float measure_vout(uint16_t raw); // V - tension de sortie HT
|
||||
float measure_iout(uint16_t raw); // A - courant de sortie HT
|
||||
|
||||
// Courant shunt MOSFET. Deux fonctions distinctes et non une seule
|
||||
// parametree : les deux voies ont leur propre gain ET leur propre offset,
|
||||
// mesures separement.
|
||||
float measure_i1(uint16_t raw); // A - shunt etage 1
|
||||
float measure_i2(uint16_t raw); // A - shunt etage 2
|
||||
|
||||
// Temperature NTC, en degres Celsius. Meme chaine de mesure sur les deux
|
||||
// etages, donc une seule fonction.
|
||||
float measure_temp(uint16_t raw); // degC
|
||||
|
||||
#endif
|
||||
54
src/protocol.h
Normal file
54
src/protocol.h
Normal file
@ -0,0 +1,54 @@
|
||||
#ifndef PROTOCOL_H
|
||||
#define PROTOCOL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
// 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;
|
||||
float freq2_hz;
|
||||
float duty1_pct;
|
||||
float duty2_pct;
|
||||
float vin_v;
|
||||
float iin_a;
|
||||
float v1_v;
|
||||
float i1_a;
|
||||
float t1_c;
|
||||
float vout_v;
|
||||
float i2_a;
|
||||
float t2_c;
|
||||
float iout_a;
|
||||
fault_code_t fault;
|
||||
} telemetry_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
bool ht_enabled;
|
||||
bool pwm1_enabled;
|
||||
bool pwm2_enabled;
|
||||
} command_state_t;
|
||||
|
||||
#endif
|
||||
185
src/pwm.c
Normal file
185
src/pwm.c
Normal file
@ -0,0 +1,185 @@
|
||||
#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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
40
src/pwm.h
Normal file
40
src/pwm.h
Normal file
@ -0,0 +1,40 @@
|
||||
#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);
|
||||
|
||||
// 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).
|
||||
void pwm_enable(stage_id_t stage, bool enabled);
|
||||
|
||||
#endif
|
||||
224
src/safety.c
Normal file
224
src/safety.c
Normal file
@ -0,0 +1,224 @@
|
||||
#include "DSP28x_Project.h"
|
||||
#include "safety.h"
|
||||
#include "bsp_gpio.h"
|
||||
#include "calib.h"
|
||||
|
||||
static volatile bool s_stage1_fault = false;
|
||||
static volatile bool s_stage2_fault = false;
|
||||
static volatile bool s_overcurrent = false;
|
||||
static volatile bool s_emustop = false;
|
||||
|
||||
interrupt void epwm1_tzint_isr(void);
|
||||
interrupt void epwm2_tzint_isr(void);
|
||||
|
||||
void safety_init(void)
|
||||
{
|
||||
EALLOW;
|
||||
|
||||
// Le comparateur partage la reference bandgap de l'ADC : elle doit etre
|
||||
// alimentee meme si l'ADC lui-meme n'est pas encore utilise (etape 4).
|
||||
SysCtrlRegs.PCLKCR0.bit.ADCENCLK = 1;
|
||||
SysCtrlRegs.PCLKCR3.bit.COMP1ENCLK = 1;
|
||||
SysCtrlRegs.PCLKCR3.bit.COMP2ENCLK = 1;
|
||||
AdcRegs.ADCCTL1.bit.ADCBGPWD = 1;
|
||||
EDIS;
|
||||
DELAY_US(1000L);
|
||||
|
||||
EALLOW;
|
||||
|
||||
// Comparateur 1 : shunt etage 1 sur COMP1A (entree non-inverseuse fixe),
|
||||
// seuil DAC sur l'entree inverseuse (COMPSOURCE=0).
|
||||
Comp1Regs.COMPCTL.bit.COMPDACEN = 1;
|
||||
Comp1Regs.COMPCTL.bit.COMPSOURCE = 0;
|
||||
Comp1Regs.COMPCTL.bit.SYNCSEL = 0; // asynchrone -> ~30 ns vers le Trip Zone
|
||||
Comp1Regs.COMPCTL.bit.CMPINV = 1; // driver actif haut : 1=OK, 0=defaut
|
||||
Comp1Regs.COMPCTL.bit.QUALSEL = 0; // pas de qualification pour l'instant
|
||||
// Seuil propre a l'etage 1 : les deux chaines de mesure different d'environ
|
||||
// 1 % en gain et n'ont pas le meme offset (docs/mesure-cartepuissance.md).
|
||||
Comp1Regs.DACVAL.bit.DACVAL = SAFETY_DAC_CODE_STAGE1;
|
||||
GpioCtrlRegs.GPAMUX1.bit.GPIO1 = 3; // COMP1OUT
|
||||
|
||||
// Comparateur 2 : idem, shunt etage 2 sur COMP2A.
|
||||
Comp2Regs.COMPCTL.bit.COMPDACEN = 1;
|
||||
Comp2Regs.COMPCTL.bit.COMPSOURCE = 0;
|
||||
Comp2Regs.COMPCTL.bit.SYNCSEL = 0;
|
||||
Comp2Regs.COMPCTL.bit.CMPINV = 1;
|
||||
Comp2Regs.COMPCTL.bit.QUALSEL = 0;
|
||||
Comp2Regs.DACVAL.bit.DACVAL = SAFETY_DAC_CODE_STAGE2;
|
||||
GpioCtrlRegs.GPAMUX1.bit.GPIO3 = 3; // COMP2OUT
|
||||
|
||||
// Digital Compare : COMPxOUT -> DCAEVT1 -> Trip Zone one-shot (latche)
|
||||
EPwm1Regs.DCTRIPSEL.bit.DCAHCOMPSEL = DC_COMP1OUT;
|
||||
EPwm1Regs.TZDCSEL.bit.DCAEVT1 = TZ_DCAH_LOW;
|
||||
EPwm1Regs.TZSEL.bit.DCAEVT1 = 1;
|
||||
EPwm1Regs.TZCTL.bit.TZA = TZ_FORCE_LO;
|
||||
EPwm1Regs.TZEINT.bit.OST = 1;
|
||||
// TZ6 = EMUSTOP (signal cable en dur depuis le CPU, TRM SPRUI09A section
|
||||
// 3.2.7) : coupe le PWM des que le debugger arrete le coeur. FREE_SOFT
|
||||
// seul ne suffit pas, il ne fait que geler le compteur de base de temps,
|
||||
// la broche reste figee dans son dernier etat.
|
||||
EPwm1Regs.TZSEL.bit.OSHT6 = 1;
|
||||
EPwm1Regs.TBCTL.bit.FREE_SOFT = 0;
|
||||
|
||||
EPwm2Regs.DCTRIPSEL.bit.DCAHCOMPSEL = DC_COMP2OUT;
|
||||
EPwm2Regs.TZDCSEL.bit.DCAEVT1 = TZ_DCAH_LOW;
|
||||
EPwm2Regs.TZSEL.bit.DCAEVT1 = 1;
|
||||
EPwm2Regs.TZCTL.bit.TZA = TZ_FORCE_LO;
|
||||
EPwm2Regs.TZEINT.bit.OST = 1;
|
||||
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;
|
||||
|
||||
IER |= M_INT2;
|
||||
PieCtrlRegs.PIEIER2.bit.INTx1 = 1; // EPWM1_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 f;
|
||||
f.stage1_fault = s_stage1_fault;
|
||||
f.stage2_fault = s_stage2_fault;
|
||||
f.overcurrent = s_overcurrent;
|
||||
f.emustop = s_emustop;
|
||||
return f;
|
||||
}
|
||||
|
||||
void safety_clear_faults(void)
|
||||
{
|
||||
EALLOW;
|
||||
// OST libere la sortie PWM, DCAEVT1 remet a zero la cause (sinon le
|
||||
// prochain trip serait attribue a une surintensite meme s'il vient
|
||||
// d'EMUSTOP), INT autorise a nouveau la generation d'interruptions, et
|
||||
// TZEINT.OST rearme la notification desarmee par l'ISR.
|
||||
EPwm1Regs.TZCLR.bit.DCAEVT1 = 1;
|
||||
EPwm1Regs.TZCLR.bit.OST = 1;
|
||||
EPwm1Regs.TZCLR.bit.INT = 1;
|
||||
EPwm1Regs.TZEINT.bit.OST = 1;
|
||||
|
||||
EPwm2Regs.TZCLR.bit.DCAEVT1 = 1;
|
||||
EPwm2Regs.TZCLR.bit.OST = 1;
|
||||
EPwm2Regs.TZCLR.bit.INT = 1;
|
||||
EPwm2Regs.TZEINT.bit.OST = 1;
|
||||
EDIS;
|
||||
|
||||
s_stage1_fault = false;
|
||||
s_stage2_fault = false;
|
||||
s_overcurrent = false;
|
||||
s_emustop = false;
|
||||
// Les LED ne sont plus pilotees ici : c'est status_led.c qui detient
|
||||
// l'affichage, a partir de ces drapeaux.
|
||||
}
|
||||
|
||||
void safety_force_trip_test(void)
|
||||
{
|
||||
EALLOW;
|
||||
EPwm1Regs.TZFRC.bit.OST = 1;
|
||||
EPwm2Regs.TZFRC.bit.OST = 1;
|
||||
EDIS;
|
||||
}
|
||||
|
||||
// Diagnostic uniquement : la coupure est deja faite en materiel (PROMPT §6
|
||||
// etape 2 point 7). On se contente de relever l'origine du trip.
|
||||
//
|
||||
// Deux subtilites, decouvertes au banc :
|
||||
//
|
||||
// 1. TZFLG.INT est le drapeau GLOBAL d'interruption Trip Zone. Tant qu'il
|
||||
// n'est pas efface via TZCLR.INT, plus AUCUNE interruption TZ n'est
|
||||
// generee. Sans ce clear, seul le tout premier trip de la session etait
|
||||
// signale au logiciel : les suivants coupaient bien le PWM en materiel,
|
||||
// mais en silence, sans lever de drapeau.
|
||||
//
|
||||
// 2. Effacer INT alors que la condition de trip persiste (surintensite
|
||||
// toujours presente) regenere l'evenement immediatement -> tempete
|
||||
// d'interruptions qui affamerait la boucle principale. On desarme donc
|
||||
// TZEINT.OST ici : une seule notification par episode de defaut. Le
|
||||
// rearmement est fait par safety_clear_faults(), c'est-a-dire jamais
|
||||
// automatiquement (PROMPT §8).
|
||||
//
|
||||
// Le drapeau DCAEVT1 n'est volontairement PAS efface ici : il porte la
|
||||
// cause du defaut et doit rester lisible jusqu'a l'effacement explicite.
|
||||
interrupt void epwm1_tzint_isr(void)
|
||||
{
|
||||
s_stage1_fault = true;
|
||||
|
||||
if (EPwm1Regs.TZFLG.bit.DCAEVT1)
|
||||
{
|
||||
s_overcurrent = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
s_emustop = true;
|
||||
}
|
||||
|
||||
EALLOW;
|
||||
EPwm1Regs.TZEINT.bit.OST = 0;
|
||||
EPwm1Regs.TZCLR.bit.INT = 1;
|
||||
EDIS;
|
||||
|
||||
PieCtrlRegs.PIEACK.all = PIEACK_GROUP2;
|
||||
}
|
||||
|
||||
interrupt void epwm2_tzint_isr(void)
|
||||
{
|
||||
s_stage2_fault = true;
|
||||
|
||||
if (EPwm2Regs.TZFLG.bit.DCAEVT1)
|
||||
{
|
||||
s_overcurrent = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
s_emustop = true;
|
||||
}
|
||||
|
||||
EALLOW;
|
||||
EPwm2Regs.TZEINT.bit.OST = 0;
|
||||
EPwm2Regs.TZCLR.bit.INT = 1;
|
||||
EDIS;
|
||||
|
||||
PieCtrlRegs.PIEACK.all = PIEACK_GROUP2;
|
||||
}
|
||||
41
src/safety.h
Normal file
41
src/safety.h
Normal file
@ -0,0 +1,41 @@
|
||||
#ifndef SAFETY_H
|
||||
#define SAFETY_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
bool stage1_fault;
|
||||
bool stage2_fault;
|
||||
|
||||
// Origine du trip, relevee dans TZFLG au moment ou l'ISR s'execute.
|
||||
// TZFLG.DCAEVT1 (bit 3) est distinct de TZFLG.OST (bit 2), ce qui permet
|
||||
// de separer une vraie surintensite d'un arret du debugger -- les deux
|
||||
// n'appellent pas du tout la meme reaction.
|
||||
bool overcurrent; // DCAEVT1 : comparateur, surintensite reelle
|
||||
bool emustop; // OST sans DCAEVT1 : EMUSTOP (ou TZFRC de test)
|
||||
} safety_faults_t;
|
||||
|
||||
void safety_init(void);
|
||||
|
||||
// BRING-UP UNIQUEMENT -- arme seulement TZ6 (EMUSTOP) et l'interruption de
|
||||
// diagnostic, SANS les comparateurs ni le Digital Compare. Permet de valider
|
||||
// isolement la coupure du PWM a l'arret du debugger, tant que les entrees
|
||||
// shunt ne sont pas cablees : avec safety_init() complet, elles flottent
|
||||
// au-dessus du seuil et le trip comparateur masquerait le test.
|
||||
//
|
||||
// safety_init() arme deja TZ6 : ces deux fonctions sont exclusives.
|
||||
void safety_arm_emustop_only(void);
|
||||
|
||||
safety_faults_t safety_get_fault_flags(void);
|
||||
|
||||
// Ecrit TZCLR[OST] sur les deux etages. Ne doit JAMAIS etre appele
|
||||
// automatiquement par le firmware (voir document PROMPT, section 8) --
|
||||
// uniquement sur commande explicite (UART, etape 7).
|
||||
void safety_clear_faults(void);
|
||||
|
||||
// Force un trip logiciel (TZFRC[OSHT]) pour valider le mecanisme de
|
||||
// protection sans provoquer de vrai court-circuit (critere de test etape 2).
|
||||
void safety_force_trip_test(void);
|
||||
|
||||
#endif
|
||||
132
src/status_led.c
Normal file
132
src/status_led.c
Normal file
@ -0,0 +1,132 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "status_led.h"
|
||||
#include "bsp_gpio.h"
|
||||
|
||||
// Durees exprimees en ticks de 10 ms. Ce sont des constantes de
|
||||
// presentation, pas de calibration materielle : elles restent ici plutot
|
||||
// que dans calib.h, qui decrit la carte.
|
||||
#define TICKS_PER_100MS 10U
|
||||
|
||||
#define STARTUP_MIN_TICKS 100U // 1 s : rend l'etat de demarrage visible
|
||||
|
||||
#define NOMINAL_PERIOD 200U // 2,0 s
|
||||
#define NOMINAL_ON 20U // 0,2 s
|
||||
|
||||
#define LINK_LOST_PERIOD 40U // 0,4 s
|
||||
#define LINK_LOST_ON 20U // 0,2 s
|
||||
|
||||
#define EMUSTOP_PERIOD 100U // 1,0 s
|
||||
#define EMUSTOP_HALF 50U // 0,5 s bleu puis 0,5 s rouge
|
||||
|
||||
#define OVERTEMP_PERIOD 100U // 1,0 s
|
||||
#define OVERTEMP_ON 50U // 0,5 s
|
||||
|
||||
static led_state_t s_state = LED_STATE_STARTUP;
|
||||
static uint16_t s_phase = 0; // position dans le motif courant
|
||||
static uint16_t s_startup_hold = STARTUP_MIN_TICKS;
|
||||
|
||||
void status_led_init(void)
|
||||
{
|
||||
s_state = LED_STATE_STARTUP;
|
||||
s_phase = 0;
|
||||
s_startup_hold = STARTUP_MIN_TICKS;
|
||||
|
||||
led_set(LED_BLUE, true);
|
||||
led_set(LED_RED, false);
|
||||
}
|
||||
|
||||
void status_led_set_state(led_state_t state)
|
||||
{
|
||||
if (s_startup_hold != 0U)
|
||||
{
|
||||
return; // duree minimale de l'etat de demarrage non ecoulee
|
||||
}
|
||||
|
||||
if (state != s_state)
|
||||
{
|
||||
s_state = state;
|
||||
s_phase = 0; // le motif repart proprement a son debut
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
if (s_startup_hold != 0U)
|
||||
{
|
||||
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:
|
||||
case LED_STATE_LINK_LOST:
|
||||
blue = (s_phase < on_time);
|
||||
break;
|
||||
|
||||
case LED_STATE_EMUSTOP:
|
||||
blue = (s_phase < on_time);
|
||||
red = !blue;
|
||||
break;
|
||||
|
||||
case LED_STATE_OVERTEMP:
|
||||
red = (s_phase < on_time);
|
||||
break;
|
||||
|
||||
case LED_STATE_OVERCURRENT:
|
||||
red = true;
|
||||
break;
|
||||
|
||||
case LED_STATE_STARTUP:
|
||||
default:
|
||||
blue = true;
|
||||
break;
|
||||
}
|
||||
|
||||
led_set(LED_BLUE, blue);
|
||||
led_set(LED_RED, red);
|
||||
}
|
||||
41
src/status_led.h
Normal file
41
src/status_led.h
Normal file
@ -0,0 +1,41 @@
|
||||
#ifndef STATUS_LED_H
|
||||
#define STATUS_LED_H
|
||||
|
||||
// Affichage d'etat sur la LED bicolore (bleue = GPIO12, rouge = GPIO33).
|
||||
// Les motifs sont volontairement tous distinguables a l'oeil :
|
||||
//
|
||||
// STARTUP bleu fixe
|
||||
// NOMINAL bleu 0,2 s ON / 1,8 s OFF (battement discret)
|
||||
// LINK_LOST bleu 0,2 s ON / 0,2 s OFF (clignotement rapide)
|
||||
// EMUSTOP alternance bleu/rouge 0,5 s / 0,5 s
|
||||
// OVERTEMP rouge 0,5 s ON / 0,5 s OFF
|
||||
// OVERCURRENT rouge fixe
|
||||
//
|
||||
// L'ordre de l'enumeration EST l'ordre de priorite croissante : quand
|
||||
// plusieurs conditions sont vraies, on affiche la plus grande valeur.
|
||||
// La surintensite passe donc devant EMUSTOP -- pendant le developpement
|
||||
// EMUSTOP se declenche en permanence et ne doit jamais masquer un vrai
|
||||
// defaut de puissance.
|
||||
typedef enum
|
||||
{
|
||||
LED_STATE_STARTUP = 0,
|
||||
LED_STATE_NOMINAL,
|
||||
LED_STATE_LINK_LOST,
|
||||
LED_STATE_EMUSTOP,
|
||||
LED_STATE_OVERTEMP,
|
||||
LED_STATE_OVERCURRENT
|
||||
} led_state_t;
|
||||
|
||||
void status_led_init(void);
|
||||
|
||||
// Etat souhaite, calcule dans la boucle principale. Tout changement
|
||||
// redemarre le motif a zero. Les demandes de sortie de STARTUP sont
|
||||
// ignorees tant que la duree minimale d'affichage n'est pas ecoulee,
|
||||
// sans quoi cet etat ne serait jamais visible (l'init dure quelques ms).
|
||||
void status_led_set_state(led_state_t state);
|
||||
|
||||
// A appeler a chaque tick de 10 ms. Ne fait qu'ecrire GPASET/GPACLEAR,
|
||||
// donc utilisable depuis une ISR.
|
||||
void status_led_tick(void);
|
||||
|
||||
#endif
|
||||
487
src/uart_link.c
Normal file
487
src/uart_link.c
Normal file
@ -0,0 +1,487 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.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;
|
||||
}
|
||||
|
||||
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
|
||||
// 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);
|
||||
|
||||
{
|
||||
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
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Decode EXACTEMENT deux chiffres hexadecimaux. Renvoie false si l'un des
|
||||
// deux n'en est pas un.
|
||||
//
|
||||
// Remplace strtol, qui etait doublement piegeux ici :
|
||||
// - s_line n'est jamais terminee par un '\0', donc strtol lisait au-dela
|
||||
// de la trame, dans les residus de la ligne precedente ;
|
||||
// - sur C28x, uint8_t fait 16 bits (pas d'adressage par octet), donc le
|
||||
// cast (uint8_t) ne tronquait pas le resultat. Un "7D" suivi d'un "7D"
|
||||
// residuel donnait 0x7D7D, retenu tel quel, et toute trame etait
|
||||
// rejetee. Sur une architecture a octets le bug serait passe inapercu.
|
||||
static bool parse_hex2(const char *s, uint8_t *out)
|
||||
{
|
||||
uint16_t value = 0U;
|
||||
uint16_t i;
|
||||
|
||||
for (i = 0U; i < 2U; i++)
|
||||
{
|
||||
char c = s[i];
|
||||
uint16_t digit;
|
||||
|
||||
if ((c >= '0') && (c <= '9'))
|
||||
{
|
||||
digit = (uint16_t)(c - '0');
|
||||
}
|
||||
else if ((c >= 'A') && (c <= 'F'))
|
||||
{
|
||||
digit = (uint16_t)(c - 'A' + 10);
|
||||
}
|
||||
else if ((c >= 'a') && (c <= 'f'))
|
||||
{
|
||||
digit = (uint16_t)(c - 'a' + 10);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = (uint16_t)((value << 4) | digit);
|
||||
}
|
||||
|
||||
*out = (uint8_t)(value & 0xFFU);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (!parse_hex2(&s_line[star + 1], &cs_recv))
|
||||
{
|
||||
return false; // checksum malforme -> trame ignoree
|
||||
}
|
||||
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;
|
||||
}
|
||||
39
src/uart_link.h
Normal file
39
src/uart_link.h
Normal file
@ -0,0 +1,39 @@
|
||||
#ifndef UART_LINK_H
|
||||
#define UART_LINK_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "protocol.h"
|
||||
|
||||
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
|
||||
// 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
|
||||
Reference in New Issue
Block a user