Обновить сборку прошивки 166, TESTLAMP и кодировку исходников

Перенести настройки выпуска в JSON, добавить переносимый сценарий сборки и проверки BIN. Обновить конфигурации CCS, исходники в UTF-8, обработку TESTLAMP, образы прошивки и регрессионные проверки.
This commit is contained in:
2026-10-17 14:36:59 +03:00
parent 13f1a2c5fd
commit 31f92e89d9
56 changed files with 1493 additions and 776 deletions
-2
View File
@@ -2,6 +2,4 @@
<?ccsproject version="1.0"?>
<projectOptions>
<deviceFamily value="C2000"/>
<connection value="common/targetdb/connections/SAU510USBIsoPlusV2_Connection.xml"/>
<isTargetManual value="false"/>
</projectOptions>
File diff suppressed because one or more lines are too long
+52 -48
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Balsam_166</name>
<name>28335_periph</name>
<comment></comment>
<projects>
</projects>
@@ -25,180 +25,180 @@
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
</natures>
<linkedResources>
<link> <!-- Подключает папку заголовков приложения в дерево проекта CCS. -->
<name>Source/Internal/Include</name> <!-- Показывает заголовки рядом с исходниками приложения. -->
<type>2</type> <!-- Тип 2 обозначает связанную папку в формате проекта Eclipse/CCS. -->
<locationURI>SRC_ROOT1/Source/Internal/Include</locationURI> <!-- Находит существующие заголовки относительно корня репозитория. -->
</link> <!-- Завершает описание ссылки без копирования файлов в проект. -->
<link> <!-- Подключает общие заголовки библиотеки TI для TMS320F28335. -->
<name>Source/External/v120/DSP2833x_common/include</name> <!-- Показывает общие заголовки TI в соответствующей ветке дерева CCS. -->
<type>2</type> <!-- Тип 2 позволяет отобразить всё содержимое связанной папки. -->
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_common/include</locationURI> <!-- Использует ту же папку, которая уже указана в путях компилятора. -->
</link> <!-- Завершает ссылку на общие заголовки TI. -->
<link> <!-- Подключает заголовки регистров и периферии процессора TI. -->
<name>Source/External/v120/DSP2833x_headers/include</name> <!-- Показывает аппаратные заголовки в дереве исходников CCS. -->
<type>2</type> <!-- Тип 2 задаёт ссылку на папку, а не на отдельный файл. -->
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_headers/include</locationURI> <!-- Ссылается на существующую папку аппаратных заголовков внутри репозитория. -->
</link> <!-- Завершает ссылку на аппаратные заголовки TI. -->
<link>
<name>F28335.cmd</name>
<type>1</type>
<locationURI>SRC_ROOT1/F28335.cmd</locationURI>
<locationURI>SRC_ROOT12/F28335.cmd</locationURI>
</link>
<link>
<name>UKSSTMS320F28335.pjt</name>
<type>1</type>
<locationURI>SRC_ROOT1/UKSSTMS320F28335.pjt</locationURI>
<locationURI>SRC_ROOT12/UKSSTMS320F28335.pjt</locationURI>
</link>
<link>
<name>Debug/UKSSTMS320F28335.out</name>
<type>1</type>
<locationURI>SRC_ROOT/Bin/UKSSTMS320F28335.out</locationURI>
<locationURI>SRC_ROOT12/Bin/UKSSTMS320F28335.out</locationURI>
</link>
<link>
<name>Libraries/rts2800_fpu32.lib</name>
<type>1</type>
<locationURI>SRC_ROOT1/Libraries/rts2800_fpu32.lib</locationURI>
<locationURI>SRC_ROOT12/Libraries/rts2800_fpu32.lib</locationURI>
</link>
<link>
<name>Source/Internal/ADC.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/ADC.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/ADC.c</locationURI>
</link>
<link>
<name>Source/Internal/DAC.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/DAC.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/DAC.c</locationURI>
</link>
<link>
<name>Source/Internal/Include</name>
<type>2</type>
<locationURI>SRC_ROOT12/Source/Internal/Include</locationURI>
</link>
<link>
<name>Source/Internal/RS485.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/RS485.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/RS485.c</locationURI>
</link>
<link>
<name>Source/Internal/bios.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/bios.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/bios.c</locationURI>
</link>
<link>
<name>Source/Internal/cntrl_adr.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/cntrl_adr.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/cntrl_adr.c</locationURI>
</link>
<link>
<name>Source/Internal/crc16.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/crc16.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/crc16.c</locationURI>
</link>
<link>
<name>Source/Internal/ecan.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/ecan.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/ecan.c</locationURI>
</link>
<link>
<name>Source/Internal/filter_bat2.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/filter_bat2.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/filter_bat2.c</locationURI>
</link>
<link>
<name>Source/Internal/kanal.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/kanal.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/kanal.c</locationURI>
</link>
<link>
<name>Source/Internal/log_to_mem.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/log_to_mem.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/log_to_mem.c</locationURI>
</link>
<link>
<name>Source/Internal/main.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/main.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/main.c</locationURI>
</link>
<link>
<name>Source/Internal/measure.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/measure.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/measure.c</locationURI>
</link>
<link>
<name>Source/Internal/message.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/message.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/message.c</locationURI>
</link>
<link>
<name>Source/Internal/peripher.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/peripher.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/peripher.c</locationURI>
</link>
<link>
<name>Source/Internal/pulto.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/pulto.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/pulto.c</locationURI>
</link>
<link>
<name>Source/Internal/spise2p.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/spise2p.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/spise2p.c</locationURI>
</link>
<link>
<name>Source/Internal/tools.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/tools.c</locationURI>
<locationURI>SRC_ROOT12/Source/Internal/tools.c</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_common/include</name>
<type>2</type>
<locationURI>SRC_ROOT12/Source/External/v120/DSP2833x_common/include</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_headers/include</name>
<type>2</type>
<locationURI>SRC_ROOT12/Source/External/v120/DSP2833x_headers/include</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_common/source/DSP2833x_ADC_cal.asm</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_common/source/DSP2833x_ADC_cal.asm</locationURI>
<locationURI>SRC_ROOT12/Source/External/v120/DSP2833x_common/source/DSP2833x_ADC_cal.asm</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_common/source/DSP2833x_Adc.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_common/source/DSP2833x_Adc.c</locationURI>
<locationURI>SRC_ROOT12/Source/External/v120/DSP2833x_common/source/DSP2833x_Adc.c</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_common/source/DSP2833x_CpuTimers.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_common/source/DSP2833x_CpuTimers.c</locationURI>
<locationURI>SRC_ROOT12/Source/External/v120/DSP2833x_common/source/DSP2833x_CpuTimers.c</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_common/source/DSP2833x_PieCtrl.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_common/source/DSP2833x_PieCtrl.c</locationURI>
<locationURI>SRC_ROOT12/Source/External/v120/DSP2833x_common/source/DSP2833x_PieCtrl.c</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_common/source/DSP2833x_SWPrioritizedDefaultIsr.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_common/source/DSP2833x_SWPrioritizedDefaultIsr.c</locationURI>
<locationURI>SRC_ROOT12/Source/External/v120/DSP2833x_common/source/DSP2833x_SWPrioritizedDefaultIsr.c</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_common/source/DSP2833x_SWPrioritizedPieVect.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_common/source/DSP2833x_SWPrioritizedPieVect.c</locationURI>
<locationURI>SRC_ROOT12/Source/External/v120/DSP2833x_common/source/DSP2833x_SWPrioritizedPieVect.c</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_common/source/DSP2833x_SysCtrl.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_common/source/DSP2833x_SysCtrl.c</locationURI>
<locationURI>SRC_ROOT12/Source/External/v120/DSP2833x_common/source/DSP2833x_SysCtrl.c</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_common/source/DSP2833x_Xintf.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_common/source/DSP2833x_Xintf.c</locationURI>
<locationURI>SRC_ROOT12/Source/External/v120/DSP2833x_common/source/DSP2833x_Xintf.c</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_common/source/DSP2833x_usDelay.asm</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_common/source/DSP2833x_usDelay.asm</locationURI>
<locationURI>SRC_ROOT12/Source/External/v120/DSP2833x_common/source/DSP2833x_usDelay.asm</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_headers/cmd/DSP2833x_Headers_nonBIOS.cmd</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_headers/cmd/DSP2833x_Headers_nonBIOS.cmd</locationURI>
<locationURI>SRC_ROOT12/Source/External/v120/DSP2833x_headers/cmd/DSP2833x_Headers_nonBIOS.cmd</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_headers/source/DSP2833x_GlobalVariableDefs.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_headers/source/DSP2833x_GlobalVariableDefs.c</locationURI>
<locationURI>SRC_ROOT12/Source/External/v120/DSP2833x_headers/source/DSP2833x_GlobalVariableDefs.c</locationURI>
</link>
</linkedResources>
<variableList>
@@ -210,5 +210,9 @@
<name>SRC_ROOT1</name>
<value>$%7BPARENT-1-PROJECT_LOC%7D</value>
</variable>
<variable>
<name>SRC_ROOT12</name>
<value>file:/K:/git_project/28335_periph</value>
</variable>
</variableList>
</projectDescription>
@@ -1,4 +0,0 @@
ccsVersionValidationPolicy=warning
compilerVersionValidationPolicy=flexible
eclipse.preferences.version=1
productVersionsValidationPolicy=flexible
@@ -1,8 +1,4 @@
eclipse.preferences.version=1
# Показывает пояснения периода CAN и обработки параметров в сохранённой кодировке UTF-8.
encoding//Source/Internal/message.c=UTF-8
# Показывает русские пояснения выбора исполнения и порядка индикаторов в кодировке UTF-8.
encoding//Source/Internal/Include/package.h=UTF-8
encoding//Debug/Libraries/subdir_rules.mk=UTF-8
encoding//Debug/Libraries/subdir_vars.mk=UTF-8
encoding//Debug/Source/External/v120/DSP2833x_common/source/subdir_rules.mk=UTF-8
@@ -108,8 +104,25 @@ encoding//Debug/optimizerAssistant/opt_for_speed__5/subdir_vars.mk=UTF-8
encoding//Debug/sources.mk=UTF-8
encoding//Debug/subdir_rules.mk=UTF-8
encoding//Debug/subdir_vars.mk=UTF-8
encoding//Release/Libraries/subdir_rules.mk=UTF-8
encoding//Release/Libraries/subdir_vars.mk=UTF-8
encoding//Release/Source/External/v120/DSP2833x_common/source/subdir_rules.mk=UTF-8
encoding//Release/Source/External/v120/DSP2833x_common/source/subdir_vars.mk=UTF-8
encoding//Release/Source/External/v120/DSP2833x_headers/cmd/subdir_rules.mk=UTF-8
encoding//Release/Source/External/v120/DSP2833x_headers/cmd/subdir_vars.mk=UTF-8
encoding//Release/Source/External/v120/DSP2833x_headers/source/subdir_rules.mk=UTF-8
encoding//Release/Source/External/v120/DSP2833x_headers/source/subdir_vars.mk=UTF-8
encoding//Release/Source/Internal/subdir_rules.mk=UTF-8
encoding//Release/Source/Internal/subdir_vars.mk=UTF-8
encoding//Release/makefile=UTF-8
encoding//Release/objects.mk=UTF-8
encoding//Release/sources.mk=UTF-8
encoding//Release/subdir_rules.mk=UTF-8
encoding//Release/subdir_vars.mk=UTF-8
encoding//Source/Internal/Include/log_to_mem.h=UTF-8
encoding//Source/Internal/Include/package.h=UTF-8
encoding//Source/Internal/Include/ump_can_protocol.h=UTF-8
encoding//Source/Internal/log_to_mem.c=UTF-8
encoding//Source/Internal/message.c=UTF-8
encoding//Source/Internal/peripher.c=UTF-8
encoding/<project>=UTF-8
+192 -48
View File
@@ -1,43 +1,188 @@
# Проект 166 для CCS 12
# Сборка прошивки Бальзам-166 / TMS320F28335
Настройки перенесены из проекта 167. Имя проекта в CCS: `Balsam_166`.
Проект CCS называется `28335_periph`. Файл настроек проекта:
`K:\git_project\28335_periph\UKSSTMS320F28335\.cproject`.
Для рабочей сборки используйте конфигурацию **Debug**.
## Импорт и сборка
## Реальные пути на этом компьютере
1. В CCS выберите **Project → Import CCS Projects** и укажите эту папку
(`166/UKSSTMS320F28335`).
2. Импортируйте проект без **Copy projects into workspace**: исходники и
библиотеки подключены из родительской папки по относительным путям.
3. Выберите конфигурацию **Debug** и выполните **Build Project**.
| Назначение | Полный путь |
|---|---|
| Корень исходников, макрос CCS `${SRC_ROOT}` | `K:\git_project\28335_periph` |
| Каталог проекта, макрос CCS `${PROJECT_LOC}` | `K:\git_project\28335_periph\UKSSTMS320F28335` |
| Общая библиотека templates, используемая этой сборкой | `K:\git_project\newProject\templates` |
| Компилятор, макрос CCS `${CG_TOOL_ROOT}` | `C:\ti\ccs1230\ccs\tools\compiler\ti-cgt-c2000_22.6.0.LTS` |
| Каталог Debug, макрос CCS `${BuildDirectory}` | `K:\git_project\28335_periph\UKSSTMS320F28335\Debug` |
| Настройки версии и путей | `K:\git_project\28335_periph\UKSSTMS320F28335\firmware_identity.json` |
| Постоянный каталог готовых образов Debug | `K:\git_project\28335_periph\UKSSTMS320F28335\Debug\firmware` |
| Дополнительная копия готовых файлов | `K:\git_project\28335_periph\Bin` |
Используется TI C2000 Compiler **22.6.1.LTS**, TMS320F28335, FPU32 и COFF ABI.
Сохранены настройки компиляции, линковки и преобразования прошивки из 167.
Исходники берутся из `166/Source`, linker-файл — `166/F28335.cmd`.
Файл `28335_RAM_lnk.cmd`, как и в 167, исключён из сборки.
## Как правильно указать templates
Результаты сборки находятся в `166/Bin`: `UKSSTMS320F28335.out`,
`UKSSTMS320F28335.hex` (Intel HEX) и `UKSSTMS320F28335_<build_id>.bin`.
Рядом с BIN создаются `<имя>.bin.manifest.json` и `<имя>.bin.sha256`.
Build ID содержит восемь символов: восемь цифр ревизии либо семь цифр и `+`
при изменённых исходниках или общей библиотеке. При недоступном Git общий
контракт 167 использует `NOGIT000`; для выпуска следует проверить ревизии
`source` и `templates` в манифесте. Время паспорта хранится в UTC.
Откройте `K:\git_project\28335_periph\UKSSTMS320F28335\firmware_identity.json`.
Поле `templates` уже содержит рабочее значение:
Prebuild `prepare_identity.py` использует тот же генератор firmware-info,
что 167, но product равен `Balsam-166-PM35`. Единственный источник версии —
`firmware_version.h`; первый выпуск с паспортом имеет версию **1.0.0**.
Обязательный `generated/firmware_identity.h` создаётся до компиляции.
Postbuild `build_identity.py` использует hex2000 из выбранного компилятора
CCS, общий манифест и SHA-256, затем независимо проверяет наличие ровно
одного соответствующего 12-словного паспорта в SCI8. Старый OUT без паспорта
или BIN с чужими полями не публикуется. Превышение границы параметров EEPROM
**32508 байт** также останавливает выпуск. Архивы сохраняются локально в
`UKSSTMS320F28335/Debug/firmware`; записи в репозиторий GUI нет.
`"templates": "../../newProject/templates"`
`build_bin.bat` сохраняет ручную точку входа для уже скомпилированного OUT,
но теперь также требует его совпадения с паспортом соответствующего prebuild.
Простая конверсия старой прошивки не может создать ей достоверную версию.
Карта линковки остаётся в корне 166, карта конверсии — в `166/Bin`.
Путь отсчитывается **от папки файла firmware_identity.json**:
два перехода `..` приводят в `K:\git_project`, затем `newProject/templates`
приводит в `K:\git_project\newProject\templates`.
Рабочая папка терминала и каталог Debug на этот расчёт не влияют.
Для этой машины допустима и абсолютная запись того же пути:
`"templates": "K:/git_project/newProject/templates"`
В JSON удобнее использовать `/`: обратный слеш пришлось бы удваивать.
Указывайте корень полной библиотеки templates. Внутри должны существовать:
| Что использует сборка | Полный путь |
|---|---|
| Генерация паспорта до компиляции | `K:\git_project\newProject\templates\tools\firmware-build\ccs\precompile.bat` |
| Конверсия после компиляции | `K:\git_project\newProject\templates\tools\firmware-build\ccs\postcompile.bat` |
| Заголовки firmware-info | `K:\git_project\newProject\templates\c\firmware-info\include` |
| Общий протокол | `K:\git_project\newProject\templates\c\set-protocol` |
В CCS → **Project Properties → Build → C2000 Compiler → Include Options**
для Debug и Release используются следующие записи:
| Запись в CCS | Какой каталог она открывает |
|---|---|
| `${SRC_ROOT}/../newProject/templates/c/firmware-info/include` | `K:\git_project\newProject\templates\c\firmware-info\include` |
| `${SRC_ROOT}/../newProject/templates/c/set-protocol` | `K:\git_project\newProject\templates\c\set-protocol` |
| `${BuildDirectory}/generated` | `K:\git_project\28335_periph\UKSSTMS320F28335\Debug\generated` для Debug |
Если перенесли templates, измените **и поле JSON, и два include path в CCS**.
Настройка JSON управляет сборочными сценариями; include paths компилятора
сохраняются отдельно в `.cproject`.
## Импорт и запуск сборки в CCS
1. Выберите **Project → Import CCS Projects**.
2. Укажите `K:\git_project\28335_periph\UKSSTMS320F28335`.
3. Оставьте **Copy projects into workspace** выключенным: исходники и
библиотеки подключены из соседних каталогов.
4. Проверьте компилятор TI C2000 **22.6.0.LTS**, контроллер
**TMS320F28335**, **FPU32**, **COFF ABI**. Python должен быть доступен
командой `python`; Git используется для определения ревизии.
5. Включите конфигурацию **Debug** и выполните **Build Project**.
Исходники находятся в `K:\git_project\28335_periph\Source`, linker-файл —
`K:\git_project\28335_periph\F28335.cmd`. `28335_RAM_lnk.cmd` исключён
из сборки. Debug создаёт OUT в `K:\git_project\28335_periph\Bin`.
## Команды Build Steps
В **Project Properties → Build → Steps** задайте команды для Debug и
Release. Каждую команду вставляйте в соответствующее поле одной строкой.
Макросы `${...}` подставляет CCS; вручную в обычном cmd они не раскрываются.
**Pre-build** — резервирует версию и создаёт заголовок и метаданные
в `${BuildDirectory}/generated`:
`cmd /c call "${PROJECT_LOC}/firmware_build.bat" prepare --build-dir "${BuildDirectory}"`
**Post-build** — конвертирует OUT, проверяет встроенный паспорт,
размер образа и SHA-256, затем сохраняет готовый комплект:
`cmd /c call "${PROJECT_LOC}/firmware_build.bat" build --compiler "${CG_TOOL_ROOT}" --build-dir "${BuildDirectory}" --artifact "${BuildArtifactFileBaseName}"`
После `--artifact` должен стоять только путь к OUT **без расширения .out**.
Дополнительный путь последним аргументом вызовет `unrecognized arguments`.
Каталог templates и каталог копирования задаются в JSON.
Для отдельного JSON добавьте `--config "полный/путь/firmware_identity.json"`
в обе команды. Тогда относительные пути будут отсчитываться от этого JSON.
После изменения `.cproject` вне IDE закройте и снова откройте проект в CCS,
проверьте Build Steps и запустите сборку. Файл
`K:\git_project\28335_periph\UKSSTMS320F28335\Debug\makefile`
генерируется CCS; постоянные исправления вносятся в настройки проекта.
## Где искать готовую прошивку
Основной каталог:
`K:\git_project\28335_periph\UKSSTMS320F28335\Debug\firmware`
В нём находятся `UKSSTMS320F28335_<build_id>.bin`, файл
`<имя>.bin.manifest.json`, файл `<имя>.bin.sha256`, HEX, карты конверсии
и архивные папки с ревизией и временем сборки.
Например, проверенный в этой сессии файл:
`K:\git_project\28335_periph\UKSSTMS320F28335\Debug\firmware\UKSSTMS320F28335_13f1a2c+.bin`
В окне выбора прошивки можно вставить полный путь в поле **«Имя файла»**.
Для актуальной сборки берите путь из последней строки журнала
`[firmware-build] Verified BIN: ...`.
Каталоги `.identity-release-...` из промежуточных строк журнала временные:
после проверки их содержимое переносится в `Debug\firmware`, а временная
папка удаляется.
Поле `"copy_to": "../Bin"` в JSON дополнительно копирует проверенные BIN,
manifest, SHA-256, HEX и карту конверсии в `K:\git_project\28335_periph\Bin`.
Для другого назначения укажите в `copy_to` реальный полный путь через `/`
или путь относительно JSON. Если дополнительная копия не нужна, удалите
это поле. Архивные папки остаются в `Debug\firmware`.
## Версия и проверки перед выпуском
| Поле в firmware_identity.json | Назначение |
|---|---|
| `product` | Идентификатор изделия, сейчас `Balsam-166-PM35`. |
| `target` | Контроллер, сейчас `TMS320F28335`. |
| `version` | Последний занятый номер `[major, minor, patch]`. |
| `increment_version` | `true` увеличивает версию перед компиляцией; `false` оставляет ручную версию. |
| `templates` | Корень общей библиотеки; путь относительно JSON или абсолютный. |
| `max_image_bytes` | Предел полного SCI8 BIN, сейчас 32508 байт. |
| `copy_to` | Необязательный дополнительный каталог проверенных файлов. |
При включённом итераторе каждый pre-build занимает следующий номер,
включая попытки, после которых компиляция завершилась ошибкой. После patch
999 увеличивается minor, после minor 255 — major; младшие поля обнуляются.
После 255.255.999 сценарий выдаёт ошибку. Clean номер не сбрасывает.
Одновременные сборки с одним JSON запускать нельзя.
Build ID состоит из восьми символов: восемь символов ревизии либо семь
символов и `+` при изменениях исходников или templates. При недоступном Git
используется `NOGIT000`; ревизии `source` и `templates` проверяйте в manifest.
Время паспорта хранится в UTC.
Post-build требует совпадения OUT с метаданными pre-build, ровно одного
12-словного паспорта в SCI8, корректных manifest и SHA-256. Ограничение
32508 байт рассчитано как `(0x3FFF - 129) * 2`: это граница области
параметров EEPROM данной платы. Неверный комплект проверку не проходит.
## Повторная конверсия готового OUT без компиляции
Запустите следующую команду в **cmd.exe**. Она использует существующие
OUT и generated от одной компиляции и повторно проверяет их соответствие.
Номер версии при этом не увеличивается.
```bat
rem Конвертирует готовый Debug OUT штатным hex2000 из указанного компилятора, сохраняя проверки паспорта и SHA-256.
call "K:\git_project\28335_periph\build_bin.bat" --compiler "C:/ti/ccs1230/ccs/tools/compiler/ti-cgt-c2000_22.6.0.LTS"
```
Если изменены исходники, версия или паспорт, выполните обычную сборку
в CCS, чтобы OUT и generated соответствовали друг другу.
## Перенос в другой проект C2000
Скопируйте `firmware_build.bat`, `build_identity.py` и
`firmware_identity.json` из
`K:\git_project\28335_periph\UKSSTMS320F28335` в каталог нового CCS-проекта.
В JSON задайте изделие, контроллер, версию, реальный путь к полной
библиотеке templates, предел памяти платы и при необходимости `copy_to`.
Добавьте Build Steps из этой инструкции и include paths к выбранной
библиотеке и `${BuildDirectory}/generated` в обе конфигурации CCS.
BAT и Python менять для переноса путей не требуется. BAT хранится в UTF-8
без BOM с окончаниями CRLF. Подключение сборочных шагов не заменяет
интеграцию firmware-info в код устройства; для C28x нужен порт с учётом
16-битного `char`.
## Проверка прошивки в SetGUI
@@ -57,22 +202,21 @@ EEPROM, а версия начнёт читаться после запуска
RS/CAN, все слова паспорта, CRC, частичные чтения, границы, запрет записи,
сохранность старой карты и логгера. Отдельно проверяются устаревшие и
повреждённые SCI8, предел EEPROM и действующий анализатор манифеста SetGUI.
`python tests/firmware_identity/test_build_steps.py` проверяет перенос трёх
файлов в другое изделие, увеличение версии в JSON, перенос разрядов,
настоящий вызов BAT и общего precompile из постороннего рабочего каталога,
пути с пробелами, копирование и остановку до публикации при неверном BIN
или SHA-256.
При заданной переменной окружения `C2000_COMPILER` (корень toolchain)
этот же тест дополнительно компилирует минимальный C28x COFF и проходит
настоящий postbuild через BAT, `hex2000`, проверку паспорта и копирование.
Проверка 17.10.2026: post-build готового OUT выполнен с TI C2000 22.6.0.LTS;
проверки встроенного паспорта и SHA-256 пройдены, комплект скопирован в Bin.
SHA-256 основной и дополнительной копий BIN совпадает. Тест подключения
BAT в конфигурациях Debug и Release пройден. Полная компиляция и проверка
на физической плате в рамках этой проверки не выполнялись.
Проверено: импорт и полная сборка **Debug** в CCS **12.7.1** с компилятором
**22.6.1.LTS**, включая генерацию HEX/BIN, завершились без ошибок.
Есть шесть предупреждений в существующих `peripher.c` и `measure.c`
(преобразование знака, выход результата за диапазон и отсутствие перевода
строки в конце файла). Проверка на устройстве не выполнялась.
Перенос паспорта дополнительно проверен полной пересборкой и линковкой
TI C2000 **22.6.0.LTS** и host-тестами под GCC с оптимизацией O3. Проверенный
SCI8 BIN занимает **30248 байт**, запас до параметров EEPROM — **2260 байт**.
Совпадение встроенного паспорта, манифеста и результата штатного анализатора
SetGUI проверено программно; обмен новой прошивки с физической платой
требует её установки и перезапуска.
Конфигурация **Release** перенесена из 167 без изменений и не проверялась;
для сборки прошивки используйте **Debug**.
Для рабочей сборки используйте Debug; Release требует отдельной проверки.
## Телеметрия CAN: heartbeat, изменения и повтор 1,3 с
+142 -67
View File
@@ -1,67 +1,142 @@
"""Выпускает BIN 166 с проверяемым паспортом и SHA-256 по той же схеме, что 167.""" # Генерация версии выполняется отдельно до компиляции.
import argparse # Разбирает пути текущей конфигурации CCS и компилятора TI.
from datetime import datetime # Преобразует время UTC паспорта в те же шесть компонентов, что firmware-info v1.
import json # Читает именно паспорт завершившейся компиляции.
from pathlib import Path # Привязывает настройки и результаты к текущему проекту.
import shutil # Находит компилятор и копирует только полностью проверенный выпуск.
import struct # Проверяет 16-битные слова загрузочного SCI8 без исполнения кода прошивки.
import sys # Подключает общий postbuild вместо копии конвертера из 167.
import tempfile # Не публикует результаты конверсии, пока встроенный паспорт не прошёл проверку.
ROOT = Path(__file__).resolve().parents[1] # Корень 166 содержит существующие Source и Bin.
PROJECT = Path(__file__).resolve().parent # Конфигурации Debug/Release и заголовок версии находятся рядом со сценарием.
TEMPLATES = ROOT.parent / "newProject/templates" # Использует ту же общую библиотеку firmware-info, что проект 167.
def verify_embedded(image, metadata): # Не позволяет выдать новый манифест за паспорт старого OUT без встроенной версии.
data = image.read_bytes() # Проверяет точные октеты, которые будут отправлены в плату.
if data[:2] != b"\xaa\x08" or not 24 <= len(data) <= 32508: # SCI8 имеет ключ 0x08AA; граница параметров 166 равна (0x3FFF - 129) * 2 = 32508 байт.
raise ValueError("SCI8 is invalid or overlaps the 166 parameter area at byte 32508") # Не допускает повреждения существующих параметров EEPROM.
stamp = datetime.fromisoformat(metadata["built_at_utc"]) # Использует исходное время prebuild без подмены временем конверсии BIN.
build_id = metadata["build_id"] # Ровно восемь ASCII-символов совпадают с четырьмя регистрами firmware-info v1.
words = [1, *metadata["version"], stamp.year, stamp.month * 256 + stamp.day, stamp.hour * 256 + stamp.minute, stamp.second] # Восстанавливает первые восемь регистров независимо от C-упаковщика.
words += [ord(build_id[i]) * 256 + ord(build_id[i + 1]) for i in range(0, 8, 2)] # Восстанавливает четыре слова Build ID в сетевом порядке символов.
expected = struct.pack("<12H", *words) # Загрузочная таблица SCI8 хранит 16-битные слова C28x младшим октетом вперёд.
offset, found = 22, 0 # Первые 22 октета заняты ключом, настройками и аппаратной точкой входа.
while offset + 2 <= len(data): # Каждый шаг потребляет заголовок либо завершает конечный файл; ожидания аппаратуры нет.
count, = struct.unpack_from("<H", data, offset) # Размер очередной секции задан в 16-битных словах C28x.
offset += 2 # Переходит к адресу секции либо концу таблицы при нулевом размере.
if not count: break # Нулевая секция завершает SCI8 и не содержит адреса назначения.
end = offset + 4 + count * 2 # Четыре октета адреса не входят в проверяемую полезную нагрузку секции.
if end > len(data): raise ValueError("Truncated SCI8 section") # Останавливает выпуск при выходе секции за фактический размер файла.
payload = data[offset + 4:end] # Рассматривает загружаемые слова, а не случайное совпадение в заголовке.
found += sum(payload[i:i + 24] == expected for i in range(0, len(payload) - 23, 2)) # Паспорт должен присутствовать один раз на границе слова C28x.
offset = end # Переходит к следующей непересекающейся секции таблицы.
if offset != len(data) or count or found != 1: # Запрещает хвосты, отсутствующий терминатор и манифест от другой компиляции.
raise ValueError(f"SCI8 does not contain exactly one matching prebuild identity: {found}") # Требует полноценную пересборку вместо фиктивного подписания старого BIN.
def find_compiler(): # Сохраняет возможность ручного запуска build_bin.bat без жёсткой привязки к одной версии CCS.
executable = shutil.which("cl2000") or shutil.which("cl2000.exe") # Предпочитает компилятор среды текущего проекта, а не старый hex2000 рядом с BIN.
if executable: return Path(executable).resolve().parents[1] # Возвращает корень toolchain с каталогом bin/hex2000.exe.
installed = sorted(Path("C:/ti").glob("ccs*/ccs/tools/compiler/ti-cgt-c2000_*")) # Для установленного CCS использует только штатные каталоги TI.
installed = [path for path in installed if (path / "bin/hex2000.exe").is_file()] # Не принимает неполную установку за пригодный конвертер.
if not installed: raise FileNotFoundError("Pass --compiler with the C2000 toolchain root") # Явно останавливает ручной запуск без инструментов.
return installed[-1] # Выбирает последнюю найденную установленную цепочку только при отсутствии заданной CCS версии.
def build(compiler, build_dir, artifact): # Выполняет общий postbuild и локальную публикацию после проверки встроенной версии.
sys.path.insert(0, str(TEMPLATES / "c/firmware-info/tools")) # Открывает общие identity.py и c2000_postbuild.py из той же зависимости, что 167.
from c2000_postbuild import postbuild # Не копирует формат манифеста, архивирование и вычисление SHA-256.
metadata_path = build_dir / "generated/firmware_identity.json" # Отсутствующий prebuild должен остановить выпуск вместо использования произвольной версии.
metadata = json.loads(metadata_path.read_text(encoding="utf-8")) # Читает JSON, по которому компилятор получил firmware_identity.h.
if metadata.get("product") != "Balsam-166-PM35" or metadata.get("target") != "TMS320F28335": raise ValueError("Run the 166 prebuild before publishing this image") # Старый generated от ветки 167 не должен выдавать её product за паспорт 166.
output = build_dir / "firmware" # Сохраняет прежнюю для 167 структуру локального каталога выпусков.
with tempfile.TemporaryDirectory(prefix=".identity-release-", dir=build_dir) as directory: # Непроверенные BIN и manifest существуют только во временном каталоге текущего вызова.
stage = Path(directory); staged_image = postbuild(compiler, build_dir, artifact, metadata_path, stage) # Общий код создаёт HEX, BIN, SHA-256 и архив, пока не доступные как готовый выпуск.
verify_embedded(staged_image, metadata) # Не переносит в firmware или Bin результаты от старого OUT либо другой компиляции.
output.mkdir(parents=True, exist_ok=True) # Создаёт каталог публикации только после успешной проверки.
for item in stage.iterdir(): # Сохраняет только результаты этого вызова, не сканируя старые выпуски.
if item.is_dir(): shutil.copytree(item, output / item.name, dirs_exist_ok=True) # Публикует полный архив проверенной сборки.
else: shutil.copy2(item, output / item.name) # Переносит плоские HEX/BIN и контрольные файлы без подмены других исходников.
image = output / staged_image.name # После удаления временного каталога возвращает постоянный путь проверенного BIN.
for file in (image, image.with_suffix(".bin.manifest.json"), image.with_suffix(".bin.sha256"), image.parent / (image.stem.rsplit("_", 1)[0] + ".hex")): # Сохраняет рядом проверенный BIN, его контрольные файлы и прежний Intel HEX для CCS.
shutil.copy2(file, ROOT / "Bin" / file.name) # Основной каталог получает только результат успешной текущей сборки.
shutil.copy2(image.parent / (image.stem.rsplit("_", 1)[0] + ".hex.map"), ROOT / "Bin/UKSSTMS320F28335.map") # Согласовывает прежнюю основную карту конверсии с новым HEX и OUT.
print(ROOT / "Bin" / image.name) # Показывает конкретный BIN, который нужно выбрать в SetGUI вместе с лежащим рядом манифестом.
return image # Позволяет тестам проверить результат без поиска случайного старого файла.
if __name__ == "__main__": # Импорт проверки SCI8 не выполняет сборку или публикацию файлов.
parser = argparse.ArgumentParser(description=__doc__) # Описывает контракт запуска из CCS и build_bin.bat.
parser.add_argument("--compiler", type=Path) # CCS передаёт CG_TOOL_ROOT; ручной вызов может использовать обнаружение установленного компилятора.
parser.add_argument("--build-dir", type=Path, default=PROJECT / "Debug") # Изолирует паспорт и архив выбранной конфигурации.
parser.add_argument("--artifact", default=str(ROOT / "Bin/UKSSTMS320F28335")) # Общий postbuild ожидает полный путь OUT без расширения.
args = parser.parse_args() # Проверяет параметры до выполнения конверсии.
build((args.compiler or find_compiler()).resolve(), args.build_dir.resolve(), args.artifact) # Публикует только образ, соответствующий своему встроенному паспорту.
"""Подготавливает версию и выпускает проверенный C2000 SCI8 по настройкам JSON.""" # Один переносимый сценарий обслуживает оба шага CCS через BAT.
import argparse # Разбирает команды prepare и build без зависимости от установленной IDE.
from datetime import datetime # Восстанавливает регистры даты из времени UTC, зафиксированного до компиляции.
import hashlib # Проверяет SHA-256 точных байтов BIN перед переносом в постоянные каталоги.
import json # Хранит изделие, счётчик версии, пути и предел EEPROM в одном проектном файле.
from pathlib import Path # Разрешает относительные пути от JSON, а не от рабочего каталога CCS.
import shutil # Переносит только прошедшие проверку файлы текущей сборки.
import struct # Читает загрузочные секции SCI8 как 16-битные слова C28x.
import subprocess # Запускает публичные BAT общей библиотеки и передаёт ошибки вызывающей сборке.
import tempfile # Изолирует конверсию и обеспечивает атомарную замену файла счётчика.
def read_settings(config): # Проверяет настройки до изменения счётчика и запуска общих шагов.
settings = json.loads(config.read_text(encoding="utf-8-sig")) # Поддерживает UTF-8 как с BOM, так и без него.
version = settings.get("version") # Единственным сохраняемым источником версии является проектный JSON.
if not isinstance(version, list) or len(version) != 3 or any(type(value) is not int or not 0 <= value <= limit for value, limit in zip(version, (255, 255, 999))): # Контракт firmware-info v1 допускает major/minor 0..255 и patch 0..999.
raise ValueError("version must be [major 0..255, minor 0..255, patch 0..999]") # Не допускает неверную версию в счётчик или заголовок компилятора.
for field in ("product", "target", "templates"): # Требует идентификацию изделия и явно выбранную общую библиотеку.
if not isinstance(settings.get(field), str) or not settings[field].strip(): # Не принимает отсутствующее или пустое значение за пригодную настройку.
raise ValueError(f"missing {field} in {config}") # Указывает поле, которое нужно заполнить при переносе проекта.
if type(settings.get("increment_version", False)) is not bool: # Переключатель итератора допускает только настоящие JSON true/false.
raise ValueError("increment_version must be true or false") # Не трактует строку false как включённый счётчик Python.
limit = settings.get("max_image_bytes") # Предел образа задаёт плата; отсутствие поля отключает только ограничение размера.
if limit is not None and (type(limit) is not int or limit < 24): # Минимальная таблица SCI8 занимает 22 байта заголовка и два байта терминатора.
raise ValueError("max_image_bytes must be an integer >= 24") # Не принимает отрицательный либо дробный размер EEPROM.
if "copy_to" in settings and (not isinstance(settings["copy_to"], str) or not settings["copy_to"].strip()): # Дополнительный каталог копирования должен быть явно задан непустым путём.
raise ValueError("copy_to must be a nonempty path or omitted") # Для отключения копирования достаточно удалить поле из JSON.
return settings # Возвращает проверенные настройки без скрытых значений конкретного изделия.
def config_path(config, value): # Делает размещение templates и дополнительного выпуска независимым от текущего каталога.
return (config.parent / value).resolve() # Абсолютный путь остаётся абсолютным, относительный отсчитывается от JSON.
def next_version(version): # Вычисляет следующий номер по пределам общего контракта паспорта.
result = list(version) # Не меняет исходные настройки до успешной проверки переполнения.
for index, limit in ((2, 999), (1, 255), (0, 255)): # Переносит единицу из patch в minor, затем в major.
if result[index] < limit: # Увеличивает первое поле, ещё не достигшее своего предела.
result[index] += 1 # Резервирует один следующий номер для очередного запуска prebuild.
return result # Старшие поля сохраняются, младшие уже обнулены при переносе.
result[index] = 0 # Сбрасывает переполненное поле перед увеличением следующего.
raise ValueError("Firmware version exhausted at 255.255.999") # Запрещает повторное использование номеров после полного переполнения.
def prepare(config, build_dir): # Увеличивает сохраняемую версию и вызывает штатный precompile templates.
settings = read_settings(config) # Сначала проверяет настройки, чтобы ошибочный JSON не занимал номер сборки.
script = config_path(config, settings["templates"]) / "tools/firmware-build/ccs/precompile.bat" # Выбирает публичную точку входа общей зависимости из JSON.
if not script.is_file(): raise FileNotFoundError(script) # Отсутствие templates останавливает шаг до изменения счётчика.
if settings.get("increment_version", False): # Проект включает автоматический итератор явным флагом в JSON.
settings["version"] = next_version(settings["version"]) # Получает следующий номер без отдельного C-заголовка.
with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", newline="\n", dir=config.parent, prefix=".firmware-version-", delete=False) as stream: # Создаёт UTF-8 файл на том же диске для атомарной замены настроек.
temporary = Path(stream.name) # Запоминает только собственный временный файл для очистки при отказе.
try: stream.write(json.dumps(settings, ensure_ascii=False, indent=2) + "\n") # Сохраняет все поля проекта вместе с новым номером версии.
except BaseException: stream.close(); temporary.unlink(); raise # При ошибке записи сохраняет исходный JSON и удаляет незавершённый файл.
try: temporary.replace(config) # Фиксирует занятый номер до запуска генератора и компилятора.
finally: temporary.unlink(missing_ok=True) # Не оставляет временный файл при отказе замены конфигурации.
output = build_dir / "generated" # Каждая конфигурация CCS получает отдельные заголовок и метаданные.
subprocess.run(["cmd.exe", "/d", "/c", "call", str(script), "--identity", str(config.parent), str(config), str(output)], check=True) # Общий precompile создаёт заголовок, UTC и Git-метаданные; ошибка останавливает сборку.
return json.loads((output / "firmware_identity.json").read_text(encoding="utf-8")) # Возвращает фактический паспорт без повторного вычисления времени или Build ID.
def verify_embedded(image, metadata, max_image_bytes=None): # Проверяет общий паспорт firmware-info v1 независимо от имени платы и проекта.
data = image.read_bytes() # Проверяет именно октеты транспортного BIN, предназначенного для загрузки.
if data[:2] != b"\xaa\x08" or len(data) < 24: # Ключ загрузчика C2000 SCI8 равен 0x08AA; минимальная таблица содержит терминатор.
raise ValueError("Invalid SCI8 header or missing terminator") # Не допускает выпуск произвольного файла под видом загрузочного BIN.
if max_image_bytes is not None and len(data) > max_image_bytes: # Сравнивает размер в байтах с пределом из настроек конкретной платы.
raise ValueError(f"SCI8 exceeds max_image_bytes={max_image_bytes}") # Сохраняет защиту области параметров EEPROM при переносе между изделиями.
stamp = datetime.fromisoformat(metadata["built_at_utc"]) # Берёт время именно того prebuild, который подготовил заголовок.
build_id = metadata["build_id"] # Восемь ASCII-символов занимают четыре регистра контракта.
words = [1, *metadata["version"], stamp.year, stamp.month * 256 + stamp.day, stamp.hour * 256 + stamp.minute, stamp.second] # Восстанавливает первые восемь регистров; множитель 256 упаковывает два октета в слово.
words += [ord(build_id[index]) * 256 + ord(build_id[index + 1]) for index in range(0, 8, 2)] # Упаковывает символы Build ID в том же сетевом порядке, что обработчик устройства.
expected = struct.pack("<12H", *words) # SCI8 хранит двенадцать 16-битных регистров младшим октетом вперёд.
offset, found = 22, 0 # Пропускает 22 байта ключа, настроек загрузчика и точки входа C2000.
while offset + 2 <= len(data): # Обрабатывает конечную таблицу без чтения за пределами BIN.
count, = struct.unpack_from("<H", data, offset) # Размер секции задан количеством 16-битных слов.
offset += 2 # Пропускает поле размера перед адресом секции или терминатором.
if not count: break # Нулевая длина завершает таблицу и не содержит адреса назначения.
end = offset + 4 + count * 2 # После четырёх байтов адреса идут count слов полезной нагрузки.
if end > len(data): raise ValueError("Truncated SCI8 section") # Не принимает усечённую секцию даже при совпавшем паспорте в её начале.
payload = data[offset + 4:end] # Проверяет только загружаемые слова, исключая адресные поля.
found += sum(payload[index:index + 24] == expected for index in range(0, len(payload) - 23, 2)) # Ищет паспорт из 24 байтов только на границе слова C28x.
offset = end # Переходит к следующей непересекающейся секции.
if offset != len(data) or count or found != 1: # Требует терминатор в конце файла и ровно один паспорт текущей компиляции.
raise ValueError(f"SCI8 does not contain exactly one matching prebuild identity: {found}") # Отклоняет старый OUT, чужую версию, дубликат и лишний хвост.
def verify_package(image, metadata): # Проверяет согласованность BIN и контрольных файлов до публикации.
manifest = json.loads(image.with_suffix(".bin.manifest.json").read_text(encoding="utf-8")) # Читает manifest, созданный общим postcompile для текущего файла.
data = image.read_bytes() # Вычисляет контрольные значения по точным байтам результата конверсии.
digest = hashlib.sha256(data).hexdigest() # SHA-256 служит проверкой целостности конкретного транспортного файла.
if manifest.get("file") != image.name or manifest.get("size_bytes") != len(data) or manifest.get("sha256") != digest: # Связывает имя, размер и содержимое BIN с manifest.
raise ValueError("BIN does not match its manifest") # Не переносит повреждённый выпуск в постоянный каталог.
if any(manifest.get(field) != metadata[field] for field in ("product", "target", "version", "build_id", "built_at_utc")): # Проверяет, что manifest относится к тому же паспорту prebuild.
raise ValueError("Manifest does not match prebuild identity") # Запрещает контрольные файлы от другой сборки.
if image.with_suffix(".bin.sha256").read_text(encoding="utf-8").strip() != f"{digest} {image.name}": # Общий формат SHA-256 содержит хеш и точное имя BIN через два пробела.
raise ValueError("Invalid BIN SHA-256 sidecar") # Не допускает рассогласованный комплект файлов для загрузки.
def build(compiler, build_dir, artifact, config): # Конвертирует OUT общей библиотекой и публикует только проверенную сборку.
settings = read_settings(config) # Берёт ограничения и пути из того же JSON, что использовался перед компиляцией.
script = config_path(config, settings["templates"]) / "tools/firmware-build/ccs/postcompile.bat" # Не содержит пути к конкретному репозиторию templates.
metadata_path = build_dir / "generated/firmware_identity.json" # Выбирает паспорт текущей конфигурации Debug или Release.
metadata = json.loads(metadata_path.read_text(encoding="utf-8")) # Отсутствующий prebuild останавливает выпуск до конверсии.
if any(metadata.get(field) != settings[field] for field in ("product", "target", "version")): # Сравнивает изделие, контроллер и сохраняемую версию с текущей конфигурацией.
raise ValueError("Configuration changed: run prebuild and rebuild before publishing") # Не выпускает старый OUT после изменения настроек или версии.
output = build_dir / "firmware" # Сохраняет стандартную структуру templates внутри BuildDirectory.
with tempfile.TemporaryDirectory(prefix=".identity-release-", dir=build_dir) as directory: # Все результаты до проверки остаются во временном каталоге этой сборки.
stage = Path(directory) # Изолирует новые результаты от предыдущих архивов и каталога копирования.
subprocess.run(["cmd.exe", "/d", "/c", "call", str(script), "--identity", str(compiler), str(build_dir), artifact, str(metadata_path), str(stage)], check=True) # Общий BAT формирует SCI8, manifest, SHA-256 и архив; ненулевой код прерывает выпуск.
staged_image = stage / (Path(artifact).name + "_" + metadata["build_id"] + ".bin") # Находит текущий BIN без поиска по времени среди старых файлов.
verify_embedded(staged_image, metadata, settings.get("max_image_bytes")) # Проверяет паспорт и проектный предел EEPROM перед постоянной публикацией.
verify_package(staged_image, metadata) # Проверяет SHA-256 и соответствие сопроводительных файлов до копирования.
output.mkdir(parents=True, exist_ok=True) # Создаёт постоянный каталог только после успешной проверки комплекта.
for item in stage.iterdir(): # Переносит результаты только этого вызова общего postcompile.
if item.is_dir(): shutil.copytree(item, output / item.name, dirs_exist_ok=True) # Сохраняет полный архив текущей сборки, не удаляя предыдущие.
else: shutil.copy2(item, output / item.name) # Сохраняет плоские HEX, BIN, карты и контрольные файлы.
image = output / staged_image.name # Возвращает постоянный путь после удаления временного каталога.
if "copy_to" in settings: # Копирование в прежний Bin включается только настройкой конкретного проекта.
destination = config_path(config, settings["copy_to"]) # Разрешает дополнительный каталог относительно JSON без привязки к имени репозитория.
destination.mkdir(parents=True, exist_ok=True) # Позволяет перенести проект в ещё не созданную папку выпусков.
for file in (image, image.with_suffix(".bin.manifest.json"), image.with_suffix(".bin.sha256"), output / (Path(artifact).name + ".hex")): # Копирует проверенный комплект, сохраняя прежние имена потребителей BIN и HEX.
if file.resolve() != destination / file.name: shutil.copy2(file, destination / file.name) # Допускает совпадение дополнительной папки с firmware без копирования файла в себя.
shutil.copy2(output / (Path(artifact).name + ".hex.map"), destination / (Path(artifact).name + ".map")) # Поддерживает старое имя карты конверсии без зашитого имени изделия.
verify_package(destination / image.name, metadata) # Подтверждает целостность также скопированного BIN и его контрольных файлов.
print(f"[firmware-build] Verified BIN: {image}") # Показывает пользователю точный файл проверенной сборки.
return image # Даёт тестам и ручным средствам явный результат без сканирования каталогов.
if __name__ == "__main__": # Импорт из тестов не запускает счётчик или конверсию.
parser = argparse.ArgumentParser(description=__doc__) # Объясняет назначение переносимого сценария при вызове через BAT.
commands = parser.add_subparsers(dest="command", required=True) # Разделяет prebuild и postbuild явными командами.
for command in ("prepare", "build"): # Оба шага принимают одинаковые настройки и BuildDirectory.
step = commands.add_parser(command) # Создаёт независимый интерфейс выбранного шага сборки.
step.add_argument("--config", type=Path, default=Path(__file__).with_name("firmware_identity.json")) # По умолчанию читает JSON рядом со скриптом; другой путь можно передать явно.
step.add_argument("--build-dir", type=Path, required=True) # CCS выбирает отдельную папку для каждой конфигурации.
if command == "build": # Компилятор и имя OUT требуются только после компиляции.
step.add_argument("--compiler", type=Path, required=True) # Получает CG_TOOL_ROOT без поиска конкретной установки CCS.
step.add_argument("--artifact", required=True) # Получает BuildArtifactFileBaseName без расширения OUT и без изменения пути.
args = parser.parse_args() # Проверяет обязательные параметры до изменения файлов.
config, build_dir = args.config.resolve(), args.build_dir.resolve() # Нормализует пути до передачи BAT, независимо от текущего каталога.
if args.command == "prepare": print(json.dumps(prepare(config, build_dir), ensure_ascii=False)) # Показывает фактическую версию и метаданные текущего prebuild.
else: build(args.compiler.resolve(), build_dir, args.artifact, config) # Запускает конверсию и обязательные проверки готового образа.
+8
View File
@@ -0,0 +1,8 @@
@rem Запускает переносимый Python-сценарий рядом с BAT, независимо от текущего каталога CCS.
@echo off
rem Ограничивает изменения окружения текущим вызовом и запрещает раскрытие восклицательных знаков в путях.
setlocal EnableExtensions DisableDelayedExpansion
rem Передаёт команду prepare или build и все аргументы без привязки к изделию, расположению templates или версии CCS.
python "%~dp0build_identity.py" %*
rem Возвращает CCS код Python: ошибка генерации или проверки BIN должна остановить сборку.
exit /b %errorlevel%
+10 -1
View File
@@ -1,4 +1,13 @@
{
"product": "Balsam-166-PM35",
"target": "TMS320F28335"
"target": "TMS320F28335",
"version": [
1,
0,
4
],
"increment_version": true,
"templates": "../../newProject/templates",
"max_image_bytes": 32508,
"copy_to": "../Bin"
}
-6
View File
@@ -1,6 +0,0 @@
#ifndef BALSAM_FIRMWARE_VERSION_H /* Защищает единственный источник версии продукта 166 от повторного включения. */
#define BALSAM_FIRMWARE_VERSION_H /* Отделяет номер версии приложения от версии логгера и контракта паспорта. */
#define FIRMWARE_VERSION_MAJOR 1U /* Первая версия 166 с читаемым паспортом; это версия продукта, а не заимствованный номер прошивки 167. */
#define FIRMWARE_VERSION_MINOR 0U /* Нулевая минорная версия начинает самостоятельную линию выпусков Balsam-166-PM35. */
#define FIRMWARE_VERSION_PATCH 0U /* Нулевой номер исправления меняется при следующем выпуске продукта. */
#endif /* Генератор читает эти три определения перед компиляцией и помещает их в паспорт BIN. */
+2 -2
View File
@@ -2,7 +2,7 @@
@echo off
rem Сохраняет локальность настроек процесса конвертации.
setlocal
rem Сценарий сам находит Debug и проверяет его generated/firmware_identity.json; старые служебные аргументы CCS здесь не нужны.
python "%~dp0build_identity.py"
rem Совместимый ручной вызов использует build_bin.bat; --compiler указывает toolchain, создавший OUT.
call "%~dp0..\build_bin.bat" %*
rem Возвращает ошибку при старом OUT, отсутствующем паспорте либо превышении области параметров.
exit /b %errorlevel%
-35
View File
@@ -1,35 +0,0 @@
"""Подготавливает паспорт 166 тем же генератором firmware-info, что используется в 167.""" # Версия, время UTC и ревизии фиксируются до компиляции.
import argparse # Разбирает одинаковые с проектом 167 пути prebuild.
import importlib.util # Подключает общую реализацию без копирования генератора в прошивку.
import json # Объединяет идентификаторы продукта с версией из C-заголовка.
from pathlib import Path # Нормализует пути независимо от рабочего каталога CCS.
import re # Проверяет единственное допустимое целое определение каждого компонента версии.
import tempfile # Хранит объединённые настройки только на время вызова общего генератора.
def read_version(header): # Сохраняет правила исходного адаптера 167 и ограничения firmware-info v1.
text = re.sub(r"/\*.*?\*/|//[^\n]*", "", header.read_text(encoding="utf-8-sig"), flags=re.S) # Не принимает комментарии или примеры за действующую версию.
result = [] # Накапливает major, minor и patch в порядке регистров контракта.
for part, limit in (("MAJOR", 255), ("MINOR", 255), ("PATCH", 999)): # Два первых компонента ограничены октетом, исправление допускает 0..999.
name = "FIRMWARE_VERSION_" + part # Использует тот же интерфейс заголовка версии, что 167.
definitions = re.findall(r"^\s*#\s*define\s+" + name + r"\b([^\n]*)", text, re.M) # Требует единственного определения без условных альтернатив.
if len(definitions) != 1 or not re.fullmatch(r"(?:0|[1-9][0-9]*)[uU]?", definitions[0].strip()): # Не допускает выражений, шестнадцатеричных литералов и неоднозначной версии.
raise ValueError(f"{header}: define {name} exactly once as a decimal integer") # Останавливает сборку вместо выпуска ложного паспорта.
value = int(definitions[0].strip().rstrip("uU")) # Суффикс C не является частью десятичного числа Python.
if value > limit: # Проверяет границы до записи какого-либо заголовка сборки.
raise ValueError(f"{name} must be in 0..{limit}") # Сохраняет ограничения отображения и Modbus-контракта 167.
result.append(value) # Записывает проверенную компоненту в правильное поле паспорта.
return result # Номер версии формируется только из firmware_version.h.
def prepare(project, templates, output): # Генерирует заголовок и JSON для одной конкретной конфигурации CCS.
settings = json.loads((project / "firmware_identity.json").read_text(encoding="utf-8-sig")) # Сохраняет собственный product 166 и тот же target TMS320F28335.
settings["version"] = read_version(project / "firmware_version.h") # Не дублирует SemVer в двух редактируемых файлах.
spec = importlib.util.spec_from_file_location("shared_firmware_identity", templates / "c/firmware-info/tools/identity.py") # Берёт проверенную общую реализацию из той же зависимости, что 167.
identity = importlib.util.module_from_spec(spec) # Создаёт изолированный модуль генератора без изменения системного Python.
spec.loader.exec_module(identity) # Загружает формат заголовка, UTC и правила формирования Build ID.
with tempfile.TemporaryDirectory(prefix="balsam166-identity-") as directory: # Удаляет только временные объединённые настройки этого вызова.
config = Path(directory) / "settings.json" # Настройки не записываются в исходники и не попадают в историю Git.
config.write_text(json.dumps(settings), encoding="utf-8") # Передаёт общему генератору проверенную версию и product.
return identity.prepare(project, templates, config, output) # Создаёт обязательные generated/firmware_identity.h и JSON до компиляции.
if __name__ == "__main__": # Импорт из теста не запускает изменение файлов сборки.
parser = argparse.ArgumentParser(description=__doc__) # Объясняет назначение prebuild при ручном вызове.
for name in ("project", "templates", "output"): parser.add_argument("--" + name, type=Path, required=True) # Совместим с параметрами prebuild исходного проекта 167.
args = parser.parse_args() # Проверяет обязательные каталоги перед генерацией.
print(json.dumps(prepare(args.project.resolve(), args.templates.resolve(), args.output.resolve()))) # Показывает фактическую идентичность текущей сборки.