Перенести улучшения 166 и добавить сборку для M95M02 и Flash

Восстановить телеметрию CAN, обработку TESTLAMP и сохранение команд при загрузке параметров.

Выбрать M95M02 основным профилем, добавить самостоятельную Flash-сборку и проверять образы до публикации.

Проверки: хостовые тесты, реальные сборки TI C2000, контроль COFF/SCI8 и защита записи при активной нагрузке.
This commit is contained in:
2026-10-19 16:33:28 +03:00
parent 985804607d
commit bbd82b3a4e
42 changed files with 1820 additions and 321 deletions
+4
View File
@@ -58,3 +58,7 @@
/Bin/**/logger_channels.png
# Снимки предпросмотра защиты используются только при визуальной проверке интерфейса.
/Bin/**/gui_preview.png
# Локальный комплект проверенных RAM/Flash-образов и их паспортов создаётся из исходников перед выпуском.
/Bin/Final167/
# Временные объекты отдельной Flash-сборки зависят от установленного TI toolchain и не входят в исходники.
/Bin/Flash167/
+11 -9
View File
@@ -60,16 +60,18 @@ SPI-загрузчика и драйвера, а не только правка
выбор. Не задавайте противоречащий ему отдельный define только компилятору CCS:
тогда компилятор, линкер и проверка BIN могут получить разные настройки.
### Сборка скриптом
### Отдельная сборка
```powershell
./build_logger.ps1 -Eeprom AT25256B -OutputDirectory ./Bin/EepromProfiles/AT25256B -ImageName UKSSTMS320F28335_AT25256B
./build_logger.ps1 -Eeprom M95M02 -OutputDirectory ./Bin/EepromProfiles/M95M02 -ImageName UKSSTMS320F28335_M95M02
```
Параметр скрипта передаётся компилятору, линкеру и проверке размера; исходный
config не меняется. Скрипт по умолчанию собирает AT25256B. Рядом с BIN создаются
manifest и SHA-256; поле `eeprom` в manifest различает аппаратные варианты.
Скрипт `build_logger.ps1` отсутствует в текущем дереве. Для штатного выпуска
выберите профиль в заголовке и выполните Clean/Rebuild CCS.
`UKSSTMS320F28335/prepare_identity.py` подготавливает паспорт;
`UKSSTMS320F28335/publish_identity.py` проверяет и публикует результат TI.
При отдельной сборке явный выбор `--eeprom AT25256B` или `--eeprom M95M02`
в обоих Python-шагах должен совпадать с `EEPROM_M95M02` у компилятора и
препроцессора линкера. Защищённый postbuild сверяет профиль с символами MAP,
проверяет структуру SCI8, встроенный паспорт и предел до параметров EEPROM
перед копированием в постоянные каталоги. Рядом с BIN создаются manifest и
SHA-256; поле `eeprom` в manifest различает аппаратные варианты.
UART остаётся 115200; алгоритм UART и FIFO не изменялся.
| Параметр | AT25256B | M95M02-DR |
+32 -38
View File
@@ -15,42 +15,36 @@
| `UKSSTMS320F28335/firmware_identity.json` | Product и target; версия берётся из C-заголовка |
| `Source/Internal/shared_ump.c` | Подключение общей таблицы версии и привязка к UMP ПМ35 |
| `UKSSTMS320F28335/prepare_identity.py` | Генерация идентичности из C-заголовка перед CCS Debug/Release |
| `build_logger.ps1` | Полный CLI build: prepare, compile, link, SCI8, size check, seal |
| `build_bin.bat` | Debug post-build: проверка SCI8 и паспорт конечного BIN |
| `UKSSTMS320F28335/publish_identity.py` | Защищённый post-build Debug/Release: SCI8, встроенный паспорт, профиль EEPROM, размер, manifest и SHA |
| `tests/firmware_identity/port_test.c` | Host-проверка привязки версии к реальному порту 167 |
Номер `0.1.0` — начальная версия разработки этого механизма; он не выдаётся за
историческую версию предыдущих BIN. Назначайте release-номер вручную перед выпуском.
## Сборка из PowerShell
## Выпуск и отдельные проверки
Из корня 167:
Скрипты `build_logger.ps1` и `build_bin.bat` отсутствуют в текущем дереве.
Штатный выпуск выполняется через Clean/Rebuild CCS с оптимизацией
`-O3 --opt_for_speed=0`. Обе конфигурации вызывают `prepare_identity.py`
перед компиляцией и `publish_identity.py` после линковки.
```powershell
./build_logger.ps1 -OutputDirectory "$PWD/Bin/FirmwareIdentity" `
-ImageName UKSSTMS320F28335
```
Prebuild сохраняет заголовок и JSON в `${BuildDirectory}/generated`.
Защищённый postbuild сначала вызывает общий конвертор во временный каталог,
затем проверяет границы EEPROM, структуру SCI8, точку входа и адреса RAM,
ровно один встроенный паспорт текущей компиляции и весь комплект manifest/SHA.
Аппаратный профиль сверяется с абсолютными символами EEPROM в карте линкера.
Ошибка любой проверки не публикует новый BIN или архив.
По умолчанию применяется `-O2 --opt_for_speed=0`. Результаты:
Проверенный выпуск сохраняется в `${BuildDirectory}/firmware` вместе с
manifest и SHA-256. Архив содержит OUT и сопроводительные файлы; его копия
передаётся в каталог, заданный аргументом `--export` в `.cproject`.
Поле `eeprom` в manifest обязательно различает AT25256B и M95M02.
```text
Bin/FirmwareIdentity/UKSSTMS320F28335.bin
Bin/FirmwareIdentity/UKSSTMS320F28335.bin.manifest.json
Bin/FirmwareIdentity/UKSSTMS320F28335.bin.sha256
Bin/FirmwareIdentity/generated/firmware_identity.h
Bin/FirmwareIdentity/generated/firmware_identity.json
```
Проверка файла перед передачей:
```powershell
python ../newProject/SETGUI/third_party/templates/c/firmware-info/tools/identity.py verify `
--image Bin/FirmwareIdentity/UKSSTMS320F28335.bin `
--manifest Bin/FirmwareIdentity/UKSSTMS320F28335.bin.manifest.json
```
CLI принимает `-TemplatesRoot`, если общий checkout расположен иначе. Все
исходники общего механизма собираются непосредственно из этого checkout.
При отдельной сборке параметры обоих Python-шагов можно посмотреть через
`--help`. Явный `--eeprom` должен совпадать с define компилятора и линкера.
Паспорт зависимости относится к фактически компилируемой копии
`SETGUI/third_party/templates`; инструменты конверсии остаются в соседнем
основном checkout `templates`.
## Сборка CCS
@@ -62,7 +56,7 @@ Pre-build требует Python 3.10+ и Git в PATH процесса CCS. `Gene
этому проекту: не запускайте Debug/Release одновременно. Для параллельных
сборок используйте CLI с разными output-каталогами.
Штатный Debug post-build создаёт в `Bin`:
Штатный post-build Debug/Release создаёт в `${BuildDirectory}/firmware`:
```text
UKSSTMS320F28335_<commit>.bin
@@ -70,12 +64,9 @@ UKSSTMS320F28335_<commit>.bin.manifest.json
UKSSTMS320F28335_<commit>.bin.sha256
```
Имя BIN исторически содержит HEAD без dirty-суффикса; достоверные признаки dirty
находятся внутри ответа версии и паспорта. Не определяйте чистоту только по имени.
Release-конфигурация получает embedded identity через pre-build, но её старый
проект не имел автоматического BIN post-build. Для готового проверенного BIN с
паспортом используйте описанный CLI либо настройте release-конверсию по общей
инструкции; нельзя запускать Debug `build_bin.bat` на Release и ожидать другой `.out`.
Идентификатор в имени BIN соответствует встроенному Build ID; знак `+`
обозначает незакоммиченные изменения приложения либо компилируемой зависимости.
Полная ревизия и состояние обоих деревьев находятся в паспорте.
Если CCS пишет `Error ... (ignored)`, сборка не считается успешной: убедитесь,
что последняя проверка и `seal` завершились без ошибок. Старый BIN с таким же
@@ -119,14 +110,17 @@ CAN ПМ67 не переназначается. Запись функции 06
## Влияние на память и параметры
С проверенными настройками текущий образ занимает **32 286 байт**, предел —
**32 508**, запас **222 байта**. Механизм добавил 116 байт относительно сборки
без него. Это результат конкретной сборки, не обещание для следующих изменений.
Историческая сборка при внедрении механизма занимала **32 286 байт** при
пределе AT25256B **32 508 байт**. Этот результат не относится к текущему
полному набору функций. Основной профиль теперь M95M02, его предел до
параметров — **261 884 байта**; актуальные результаты сборок приведены в
`UKSSTMS320F28335/README.md`. Postbuild проверяет размер каждого выпуска заново.
Таблица версии — часть `.econst` прошивки. Её не сохраняют в область параметров;
старые настройки не могут подменить версию после обновления. Размер буфера лога
и размещение EEPROM-параметров не менялись. Дата не разбирается на MCU, SHA-256
не вычисляется на MCU. Проверка размера `check_firmware.ps1` остаётся обязательной.
не вычисляется на MCU. Проверка размера встроена в `publish_identity.py` и
выполняется до публикации каждого нового BIN.
## Проверки и ограничения
+1 -1
View File
@@ -43,7 +43,7 @@ CLI define EEPROM_M95M02 должен передаваться и компиля
| 0x5F04 | 0x5000 | Начало технологических регистров |
| 0x5F05 | 128 | Количество технологических регистров |
| 0x5F06 | 7 | Бит 0: RTU; бит 1: служебный CAN; бит 2: запись FC06 |
| 0x5F07 | 1 | EEPROM M95M02 |
| 0x5F07 | 1 / 2 | 1 — EEPROM M95M02 с исполнением из RAM; 2 — отдельный образ исполнения из внутренней Flash |
В сборке AT25256B эти адреса отклоняются как недопустимые; сигнатуру GAS она не возвращает.
+3 -3
View File
@@ -138,8 +138,8 @@ GAS предоставляет доступ к тем же значениям `m
| `0x005D` | 93 | `0x505D` | 20573 | 5093 | 6093 | `sens_lo_edge[21]` | RW |
| `0x005E` | 94 | `0x505E` | 20574 | 5094 | 6094 | `sens_lo_edge[22]` | RW |
| `0x005F` | 95 | `0x505F` | 20575 | 5095 | 6095 | `sens_lo_edge[23]` | RW |
| `0x0060` | 96 | `0x5060` | 20576 | 5096 | 6096 | `Cancount[m_FAST]` — пауза быстрых посылок CAN | RW |
| `0x0061` | 97 | `0x5061` | 20577 | 5097 | 6097 | `Cancount[m_SLOW]` — пауза медленных посылок CAN | RW |
| `0x0060` | 96 | `0x5060` | 20576 | 5096 | 6096 | `Cancount[m_FAST]` — включение быстрого потока CAN: 0 выключен, иначе включён | RW |
| `0x0061` | 97 | `0x5061` | 20577 | 5097 | 6097 | `Cancount[m_SLOW]` — включение медленного потока CAN: 0 выключен, иначе включён | RW |
| `0x0062` | 98 | `0x5062` | 20578 | 5098 | 6098 | `Brightness` / `Bright[0]` — яркость | RW |
| `0x0063` | 99 | `0x5063` | 20579 | 5099 | 6099 | `Owncount` / `Bright[1]`; LOAD: исходное значение АЦП | RW |
| `0x0064` | 100 | `0x5064` | 20580 | 5100 | 6100 | `Bright[2]`; `Current_count`: исходное значение канала 0 | RW |
@@ -186,7 +186,7 @@ GAS предоставляет доступ к тем же значениям `m
| `0x5F04` | 24324 | `0x5000` | База технологических GAS-регистров |
| `0x5F05` | 24325 | `128` | Число технологических регистров |
| `0x5F06` | 24326 | `0x0007` | Возможности: бит 0 — RTU, бит 1 — служебный CAN, бит 2 — FC06 |
| `0x5F07` | 24327 | `1` | Аппаратный вариант EEPROM M95M02 |
| `0x5F07` | 24327 | `1` / `2` | Исполнение: `1` — загрузка из M95M02 в RAM, `2` — внутренняя Flash; физическая EEPROM указывается отдельно в manifest |
Все восемь слов — **RO**. В сборке AT25256B GAS-адреса, включая этот блок, отклоняются с кодом недопустимого адреса.
+15
View File
@@ -69,6 +69,21 @@ UMP_CAN_service(). Ожидания передачи в цикле нет. Пе
## Формат кадров
После переноса планировщика из ветки 166 штатная телеметрия MBOX0 передаёт
изменённые трёхсловные пакеты и повторяет каждый подтверждённый пакет через
1,3 с. Изменения обнаруживаются с шагом 20 мс; потоки обслуживаются по очереди,
поэтому включённая быстрая маска не блокирует медленную. R0–R3 пульта находятся
в быстрой маске. При свободной шине и своевременном ACK обновления R3 проходят
не реже одного раза за 20 мс, что проверяется хостовой моделью.
R96/R97 сохраняют прежние адреса, но теперь являются выключателями потоков:
ноль отключает соответствующий поток, любое ненулевое значение включает.
Значение больше не умножается на период 20 мс. Отдельный heartbeat с адресом
`0x0100` и счётчиком секунд передаётся с частотой 1 Гц независимо от этих настроек.
Кэш обновляется только после аппаратного ACK. При ошибке пакет остаётся
неподтверждённым; повтор разрешается после секундной паузы. Служебный канал
УМП/GAS продолжает использовать отдельный MBOX2.
CAN 2.0B, расширенный ID, 8 байт данных. Порядок соответствует существующей
прошивке: MDL (слова 1,2), затем MDH (заголовок, слово 0). Каждое слово
содержит сначала старший байт.
+263
View File
@@ -0,0 +1,263 @@
/* Отдельный образ исполнения из Flash F28335: код хранится в секторах H/G, загрузчик RS и Flash API исполняются из RAM; параметры остаются во внешней EEPROM. */
/*
// TI File $Revision: /main/10 $
// Checkin $Date: July 9, 2008 13:43:56 $
//###########################################################################
//
// FILE: F28335.cmd
//
// TITLE: Linker Command File For F28335 Device
//
//###########################################################################
// $TI Release: DSP2833x/DSP2823x Header Files V1.20 $
// $Release Date: August 1, 2008 $
//###########################################################################
*/
/* ======================================================
// 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>\DSP2833x_Headers\cmd
//
// For BIOS applications add: DSP2833x_Headers_BIOS.cmd
// For nonBIOS applications add: DSP2833x_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 DSP2833x_Headers_nonBIOS.cmd */
/* Uncomment this line to include file only for BIOS applications */
/* -l DSP2833x_Headers_BIOS.cmd */
/* 2) In your project add the path to <base>\DSP2833x_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 F28335
PAGE 0 will be used to organize program sections
PAGE 1 will be used to organize data sections
Notes:
Memory blocks on F28335 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.
L0/L1/L2 and L3 memory blocks are mirrored - that is
they can be accessed in high memory or low memory.
For simplicity only one instance is used in this
linker file.
Contiguous SARAM memory blocks can be combined
if required to create a larger memory block.
*/
/* Serial EEPROM is NOT CPU-addressable RAM/Flash. Do not add a fake MEMORY
* region at address zero: SPI boot copies its table into the RAM areas below.
* Export its physical layout as absolute byte/word symbols for .map/debugging.
* The final SCI8 image size (including boot headers) is checked after hex2000. */
/* Подключает "Source/Internal/Include/eeprom_config.h": выбор профиля физически установленной EEPROM при сборке. */
#include "Source/Internal/Include/eeprom_config.h"
/* Подключает "include/balsam167_eeprom_layout.h": настройки и интерфейс balsam167_eeprom_layout. */
#include "include/balsam167_eeprom_layout.h"
/* Записывает _EEPROM_TOTAL_BYTES значением BALSAM167_EEPROM_BYTES; данные _EEPROM_TOTAL_BYTES. */
_EEPROM_TOTAL_BYTES = BALSAM167_EEPROM_BYTES;
/* Записывает _EEPROM_FIRMWARE_LIMIT_BYTES значением BALSAM167_EEPROM_FIRMWARE_BYTES; данные _EEPROM_FIRMWARE_LIMIT_BYTES. */
_EEPROM_FIRMWARE_LIMIT_BYTES = BALSAM167_EEPROM_FIRMWARE_BYTES;
/* Записывает _EEPROM_PARAMS_START_WORD значением BALSAM167_EEPROM_PARAMS_START_WORD; EEPROM PARAMS START слово. */
_EEPROM_PARAMS_START_WORD = BALSAM167_EEPROM_PARAMS_START_WORD;
/* Записывает _EEPROM_PARAMS_START_BYTE значением BALSAM167_EEPROM_FIRMWARE_BYTES; данные _EEPROM_PARAMS_START_BYTE. */
_EEPROM_PARAMS_START_BYTE = BALSAM167_EEPROM_FIRMWARE_BYTES;
/* Записывает _EEPROM_PARAMS_END_BYTE значением BALSAM167_EEPROM_PARAMS_END_WORD * 2; данные _EEPROM_PARAMS_END_BYTE. */
_EEPROM_PARAMS_END_BYTE = BALSAM167_EEPROM_PARAMS_END_WORD * 2;
/* Начинает описание физических блоков памяти для линкера. */
MEMORY
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
PAGE 0: /* Program Memory */ /* Выбирает страницу адресного пространства линкера: 0 — программа, 1 — данные. */
/* Memory (RAM/FLASH/OTP) blocks can be moved to PAGE1 for data allocation */
ZONE0 : origin = 0x004000, length = 0x001000 /* XINTF zone 0 */ /* Записывает origin значением 0x004000, length = 0x001000; данные origin. */
RAML0 : origin = 0x008000, length = 0x004000 /* on-chip RAM block L0 */ /* Записывает origin значением 0x008000, length = 0x004000; данные origin. */
/* RAML1 : origin = 0x009000, length = 0x001000 /* on-chip RAM block L1 */
/* RAML2 : origin = 0x00A000, length = 0x001000 /* on-chip RAM block L2 */
// RAML3 : origin = 0x00B000, length = 0x001000 /* on-chip RAM block L3 */
RAML4 : origin = 0x00C000, length = 0x001000 /* on-chip RAM block L1 */ /* Записывает origin значением 0x00C000, length = 0x001000; данные origin. */
ZONE6 : origin = 0x0100000, length = 0x100000 /* XINTF zone 6 */ /* Записывает origin значением 0x0100000, length = 0x100000; данные origin. */
FLASHH : origin = 0x300000, length = 0x008000 /* on-chip FLASH */ /* Записывает origin значением 0x300000, length = 0x008000; данные origin. */
FLASHG : origin = 0x308000, length = 0x008000 /* on-chip FLASH */ /* Записывает origin значением 0x308000, length = 0x008000; данные origin. */
FLASHF : origin = 0x310000, length = 0x008000 /* on-chip FLASH */ /* Записывает origin значением 0x310000, length = 0x008000; данные origin. */
FLASHE : origin = 0x318000, length = 0x008000 /* on-chip FLASH */ /* Записывает origin значением 0x318000, length = 0x008000; данные origin. */
FLASHD : origin = 0x320000, length = 0x008000 /* on-chip FLASH */ /* Записывает origin значением 0x320000, length = 0x008000; данные origin. */
FLASHC : origin = 0x328000, length = 0x008000 /* on-chip FLASH */ /* Записывает origin значением 0x328000, length = 0x008000; данные origin. */
FLASHA : origin = 0x338000, length = 0x007F80 /* on-chip FLASH */ /* Записывает origin значением 0x338000, length = 0x007F80; данные origin. */
CSM_RSVD : origin = 0x33FF80, length = 0x000076 /* Part of FLASHA. Program with all 0x0000 when CSM is in use. */ /* Записывает origin значением 0x33FF80, length = 0x000076; данные origin. */
BEGIN : origin = 0x33FFF6, length = 0x000002 /* Part of FLASHA. Used for "boot to Flash" bootloader mode. */ /* Записывает origin значением 0x33FFF6, length = 0x000002; данные origin. */
CSM_PWL : origin = 0x33FFF8, length = 0x000008 /* Part of FLASHA. CSM password locations in FLASHA */ /* Записывает origin значением 0x33FFF8, length = 0x000008; данные origin. */
OTP : origin = 0x380400, length = 0x000400 /* on-chip OTP */ /* Записывает origin значением 0x380400, length = 0x000400; данные origin. */
ADC_CAL : origin = 0x380080, length = 0x000009 /* ADC_cal function in Reserved memory */ /* Записывает origin значением 0x380080, length = 0x000009; данные origin. */
IQTABLES : origin = 0x3FE000, length = 0x000b50 /* IQ Math Tables in Boot ROM */ /* Записывает origin значением 0x3FE000, length = 0x000b50; данные origin. */
IQTABLES2 : origin = 0x3FEB50, length = 0x00008c /* IQ Math Tables in Boot ROM */ /* Записывает origin значением 0x3FEB50, length = 0x00008c; данные origin. */
FPUTABLES : origin = 0x3FEBDC, length = 0x0006A0 /* FPU Tables in Boot ROM */ /* Записывает origin значением 0x3FEBDC, length = 0x0006A0; данные origin. */
ROM : origin = 0x3FF27C, length = 0x000D44 /* Boot ROM */ /* Записывает origin значением 0x3FF27C, length = 0x000D44; данные origin. */
RESET : origin = 0x3FFFC0, length = 0x000002 /* part of boot ROM */ /* Записывает origin значением 0x3FFFC0, length = 0x000002; данные origin. */
VECTORS : origin = 0x3FFFC2, length = 0x00003E /* part of boot ROM */ /* Записывает origin значением 0x3FFFC2, length = 0x00003E; данные origin. */
PAGE 1 : /* Data Memory */ /* Выбирает страницу адресного пространства линкера: 0 — программа, 1 — данные. */
/* 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 */ /* Записывает origin значением 0x000000, length = 0x000050; данные origin. */
RAMM0 : origin = 0x000050, length = 0x0003B0 /* on-chip RAM block M0 */ /* Записывает origin значением 0x000050, length = 0x0003B0; данные origin. */
RAMM1 : origin = 0x000400, length = 0x000400 /* on-chip RAM block M1 */ /* Записывает origin значением 0x000400, length = 0x000400; данные origin. */
RAML5 : origin = 0x00D000, length = 0x003000 /* on-chip RAM block L1 */ /* Записывает origin значением 0x00D000, length = 0x003000; данные origin. */
/* RAML6 : origin = 0x00E000, length = 0x001000 /* on-chip RAM block L1 */
/* RAML7 : origin = 0x00F000, length = 0x001000 /* on-chip RAM block L1 */
ZONE7A : origin = 0x0200000, length = 0x00FC00 /* XINTF zone 7 - program space */ /* Записывает origin значением 0x0200000, length = 0x00FC00; данные origin. */
ZONE7B : origin = 0x20FC00, length = 0x000400 /* XINTF zone 7 - data space */ /* Записывает origin значением 0x20FC00, length = 0x000400; данные origin. */
FLASHB : origin = 0x330000, length = 0x008000 /* on-chip FLASH */ /* Записывает origin значением 0x330000, length = 0x008000; данные origin. */
/* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */
}
/* 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: */
/* Keep initialization tables out of the code RAM block. */
/* Записывает PAGE значением 0; данные PAGE. */
.cinit : > FLASHG PAGE = 0 /* Таблица инициализации C хранится в секторе G и применяется ROM-моделью startup. */
/* Записывает PAGE значением 0; данные PAGE. */
.pinit : > FLASHG PAGE = 0 /* Конструкторы используют тот же непрерывный сектор данных, что и таблица C startup. */
/* Записывает PAGE значением 0; данные PAGE. */
.text : > FLASHH PAGE = 0 /* Основной код запускается непосредственно из сектора H, начиная с адреса слова 0x300000. */
/* Записывает PAGE значением 0; данные PAGE. */
codestart : > BEGIN PAGE = 0
flash_rs : /* Собирает весь автономный участок, который должен пережить стирание исполняемой Flash. */
{ /* Начинает перечень функций и таблиц загрузчика RS и официального TI API. */
*(flash_rs) /* Включает декодер, CRC, проверку образа, стирание, программирование и автономный ответ SCI. */
-lFlash28335_API_V210.lib(.text) /* Копирует машинный код штатного Flash API v2.10 F28335 во внутреннюю RAM. */
-lFlash28335_API_V210.lib(.econst) /* Сохраняет таблицы API в той же RAM, чтобы во время стирания не читать сектор F. */
} LOAD = FLASHF, RUN = RAML0, /* Начальная копия хранится в секторе F; рабочий диапазон RAML0–L3 имеет 0x4000 16-битных слов. */
LOAD_START(_FlashRsLoadStart), LOAD_END(_FlashRsLoadEnd), RUN_START(_FlashRsRunStart), PAGE = 0 /* Экспортирует адреса копирования в адресуемых словах C28x. */
/* Записывает LOAD значением RAML4,; данные LOAD. */
ramfuncs : LOAD = FLASHG, /* InitFlash хранится в G, но вызывается только после копирования в RAML4. */
/* Записывает RUN значением RAML4,; данные RUN. */
RUN = RAML4,
/* Вызывает LOAD_START с аргументами (_RamfuncsLoadStart), в текущей последовательности операций. */
LOAD_START(_RamfuncsLoadStart),
/* Вызывает LOAD_END с аргументами (_RamfuncsLoadEnd), в текущей последовательности операций. */
LOAD_END(_RamfuncsLoadEnd),
/* Вызывает RUN_START с аргументами (_RamfuncsRunStart), в текущей последовательности операций. */
RUN_START(_RamfuncsRunStart),
/* Записывает PAGE значением 0; данные PAGE. */
PAGE = 0
/* Записывает PAGE значением 0; данные PAGE. */
csmpasswds : > CSM_PWL PAGE = 0
/* Записывает PAGE значением 0; данные PAGE. */
csm_rsvd : > CSM_RSVD PAGE = 0
/* Allocate uninitalized data sections: */
/* Записывает PAGE значением 1; данные PAGE. */
.stack : > RAMM1 PAGE = 1
/* Записывает PAGE значением 1; данные PAGE. */
.ebss : > RAML5 PAGE = 1
/* Записывает PAGE значением 0; данные PAGE. */
.esysmem : > RAML0 PAGE = 0
/* Записывает PAGE значением 1; данные PAGE. */
.logg : > ZONE7A PAGE = 1
/* Initalized sections go in Flash */
/* For SDFlash to program these, they must be allocated to page 0 */
/* Записывает PAGE значением 0; данные PAGE. */
.econst : > FLASHG PAGE = 0 /* Неизменяемые таблицы, GAS и паспорт выпуска остаются частью исполняемого образа во Flash. */
/* Записывает PAGE значением 0; данные PAGE. */
.switch : > FLASHG PAGE = 0 /* Таблицы переходов хранятся вместе с прочими константами, не занимая рабочую RAM. */
/* Allocate IQ math areas: */
IQmath : > FLASHC PAGE = 0 /* Math Code */ /* Записывает PAGE значением 0; данные PAGE. */
/* Записывает PAGE значением 0, TYPE = NOLOAD; данные PAGE. */
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)
}
*/
/* Записывает PAGE значением 0, TYPE = NOLOAD; данные PAGE. */
FPUmathTables : > FPUTABLES, PAGE = 0, TYPE = NOLOAD
/* Allocate DMA-accessible RAM sections: * /
DMARAML4 : > RAML4, PAGE = 1
DMARAML5 : > RAML5, PAGE = 1
/* DMARAML6 : > RAML6, PAGE = 1
DMARAML7 : > RAML7, PAGE = 1
*/
/* Allocate 0x400 of XINTF Zone 7 to storing data */
/* Записывает PAGE значением 1; данные PAGE. */
ZONE7DATA : > ZONE7B, PAGE = 1
/* .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 */
/* Записывает PAGE значением 0, TYPE = DSECT; данные PAGE. */
.reset : > RESET, PAGE = 0, TYPE = DSECT
/* Записывает PAGE значением 0, TYPE = DSECT; данные PAGE. */
vectors : > VECTORS PAGE = 0, TYPE = DSECT
/* Allocate ADC_cal function (pre-programmed by factory into TI reserved memory) */
/* Записывает load значением ADC_CAL, PAGE = 0, TYPE = NOLOAD; данные load. */
.adc_cal : load = ADC_CAL, PAGE = 0, TYPE = NOLOAD
/* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */
}
/*
//===========================================================================
// End of file.
//===========================================================================
*/
+20
View File
@@ -65,6 +65,9 @@ static unsigned int load_state, load_attempts, load_inhibit;
static unsigned int load_count_dac, load_count_ramp, load_init_count;
/* Объявляет load_zero_sent — данные load_zero_sent; load_stop_asserted — признак установленного выхода STOP; load_initialized — данные load_initialized. */
static unsigned int load_zero_sent, load_stop_asserted, load_initialized;
#if defined(FLASH_BOOT) /* Дополнительный контроль записи Flash не увеличивает обычный EEPROM/RAM-образ. */
static unsigned int load_last_dac_code; /* Хранит именно последний переданный код SPI, отдельно от доступного для записи диагностического Modbus-регистра. */
#endif /* Приватное подтверждение передачи используется только автономным Flash-загрузчиком. */
/* Объявляет load_go — начальный ток за вычетом базовых 4 мА, ограниченный диапазоном 0..5; load_end — конечный ток за вычетом 4 мА, ограниченный сверху значением 16. */
static float load_go, load_end;
/* 16-битные поля общие с CAN ISR. Снимок и увеличение возраста выполняются
@@ -86,6 +89,9 @@ void Load_control_init(void)
load_count_dac = 0; load_count_ramp = 0; load_init_count = 1;
/* Записывает load_zero_sent значением 0; load_stop_asserted = 0; load_initialized = 0; данные load_zero_sent. */
load_zero_sent = 0; load_stop_asserted = 0; load_initialized = 0;
#if defined(FLASH_BOOT) /* Начальное значение не считается подтверждением безопасных 4 мА без load_zero_sent. */
load_last_dac_code = 0; /* Явно очищает COFF RAM; первое подтверждение будет получено только после штатного dSEND. */
#endif /* Завершает инициализацию дополнительного приватного контроля Flash. */
/* Записывает load_go значением 0; load_end = 0; начальный ток за вычетом базовых 4 мА, ограниченный диапазоном 0..5. */
load_go = 0; load_end = 0;
/* Записывает load_voltage значением 0; load_voltage_age = 0; load_voltage_seen = 0; последнее выбранное напряжение CAN, масштаб 1 В на слово. */
@@ -205,6 +211,17 @@ static void load_start(void)
/* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */
}
#if defined(FLASH_BOOT) /* Проверка доступна только отдельному варианту с внутренней Flash и официальным TI API. */
int Load_flash_safe(void) /* Вызывается с заблокированными IRQ; не меняет команды, GPIO или алгоритм штатной остановки. */
{ /* Фиксирует согласованное состояние автомата, потому что вызывающий FlashRs_Command не разрешает Timer1 между проверкой и erase. */
if (Desk != dsk_LOAD) return 1; /* У других плат нет автомата аналоговой нагрузки; их PWM дополнительно блокирует автономный загрузчик. */
return load_initialized && READY && period_dac && time_dac && load_state == LOAD_IDLE && /* Требует завершённую инициализацию и штатное состояние ожидания вместо RUN/RELOAD/FAULT. */
load_stop_asserted && load_zero_sent && !IMPowse && /* STOP уже удержан не менее 1000 мс, а безопасный ток действительно передан штатным SPI-путём. */
!((unsigned int)Commands & 0x03C0U) && !cInitDac && /* START, RESET, калибровка и переинициализация ЦАП не должны создавать отложенные переходы во время прошивки. */
load_last_dac_code == ((unsigned int)DAC_min() & 0x0FFFU); /* Подтверждает программную передачу текущего кода 4 мА; это не измерение физического тока на плате. */
} /* После успешной проверки IRQ остаются заблокированы до автономной записи, поэтому разрешение не устаревает. */
#endif /* Обычная RAM/EEPROM-сборка не включает дополнительный публичный обработчик Flash. */
/* Вычисляет такты ЦАП; для недопустимого сохранённого тайм-аута CAN возвращает 600 мс. */
void Setup_DAC_time(void)
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
@@ -317,6 +334,9 @@ static void anal_output(long vrot, long maxx, int calibration)
if(x) dSEND(out|0x0000|0x0000);
/* Выполняет альтернативу, если предыдущее условие if оказалось ложным. */
else dSEND(out|0x8000|0x0000);
#if defined(FLASH_BOOT) /* Снимок обновляется только после фактической отправки слова в SPI ЦАП. */
load_last_dac_code = (unsigned int)out; /* Приватное поле не может быть подменено обычной записью в технологический регистр 0x7B. */
#endif /* Завершает подтверждение последнего переданного уровня для проверки перед erase. */
/* Публикует фактически отправляемый 12-битный код ЦАП в технологическом регистре 0x7B для диагностики и регистрации. */
modbus[123] = out;
+3
View File
@@ -17,6 +17,9 @@ extern unsigned int period_dac, time_dac;
/* Call once before IRQ; complete technological CAN packets update voltage. */
/* Сбрасывает все переменные автомата нагрузки до разрешения прерываний; COFF не обнуляет всю .ebss. */
void Load_control_init(void);
#if defined(FLASH_BOOT) /* Только Flash-загрузчик использует проверку штатного безопасного состояния нагрузки. */
int Load_flash_safe(void); /* При запрещённых IRQ требует завершённый STOP, снятые команды и отправленный текущий код 4 мА; не выполняет аварийных записей GPIO. */
#endif /* RAM-вариант сохраняет прежний набор обработчиков и размер кода. */
/* Принимает напряжение выбранной нагрузки из достоверных слов CAN и сбрасывает возраст только нужного измерения. */
void Load_voltage_can(unsigned int header, unsigned int a,
/* Объявляет b — данные b. */
@@ -0,0 +1,123 @@
#ifndef CAN_TELEMETRY_IMPL_H /* Реализация включается только в ecan.c после аппаратных типов и переменных штатного передатчика. */
#define CAN_TELEMETRY_IMPL_H /* Не допускает повторного определения состояния и функций в одной единице трансляции. */
#define CAN_HEARTBEAT_ADDR 0x0100U /* Адрес heartbeat из реализации 166 находится вне технологической карты R0–R127 и служебной области логгера 0x1000. */
#define CAN_HEARTBEAT_TICKS ((Uint32)READY_FREQ) /* READY_FREQ задаёт частоту Timer1 в герцах; это ровно одна секунда его тактов. */
#define CAN_REFRESH_TICKS ((Uint32)READY_FREQ * 13UL / 10UL) /* Тринадцать десятых секунды дают период повторения данных 1,3 с независимо от числа циклов main. */
#define CAN_SCAN_TICKS ((Uint32)READY_FREQ * CANPOWSE / 1000UL) /* Существующие CANPOWSE=20 мс ограничивают задержку обнаружения обновлений, но не скорость выдачи уже готовой очереди. */
#define CAN_ACK_TICKS ((Uint32)READY_FREQ / 10UL) /* Программная страховка через 100 мс отменяет зависший TX, если аппаратный timeout IRQ не был обработан. */
static Uint32 can_last_hi[ANSWER_LEN], can_last_lo[ANSWER_LEN], can_last_at[ANSWER_LEN]; /* Последний подтверждённый пакет и время ACK хранятся отдельно для каждого начального адреса R0–R127. */
static unsigned int can_valid[8], can_cursor[2], can_stream; /* Восемь 16-битных масок покрывают 128 адресов; два курсора и очередность обеспечивают справедливость потоков. */
static Uint32 can_tx_at, can_failed_at, can_heartbeat_at, can_scan_at; /* Беззнаковые отметки Timer1 поддерживают вычитание через переполнение 32-битного счётчика. */
static unsigned int can_backoff, can_started, can_heartbeat_count, can_heartbeat_due, can_scan_due; /* Отделяет секундную паузу после ошибки, старт часов, heartbeat и незавершённое сканирование масок. */
static int can_pending_addr; /* Значение -1 обозначает heartbeat, который не должен индексировать кэш технологических регистров. */
static volatile unsigned int can_tx_failed; /* Timeout ISR отмечает ошибку текущего mailbox до обработки ACK в основном цикле. */
static Uint32 CAN_millis(void) /* Возвращает согласованный счётчик Timer1; при READY_FREQ=1000 один такт соответствует миллисекунде. */
{ /* Чтение 32 бит защищается отдельно на 16-битном C28x без ожидания аппаратуры. */
unsigned int saved = __disable_interrupts(); /* Сохраняет исходное состояние IRQ, включая вызовы из уже защищённой отправки. */
Uint32 now = CpuTimer1.InterruptCount; /* Счётчик обновляют оба существующих обработчика Timer1 для пульта и измерительной платы. */
__restore_interrupts(saved); /* Немедленно возвращает прежнюю маску прерываний. */
return now; /* Наружу передаётся один целостный снимок времени. */
} /* Длительные расчёты масок выполняются с разрешёнными прерываниями. */
static void CAN_telemetry_init(void) /* Сбрасывает программное состояние при каждом InitCan, в том числе после запуска с грязной RAM. */
{ /* Крупные массивы не очищаются, потому что их элементы недоступны без valid-бита. */
unsigned int i; /* Перебирает восемь масок достоверности кэша. */
for (i = 0U; i < 8U; ++i) can_valid[i] = 0U; /* Первый пакет каждого разрешённого адреса обязательно передаётся после запуска. */
can_cursor[0] = can_cursor[1] = can_stream = 0U; /* Обе маски начинают обход с R0; далее потоки чередуются после реально поставленного кадра. */
can_backoff = can_started = can_heartbeat_count = can_heartbeat_due = 0U; /* Новые часы и секундный heartbeat начинают отсчёт при первом обслуживании main. */
can_scan_due = 1U; /* Первый снимок данных выдаётся сразу, без ожидания периода 1,3 с. */
can_pending_addr = -1; can_tx_failed = 0U; wait = 0; /* Исключает подтверждение кадра и ошибку из предыдущего запуска программы. */
} /* Сброс не меняет аппаратные CAN ID, маски регистров и настройки EEPROM. */
static int CAN_tx_ready(Uint32 now) /* Обслуживает завершение штатного mailbox 0; вызывающий код уже запретил IRQ на короткое время. */
{ /* Возвращает готовность без ожидания ACK, CANAA или освобождения шины в цикле. */
if ((ECanaRegs.CANTRS.all | ECanaRegs.CANTRR.all) & 1UL) { /* Бит 0 относится только к штатному TX; служебный mailbox 2 не перезаписывается. */
if (wait && !can_tx_failed && (ECanaRegs.CANTRS.all & 1UL) && now - can_tx_at >= CAN_ACK_TICKS) { /* Программный timeout страхует потерянное аппаратное прерывание спустя 100 мс. */
can_tx_failed = 1U; ++CanTimeOutErrorTR; /* Отмечает ошибку ровно один раз, пока этот запрос ещё ожидает завершения отмены. */
EALLOW; ECanaRegs.CANTOC.all &= ~1UL; EDIS; /* Снимает источник аппаратного timeout только для mailbox 0, сохраняя остальные биты контроля. */
ECanaRegs.CANTRR.all = 1UL; /* Запрашивает аппаратную отмену без блокирующего ожидания CANAA. */
} /* Данные занятого mailbox остаются неизменными до аппаратного освобождения. */
return 0; /* Текущий адрес и признак готовности сканирования сохраняются для повторного обслуживания. */
} /* Только свободный mailbox позволяет обработать завершение предыдущей передачи. */
if (wait) { /* ACK или ошибка относятся ровно к одному ранее запущенному кадру. */
if ((ECanaRegs.CANTA.all & 1UL) && !can_tx_failed) { /* Только успешный ACK позволяет подавлять повтор неизменившихся данных. */
if (can_pending_addr >= 0) { /* Heartbeat не занимает ограниченный технологический кэш. */
can_last_hi[can_pending_addr] = ECanaMboxes.MBOX0.MDH.all; /* Запоминает действительно доставленные адрес и первое слово из неизменявшегося mailbox. */
can_last_lo[can_pending_addr] = ECanaMboxes.MBOX0.MDL.all; /* Сохраняет второе и третье слова того же подтверждённого кадра. */
can_last_at[can_pending_addr] = now; /* Отсчитывает повтор через 1,3 с от последнего подтверждения, а не от неудачной попытки. */
can_valid[can_pending_addr >> 4] |= 1U << (can_pending_addr & 15); /* Устанавливает один бит достоверности начального адреса пакета. */
} /* Перекрывающиеся пакеты разных масок имеют независимые начальные адреса. */
} else { /* Отмена, ошибка или свободный TX без CANAA не подтверждают доставку текущих данных. */
can_failed_at = now; can_backoff = 1U; /* Ограничивает новые попытки одной секундой при отсутствии получателя и CAN ACK. */
} /* Старый подтверждённый кэш сохраняется, поэтому неудачная передача не теряет обновление. */
wait = 0; can_tx_failed = 0U; can_pending_addr = -1; /* Завершение обрабатывается один раз даже без отдельного CANAA после отмены. */
} /* Проверяет паузу после ошибки до подготовки следующего кадра. */
if (can_backoff && now - can_failed_at < CAN_HEARTBEAT_TICKS) return 0; /* Во время секундной паузы данные и heartbeat остаются ожидающими. */
can_backoff = 0U; /* Снятие паузы не требует изменения таймеров технологических данных. */
return 1; /* Mailbox свободен и может принять один новый актуальный кадр. */
} /* Чужие mailbox, аппаратный CAN ID и получаемые команды не меняются. */
int CAN_send(int Port, int data[], int Addr) /* Возвращает 0 при занятости, 1 после запуска TX и 2 при неизменном пакете до срока повторения. */
{ /* Снимок данных и запуск mailbox защищаются одним коротким запретом IRQ. */
Uint32 hiword, loword, now; /* Ровно два 32-битных поля соответствуют восьми октетам существующего формата BALZAM. */
unsigned int saved = __disable_interrupts(); /* Исключает разрыв трёхсловного снимка и гонку запуска с timeout ISR. */
(void)Port; now = CAN_millis(); /* Как прежний передатчик, использует eCAN-A; время берётся из общего Timer1. */
if (!CAN_tx_ready(now)) { __restore_interrupts(saved); return 0; } /* При занятости ничего не перезаписывает и не подтверждает данные программно. */
if (Addr == CAN_HEARTBEAT_ADDR) { /* Отдельный пакет состояния узла не обращается к data=NULL или к R256. */
hiword = 0xE0000000UL | ((Uint32)CAN_HEARTBEAT_ADDR << 16) | can_heartbeat_count; /* Сохраняет маркер BALZAM и передаёт циклический 16-битный счётчик секунд. */
loword = ((Uint32)(unsigned int)Mode << 16) | (unsigned int)Desk; /* Два следующих слова сообщают аппаратный Mode и тип платы Desk. */
can_pending_addr = -1; /* Успешный heartbeat не подтверждает ни один технологический пакет. */
} else { /* Первый снимок, изменение любого слова либо истечение периода вызывают одинаковую отправку данных. */
if (Addr < 0 || Addr >= ANSWER_LEN) { __restore_interrupts(saved); return 2; } /* Не допускает чтения за картой R0–R127 и позволяет планировщику пропустить неверный адрес. */
hiword = 0xE0000000UL | ((Uint32)Addr << 16) | ((Uint32)data[Addr] & 0xFFFFUL); /* Первый регистр занимает младшие шестнадцать бит старшей половины CAN-данных. */
loword = Addr + 1 < ANSWER_LEN ? ((Uint32)data[Addr + 1] & 0xFFFFUL) << 16 : 0UL; /* Отсутствующее второе слово у конца карты заменяется нулём. */
if (Addr + 2 < ANSWER_LEN) loword |= (Uint32)data[Addr + 2] & 0xFFFFUL; /* Третье слово дополняется только в пределах реального массива регистров. */
if ((can_valid[Addr >> 4] & (1U << (Addr & 15))) && can_last_hi[Addr] == hiword && can_last_lo[Addr] == loword && now - can_last_at[Addr] < CAN_REFRESH_TICKS) { /* Совпадение всех слов подавляет передачу только до индивидуального срока 1,3 с. */
__restore_interrupts(saved); return 2; /* Не сдвигает время подтверждения: неизменные данные всё равно повторятся по таймеру. */
} /* Изменение данных не ждёт окончания периода повторения. */
can_pending_addr = Addr; /* Подготовленное состояние попадёт в кэш только после успешного аппаратного ACK. */
} /* Для heartbeat и данных используется один и тот же безопасный жизненный цикл TX. */
ECanaRegs.CANTA.all = 1UL; ECanaRegs.CANAA.all = 1UL; /* Очищает старые ACK и отмену mailbox 0 записью единиц в соответствующие биты eCAN. */
ECanaMboxes.MBOX0.MDH.all = hiword; ECanaMboxes.MBOX0.MDL.all = loword; /* Записывает замороженный трёхсловный снимок без последующей подмены во время ожидания ACK. */
EALLOW; ECanaRegs.CANTOC.all &= ~1UL; ECanaRegs.CANTSC = 0UL; ECanaRegs.CANTOC.all |= 1UL; EDIS; /* Перезапускает аппаратный timeout только для нового штатного запроса TX. */
can_tx_at = now; can_tx_failed = 0U; wait = 1; /* Сохраняет момент запуска и принадлежность будущего ACK до разрешения прерываний. */
ECanaRegs.CANTRS.all = 1UL; /* Однократно запускает аппаратную передачу в штатном mailbox 0. */
led1_toggle(); __restore_interrupts(saved); /* Сохраняет индикацию передачи и возвращает исходную маску IRQ без ожидания шины. */
return 1; /* Подтверждает только постановку кадра, оставляя окончательную доставку аппаратному ACK. */
} /* Формат CAN ID, порядок слов и служебный mailbox логгера остаются прежними. */
void CAN_heartbeat_service(void) /* Обслуживает независимый секундный heartbeat и завершение ранее запущенного TX. */
{ /* Вызывается из main и не зависит от Cancount, масок данных или признака CanGO. */
Uint32 now = CAN_millis(), elapsed, seconds; /* Время Timer1 учитывает задержки main без накопления дрейфа секундной сетки. */
unsigned int saved = __disable_interrupts(); /* Короткая обработка ACK синхронизируется с timeout ISR. */
CAN_tx_ready(now); __restore_interrupts(saved); /* Сохраняет успешное завершение данных даже тогда, когда новых пакетов пока нет. */
if (!can_started) { can_started = 1U; can_heartbeat_at = can_scan_at = now; } /* Первый heartbeat появляется спустя полную секунду от начала обслуживания текущего запуска. */
elapsed = now - can_heartbeat_at; /* Беззнаковое вычитание корректно при переполнении 32-битного счётчика. */
if (elapsed >= CAN_HEARTBEAT_TICKS) { /* Формирует новую отметку на каждой секундной границе Timer1. */
seconds = elapsed / CAN_HEARTBEAT_TICKS; can_heartbeat_at += seconds * CAN_HEARTBEAT_TICKS; /* После паузы main оставляет последнее значение без очереди устаревших кадров. */
can_heartbeat_count = (unsigned int)((can_heartbeat_count + seconds) & 0xFFFFUL); can_heartbeat_due = 1U; /* Счётчик heartbeat циклически проходит диапазон 0..65535 секунд. */
} /* Занятый передатчик не теряет ожидающий heartbeat. */
if (can_heartbeat_due && CAN_send(0, 0, CAN_HEARTBEAT_ADDR) == 1) can_heartbeat_due = 0U; /* Приоритетно ставит один актуальный heartbeat после освобождения mailbox. */
} /* Следующий повтор heartbeat планируется секундной сеткой, а не длительностью обхода масок. */
void CAN_telemetry_service(void) /* Передаёт изменившиеся данные и повторяет каждый подтверждённый пакет через 1,3 с. */
{ /* Фоновое обслуживание не ждёт CAN ACK и выдаёт готовую очередь по мере освобождения mailbox. */
Uint32 now; unsigned int attempt, stream, pass, address, cursor; int result; /* Локальные счётчики ограничивают обход двумя потоками и конечной картой из 128 регистров. */
CAN_heartbeat_service(); now = CAN_millis(); /* Секундный heartbeat получает приоритет перед обычными данными независимо от настройки потоков. */
if (now - can_scan_at >= CAN_SCAN_TICKS) { can_scan_at += (now - can_scan_at) / CAN_SCAN_TICKS * CAN_SCAN_TICKS; can_scan_due = 1U; } /* Сохраняет сетку обнаружения изменений с интервалом 20 мс без накопления дрейфа после задержек main. */
if (!can_scan_due) return; /* При неизменных данных main не повторяет полный обход масок на каждой итерации. */
for (attempt = 0U; attempt < 2U; ++attempt) { /* После успешной постановки чередует потоки, исключая голодание медленной маски. */
stream = (can_stream + attempt) & 1U; cursor = can_cursor[stream]; /* Начинает следующий поиск после последнего принятого кадра данного потока. */
if (!Cancount[stream]) continue; /* Ноль сохраняет штатное отключение соответствующего потока; ненулевой период заменяется общим повтором 1,3 с. */
for (pass = 0U; pass < 2U; ++pass) { /* Два конечных прохода реализуют циклический обход без зависания на пустой маске. */
for (address = 0U; address < ANSWER_LEN;) { /* Формирует те же трёхсловные группы от первого разрешённого адреса маски. */
if (!(Maska[stream][address >> 4] & (1U << (address & 15)))) { ++address; continue; } /* Пропускает запрещённые регистры и гарантированно продвигает поиск. */
if ((pass == 0U && address >= cursor) || (pass == 1U && address < cursor)) { /* Сначала рассматривает хвост карты после курсора, затем оставшееся начало. */
result = CAN_send(0, modbus, address); /* Сравнивает полный снимок с подтверждённым кэшем и индивидуальным временем повтора. */
if (result != 2) { /* Только неизменный пакет до срока допускает переход к следующему кандидату в этом же вызове. */
if (result == 1) { can_cursor[stream] = address + 3U < ANSWER_LEN ? address + 3U : 0U; can_stream = stream ^ 1U; } /* Продвигает курсор лишь после постановки кадра и отдаёт следующий свободный слот другому потоку. */
return; /* При занятости сохраняет адрес; при постановке продолжит готовую очередь следующей итерацией main после ACK. */
} /* Пропущенные неизменные пакеты не занимают физическую шину. */
} /* Завершает проверку одного допустимого начального адреса. */
address += 3U; /* Как прежний протокол, один кадр покрывает три последовательных регистра независимо от разреженности маски. */
} /* Максимальный адрес ограничен размером реальной технологической карты. */
} /* После обхода обеих частей все пакеты потока рассмотрены ровно один раз. */
} /* Ни один разрешённый пакет не требует немедленной передачи. */
can_scan_due = 0U; /* Следующее плановое сканирование произойдёт через 20 мс; выдача готовой очереди этим ожиданием не задерживается. */
} /* Период 1,3 с относится к каждому пакету, а не к одному кадру всей маски за один период. */
#endif /* Закрывает единственную реализацию фоновой телеметрии текущей прошивки 166. */
+6 -2
View File
@@ -1,8 +1,12 @@
/* Штатный eCAN и отдельный служебный обмен УМП/GAS. Русские пояснения сохранены в UTF-8. */
/* Настраивает eCAN, скорость, почтовые ящики и маршрутизацию прерываний для выбранного режима платы. */
void InitCan(int Port, int DevNum);
/* Упаковывает адрес и технологические слова в штатный CAN-кадр и запускает передачу mailbox. */
void CAN_send(int Port, int data[], int Addr);
/* Возвращает 0 при занятости mailbox, 1 при запуске кадра, 2 для неизменного пакета до индивидуального повтора 1,3 с. */
int CAN_send(int Port, int data[], int Addr);
/* Поддерживает секундный heartbeat независимо от масок и подтверждает доставку предыдущего технологического кадра. */
void CAN_heartbeat_service(void);
/* Передаёт изменившиеся данные и повторяет подтверждённые пакеты через 1,3 с, не ожидая CAN ACK в главном цикле. */
void CAN_telemetry_service(void);
/* Объявляет CAN_input_data — CAN вход данные. Память определена в другом модуле. */
extern int CAN_input_data[];
+2 -2
View File
@@ -12,8 +12,8 @@
*/
/* Начинает условие: EEPROM_M95M02 ещё не определён; защищает повторное включение или задаёт значение сборки. */
#ifndef EEPROM_M95M02
/* Задаёт EEPROM_M95M02 = 0; выбор аппаратной EEPROM при сборке: 0 — AT25256B 32 КиБ, 1 — M95M02 256 КиБ и GAS. */
#define EEPROM_M95M02 0
/* Основная плата использует M95M02-DR ёмкостью 256 КиБ с областью GAS; старую AT25256B выбирают явно define=0 при отдельной сборке. */
#define EEPROM_M95M02 1
/* Закрывает условный блок препроцессора; дальнейшие строки относятся к внешнему уровню. */
#endif
/* Условие компиляции EEPROM_M95M02 != 0 и EEPROM_M95M02 != 1: выбранный блок включается в текущий вариант прошивки. */
+5
View File
@@ -0,0 +1,5 @@
#ifndef BALSAM_FLASH_RS_H /* Исключает повторное объявление интерфейса обновления внутренней Flash. */
#define BALSAM_FLASH_RS_H /* Помечает уже подключённый интерфейс RS-загрузчика. */
#include "RS485.h" /* Передаёт выбранный SCI-порт и сведения о проверенном блоке из действующего загрузчика. */
void FlashRs_Command(RS_DATA *rs); /* Выполняет пробу возможностей или запись Flash; после стирания никогда не возвращается в прежний код. */
#endif /* Завершает защиту заголовка обновления через RS. */
+1 -1
View File
@@ -13,7 +13,7 @@
* Дополнительные области GAS присутствуют только при установленной M95M02.
*/
/* Условие компиляции EEPROM_M95M02: выбранный блок включается в текущий вариант прошивки. */
#if EEPROM_M95M02
#if EEPROM_M95M02 || defined(FLASH_BOOT) /* Flash-вариант тоже публикует GAS; физический профиль EEPROM по-прежнему выбирается независимо. */
/* Задаёт GAS167_REG_BASE = 0x5000U; начало псевдонимов 128 технологических регистров GAS, адрес 0x5000. */
#define GAS167_REG_BASE 0x5000U
/* Задаёт GAS167_REG_COUNT = 128U; число технологических слов GAS; скрытое слово modbus[128] в диапазон не входит. */
+2 -2
View File
@@ -181,8 +181,8 @@
/* Сохраняет прежний заводской предел пяти пусков для сброса настроек и замены нуля из старой EEPROM. */
#define LOAD_DEFAULT_MAX_ATTEMPTS 5U
/* Задаёт Cancount = (modbus+0x60); данные Cancount. */
#define Cancount (modbus+0x60) // пауза между I посылками CAN
/* Регистры R96/R97 включают быстрый и медленный потоки CAN: 0 отключает поток, любое ненулевое значение разрешает изменения и индивидуальный повтор каждого пакета через 1,3 с. */
#define Cancount (modbus+0x60) /* Сохраняет адреса настроек EEPROM и Modbus; интервалы задаёт аппаратное время в can_telemetry_impl.h. */
/* Задаёт Bright = (modbus+0x62); данные Bright. */
#define Bright (modbus+0x62) // аркость сигнальных лампочек
/* Задаёт Brightness = modbus[0x62]; данные Brightness. */
+17
View File
@@ -11,6 +11,9 @@
#include "spise2p.h"
/* Подключает "log_to_mem.h": двухбанковый регистратор событий нагрузки во внешней RAM. */
#include "log_to_mem.h"
#if defined(FLASH_BOOT) /* Только отдельный Flash-образ содержит официальный алгоритм программирования, выполняемый из RAM. */
#include "flash_rs.h" /* Передаёт выбранный SCI и сведения о CRC-подтверждённом LOAD в автономный Flash-загрузчик. */
#endif /* RAM/EEPROM-вариант сохраняет прежний объём без подключения TI Flash API. */
//#include "flash_tools.h"
//#include "spartan_tools.h"
//#include "big_dsp_module.h"
@@ -524,6 +527,20 @@ void xflash(RS_DATA *rs_arr)
void tflash(RS_DATA *rs_arr)
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
#if defined(FLASH_BOOT) /* Разрешает внутреннюю Flash только в образе с отдельными секциями LOAD/RUN и TI API. */
FlashRs_Command(rs_arr); /* Нулевой запрос проверяет поддержку; ненулевой программирует только полностью проверенную таблицу Flash SCI8. */
#else /* Обычная прошивка явно сообщает отсутствие Flash-загрузчика вместо молчаливого игнорирования команды 55. */
unsigned int crc; /* Контрольная сумма защищает адрес, CMD_TFLASH и двухоктетный статус отказа. */
rs_arr->buffer[0] = rs_arr->addr_recive; /* Ответ относится к фактическому адресу запроса, включая служебный адрес платы. */
rs_arr->buffer[1] = CMD_TFLASH; /* Сохраняет номер 55 существующего BIOS-протокола и таблицы длин. */
rs_arr->buffer[2] = 1U; /* Младшая октета статуса 0x1001 означает отсутствие Flash API в RAM/EEPROM-сборке. */
rs_arr->buffer[3] = 0x10U; /* Старшая октета отличает отказ загрузчика от штатных кодов официального TI API. */
crc = get_crc_16(0xFFFFU, rs_arr->buffer, 4); /* Использует Modbus CRC16 с начальным 0xFFFF для первых четырёх октет ответа. */
rs_arr->buffer[4] = LOBYTE(crc); /* CRC передаётся младшей октетой вперёд, как в остальных BIOS-командах. */
rs_arr->buffer[5] = HIBYTE(crc); /* Завершает защищённую часть кадра старшей октетой CRC. */
rs_arr->buffer[6] = rs_arr->buffer[7] = 0; /* Сохраняет два исторических завершающих нуля короткого BIOS-ответа. */
RS_Send(rs_arr, rs_arr->buffer, 8); /* Передаёт явный отказ без обращения к EEPROM, RAM-буферу загрузки или внутренней Flash. */
#endif /* Завершает выбор обработчика по типу исполняемого образа. */
// volatile unsigned long Address1,Address2;
// volatile unsigned long Length, LengthW;
/*
+7 -73
View File
@@ -75,6 +75,8 @@ static unsigned int ump_can_waiting;
/* Объявляет ump_can_sent_at — время последней передачи фрагмента ответа CAN. */
static unsigned long ump_can_sent_at;
/* Подключает планировщик после объявления wait: оба CAN-канала сохраняют независимое состояние и тайм-ауты. */
#include "can_telemetry_impl.h"
/* Сбрасывает служебный обмен до включения CAN: загрузка COFF не гарантирует обнуление неинициализированной RAM. */
static void ump_can_init(void)
/* Начинает сброс состояния; InitCan вызывается при запрещённых глобальных прерываниях. */
@@ -118,6 +120,7 @@ void InitCan(int Port, int DevNum)
long id = 0x80BA0000;
/* Обнуляет состояние служебного обмена до настройки mailbox и разрешения прерываний CAN. */
CAN_telemetry_init(); /* Сбрасывает ACK-кэш, курсоры, heartbeat и wait до разрешения прерываний, включая перезапуск с остатками RAM. */
ump_can_init();
/* Изменяет счётчик в выражении DevNum--; на один для следующего шага. */
@@ -295,77 +298,7 @@ void InitCan(int Port, int DevNum)
/* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */
}
/* Упаковывает адрес и технологические слова в штатный CAN-кадр и запускает передачу mailbox. */
void CAN_send(int Port, int data[], int Addr)
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
/* Объявляет hiword — данные hiword; loword — данные loword. */
unsigned long hiword,loword;
/* Продолжает список аргументов, полей или выражение volatile struct ECAN_REGS * ECanRegs;; порядок элементов сохраняется. */
volatile struct ECAN_REGS * ECanRegs;
/* Продолжает список аргументов, полей или выражение volatile struct ECAN_MBOXES * ECanMboxes;; порядок элементов сохраняется. */
volatile struct ECAN_MBOXES * ECanMboxes;
/* Записывает ECanRegs значением &ECanaRegs; E CAN Regs. */
ECanRegs = &ECanaRegs;
/* Записывает ECanMboxes значением &ECanaMboxes; E CAN Mboxes. */
ECanMboxes = &ECanaMboxes;
/* Не меняет данные mailbox 0, пока передача либо её аппаратная отмена ещё выполняется; бит 0 соответствует штатному TX. */
if ((ECanRegs->CANTRS.all | ECanRegs->CANTRR.all) & 1UL)
return; /* Сохраняет текущий кадр до завершения CANTRS/CANTRR, не ожидая аппаратного ответа в главном цикле. */
/* Проверяет if(wait); последующий оператор выполняется только при истинном условии. */
if(wait)
/* Проверяет if(!(ECanRegs->CANTA.all & 1)); последующий оператор выполняется только при истинном условии. */
if(!(ECanRegs->CANTA.all & 1))
/* Проверяет if(!(ECanRegs->CANAA.all & 1)); последующий оператор выполняется только при истинном условии. */
if(!(ECanRegs->CANAA.all & 1))
/* Завершает CAN_send без возвращаемого значения. */
return;
/* Записывает ECanRegs->CANTA.all значением 1; представление регистра целым словом без разбиения на битовые поля. */
ECanRegs->CANTA.all = 1;
/* Записывает ECanRegs->CANAA.all значением 1; представление регистра целым словом без разбиения на битовые поля. */
ECanRegs->CANAA.all = 1;
/* Записывает hiword значением ((((Uint32) Addr ) & 0xffff)<<16)| 0xE0000000 |; данные hiword. */
hiword= ((((Uint32) Addr ) & 0xffff)<<16)| 0xE0000000 |
/* Продолжает список аргументов, полей или выражение ((((Uint32)data[Addr ]) & 0xffff) );; порядок элементов сохраняется. */
((((Uint32)data[Addr ]) & 0xffff) );
/* Записывает loword значением ((((Uint32)data[Addr+1]) & 0xffff)<<16)|; данные loword. */
loword= ((((Uint32)data[Addr+1]) & 0xffff)<<16)|
/* Продолжает список аргументов, полей или выражение ((((Uint32)data[Addr+2]) & 0xffff) );; порядок элементов сохраняется. */
((((Uint32)data[Addr+2]) & 0xffff) );
/* Записывает ECanMboxes->MBOX0.MDH.all значением hiword; представление регистра целым словом без разбиения на битовые поля. */
ECanMboxes->MBOX0.MDH.all = hiword;
/* Записывает ECanMboxes->MBOX0.MDL.all значением loword; представление регистра целым словом без разбиения на битовые поля. */
ECanMboxes->MBOX0.MDL.all = loword;
/* Разрешение записи в защищённые регистры периферии. */
EALLOW;
/* Отключает старый контроль mailbox 0 перед перезапуском счётчика; остальные mailbox используют собственный контроль. */
ECanRegs->CANTOC.all = 0;
/* Записывает ECanRegs->CANTSC значением 0; E CAN Regs CANTSC. */
ECanRegs->CANTSC = 0; // clear time-out counter
/* Заново разрешает тайм-аут штатного TX с порогом MOTO0, установленным в InitCan, только для следующего кадра. */
ECanRegs->CANTOC.all = 1UL;
/* Закрытие доступа к защищённым регистрам периферии. */
EDIS;
/* Записывает ECanRegs->CANTRS.all значением 1; представление регистра целым словом без разбиения на битовые поля. */
ECanRegs->CANTRS.all = 1; // запустить передачу
/* Записывает wait значением 1; данные wait. */
wait=1;
/* Вызывает led1_toggle с аргументами () в текущей последовательности операций. */
led1_toggle();
/* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */
}
/* Начинает функцию Handlai; назначение модуля — штатный eCAN и отдельный служебный обмен УМП/GAS. */
/* Разделяет служебные запросы УМП и технологические команды CAN, сохраняя защиту нагрузки и журналирование версии 167. */
void Handlai(volatile struct ECAN_MBOXES * ECanMboxes)
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
@@ -497,10 +430,11 @@ interrupt void CANa_reset_err(void)
if (ECanaRegs.CANTOS.all & 1UL)
{ /* Обрабатывает только штатный передатчик; служебный mailbox 2 контролируется отдельно в UMP_CAN_service. */
EALLOW; /* Разрешает изменение управляющего регистра eCAN перед снятием источника прерывания. */
ECanaRegs.CANTOC.all = 0; /* Сброс TOC снимает TOS и MTOF согласно SPRUI07, 13.8.18.3; одной отмены TX недостаточно. */
ECanaRegs.CANTOC.all &= ~1UL; /* Снимает источник timeout только MBOX0 по SPRUI07 13.8.18.3; остальные каналы сохраняют свой контроль. */
EDIS; /* Восстанавливает защиту регистров до выхода из ветви обработки тайм-аута. */
if (ECanaRegs.CANTRS.all & 1UL) /* Передача могла завершиться между чтением TOS и снятием контроля. */
ECanaRegs.CANTRR.all = 1UL; /* Запрашивает отмену только ещё занятого mailbox 0, не задерживая прерывание ожиданием CANAA. */
can_tx_failed = 1U; /* Не позволяет отложенному ACK после тайм-аута подтвердить недоставленный снимок и подавить повтор данных. */
CanTimeOutErrorTR++; /* Считает обнаруженный тайм-аут один раз, исключая фантомные повторные входы PIE. */
} /* Следующая штатная передача сама заново включит TOC после сброса временного счётчика. */
@@ -575,7 +509,7 @@ void UMP_CAN_service(void)
/* Записывает ump_can_length значением Load_protection_prepare(tag & 0xFF, address, value, ump_can_reply, &error); ump CAN длина. */
ump_can_length = Load_protection_prepare(tag & 0xFF, address, value, ump_can_reply, &error);
/* Условие компиляции EEPROM_M95M02: выбранный блок включается в текущий вариант прошивки. */
#if EEPROM_M95M02
#if EEPROM_M95M02 || defined(FLASH_BOOT) /* Отдельный Flash-вариант использует тот же GAS provider и согласованный снимок служебного CAN. */
/* Проверяет else if (gas167_contains(address)); последующий оператор выполняется только при истинном условии. */
else if (gas167_contains(address))
/* Записывает ump_can_length значением gas167_prepare(tag & 0xFF, address, value, 1, ump_can_reply, &error); ump CAN длина. */
+12
View File
@@ -0,0 +1,12 @@
.ref _c_int00 ; Стандартный COFF startup применяет .cinit и вызывает main после отключения раннего watchdog.
.sect "codestart" ; Boot ROM передаёт управление двухсловному переходу по аппаратному адресу слова 0x33FFF6.
LB flash_boot_start ; Переходит к короткой подготовке C28x во Flash, сохраняя точный размер аппаратного входа два слова.
.text ; Последовательность взята из штатного TI DSP2833x_CodeStartBranch.asm и размещается в секторе основного кода.
flash_boot_start: ; Отключает watchdog до возможной длительной обработки таблиц инициализации C, а не только после входа в main.
SETC OBJMODE ; Выбирает C28x object mode, требуемый стандартным COFF startup процессора F28335.
EALLOW ; Разрешает запись защищённого регистра WDCR перед запуском C runtime.
MOVZ DP, #7029h>>6 ; Адрес слова WDCR=0x7029 из карты F28335; DP выбирает соответствующую страницу по 64 слова.
MOV @7029h, #0068h ; Значение 0x0068 содержит контрольные биты WDCHK=101 и WDDIS=1, как официальный TI startup.
EDIS ; Возвращает защиту системных регистров перед переходом в библиотечную инициализацию.
LB _c_int00 ; Применяет штатную ROM-модель инициализации и затем запускает текущий main 167.
.end ; Завершает модуль, не добавляя произвольных данных рядом с CSM-паролями сектора A.
+179
View File
@@ -0,0 +1,179 @@
#if defined(FLASH_BOOT) /* Подключает Flash API только к образу, исполняемому из внутренней Flash. */
#include "DSP2833x_Device.h" /* Предоставляет регистры SCI, watchdog, CSM и действующую частоту SYSCLKOUT в герцах. */
#include "flash_rs.h" /* Связывает алгоритм с существующим загрузчиком RAM и выбранным RS-портом. */
#include "bios_dsp.h" /* Использует неизменный номер CMD_TFLASH действующего BIOS-протокола. */
#include "DAC.h" /* Проверяет завершённую штатную остановку нагрузки до отключения Timer1 на время стирания. */
#include "Flash2833x_API_Library.h" /* Вызывает официальный алгоритм TI v2.10 для стирания, записи и проверки F28335. */
#include <string.h> /* Переносит код и константы Flash API во внутреннюю RAM до изменения Flash. */
extern Uint16 FlashRsLoadStart, FlashRsLoadEnd, FlashRsRunStart; /* Границы отдельной секции задаёт F28335_flash.cmd в адресуемых 16-битных словах. */
static Uint16 program_buffer[128]; /* Буфер на 128 слов хранится во внутренней RAM; он не зависит от стираемых секторов или порядка байтов RS. */
static FLASH_ST api_status; /* Внутренняя RAM сохраняет адрес и данные первой ошибки официального Flash API. */
static unsigned int flash_load_guard(unsigned long length) /* Проверяет разрешение записи до CRC/erase, сохраняя безопасную пробу поддержки с нулевой длиной. */
{ /* Делегирует состояние нагрузки её единственному владельцу, не дублируя управление GPIO внутри загрузчика. */
return length && !Load_flash_safe() ? 0x1005U : 0U; /* Статус 0x1005 требует штатного STOP и подтверждённых 4 мА; probe длины ноль никогда не останавливает работу нагрузки. */
} /* После положительной проверки вызывающий код удерживает IRQ заблокированными до автономной записи. */
#pragma CODE_SECTION(byte_at, "flash_rs") /* Размещает декодер упакованных RS-байтов в копируемой секции RAM. */
static Uint16 byte_at(const Uint16 *data, Uint32 offset) /* Читает октет из старшего байта чётного слова и младшего байта нечётного, как RS_RX_Handler. */
{ /* Начинает независимый от Flash декодер входного блока. */
return (offset & 1UL) ? (data[offset >> 1] & 255U) : (data[offset >> 1] >> 8); /* Преобразует упаковку RS в октеты без обращения к таблицам или runtime. */
} /* Завершает чтение одного октета. */
#pragma CODE_SECTION(word_at, "flash_rs") /* Делает декодирование формата SCI8 доступным после стирания Flash. */
static Uint16 word_at(const Uint16 *data, Uint32 offset) /* Читает слово TI boot table, в котором младший октет передаётся первым. */
{ /* Начинает объединение двух октетов. */
return byte_at(data, offset) | (byte_at(data, offset + 1UL) << 8); /* Восстанавливает 16-битное слово, сохраняя отличие от внутренней упаковки RS. */
} /* Завершает чтение слова SCI8. */
#pragma CODE_SECTION(long_at, "flash_rs") /* Декодирует адреса без обращения к коду стираемой программы. */
static Uint32 long_at(const Uint16 *data, Uint32 offset) /* Формат TI передаёт старшее 16-битное слово адреса перед младшим. */
{ /* Начинает чтение 32-битного адреса в словах C28x. */
return ((Uint32)word_at(data, offset) << 16) | word_at(data, offset + 2UL); /* Объединяет два слова без зависимости от порядка байтов процессора ПК. */
} /* Завершает декодирование адреса. */
#pragma CODE_SECTION(crc_byte, "flash_rs") /* Вычисляет CRC без таблицы crc16, расположенной в стираемом секторе G. */
static Uint16 crc_byte(Uint16 crc, Uint16 value) /* Продвигает стандартный CRC16 Modbus, используемый существующим RS-протоколом. */
{ /* Начинает обработку одного октета сообщения. */
Uint16 bit; /* Счётчик восьми битов октета не требует библиотечной арифметики. */
crc ^= value; /* Добавляет младший октет к состоянию отражённого CRC. */
for (bit = 0; bit < 8U; ++bit) /* Полином применяется ровно к восьми передаваемым битам. */
crc = (crc & 1U) ? ((crc >> 1) ^ 0xA001U) : (crc >> 1); /* 0xA001 — отражённый полином CRC16 Modbus, совпадающий с проверкой GUI. */
return crc; /* Возвращает состояние без вызовов функции из Flash. */
} /* Завершает шаг CRC. */
#pragma CODE_SECTION(reply_status, "flash_rs") /* Передаёт итог даже после удаления старой программы и её ISR. */
static void reply_status(RS_DATA *rs, Uint16 status) /* Ответ содержит адрес, команду, 16-битный статус и CRC; SCI и RS485 обслуживаются опросом. */
{ /* Начинает автономную передачу подтверждения из RAM. */
Uint16 frame[8], crc = 0xFFFFU, i; /* Шесть октетов кадра и два завершающих нуля соответствуют традиционным ответам BIOS. */
volatile Uint32 guard; /* Ограничивает ожидание SCI, чтобы отказ передатчика не блокировал завершение навсегда. */
frame[0] = rs->addr_recive; /* Отвечает на фактический адрес запроса, поддерживая обычный и сервисный адрес платы. */
frame[1] = CMD_TFLASH; /* Номер 55 сохраняет совместимость с таблицей длин существующего загрузчика. */
frame[2] = status & 255U; /* Передаёт младший октет результата TI API либо ошибки проверки образа. */
frame[3] = status >> 8; /* Передаёт старший октет статуса для различения ошибок загрузчика и API. */
for (i = 0; i < 4U; ++i) crc = crc_byte(crc, frame[i]); /* Контрольная сумма защищает и адрес, и конкретный результат записи. */
frame[4] = crc & 255U; /* CRC Modbus передаёт младший октет первым. */
frame[5] = crc >> 8; /* Передаёт старший октет контрольной суммы. */
frame[6] = frame[7] = 0; /* Сохраняет два завершающих нуля действующего RS-протокола. */
rs->SciRegs->SCICTL2.bit.TXINTENA = 0; /* Передача не должна вызывать ISR из удаляемой Flash. */
rs->SciRegs->SCICTL2.bit.RXBKINTENA = 0; /* Приём во время итогового ответа также не обращается к старому обработчику. */
rs->SciRegs->SCIFFTX.bit.TXFFIENA = 0; /* Отключает FIFO-прерывание для автономной передачи опросом. */
rs->SciRegs->SCIFFRX.bit.RXFFIENA = 0; /* Исключает FIFO RX ISR до восстановления обычного приёма или перезапуска. */
rs->SciRegs->SCICCR.bit.PARITYENA = 0; /* Ответ передаётся в режиме 8N1, принятом в обычном подтверждении BIOS. */
if (rs->commnumber == COM_2) GpioDataRegs.GPBDAT.bit.GPIO34 = 0; /* На этой плате низкий GPIO34 включает передатчик RS485, как RS_Line_to_send. */
for (i = 0; i < 8U; ++i) /* Последовательно отправляет кадр и завершающие байты без прерываний. */
{ /* Начинает ожидание свободного места в передатчике. */
guard = 10000000UL; /* Десять миллионов опросов дают запас на восемь байтов при штатных скоростях RS. */
while (!rs->SciRegs->SCICTL2.bit.TXRDY && --guard) {} /* Не переписывает SCITXBUF, пока предыдущее слово не принято передатчиком. */
if (!guard) break; /* Завершает попытку при неисправном или отключённом SCI. */
rs->SciRegs->SCITXBUF = frame[i]; /* Передаёт один восьмибитный октет по выбранному SCI-A или SCI-B. */
} /* Завершает отправку кадра. */
guard = 10000000UL; /* Ограничивает ожидание ухода последнего стопового бита тем же запасом. */
while ((rs->SciRegs->SCIFFTX.bit.TXFFST || !rs->SciRegs->SCICTL2.bit.TXEMPTY) && --guard) {} /* Для FIFO SCI-A ждёт также опустошения очереди, чтобы не потерять конец кадра при выключении RS485 или reset. */
if (rs->commnumber == COM_2) GpioDataRegs.GPBDAT.bit.GPIO34 = 1; /* Возвращает направление RS485 к приёму после завершения кадра. */
} /* Завершает автономный ответ. */
#pragma CODE_SECTION(validate_image, "flash_rs") /* Проверка таблицы также находится в RAM и не зависит от Flash API. */
static Uint16 validate_image(const Uint16 *data, Uint32 length) /* Проверяет весь образ до первого стирания, включая загрузочный переход и запрещённые адреса CSM. */
{ /* Начинает проверку структуры таблицы TI SCI8. */
Uint32 offset = 22UL, address, bytes, entry_address, prior, prior_address; /* Заголовок TI занимает 11 слов; дополнительные курсоры проверяют перекрытия и наличие данных по адресу входа. */
Uint16 count, boot = 0, entry = 0, prior_count; /* Требует единственный boot-переход и хотя бы один блок, содержащий точку входа C runtime. */
if (length < 24UL || (length & 1UL) || word_at(data, 0) != 0x08AAU) return 0x1002U; /* Ключ SCI8 и чётный размер исключают обычный плоский EEPROM BIN. */
address = long_at(data, 18UL); /* Адрес входа следует после восьми резервных слов заголовка TI. */
entry_address = address; /* Сохраняет адрес для проверки наличия соответствующих исполняемых слов в таблице. */
if (address < 0x300000UL || address >= 0x33FF80UL) return 0x1002U; /* Точка входа C runtime обязана находиться в исполняемой Flash вне CSM. */
while (offset + 2UL <= length) /* Читает блоки только в пределах подтверждённого загрузчиком массива. */
{ /* Начинает проверку очередного блока. */
count = word_at(data, offset); /* Размер блока выражен в 16-битных словах C28x, а не октетах RS. */
offset += 2UL; /* Продвигает курсор за поле длины. */
if (!count) return (offset == length && boot == 1U && entry) ? 0U : 0x1002U; /* Нулевой блок завершает образ; хвост, отсутствие boot-перехода или данных C runtime запрещены. */
if (offset + 4UL > length) return 0x1002U; /* Не читает обрезанный 32-битный адрес назначения. */
address = long_at(data, offset); /* Читает адрес в формате старшее слово, затем младшее. */
offset += 4UL; /* Переводит курсор к первому слову данных блока. */
bytes = (Uint32)count << 1; /* Каждое слово программы соответствует двум октетам файла. */
if (bytes > length - offset) return 0x1002U; /* Проверяет остаток файла без возможности выхода за границы массива. */
if (address == 0x33FFF6UL && count == 2U) ++boot; /* Единственное исключение рядом с CSM — аппаратный переход из двух слов. */
else if (address < 0x300000UL || address >= 0x33FF80UL || (Uint32)count > 0x33FF80UL - address) return 0x1002U; /* Запрещает OTP, RAM, пароли CSM и зарезервированный диапазон сектора A. */
if (entry_address >= address && entry_address < address + count) entry = 1U; /* Подтверждает, что после boot-перехода процессор получит код из текущего образа. */
prior = 22UL; /* Начинает сравнение диапазона с уже проверенными блоками таблицы. */
while (prior < offset - 6UL) /* Повторно рассматривает только заголовки, прошедшие проверки границ выше. */
{ /* Начинает проверку одного предыдущего диапазона. */
prior_count = word_at(data, prior); /* Предыдущая длина уже подтверждена и не может вывести курсор за массив. */
prior_address = long_at(data, prior + 2UL); /* Читает ранее проверенный Flash-адрес. */
if (address < prior_address + prior_count && prior_address < address + count) return 0x1002U; /* Не разрешает повторную запись слова Flash из разных блоков даже при корректном CRC. */
prior += 6UL + ((Uint32)prior_count << 1); /* Пропускает предыдущий заголовок и все его октеты данных. */
} /* Завершает сравнение с предыдущими секциями. */
offset += bytes; /* Переходит к следующему полностью проверенному блоку. */
} /* Завершает обход блоков. */
return 0x1002U; /* Отсутствие нулевого терминатора делает образ неполным. */
} /* Завершает проверку адресов и структуры. */
#pragma CODE_SECTION(program_image, "flash_rs") /* Весь участок от стирания до завершения выполняется из внутренней RAM. */
static void program_image(RS_DATA *rs, const Uint16 *data, Uint32 length) /* Никогда не возвращается в удалённую программу после первого вызова Flash_Erase. */
{ /* Начинает автономный алгоритм записи и проверки. */
Uint32 offset = 22UL, address, boot_offset = 0UL; /* Хранит курсоры данных и отложенного загрузочного перехода во внутренней RAM. */
Uint16 count, chunk, i, status; /* Состояние API и размеры блоков не зависят от внешних таблиц. */
status = Flash_Erase(0x00FFU, &api_status); /* Маска TI содержит биты A–H; обновление заменяет весь исполняемый образ, параметры внешней EEPROM сохраняются. */
while (!status && offset < length) /* Программирует блоки только после успешного стирания всех секторов. */
{ /* Начинает обработку очередной секции изображения. */
count = word_at(data, offset); /* Использует размер, уже проверенный до входа в критический участок. */
offset += 2UL; /* Пропускает поле количества слов. */
if (!count) break; /* Нулевой блок означает конец файла TI. */
address = long_at(data, offset); /* Восстанавливает Flash-адрес текущей секции. */
offset += 4UL; /* Переводит курсор к полезным словам секции. */
if (address == 0x33FFF6UL) { boot_offset = offset; offset += 4UL; continue; } /* Откладывает boot-переход до успешной записи и проверки остальных секций. */
while (count && !status) /* Делит большие секции на буферные порции без переполнения RAM. */
{ /* Начинает запись очередной порции. */
chunk = count > 128U ? 128U : count; /* Ограничение совпадает с размером внутреннего программирующего буфера в словах. */
for (i = 0; i < chunk; ++i) program_buffer[i] = word_at(data, offset + ((Uint32)i << 1)); /* Распаковывает байты RS в слова, ожидаемые Flash API. */
status = Flash_Program((Uint16 *)address, program_buffer, chunk, &api_status); /* Использует официальный алгоритм записи F28335 вместо обычной записи указателем. */
if (!status) status = Flash_Verify((Uint16 *)address, program_buffer, chunk, &api_status); /* Подтверждает записанные слова чтением из Flash до перехода к следующей порции. */
offset += (Uint32)chunk << 1; /* Продвигает курсор файла на фактически обработанные октеты. */
address += chunk; /* Продвигает Flash-адрес на количество адресуемых слов C28x. */
count -= chunk; /* Уменьшает остаток текущей секции. */
} /* Завершает запись секции. */
} /* Завершает запись обычных блоков. */
if (!status && boot_offset) /* Разрешает запуск нового образа только после проверки всех предыдущих секций. */
{ /* Начинает фиксацию аппаратной точки входа. */
program_buffer[0] = word_at(data, boot_offset); /* Восстанавливает первое слово инструкции длинного перехода. */
program_buffer[1] = word_at(data, boot_offset + 2UL); /* Восстанавливает второе слово адреса перехода. */
status = Flash_Program((Uint16 *)0x33FFF6UL, program_buffer, 2UL, &api_status); /* Записывает boot-переход последним, исключая запуск неполного образа. */
if (!status) status = Flash_Verify((Uint16 *)0x33FFF6UL, program_buffer, 2UL, &api_status); /* Проверяет обе части аппаратной точки входа. */
} /* Завершает фиксацию загрузочного перехода. */
reply_status(rs, status); /* Отправляет конкретный результат после записи и аппаратной проверки. */
if (!status) { EALLOW; SysCtrlRegs.WDCR = 0x0028U; EDIS; } /* Успех включает watchdog для аппаратного перезапуска; режим загрузки задают GPIO84–87. */
for (;;) {} /* Не возвращается в удалённый код; при успехе watchdog перезапустит устройство, при ошибке требуется восстановление. */
} /* Завершает автономный алгоритм. */
void FlashRs_Command(RS_DATA *rs) /* До стирания проверяет исходный блок и копирует все необходимые функции во внутреннюю RAM. */
{ /* Начинает обработку команды действующего загрузчика. */
Uint32 source, length, destination, i; /* Адреса и длина команды передаются младшим октетом вперёд в BIOS-протоколе. */
Uint16 status = 0U, crc, saved; /* Сохраняет код API, CRC и прежнюю маску IRQ для безопасного возврата после пробы либо отказа до стирания. */
source = (Uint32)rs->RS_Header[6] | ((Uint32)rs->RS_Header[7] << 8) | ((Uint32)rs->RS_Header[8] << 16) | ((Uint32)rs->RS_Header[9] << 24); /* Поле источника задаёт адрес ранее загруженного блока RAM. */
length = (Uint32)rs->RS_Header[10] | ((Uint32)rs->RS_Header[11] << 8) | ((Uint32)rs->RS_Header[12] << 16) | ((Uint32)rs->RS_Header[13] << 24); /* Поле длины выражено в октетах файла, как команда INITLOAD. */
destination = (Uint32)rs->RS_Header[2] | ((Uint32)rs->RS_Header[3] << 8) | ((Uint32)rs->RS_Header[4] << 16) | ((Uint32)rs->RS_Header[5] << 24); /* Адрес 0x300000 подтверждает назначение команды внутренней Flash. */
memcpy(&FlashRsRunStart, &FlashRsLoadStart, &FlashRsLoadEnd - &FlashRsLoadStart); /* Копирует функции, константы и библиотеку TI до возможного стирания их загрузочного сектора. */
saved = __disable_interrupts(); /* Не позволяет CAN/таймеру регистратора изменить внешний RAM-буфер между повторной проверкой CRC и передачей управления автономному загрузчику. */
if (CsmRegs.CSMSCR.bit.SECURE) status = 0x1004U; /* Защищённое устройство не должно переходить к стиранию без корректного разблокирования CSM. */
if (!status) status = flash_load_guard(length); /* Активная/неостановленная нагрузка получает отказ до доступа к Flash API; GPIO и уставку меняет только штатный автомат Timer1. */
if (!status && (source || length || destination)) /* Нулевая тройка параметров — только проверка поддержки, без доступа к буферу и стирания. */
{ /* Начинает проверку команды программирования. */
if (destination != 0x300000UL || source < 0x200000UL || source >= 0x20FC00UL || length < 24UL || length > ((0x20FC00UL - source) << 1) - 2UL || !rs->BS_LoadOK || (Uint32)rs->pRecvPtr != source || rs->RS_Length != length + 2UL) status = 0x1003U; /* Принимает лишь CRC-подтверждённый блок в штатной внешней RAM, включая место для принятого CRC. */
if (!status) /* Перепроверяет байты буфера после LOAD, защищая от изменения RAM до стирания. */
{ /* Начинает повторное вычисление CRC исходного кадра LOAD. */
crc = crc_byte(crc_byte(0xFFFFU, rs->addr_recive), CMD_LOAD); /* CRC включает тот же адрес и номер команды, что и первоначальная передача блока. */
for (i = 0; i < length; ++i) crc = crc_byte(crc, byte_at((Uint16 *)source, i)); /* Проверяет каждую полезную октету без таблицы из Flash. */
if (crc != word_at((Uint16 *)source, length)) status = 0x1003U; /* Запрещает стирание, если блок RAM отличается от подтверждённого загрузчиком. */
else status = validate_image((Uint16 *)source, length); /* Проверяет все адреса, завершённость и аппаратный переход до изменения Flash. */
} /* Завершает проверки буфера. */
} /* Завершает предварительную проверку команды. */
if (!status && length) /* Входит в автономный участок только для полностью проверенной ненулевой записи. */
{ /* Начинает остановку обычного приложения перед обновлением. */
Flash_CPUScaleFactor = (Uint32)(1048576.0L * (200.0L * (long double)SYSCLKOUT / 1000000000.0L)); /* Формула TI IQ20 использует реальный SYSCLKOUT в герцах вместо ошибочного фиксированного значения 150 МГц. */
Flash_CallbackPtr = 0; /* Исключает callback из стираемой программы во время операции Flash API. */
DINT; /* Запрещает все обычные ISR, размещённые в обновляемой Flash. */
EALLOW; /* Разрешает запись защищённых регистров watchdog и аварийного отключения PWM. */
SysCtrlRegs.WDCR = 0x0068U; /* Отключает watchdog на время стирания, которое может длиться несколько секунд. */
EPwm1Regs.TZCTL.all = EPwm2Regs.TZCTL.all = EPwm3Regs.TZCTL.all = EPwm4Regs.TZCTL.all = EPwm5Regs.TZCTL.all = EPwm6Regs.TZCTL.all = 0x000AU; /* Коды TZA=2 и TZB=2 переводят оба PWM-выхода в низкий уровень при аварийном отключении. */
EPwm1Regs.TZFRC.bit.OST = EPwm2Regs.TZFRC.bit.OST = EPwm3Regs.TZFRC.bit.OST = EPwm4Regs.TZFRC.bit.OST = EPwm5Regs.TZFRC.bit.OST = EPwm6Regs.TZFRC.bit.OST = 1; /* Принудительно блокирует PWM до перезапуска новой прошивки. */
EDIS; /* Завершает доступ к защищённым регистрам перед вызовом TI API. */
program_image(rs, (Uint16 *)source, length); /* После первого стирания управление остаётся в RAM до ответа и перезапуска. */
} /* Завершает путь программирования. */
reply_status(rs, status); /* Проба поддержки и ошибки до стирания возвращают короткий проверяемый ответ. */
RS_SetBitMode(rs, 9); /* Восстанавливает стандартный приём BIOS после безопасного отказа или пробы возможностей. */
rs->SciRegs->SCICTL2.bit.RXBKINTENA = 1; /* Возвращает обычное обслуживание следующих запросов RS. */
if (rs->commnumber == COM_1) rs->SciRegs->SCIFFRX.bit.RXFFIENA = 1; /* SCI-A в существующем драйвере принимает данные через FIFO-прерывание. */
__restore_interrupts(saved); /* При пробе или отказе до стирания возвращает ровно прежнее состояние прерываний приложения. */
} /* Завершает команду до стирания; после стирания эта функция не получает управление. */
#endif /* Обычный EEPROM/RAM-образ не включает объёмную библиотеку программирования Flash. */
+7 -2
View File
@@ -2,7 +2,7 @@
/* Подключает границы GAS и общий выбор EEPROM; при профиле 0 интерфейс GAS исключён. */
#include "gas167.h"
/* Весь дополнительный код включается только для физически установленной M95M02; вариант AT25256B не растёт. */
#if EEPROM_M95M02
#if EEPROM_M95M02 || defined(FLASH_BOOT) /* Отдельный Flash-вариант включает ту же карту GAS даже при совместимой сборке с AT25256B. */
/* Подключает тип платы dsk_LOAD и псевдонимы регистров, чтобы сохранять правила обычного Modbus. */
#include "package.h"
@@ -18,7 +18,12 @@ static const uint16_t gas167_info[GAS167_INFO_WORDS] = {
/* Слова 0..3: сигнатура GA/S1, версия профиля и номер проекта 167. */
0x4741U, 0x5331U, GAS167_PROFILE_VERSION, 167U,
/* Слова 4..7: база технологических регистров, число слов, маска RTU/CAN/FC06 и признак EEPROM M95M02 = 1; последний элемент описывает аппаратный профиль. */
GAS167_REG_BASE, GAS167_REG_COUNT, GAS167_CAPABILITIES, 1U
GAS167_REG_BASE, GAS167_REG_COUNT, GAS167_CAPABILITIES, /* Первые три поля второй половины дескриптора сохраняют базу, длину и возможности общего GAS 167/v1. */
#if defined(FLASH_BOOT) /* Код исполнения отделяет Flash от загрузки кода из внешней EEPROM без изменения адресов GAS. */
2U /* Значение 2 описывает исполнение из внутренней Flash; физическая EEPROM остаётся заданной EEPROM_M95M02. */
#else /* EEPROM/RAM-вариант M95M02 сохраняет прежний аппаратный признак дескриптора. */
1U /* Значение 1 соответствует прежнему GAS-профилю M95M02 и совместимо с существующими клиентами. */
#endif /* Завершает выбор только последнего слова дескриптора; технологические регистры остаются общими. */
/* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */
};
+19 -69
View File
@@ -1,6 +1,8 @@
/* Порядок запуска периферии и обслуживание команд в главном цикле. Русские пояснения сохранены в UTF-8. */
/* Подключает "DSP2833x_Device.h": поставляемое TI описание Device для семейства C2833x. */
#include "DSP2833x_Device.h" // DSP2833x Headerfile Include File
#include <string.h> /* Переносит только функции ramfuncs из загрузочного диапазона Flash в рабочую внутреннюю RAM. */
extern unsigned int RamfuncsLoadStart, RamfuncsLoadEnd, RamfuncsRunStart; /* Оба linker-варианта экспортируют границы копирования в адресуемых словах C28x. */
/* Подключает "cntrl_adr.h": собственный и универсальный адрес устройства. */
#include "cntrl_adr.h"
@@ -70,9 +72,9 @@ void main()
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
/* Объявляет i — данные i; j — данные j; mask — маска. */
int i,j,mask;
/* Объявляет cancount — данные cancount; cancell — данные cancell; rscount — данные rscount. */
static unsigned int cancount[2], cancell[2], rscount;
int i,j; /* Счётчики обслуживают RS-порты; курсоры CAN и маски теперь принадлежат независимому сервису телеметрии. */
/* Делитель внешнего Modbus-опроса принадлежит main; состояние рассылки CAN хранится и сбрасывается в ecan.c. */
static unsigned int rscount; /* Сохраняет только делитель опроса OWEN; частота CAN больше не зависит от этого счётчика. */
/* Объявляет rs — данные rs. Хранит указатель на данные. */
RS_DATA * rs;
/* Объявляет presbyt — данные presbyt. Начальное значение задаётся справа от знака равенства. */
@@ -80,6 +82,9 @@ void main()
/* Инициализирует модуль вызовом InitSysCtrl(); перед его дальнейшим использованием. */
InitSysCtrl();
if (&RamfuncsLoadStart != &RamfuncsRunStart) /* RAM-образ не копирует одинаковые диапазоны; Flash-образ переносит InitFlash перед изменением wait states. */
memcpy(&RamfuncsRunStart, &RamfuncsLoadStart, &RamfuncsLoadEnd - &RamfuncsLoadStart); /* Размер равен разности адресуемых 16-битных слов TI, а не физическим октетам ПК. */
InitFlash(); /* Вызывает находящийся в RAM код настройки Flash-пайплайна и задержек для штатного SYSCLKOUT. */
// Disable CPU interrupts and clear all CPU interrupt flags:
/* Запрет маскируемых прерываний ядра. */
@@ -148,7 +153,8 @@ void main()
Load_control_init(); /* Explicit initialization before IRQ. */ /* Вызывает Load_control_init. Сбрасывает все переменные автомата нагрузки до разрешения прерываний; COFF не обнуляет всю .ebss. */
/* Вызывает set_cntrl_addr. Устанавливает собственный и универсальный адрес для проверки адресованных команд. */
set_cntrl_addr(Mode,16);
/* Вызывает timer_Init. Назначает обработчик Timer1 по типу платы и задаёт период основного такта. */
rscount = 0U; /* Обнуляет делитель OWEN до Timer1, чтобы остатки RAM не задержали первый опрос внешнего прибора. */
/* Назначает обработчик Timer1 по типу платы и задаёт период основного такта после сброса программных делителей. */
timer_Init();
/* Вызывает EnableInterrupts с аргументами () в текущей последовательности операций. */
EnableInterrupts();
@@ -196,72 +202,16 @@ void main()
/* Чтение логгера по CAN не блокирует цикл управления. */
/* Вызывает UMP_CAN_service. В главном цикле готовит замороженный ответ служебного CAN и отправляет его двухсловными фрагментами. */
UMP_CAN_service();
/* Проверяет if(CanGO); последующий оператор выполняется только при истинном условии. */
if(CanGO)
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
/* Записывает CanGO значением 0; CAN GO. */
CanGO=0;
CAN_telemetry_service(); /* Обслуживает heartbeat 1 Гц, изменения и повторы 1,3 с независимо от делителя OWEN и состояния CanGO. */
if (CanGO) { /* Миллисекундный Timer1 по-прежнему выдаёт один флаг каждые CANPOWSE=20 мс для внешнего опроса. */
CanGO = 0; /* Поглощает текущий такт опроса, не ограничивая выдачу уже готовой очереди CAN. */
if (Owncount && ++rscount >= Owncount) { /* Нулевой период отключает OWEN; ненулевой задаёт прежнее число шагов по 20 мс. */
rscount = 0U; /* Начинает очередной интервал после отправки запроса внешнему регулятору. */
SendCommandModbus4(&rs_b); /* Сохраняет добавленный в 167 опрос измерений OWEN по второму UART. */
} /* Заканчивает обслуживание делителя внешнего прибора без блокирующих ожиданий CAN. */
} /* Следующие фоновые операции выполняются при любом состоянии флага периодического опроса. */
/* Проверяет if(Owncount); последующий оператор выполняется только при истинном условии. */
if(Owncount)
/* Проверяет if(++rscount>=Owncount); последующий оператор выполняется только при истинном условии. */
if(++rscount>=Owncount)
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
/* Записывает rscount значением 0; данные rscount. */
rscount=0;
/* Вызывает SendCommandModbus4 с аргументами (&rs_b) в текущей последовательности операций. */
SendCommandModbus4(&rs_b);
/* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */
}
/* Цикл for(i=0;i<2;i++); задаёт начальное значение, условие продолжения и шаг перебора. */
for(i=0;i<2;i++)
/* Проверяет if(++cancount[i] >= Cancount[i]); последующий оператор выполняется только при истинном условии. */
if(++cancount[i] >= Cancount[i])
/* Проверяет if(Cancount[i] и cancount[0]); последующий оператор выполняется только при истинном условии. */
if(Cancount[i] && cancount[0])
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
/* Записывает cancount[i] значением 0; данные cancount. */
cancount[i] = 0;
/* Повторяет или ожидает, пока выполняется while(1). */
while(1)
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
/* Проверяет if(cancell[i] >= 0x80) cancell[i]=0; последующий оператор выполняется только при истинном условии. */
if(cancell[i] >= 0x80) cancell[i]=0;
/* Записывает mask значением Maska[i][cancell[i]/16] >> (cancell[i]%16); маска. */
mask = Maska[i][cancell[i]/16] >> (cancell[i]%16);
/* Проверяет if(!mask) cancell[i] = (cancell[i] + 0x10) & 0xFFF0; последующий оператор выполняется только при истинном условии. */
if(!mask) cancell[i] = (cancell[i] + 0x10) & 0xFFF0;
/* Выполняет альтернативу, если предыдущее условие if оказалось ложным. */
else
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
/* Повторяет или ожидает, пока выполняется while(!(mask & 1)). */
while(!(mask & 1))
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
/* Изменяет счётчик в выражении cancell[i]++; mask >>= 1; на один для следующего шага. */
cancell[i]++; mask >>= 1;
/* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */
}
/* Прерывает ближайший цикл или ветвь switch и продолжает после него. */
break;
/* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */
} }
/* Вызывает CAN_send. Упаковывает адрес и технологические слова в штатный CAN-кадр и запускает передачу mailbox. */
CAN_send(0,modbus,cancell[i]);
/* Увеличивает cancell[i] значением 3; данные cancell. */
cancell[i]+=3;
/* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */
} }
/* Проверяет if(cDefParam); последующий оператор выполняется только при истинном условии. */
if(cDefParam)
if(cDefParam) /* Применяет заводские параметры по команде, сохраняя действующие биты управления нагрузкой в обработчике параметров. */
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
/* Записывает cDefParam значением 0; данные cDefParam. */
+2 -2
View File
@@ -698,8 +698,8 @@ void Init_packMask()
{
/* Ветвь выбора для dsk_EPLT; выполняется при совпадении селектора switch. */
case dsk_EPLT:
/* Устанавливает биты Maska[m_SLOW][0] значением 0x000F; данные Maska. */
Maska[m_SLOW][0]|= 0x000F; // Полученное
/* Маска 0x000F выбирает R0–R3 пульта для быстрой передачи аварийных и управляющих флагов. */
Maska[m_FAST][0]|= 0x000F; /* Возвращает период передачи этих четырёх слов из ветки 166, не добавляя их в медленный поток. */
/* Устанавливает биты Maska[m_SLOW][6] значением 0x01FC; данные Maska. */
Maska[m_SLOW][6]|= 0x01FC; // Яркость ламп и одиночество
/* Прерывает ближайший цикл или ветвь switch и продолжает после него. */
+26 -12
View File
@@ -59,12 +59,21 @@ int modbus[ANSWER_LEN+1];
/* Объявляет param — данные param. */
unsigned int param[ANSWER_LEN+1];
/* Сброс параметров не является командой пуска: у работающей нагрузки сохраняются только текущие START/RESET, полученные по интерфейсу. */
static void reset_parameter_commands(void) /* Объединяет поведение Load/Default и исключает пуск из сохранённого слова R127. */
{ /* Начинает короткую критическую секцию без EEPROM, ожиданий и переключения GPIO. */
unsigned int saved = __disable_interrupts(); /* Сохраняет маску IRQ, чтобы команда CAN не затерялась между чтением и очисткой служебных битов. */
if (Desk == dsk_LOAD && READY) Commands &= 0x00C0U; /* Биты 7/6 — START/RESET; сохранение действующих уровней не сбрасывает RUN, STOP и запрет повтора автомата167. */
else Commands = 0; /* До готовности платы и у остальных типов очищает все команды: EEPROM и неинициализированная RAM не задают пуск. */
__restore_interrupts(saved); /* Возвращает прежнюю маску, включая вызов внутри защищённого применения параметров. */
} /* Завершает сброс служебных команд, не меняя счётчики, импульсы и состояние защиты нагрузки. */
/* Заполняет регистры заводскими значениями; для LOAD задаёт тайм-аут свежести CAN 2000 мс. Значение 600 мс применяется отдельно при восстановлении недопустимого параметра. */
void Default_params()
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
/* Объявляет i — данные i; def — данные def. */
unsigned int i,def;
/* Объявляет индекс параметров, номер заводской калибровки и сохранённую маску IRQ. */
unsigned int i,def,saved; /* Хранит индекс, номер заводской калибровки и прежнюю маску IRQ для согласованного применения параметров. */
/* Объявляет shkf_lo_edge — данные shkf_lo_edge. Начальное значение задаётся справа от знака равенства. */
int shkf_lo_edge[] = {300, // 380
@@ -88,12 +97,13 @@ void Default_params()
/* Объявляет bright — данные bright. Начальное значение задаётся справа от знака равенства. */
int bright[] = {10,8,6,8,6,8};
saved = __disable_interrupts(); /* Не позволяет Timer1 увидеть промежуточные нулевые лимит и тайм-аут защиты при заполнении заводских параметров. */
/* Цикл for(i=0;i<ANSWER_LEN;i++); задаёт начальное значение, условие продолжения и шаг перебора. */
for(i=0;i<ANSWER_LEN;i++)
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
/* Записывает Modbus[i].all значением 0; представление регистра целым словом без разбиения на битовые поля. */
Modbus[i].all = 0;
if (Desk != dsk_LOAD || i != 127U) Modbus[i].all = 0; /* Слово R127 нагрузки содержит живые команды; его очистка создала бы ложный STOP и повторный START. */
/* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */
}
@@ -263,6 +273,9 @@ void Default_params()
/* Записывает Zero_lev[i] значением 0x800; данные Zero_lev. */
Zero_lev[i] = 0x800;
reset_parameter_commands(); /* Оставляет у готовой нагрузки текущие START/RESET и удаляет запросы Load/Default без повторения управляющих фронтов. */
__restore_interrupts(saved); /* После согласованной записи всех настроек снова разрешает Timer1 и CAN с прежней маской IRQ. */
/* Инициализирует модуль вызовом Init_sensors_more(); перед его дальнейшим использованием. */
Init_sensors_more();
/* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */
@@ -272,8 +285,8 @@ void Default_params()
void Load_params()
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
/* Объявляет i — данные i; crc — данные crc. */
unsigned int i,crc;
/* Объявляет индекс параметров, контрольную сумму и сохранённую маску IRQ. */
unsigned int i,crc,saved; /* Хранит индекс, проверенную CRC и маску IRQ только для короткого копирования параметров из RAM. */
/* Вызывает Seeprom_read. Читает EEPROM порциями; адрес задаётся словами C28x, размер переводится в число передаваемых слов. */
Seeprom_read(BALSAM167_EEPROM_PARAMS_START_WORD, param, (ANSWER_LEN+1)*2);
@@ -287,10 +300,12 @@ void Load_params()
(Mode == param[126]) )
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
/* Цикл for(i=0;i<ANSWER_LEN;i++) modbus[i] = param[i];; задаёт начальное значение, условие продолжения и шаг перебора. */
for(i=0;i<ANSWER_LEN;i++) modbus[i] = param[i];
/* Записывает Commands значением 0; слово команд, технологический регистр 127. */
Commands=0;
/* Чтение EEPROM уже завершено: короткая секция ниже согласованно применяет настройки защиты из буфера param. */
saved = __disable_interrupts(); /* Применяет лимит попыток и тайм-аут единым снимком; длительное чтение EEPROM выше остаётся доступным для IRQ. */
for(i=0;i<ANSWER_LEN;i++) /* Переносит проверенную карту параметров, не выдавая аппаратных сигналов и не сбрасывая автомат167. */
if (Desk != dsk_LOAD || i != 127U) modbus[i] = param[i]; /* Никогда не копирует сохранённый START/RESET нагрузки, даже на один промежуточный такт Timer1. */
reset_parameter_commands(); /* Сохраняет последние принятые START/RESET после чтения EEPROM; до READY очищает команды полностью. */
__restore_interrupts(saved); /* Возвращает исходное состояние IRQ после согласованного применения RAM-настроек. */
/* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */
}
/* Выполняет альтернативу, если предыдущее условие if оказалось ложным. */
@@ -299,8 +314,7 @@ void Load_params()
{
/* Восстанавливает заводские параметры после неверного CRC или несоответствия режима платы; LOAD_CAN_TIMEOUT получает 2000 мс. */
Default_params();
/* Записывает Commands значением 0; слово команд, технологический регистр 127. */
Commands=0;
/* Default_params уже сохранила текущие управляющие уровни нагрузки; повторная очистка R127 здесь недопустима. */
/* Вызывает Save_params. Сохраняет технологические регистры в область параметров, отделённую от загрузочного образа EEPROM. */
Save_params();
/* Проверяет if(TermoSW); последующий оператор выполняется только при истинном условии. */
@@ -437,7 +451,7 @@ static int receive_ump(RS_DATA *rs_arr)
/* Записывает saved значением __disable_interrupts(); данные saved. */
saved = __disable_interrupts();
/* Условие компиляции EEPROM_M95M02: выбранный блок включается в текущий вариант прошивки. */
#if EEPROM_M95M02
#if EEPROM_M95M02 || defined(FLASH_BOOT) /* Карта GAS доступна физической M95M02 и отдельному исполняемому Flash-образу. */
/* Проверяет if (gas167_contains(address)); последующий оператор выполняется только при истинном условии. */
if (gas167_contains(address))
/* Записывает count значением gas167_prepare(rs_arr->RS_Header[1], address, value, 0, words, &error); число. */
+1 -2
View File
@@ -405,8 +405,7 @@ void get_Buttons()
if(Desk==dsk_EPLT)
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
{
/* Сохраняет предыдущее состояние TESTLAMP, пока накопитель INcount0 не достигнет одного из порогов; 1000 — число опросов главного цикла, а не миллисекунды. Краткий провал GPIO26 больше не переключает передачу с тестовых масок на рабочие. */
bTestLamp = er_anal(GpioDataRegs.GPADAT.bit.GPIO26, &INcount0, 1000, bTestLamp);
/* TESTLAMP обрабатывается Timer1 в pulto.c: опрос остальных кнопок не должен менять отсчитанную аппаратным таймером задержку. */
/* Проверяет if(GpioDataRegs.GPADAT.bit.GPIO27) butt +=0x0001; последующий оператор выполняется только при истинном условии. */
if(GpioDataRegs.GPADAT.bit.GPIO27) butt +=0x0001;
+10
View File
@@ -70,6 +70,8 @@ interrupt void cpu_timer1_isr_PULT(void)
unsigned int light=0, i;
/* Объявляет Alone — данные Alone; READY_FREQ — частота основного такта Timer1 в герцах. Хранит указатель на данные. Начальное значение задаётся справа от знака равенства. */
unsigned int Alone = 20*READY_FREQ;
/* Считает непрерывное удержание TESTLAMP в тактах Timer1: READY_FREQ задаёт число тактов за секунду, независимо от занятости main. */
static unsigned int test_lamp_hold_ticks = 0;
/* Разрешение записи в защищённые регистры периферии. */
EALLOW;
@@ -121,6 +123,14 @@ interrupt void cpu_timer1_isr_PULT(void)
/* Проверяет if(++cownt>2) cownt=0; последующий оператор выполняется только при истинном условии. */
if(++cownt>2) cownt=0;
if(GpioDataRegs.GPADAT.bit.GPIO26) /* Высокий уровень входа означает нажатую физическую кнопку TESTLAMP на плате пульта. */
{ /* Накапливает только непрерывное удержание; насыщение защищает счётчик от переполнения при долгом нажатии. */
if(test_lamp_hold_ticks < 2U * (unsigned int)READY_FREQ) /* Порог двух секунд перенесён из 166: короткое нажатие не включает общий тест индикации. */
test_lamp_hold_ticks++; /* Добавляет один аппаратный такт, поэтому задержка не зависит от обмена и чтения EEPROM в главном цикле. */
} /* Завершает накопление текущего удержания физической кнопки. */
else test_lamp_hold_ticks = 0; /* Отпускание сразу отменяет тест и не позволяет суммировать отдельные короткие нажатия. */
bTestLamp = (test_lamp_hold_ticks >= 2U * (unsigned int)READY_FREQ); /* Обновляет флаг до отправки очередного пакета; удалённая команда cTestLamp сохраняет отдельное управление. */
/* Проверяет if(cTestLamp|bTestLamp); последующий оператор выполняется только при истинном условии. */
if(cTestLamp|bTestLamp)
/* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */
File diff suppressed because one or more lines are too long
+5
View File
@@ -421,6 +421,11 @@
<locationURI>SRC_ROOT1/Source/Internal/gas167.c</locationURI>
<!-- Завершает раздел link настроек CCS. -->
</link>
<link> <!-- Подключает Flash RS-модуль к общему списку CCS; в обычном RAM-варианте его тело исключает FLASH_BOOT. -->
<name>Source/Internal/flash_rs.c</name> <!-- Сохраняет единое расположение дополнительных исходников устройства. -->
<type>1</type> <!-- Тип 1 обозначает отдельный исходный файл, который CCS компилирует независимо. -->
<locationURI>SRC_ROOT1/Source/Internal/flash_rs.c</locationURI> <!-- Ссылается на production-загрузчик текущей 167, не на старую ветку Flash. -->
</link> <!-- Завершает ссылку; boot ASM и Flash linker подключаются только явным альтернативным сборщиком. -->
<!-- Завершает раздел linkedResources настроек CCS. -->
</linkedResources>
<!-- Настройка variableList. -->
+95 -12
View File
@@ -21,31 +21,37 @@ Debug и Release вызывают prepare_identity.py. Он читает ном
Для текущих конфигураций нужны Python, Git и TI C2000 22.6 LTS.
Debug и Release используют оптимизацию уровня 3 и приоритет размера
`--opt_for_speed=0`: это необходимо для размещения образа с настраиваемым
лимитом попыток в AT25256B. Проверенная локальная сборка находится в
`Bin/AttemptLimit`; перед загрузкой применяется SCI8-проверка границы EEPROM.
`--opt_for_speed=0`. Основной аппаратный профиль — M95M02-DR, 256 КиБ.
Локальный проверенный выпуск после переноса доработок 166 находится
в `Bin/Final167/M95M02`: EEPROM/RAM SCI8 занимает 34 120 байт при пределе 261 884.
AT25256B остаётся явным альтернативным профилем, однако текущий полный образ
превышает её предел; защищённый выпуск не разрешает такой BIN.
Общая реализация УМП и порт TMS320F28335 подключаются из
../newProject/SETGUI/third_party/templates/c/set-protocol относительно корня
репозитория. shared_ump.c включает эту реализацию и модуль паспорта.
В .cproject обе конфигурации используют генератор и post-build из
K:/git_project/newProject/templates. При переносе проекта проверьте этот путь,
пути Include Options к общей реализации и каталог назначения BIN. Генератор
исходников и post-build сейчас находятся в соседнем репозитории; старые
локальные build_bin.bat/preBuildStep.bat больше не входят в этот проект.
В .cproject обе конфигурации используют локальные prepare_identity.py и
publish_identity.py. Генератор паспорта и Git-ревизия зависимости берутся из
той же копии SETGUI/third_party/templates, из которой компилируется общий C-код.
Инструмент конверсии postcompile.bat находится в
K:/git_project/newProject/templates/tools/firmware-build/ccs. При переносе
проекта проверьте оба пути, Include Options и каталог назначения BIN.
Post-build вызывает tools/firmware-build/ccs/postcompile.bat: формирует загрузочный
образ, проверяет границы, создаёт паспорт и SHA-256. Текущий каталог копирования
Защищённый post-build вызывает общий postcompile только во временный каталог.
До публикации проверяются аппаратный профиль по MAP, размер до параметров
EEPROM, структура SCI8, RAM-адреса, встроенный паспорт, manifest и SHA-256.
Поле eeprom различает AT25256B и M95M02. Текущий каталог копирования
задан в .cproject:
K:/git_project/newProject/SETGUI/..periph_28335/firmware/periph28335/balzam167.
COM_1 работает на 115200 с FIFO. COM_2 использует 57600 для OWEN либо 115200
COM_1 работает на 115200 с FIFO. COM_2 использует 9600 для OWEN либо 115200
для другого режима. Эти скорости задаются в main.c.
## Профиль EEPROM и GAS
В Source/Internal/Include/eeprom_config.h значение EEPROM_M95M02=0 выбирает
AT25256B 32 КиБ; значение 1 — M95M02-DR 256 КиБ. Выбор обязан соответствовать
AT25256B 32 КиБ; значение 1 — M95M02-DR 256 КиБ и выбрано по умолчанию.
Выбор обязан соответствовать
микросхеме на плате. После изменения нужен Clean/Rebuild. При отдельной сборке
определение должно совпадать у компилятора и у препроцессора линкера.
@@ -61,3 +67,80 @@ tests/gas167/run_test.py, tests/eeprom_profiles/run_test.py,
tests/load_control/run_test.py и проверки регистратора в tests/ump_logger.
Проверка FIFO: tests/rs232_fifo/run_test.py --cc <путь к GCC>.
Для MinGW каталог исполняемых файлов GCC должен присутствовать в PATH.
## Полезные доработки из ветки 166
В 167 перенесены heartbeat CAN 1 Гц, отправка изменившихся данных, повтор
каждого подтверждённого пакета через 1,3 с, повтор после потери ACK и защита
границы R127. R96/R97 теперь включают потоки: 0 выключает соответствующую
маску, любое ненулевое значение разрешает её. Значение больше не задаёт
делитель периода. R0–R3 пульта находятся в быстрой маске; служебный канал
УМП/GAS и опрос OWEN сохраняются. Подробности: [PERIPH28335_CAN.md](../Doc/PERIPH28335_CAN.md).
TESTLAMP включается после двух секунд непрерывного удержания физической
кнопки. Время считает Timer1, поэтому занятость главного цикла и EEPROM не
меняет задержку. Порядок индикаторов в профиле 167 сохранён.
Загрузка и заводской сброс параметров сохраняют действующие START/RESET
готовой платы нагрузки. Сохранённое в EEPROM слово команд не подаётся
автомату даже на промежуточный такт. Новый автомат защиты нагрузки и
регистратор 167 сохранены.
Проверки переноса: `tests/can_telemetry/run_test.py`,
`tests/can_vpu_rx/run_test.py`, `tests/can_timeout/run_test.py`,
`tests/testlamp/run_test.py --cc <путь к GCC>` и
`tests/load_control/run_test.py`. Проверки защищённого выпуска:
`tests/firmware_identity/test_release.py`.
## Внутренняя Flash
В 167 добавлен отдельный Flash-вариант на основе ветки `прошивка-через-flash`
(`437f250`) с сохранением текущих CAN, GAS, регистратора и защиты нагрузки.
Основной `F28335.cmd` размещает `.text` в RAM. Альтернативный
`F28335_flash.cmd` размещает код во FlashH, константы и инициализацию — во
FlashG, аппаратный переход `codestart` — по адресу слова `0x33FFF6`.
Предстартовый код TI отключает watchdog до входа в `_c_int00`, чтобы холодный
запуск не прерывал инициализацию C.
`ramfuncs` копируются в RAML4 до `InitFlash`; автономный RS-программатор и TI
Flash API копируются из FlashF в RAML0 до стирания.
Штатный защищённый postbuild вызывает `build_flash.py` и выпускает оба варианта
с одним паспортом текущей компиляции. Требуется официальный TI Flash API v2.10
для F28335; его каталог задаётся `TI_FLASH_API_ROOT` либо штатным путём controlSUITE.
Перед публикацией проверяются COFF официальным `ofd2000`, точные LOAD/RUN,
точка входа, содержимое SCI8 и побайтное совпадение загружаемых слов COFF/SCI8.
Проверенный локальный комплект: `Bin/Final167/M95M02`.
| Файл | Назначение |
|---|---|
| `firmware_9858046+.bin` | M95M02 → RAM, 34 120 байт SCI8 |
| `firmware_flash.out` | Первичная запись внутренней Flash через JTAG, COFF с символами |
| `firmware_flash_rs.bin` | Обновление внутренней Flash по RS, 39 494 байта SCI8 |
Паспорта Flash дополнены `execution=FLASH` и `image_format=COFF` либо
`SCI8-FLASH`; поле `eeprom=M95M02` описывает отдельную память параметров.
Для запуска после сброса аппаратные boot-перемычки должны выбирать Flash.
При SPI boot загружается EEPROM/RAM-образ из M95M02.
Команда записи Flash на плате нагрузки отклоняется кодом `0x1005`, пока
штатный STOP не завершён и SPI не подтвердил отправку текущего кода 4 мА.
Probe не останавливает нагрузку. После начала стирания IRQ запрещены, все
вызовы остаются в RAM, boot-переход записывается последним. При ошибке записи
может потребоваться восстановление через JTAG: резервного Flash-образа нет.
Сборка, хостовые проверки и дизассемблер не заменяют проверку записи и
самостоятельного запуска на стенде; физическая плата в этой работе не прошивалась.
## Помощник настройки linker
Общая утилита и инструкция находятся в соседнем репозитории
[`templates/tools/c2000-linker`](../../newProject/templates/tools/c2000-linker/README.md).
Она создаёт стартовые карты COFF для TMS320F28335 и TMS320F2812 в режимах
RAM/Flash, объясняет LOAD/RUN и проверяет физические пересечения памяти.
Профильные особенности платы и внешняя XINTF RAM задаются в проекте.
EEPROM M95M02 хранит загрузочный поток SCI8. Её 256 КиБ не являются
CPU-адресуемым расширением RAM и не добавляются в MEMORY linker-файла.
В текущем RAM-варианте `.text` занимает внутренние RAML0–RAML3, а `.cinit`
и константы — RAML4. При Flash-варианте код и константы загружаются во Flash,
а секции `ramfuncs` и автономного программатора имеют отдельные LOAD и RUN.
+101
View File
@@ -0,0 +1,101 @@
"""Собирает независимый Flash COFF текущей 167 с тем же паспортом и аппаратной EEPROM.""" # Альтернативная сборка сохраняет основную RAM/EEPROM-прошивку.
import argparse # Поддерживает штатный вызов общего postbuild и явную проверочную сборку без Make.
import json # Читает подготовленный паспорт, включая выбранную физическую EEPROM и компилируемую зависимость.
import os # Позволяет указать установленный TI Flash API без изменения исходников проекта.
from pathlib import Path # Разрешает все файлы относительно проекта, исключая зависимость от текущего терминала.
import subprocess # Запускает только локальные компилятор и линкер TI, не программируя устройство.
import tempfile # Каждая компиляция получает новые объектные файлы, не зависящие от старых артефактов или блокировок CCS.
import xml.etree.ElementTree as ET # Читает фактические списки исходников и include paths CCS вместо устаревшего generated Makefile.
from prepare_identity import read_profile, read_version # Сохраняет ручную версию и физическую разметку текущей 167.
PROJECT = Path(__file__).resolve().parent # Каталог CCS содержит проверяемые .project и .cproject.
ROOT = PROJECT.parent # Корень production-исходников и обоих linker-вариантов устройства.
COMPILER = Path("C:/ti/ccs1230/ccs/tools/compiler/ti-cgt-c2000_22.6.0.LTS") # Доступный установленный COFF/FPU32 toolchain проекта.
FLASH_API = Path(os.environ.get("TI_FLASH_API_ROOT", "C:/ti/controlSUITE/libs/utilities/flash_api/2833x/28335/v210")) # Официальный API v2.10 F28335 с COFF ABI; сама библиотека не копируется в репозиторий.
def configuration_paths(configuration, compiler, build_dir): # Получает include paths и оптимизацию именно выбранной конфигурации CCS.
configs = ET.parse(PROJECT / ".cproject").findall(".//configuration") # Читает актуальный XML, а не параметры старой ветки Flash.
config = next(item for item in configs if item.get("name") == configuration) # Ошибка неизвестного Debug/Release не подменяется произвольными настройками.
options = config.findall(".//tool/option") # Содержит общие и файловые настройки компилятора в штатном порядке CCS.
include_option = next(item for item in options if item.get("superClass", "").endswith("compilerID.INCLUDE_PATH")) # Использует действующие include paths, включая vendored templates.
macros = {"${SRC_ROOT}": str(ROOT), "${PROJECT_LOC}": str(PROJECT), "${PROJECT_ROOT}": str(PROJECT), "${CG_TOOL_ROOT}": str(compiler), "${BuildDirectory}": str(build_dir)} # Разрешает только известные файловые макросы CCS; PROJECT_ROOT совпадает с каталогом .project.
includes = [] # Поддерживает исходный порядок поиска заголовков компилятора.
for option in include_option.findall("listOptionValue"): # Обрабатывает каждый путь фактически выбранной конфигурации.
value = option.get("value", "") # Пустые некорректные настройки будут отвергнуты до компиляции.
if value == "${COM_TI_XDC_INCLUDE_PATH}": # Штатная конфигурация проекта без SYS/BIOS не задаёт каталогов XDC, а CCS оставляет служебный макрос пустым.
continue # Не передаёт неразрешённый встроенный placeholder отдельному CLI-компилятору.
for macro, replacement in macros.items(): # Подставляет реальные абсолютные пути данной проверки.
value = value.replace(macro, replacement) # Не использует shell expansion и не исполняет текст XML.
if not value or "${" in value: # Не передаёт компилятору неразрешённый путь вместо общей зависимости.
raise ValueError("Unresolved CCS include path: " + value) # Указывает настройку, которую требуется исправить в проекте.
includes.append(Path(value).resolve()) # Позволяет сборке запускаться из любого каталога postbuild.
level = next(item.get("value").rsplit(".", 1)[-1] for item in options if "compilerID.OPT_LEVEL" in item.get("superClass", "")) # Сохраняет нынешний O3 вместо старого O1 Flash-ветки.
speed = next(item.get("value").rsplit(".", 1)[-1] for item in options if item.get("superClass", "").endswith("compilerID.OPT_FOR_SPEED")) # Приоритет размера остаётся общим для RAM и Flash.
return includes, ["-O" + level, "--opt_for_speed=" + speed] # Прочие архитектурные настройки C28x/FPU32/COFF задаются явно ниже.
def project_sources(): # Перечисляет только связанные production C/ASM-файлы текущего CCS-проекта.
sources = [] # Каждая запись компилируется в независимый Flash-объект без использования старой RAM-сборки.
for link in ET.parse(PROJECT / ".project").findall(".//linkedResources/link"): # Сохраняет подключённые внешние TI-модули и все актуальные локальные изменения.
uri = link.findtext("locationURI", "") # Исходники должны находиться в текущем дереве SRC_ROOT1.
if uri.startswith("SRC_ROOT1/Source/") and Path(uri).suffix.lower() in (".c", ".asm"): # Заголовки и linker command файлы не компилируются как самостоятельный C-код.
sources.append(ROOT / uri.removeprefix("SRC_ROOT1/")) # Избегает ссылок на исходники другой ветки или случайный generated Source.
sources.append(ROOT / "Source/Internal/flash_codestart.asm") # Аппаратный boot-переход подключается только к Flash, сохраняя чистую RAM SCI8-таблицу.
return list(dict.fromkeys(sources)) # Исключает дубли после повторного импорта CCS, не меняя первоначальный порядок.
def build_flash(compiler, build_dir, output, configuration="Debug"): # Полностью пересобирает отдельный Flash-вариант без рекурсивного Make и без прошивки платы.
compiler, build_dir, output = compiler.resolve(), build_dir.resolve(), output.resolve() # Фиксирует реальные пути входов и единственного запрошенного результата.
api_library = FLASH_API / "lib/Flash28335_API_V210.lib" # Официальная библиотека TI содержит алгоритмы стирания, записи и проверки F28335.
if not api_library.is_file(): # Нельзя выпускать образ с обещанным RS-обновлением без Flash API.
raise FileNotFoundError(api_library) # Диагностирует отсутствующую локальную установку до компиляции проекта.
metadata = json.loads((build_dir / "generated/firmware_identity.json").read_text(encoding="utf-8")) # Использует паспорт, подготовленный до RAM-сборки той же конфигурации.
templates = Path(metadata["templates_root"]) # Не возвращается к старой другой копии templates из Flash-ветки.
profile = read_profile(PROJECT, templates, metadata["eeprom"]) # Поддерживает согласованный CLI override обеих физических EEPROM.
if any(metadata.get(key) != value for key, value in profile.items()) or metadata["version"] != read_version(PROJECT / "firmware_version.h"): # Отвергает смену версии/аппаратного профиля после prebuild.
raise ValueError("Flash configuration changed: run prebuild before compiling") # Не подписывает новый Flash старой таблицей версии.
includes, optimize = configuration_paths(configuration, compiler, build_dir) # Сохраняет актуальные include paths и настройки оптимизации RAM-проекта.
includes += [ROOT, FLASH_API / "include", templates / "c/set-protocol", templates / "c/set-protocol/ports/tms320f28335", templates / "c/firmware-info/include", build_dir / "generated"] # Изолированная проверочная сборка получает тот же generated-паспорт и локальные Flash-заголовки.
selected = int(profile["eeprom"] == "M95M02") # Только физический тип EEPROM управляет адресацией SPI и расположением сохраняемых параметров.
flags = ["-v28", "-ml", "-mt", "--float_support=fpu32", "--abi=coffabi", *optimize, "--gen_func_subsections=on", "--define=LARGE_MODEL", "--define=FLASH_BOOT", "--define=EEPROM_M95M02=" + str(selected)] # Flash boot отделён от аппаратной EEPROM; общий compiler guard проверяет выбранный профиль.
flags += ["--define=_DEBUG"] if configuration == "Debug" else [] # Сохраняет отладочный макрос без переноса других старых поведенческих изменений.
flags += ["--include_path=" + str(path) for path in dict.fromkeys(includes)] # Повторные пути не меняют поиск, а список остаётся пригодным для журнала.
object_root = build_dir / "flash" # Дополнительные объекты не подменяют RAM-компиляцию и её проверенный образ.
object_root.mkdir(parents=True, exist_ok=True) # Создаёт только выделенный корень артефактов Flash, сохраняя прежние диагностические сборки.
objects = Path(tempfile.mkdtemp(prefix="build-", dir=object_root)) # Новый каталог исключает подстановку stale OBJ и ошибки перезаписи заблокированного старого файла другой ветки.
object_files = [] # Линкер получит лишь объекты успешно завершившихся компиляций этой попытки.
log_path = objects / "build.log" # Сохраняет команды и диагностику для воспроизводимого review без засорения исходников.
with log_path.open("w", encoding="utf-8") as log: # Один журнал принадлежит одному последовательному Flash-build.
for source in project_sources(): # Пересобирает все production-модули, сохраняя текущие CAN, GAS, защиты и регистратор 167.
object_file = objects / (source.stem + ".obj") # Имена исходников текущего CCS-проекта уникальны и совпадают с его объектной моделью.
command = [str(compiler / "bin/cl2000.exe"), *flags, "-c", "--output_file=" + str(object_file), str(source)] # Не использует объект из старого Makefile либо другой ветки.
log.write(json.dumps(command, ensure_ascii=False) + "\n") # Фиксирует точные include paths, compiler define и исходник текущей попытки.
log.flush() # Команда остаётся доступной при аварийном завершении компилятора.
result = subprocess.run(command, cwd=ROOT, stdout=log, stderr=subprocess.STDOUT) # Все сообщения TI сохраняются рядом с альтернативными объектами.
if result.returncode: # Ненулевой код не допускает линковку прежнего объектного файла.
raise RuntimeError(f"Flash compilation failed: {source}; see {log_path}") # Даёт точный модуль и журнал ошибки вместо ложного успеха старого OUT.
object_files.append(str(object_file)) # Только успешный объект входит в итоговый список линкера.
output.parent.mkdir(parents=True, exist_ok=True) # Общий postcompile обычно передаёт свой временный каталог выпуска.
command = [str(compiler / "bin/cl2000.exe"), "-v28", "--float_support=fpu32", "--abi=coffabi", "-z", "--define=EEPROM_M95M02=" + str(selected), "--stack_size=0x3f0", "--entry_point=_c_int00", "--rom_model", "--warn_sections", "--reread_libs", "-m" + str(output.with_suffix(".map")), "-o", str(output)] # Стандартный C startup и Flash codestart образуют две нужные точки входа устройства.
command += ["--search_path=" + str(path) for path in (ROOT, compiler / "lib", FLASH_API / "lib", templates / "c/set-protocol")] # Разрешает общую разметку EEPROM и секции официального TI API в linker command file.
command += [str(ROOT / "F28335_flash.cmd"), str(ROOT / "Source/External/v120/DSP2833x_headers/cmd/DSP2833x_Headers_nonBIOS.cmd"), *object_files, str(api_library), "-lrts2800_fpu32.lib", "-llibc.a"] # Берёт актуальный runtime TI вместо старой локальной библиотеки, в которой ранее ломался memcpy startup.
log.write(json.dumps(command, ensure_ascii=False) + "\n") # Сохраняет полную карту входов Flash-линкера для проверки LOAD/RUN.
log.flush() # Записывает команду до запуска потенциально ошибочной линковки.
result = subprocess.run(command, cwd=ROOT, stdout=log, stderr=subprocess.STDOUT) # Итоговый OUT принадлежит только этой свежей Flash-компиляции.
if result.returncode: # Нельзя выпускать старый OUT после неудачной линковки.
raise RuntimeError(f"Flash link failed; see {log_path}") # Общий postcompile прекратит выпуск всех вариантов текущей попытки.
print(f"[firmware-build] Flash {profile['eeprom']} COFF: {output}") # Показывает конкретный физический профиль и отдельный файл исполнения из Flash.
return output # Защищённый publish проверит COFF/MAP/SCI8 перед публикацией и export.
if __name__ == "__main__": # Импорт проверяющим кодом не запускает сборку и не создаёт файлы.
parser = argparse.ArgumentParser(description=__doc__) # Описывает отдельный вариант вместо скрытого переключения обычного BIN.
parser.add_argument("--postbuild", action="store_true") # Сохраняет совместимость с автоматическим обнаружением общего postcompile.
parser.add_argument("--compiler", type=Path, default=COMPILER) # Общий postbuild передаёт фактический CG_TOOL_ROOT.
parser.add_argument("--build-dir", type=Path, default=PROJECT / "Debug") # Разрешает независимый временный каталог проверки с собственным generated.
parser.add_argument("--output", type=Path, default=ROOT / "Bin/UKSSTMS320F28335_flash.out") # Не заменяет основной RAM OUT.
parser.add_argument("--configuration", choices=("Debug", "Release")) # Позволяет явно воспроизвести настройки CCS при изолированной проверке.
arguments = parser.parse_args() # Проверяет CLI до любых операций компилятора.
configuration = arguments.configuration or ("Release" if arguments.build_dir.name.lower() == "release" else "Debug") # Штатные каталоги CCS выбирают конфигурацию без дополнительного параметра общего postcompile.
build_flash(arguments.compiler, arguments.build_dir, arguments.output, configuration) # Запускает только компиляцию/линковку; программирование устройства отсутствует.
+58 -3
View File
@@ -11,10 +11,52 @@ import json
from pathlib import Path
# Подключает модуль re для этой проверки или подготовки сборки.
import re
# Запускает Git с доверием только к явно выбранному рабочему дереву, не меняя глобальные настройки пользователя.
import subprocess
# Подключает модуль tempfile для этой проверки или подготовки сборки.
import tempfile
# Читает единственное числовое определение; вычисляемые выражения отклоняются, чтобы Python не исполнял содержимое C-заголовков.
def numeric_define(header, name):
text = header.read_text(encoding="utf-8-sig") # Поддерживает UTF-8 с BOM для пользовательских заголовков конфигурации.
text = re.sub(r"/\*.*?\*/|//[^\n]*", "", text, flags=re.S) # Удаляет комментарии перед поиском настоящих директив препроцессора.
values = re.findall(r"^\s*#\s*define\s+" + re.escape(name) + r"\s+([^\n]+)", text, re.M) # Требует определение нужного макроса, а не упоминание в тексте.
if len(values) != 1 or not re.fullmatch(r"(?:0[xX][0-9a-fA-F]+|[0-9]+)[uUlL]*", values[0].strip()): # Исключает неоднозначность и выражения, которые могут отличаться от C-препроцессора.
raise ValueError(f"{header}: define {name} once as an integer") # Останавливает выпуск при неподдерживаемой структуре общей разметки.
return int(values[0].strip().rstrip("uUlL"), 0) # Преобразует десятичную либо шестнадцатеричную величину в число Python.
# Получает физический профиль из той же конфигурации и той же копии разметки, которые читает компилятор 167.
def read_profile(project, templates, eeprom=None):
selected = numeric_define(project.parent / "Source/Internal/Include/eeprom_config.h", "EEPROM_M95M02") # По умолчанию повторяет единственный аппаратный выбор платы из заголовка.
if selected not in (0, 1): # Не допускает произвольное ненулевое значение вместо двух поддерживаемых микросхем.
raise ValueError("EEPROM_M95M02 must be 0 or 1") # Диагностирует ошибку до генерации паспорта и конверсии OUT.
name = eeprom or ("M95M02" if selected else "AT25256B") # Явное CLI-переопределение должно также передаваться компилятору и линкеру.
if name not in ("AT25256B", "M95M02"): # Проверяет также программные вызовы, которые обходят argparse.
raise ValueError("EEPROM must be AT25256B or M95M02") # Не подставляет профиль по умолчанию при неизвестном названии.
layout = templates / "c/set-protocol/include/balsam167_eeprom_layout.h" # Выбирает реальный заголовок из компилируемой зависимости.
total = numeric_define(layout, "BALSAM167_M95M02_BYTES" if name == "M95M02" else "BALSAM167_AT25256_BYTES") # Ёмкость задаётся общим контрактом в физических октетах.
words = numeric_define(layout, "BALSAM167_EEPROM_PARAM_WORDS") # Число сохраняемых 16-битных слов включает контрольную сумму параметров.
limit = total - (words + 1) * 2 # Резервирует параметры и последнее неиспользуемое слово; слово C28x занимает два октета EEPROM.
if total <= 0 or total % 2 or words <= 0 or limit < 24: # Минимальная таблица SCI8 требует 22 октета заголовка и двухоктетный терминатор.
raise ValueError("Invalid EEPROM layout") # Отклоняет повреждённую разметку вместо отключения ограничения размера.
return {"eeprom": name, "eeprom_bytes": total, "eeprom_param_words": words, "max_image_bytes": limit} # Эти значения становятся частью паспорта конечного BIN.
# Снимает состояние только явно переданного репозитория и не требует записи safe.directory в пользовательский Git config.
def git_state(repository):
directory = repository.resolve() # Рабочий каталог CCS может быть вложен в репозиторий приложения.
trusted = next((path for path in (directory, *directory.parents) if (path / ".git").exists()), directory) # Git проверяет корень checkout; .git-файл также учитывает подключённый templates submodule.
command = ["git", "-c", "safe.directory=" + trusted.as_posix(), "-C", str(directory)] # Локальное доверие ограничено корнем явно выбранной зависимости или исходников проекта.
try: # Отсутствие Git сохраняет прежний явный NOGIT-контракт общей библиотеки.
commit = subprocess.check_output(command + ["rev-parse", "HEAD"], stderr=subprocess.DEVNULL, text=True, encoding="utf-8").strip() # Получает полный SHA именно компилируемого дерева.
dirty = bool(subprocess.check_output(command + ["status", "--porcelain", "--untracked-files=normal"], stderr=subprocess.DEVNULL, text=True, encoding="utf-8").strip()) # Учитывает новые и изменённые исходники при формировании знака плюс.
return {"commit": commit, "dirty": dirty} # Возвращает совместимую с общим генератором структуру происхождения.
except (OSError, subprocess.CalledProcessError): # Не выдаёт неизвестную ревизию за чистое дерево.
return {"commit": None, "dirty": None} # Генератор пометит такую сборку идентификатором NOGIT000.
# Объявляет функцию read_version и её входные параметры.
def read_version(header):
# Читает исходный файл или образ для последующей проверки и разбора.
@@ -52,17 +94,20 @@ def read_version(header):
# Объявляет функцию prepare и её входные параметры.
def prepare(project, templates, output):
def prepare(project, templates, output, eeprom=None): # Допускает явный аппаратный профиль для CLI при согласованных define компилятора и линкера.
# Читает исходный файл или образ для последующей проверки и разбора.
settings = json.loads((project / "firmware_identity.json").read_text(encoding="utf-8-sig"))
# Задаёт settings["version"] по выражению справа; значение используется последующими операциями.
settings["version"] = read_version(project / "firmware_version.h")
settings["increment_version"] = False # Ручная версия из C-заголовка не увеличивается даже при случайном появлении старого JSON-переключателя.
profile = read_profile(project, templates, eeprom) # Вычисляет границу BIN до вызова генератора, чтобы ошибочный профиль не оставлял пригодный паспорт.
# Задаёт spec по выражению справа; значение используется последующими операциями.
spec = importlib.util.spec_from_file_location("shared_firmware_identity", templates / "c/firmware-info/tools/identity.py")
# Задаёт identity по выражению справа; значение используется последующими операциями.
identity = importlib.util.module_from_spec(spec)
# Продолжает текущий вызов, список или выражение: spec.loader.exec_module(identity); порядок аргументов сохраняется.
spec.loader.exec_module(identity)
identity.git_state = git_state # Использует штатную структуру паспорта с безопасным локальным чтением Git для выбранной копии templates.
# The source header owns SemVer; retain the shared generator and manifest format.
# Открывает управляемый ресурс tempfile.TemporaryDirectory(prefix="balsam-identity-") as directory с автоматическим освобождением.
with tempfile.TemporaryDirectory(prefix="balsam-identity-") as directory:
@@ -71,7 +116,16 @@ def prepare(project, templates, output):
# Сохраняет подготовленный файл в указанном формате для следующего шага.
config.write_text(json.dumps(settings), encoding="utf-8")
# Возвращает identity.prepare(project, templates, config, output) вызывающему коду и завершает функцию.
return identity.prepare(project, templates, config, output)
metadata = identity.prepare(project, templates, config, output) # Генерирует прежние 12 встроенных слов версии, не меняя транспортный контракт.
metadata.update(profile) # Различает физические EEPROM в JSON без добавления сохраняемых MCU-регистров.
metadata["templates_root"] = str(templates.resolve()) # Позволяет postbuild сверить, что использована та же компилируемая копия общих исходников.
header_path = output / "firmware_identity.h" # Штатный firmware_info_static.c обязательно компилирует этот заголовок внутри shared_ump.c.
guard = '\n#include "eeprom_config.h" /* Читает тот же аппаратный выбор, что SPI-драйвер и обработчики 167. */\n' # Проверка выполняется препроцессором и не увеличивает BIN малой EEPROM.
guard += f'#if EEPROM_M95M02 != {int(profile["eeprom"] == "M95M02")} /* Сверяет фактический define компилятора с профилем подготовленного паспорта. */\n' # Явный CLI-профиль требует согласованного compiler define.
guard += '#error EEPROM_profile_differs_from_prebuild /* Останавливает сборку при compiler-only override или смене EEPROM после prebuild. */\n#endif /* Завершает проверку соответствия аппаратного профиля паспорту. */\n' # Не позволяет подписать M95M02-драйвер паспортом AT25256B даже при согласованной MAP.
identity.write_atomic(header_path, header_path.read_text(encoding="utf-8") + guard) # Сохраняет исходный общий контракт и добавляет только локальную проверку конфигурации.
identity.write_atomic(output / "firmware_identity.json", json.dumps(metadata, ensure_ascii=False, indent=2) + "\n") # Сохраняет обогащённый паспорт до компиляции и последующей подписи BIN.
return metadata # Возвращает фактическую ручную версию и аппаратный профиль текущей сборки.
# Проверяет условие if __name__ == "__main__" перед выполнением следующего блока.
@@ -84,7 +138,8 @@ if __name__ == "__main__":
parser.add_argument("--templates", type=Path, required=True)
# Задаёт parser.add_argument("--output", type по выражению справа; значение используется последующими операциями.
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--eeprom", choices=("AT25256B", "M95M02")) # CLI-переопределение требует того же аппаратного define у компилятора и линкера.
# Задаёт args по выражению справа; значение используется последующими операциями.
args = parser.parse_args()
# Продолжает текущий вызов, список или выражение: print(json.dumps(prepare(args.project, args.templates, args.output))); порядок аргументов сохраняется.
print(json.dumps(prepare(args.project, args.templates, args.output)))
print(json.dumps(prepare(args.project, args.templates, args.output, args.eeprom))) # Показывает выбранный профиль вместе с прежними полями идентификации.
+202
View File
@@ -0,0 +1,202 @@
"""Публикует EEPROM/RAM SCI8 только после проверки встроенного паспорта и разметки платы.""" # Локальная защита дополняет общий конвертор без изменения соседнего templates.
import argparse # Разбирает одинаковые аргументы защищённого postbuild для CCS Debug и Release.
import base64 # Декодирует точные байты секций COFF из официального XML-вывода TI ofd2000.
from datetime import datetime # Восстанавливает слова UTC из паспорта, созданного перед компиляцией.
import hashlib # Проверяет точные байты конечного BIN и сопроводительного SHA-256.
import importlib.util # Подключает штатный механизм экспорта только после проверки временного выпуска.
import json # Сверяет изделие, аппаратный профиль и идентификацию текущей сборки.
from pathlib import Path # Разрешает пути относительно выбранной конфигурации CCS, а не текущего терминала.
import re # Читает абсолютные EEPROM-символы из карты линкера TI.
import shutil # Копирует только полностью проверенные файлы из временного каталога.
import struct # Разбирает 16-битные слова SCI8 и адреса загрузочных блоков C28x.
import subprocess # Вызывает общую BAT-точку конверсии без публикации во внешнее приложение.
import tempfile # Изолирует непроверенные результаты общего postcompile от постоянных выпусков.
import xml.etree.ElementTree as ET # Проверяет фактические LOAD/RUN адреса Flash COFF без собственного двоичного парсера TI.
from prepare_identity import read_profile, read_version # Использует тот же ручной номер и ту же физическую разметку, что и prebuild.
def verify_embedded(image, metadata, flash=False): # RAM и Flash SCI8 проверяются отдельно по разным допустимым адресам и ограничениям загрузчика.
data = image.read_bytes() # Проверяет содержимое файла, которое будет передано в EEPROM-программатор.
limit = 0xFC00 * 2 - 2 if flash else metadata["max_image_bytes"] # RS Flash использует внешний буфер 0x200000..0x20FC00 и ещё два октета CRC; RAM BIN ограничен резервом физической EEPROM.
if not 24 <= len(data) <= limit: # Учитывает заголовки SCI8 вместе с кодом, не позволяя переписать резерв параметров либо выйти за буфер RS.
raise ValueError("SCI8 exceeds EEPROM firmware boundary or is too short") # Отвергает слишком большой BIN до создания постоянного каталога выпуска.
if data[:2] != b"\xaa\x08": # 0x08AA — обязательный ключ восьмибитной boot-таблицы TI.
raise ValueError("Invalid SCI8 header") # Не принимает HEX, COFF либо плоский дамп под видом загрузочного BIN.
stamp = datetime.fromisoformat(metadata["built_at_utc"].replace("Z", "+00:00")) # Берёт момент именно того prebuild, который подготовил C-заголовок.
build_id = metadata["build_id"] # Контракт firmware-info v1 хранит ровно восемь ASCII-символов идентификатора.
if not re.fullmatch(r"[0-9a-fA-F]{8}|[0-9a-fA-F]{7}\+|NOGIT000", build_id) or stamp.utcoffset() is None or stamp.utcoffset().total_seconds(): # Исключает неоднозначную дату и неподдерживаемый идентификатор.
raise ValueError("Invalid prebuild identity") # Требует заново выполнить prebuild вместо выпуска чужих метаданных.
words = [1, *metadata["version"], stamp.year, stamp.month * 256 + stamp.day, stamp.hour * 256 + stamp.minute, stamp.second] # Независимо восстанавливает первые восемь 16-битных регистров общего контракта.
words += [ord(build_id[index]) * 256 + ord(build_id[index + 1]) for index in range(0, 8, 2)] # Упаковывает две ASCII-октеты в каждое из четырёх слов идентификатора.
expected = struct.pack("<12H", *words) # TI SCI8 хранит каждое слово полезных данных младшей октетой вперёд.
high, low = struct.unpack_from("<HH", data, 18) # Читает точку входа после ключа и восьми резервных слов загрузчика.
entry = (high << 16) | low # Адрес C28x передаётся старшим словом перед младшим.
offset, found, blocks, boot, loaded = 22, 0, [], 0, {} # Запоминает диапазоны, boot-переход и точные загружаемые слова для сопоставления Flash COFF с SCI8.
while offset + 2 <= len(data): # Перед чтением размера требует наличие полного 16-битного слова.
count, = struct.unpack_from("<H", data, offset) # Длина блока задана в адресуемых словах C28x, а не физических октетах.
offset += 2 # Пропускает размер перед адресом блока или терминатором таблицы.
if not count: # Нулевое слово завершает SCI8 без последующего адресного поля.
if offset != len(data) or found != 1 or (flash and boot != 1) or not any(start <= entry < end for start, end in blocks): # Дополнительно требует единственный двухсловный аппаратный переход Flash boot.
raise ValueError(f"SCI8 identity/entry/terminator mismatch: matching identities={found}") # Сообщает отсутствие встроенного паспорта вместо ложной подписи старого OUT.
return entry, loaded # Flash-проверка сопоставит реальные загрузочные слова с секциями исходного COFF; RAM-вызов может игнорировать результат.
if offset + 4 + count * 2 > len(data): # Требует полный 32-битный адрес и все октеты полезной нагрузки.
raise ValueError("Truncated SCI8 section") # Не допускает выпуск усечённой секции даже при паспорте в её начале.
high, low = struct.unpack_from("<HH", data, offset) # Получает адрес назначения текущего блока в словах C28x.
address, end = (high << 16) | low, ((high << 16) | low) + count # Конец исключается из диапазона, поэтому соседние блоки могут соприкасаться.
allowed = (0x300000 <= address < end <= 0x33FF80 or (address == 0x33FFF6 and count == 2)) if flash else 0x8000 <= address < end <= 0xD000 # Flash запрещает OTP, CSM и произвольную RAM; разрешён только отдельный boot-переход перед паролями CSM.
if not allowed or any(address < old_end and start < end for start, old_end in blocks): # Любое перекрытие секций либо несовместимый тип таблицы блокирует выпуск.
raise ValueError("SCI8 block outside application RAM or overlapping") # Не выпускает Flash-таблицу как EEPROM/RAM-образ.
blocks.append((address, end)) # Сохраняет проверенный диапазон для контроля следующих блоков и точки входа.
payload = data[offset + 4:offset + 4 + count * 2] # Ищет паспорт только внутри данных, исключая поля заголовка и адреса.
boot += int(flash and address == 0x33FFF6 and count == 2) # Аппаратный переход должен присутствовать ровно один раз в Flash-таблице.
loaded.update((address + index, value) for index, value in enumerate(struct.unpack("<" + "H" * count, payload))) # Фиксирует реальные 16-битные слова, которые RS-загрузчик запрограммирует во Flash.
found += sum(payload[index:index + 24] == expected for index in range(0, len(payload) - 23, 2)) # Учитывает лишь выровненные на слово экземпляры 12 регистров firmware-info.
offset += 4 + count * 2 # Переходит за полный адрес и полезные слова к следующему размеру.
raise ValueError("Missing SCI8 terminator") # Исчерпание файла без нулевого слова никогда не считается успешной конверсией.
def verify_package(image, metadata): # Связывает BIN, manifest и SHA-256 с тем же prebuild и аппаратной EEPROM.
manifest = json.loads(image.with_suffix(image.suffix + ".manifest.json").read_text(encoding="utf-8")) # Один контракт SHA/паспорта обслуживает отдельные RAM BIN, Flash COFF и Flash SCI8.
data = image.read_bytes() # Повторно считывает точные октеты перед постоянной публикацией.
digest = hashlib.sha256(data).hexdigest() # Вычисляет контрольную сумму независимо от общего seal.
if manifest.get("file") != image.name or manifest.get("size_bytes") != len(data) or manifest.get("sha256") != digest: # Требует согласованности имени, размера и содержимого файла.
raise ValueError("BIN does not match its manifest") # Не публикует повреждённый либо перепутанный комплект.
if any(manifest.get(key) != value for key, value in metadata.items()): # Проверяет также eeprom, физическую границу, версию и происхождение компилируемых templates.
raise ValueError("Manifest does not match prebuild identity/EEPROM") # Один лишь корректный SHA не разрешает паспорт другого аппаратного варианта.
if image.with_suffix(image.suffix + ".sha256").read_text(encoding="utf-8").strip() != f"{digest} {image.name}": # Общий sidecar содержит SHA и точное имя через два пробела.
raise ValueError("Invalid BIN SHA-256 sidecar") # Требует целостности всех трёх публикуемых файлов.
def verify_linker_profile(map_path, profile): # Сверяет аппаратную настройку Python с фактическими абсолютными символами линкера.
text = map_path.read_text(encoding="utf-8-sig") # MAP текущей конфигурации является обязательным результатом успешной линковки.
expected = {"_EEPROM_TOTAL_BYTES": profile["eeprom_bytes"], "_EEPROM_FIRMWARE_LIMIT_BYTES": profile["max_image_bytes"], "_EEPROM_PARAMS_START_WORD": profile["max_image_bytes"] // 2} # Символы разделяют физические октеты и адресуемые слова C28x.
for name, value in expected.items(): # Проверяет ёмкость, конец прошивки и начало сохраняемых параметров до запуска hex2000.
values = re.findall(r"^\s*(?:\w+\s+)?([0-9a-fA-F]{8})\s+" + re.escape(name) + r"\b", text, re.M) # Принимает формат глобальных TI-символов с номером страницы либо признаком ABS.
if not values or any(int(item, 16) != value for item in values): # Повторные списки MAP обязаны описывать один и тот же профиль.
raise ValueError(f"Linker EEPROM profile mismatch: {name}") # Выявляет define, переданный только компилятору или только линкеру, и устаревшую карту.
def verify_flash_coff(image, compiler): # Проверяет реальные секции COFF официальным TI-декодером и возвращает точные Flash-слова загрузки.
tool = compiler / "bin/ofd2000.exe" # Разбирает формат тем же установленным C2000 toolchain, который создал OUT.
document = ET.fromstring(subprocess.check_output([str(tool), "-x", str(image)], stderr=subprocess.STDOUT)) # XML содержит фактические адреса и двоичные данные секций, а не неподтверждённую соседнюю MAP.
coff = document.find(".//ti_coff") # Вариант предназначен строго для COFF ABI F28335.
if coff is None or coff.findtext("file_header/target_id") != "0x9d" or coff.findtext("file_header/exec") != "true": # Не принимает объект другого процессора, ELF либо незалинкованный модуль.
raise ValueError("Expected executable C2000 COFF Flash image") # Явно отделяет JTAG Flash OUT от обычного RAM OUT и чужих архитектур.
sections = {section.findtext("name"): section for section in coff.findall("section")} # Именованные выходные секции соответствуют отдельной Flash-карте линкера 167.
required = {".text": (0x300000, 0x33FF80, 0x300000, 0x33FF80), "codestart": (0x33FFF6, 0x33FFF8, 0x33FFF6, 0x33FFF8), "flash_rs": (0x8000, 0xC000, 0x300000, 0x33FF80), "ramfuncs": (0xC000, 0xD000, 0x300000, 0x33FF80)} # OFD COFF называет physical адресом RUN, virtual — LOAD; диапазоны выражены словами C28x.
for name, (run_begin, run_end, load_begin, load_end) in required.items(): # Наличие автономного RAM-загрузчика и InitFlash обязательно для выпуска Flash-варианта.
section = sections.get(name) # Отсутствующая обязательная секция не маскируется другими именами или символами MAP.
if section is None: # Проверяет отдельную карту и startup, а не одно лишь расположение основного кода.
raise ValueError("Missing Flash COFF section: " + name) # Не выдаёт RAM COFF за пригодный для автономной Flash.
run, load, size = (int(section.findtext(field, "0"), 0) for field in ("physical_addr", "virtual_addr", "raw_data_size")) # Длина OFD задана физическими октетами, оба адреса — 16-битными словами CPU.
if not size or size % 2 or not run_begin <= run < run + size // 2 <= run_end or not load_begin <= load < load + size // 2 <= load_end or (name == "codestart" and size != 4): # boot-переход имеет ровно два слова; прочие секции целиком помещаются в разрешённые LOAD/RUN-области.
raise ValueError("Invalid Flash COFF LOAD/RUN section: " + name) # Отвергает stale linker, размещение API во Flash во время стирания и аппаратный вход вне 0x33FFF6.
loaded = {} # Сопоставляет слова всех загружаемых Flash-секций с будущей SCI8-таблицей RS.
for section in sections.values(): # Служебные debug/copy-секции адреса ноль в программирование не входят.
address = int(section.findtext("virtual_addr", "0"), 0) # Для COFF TI это загрузочный адрес содержимого секции.
raw = section.findtext("raw_data") # Пустая .ebss, NOLOAD или DSECT не задаёт байтов прошивки.
if raw is None or not 0x300000 <= address < 0x340000: # Не включает отладочные записи, ROM-таблицы и рабочую RAM в образ Flash.
continue # Права оставшихся загружаемых Flash-слов дополнительно проверит общий SCI8-валидатор.
data = base64.b64decode(raw) # Получает именно те октеты, которые записаны в COFF и должны оказаться в Flash SCI8.
if len(data) % 2: # Любая программируемая секция C28x должна содержать целые 16-битные слова.
raise ValueError("Odd Flash COFF section size") # Не допускает неоднозначную упаковку образа RS.
for index, value in enumerate(struct.unpack("<" + "H" * (len(data) // 2), data)): # Восстанавливает little-endian представление каждого физического слова COFF.
if address + index in loaded: # Два выходных диапазона не должны программировать одно слово повторно.
raise ValueError("Overlapping Flash COFF load sections") # Проверяет реальный OUT независимо от доверия к линкерной карте.
loaded[address + index] = value # Сохраняет ожидаемые данные для независимого сравнения с hex2000.
entry = int(coff.findtext("optional_file_header/entry_point", "0"), 0) # Получает реальную точку входа C runtime из заголовка COFF.
if entry not in loaded or not 0x300000 <= entry < 0x33FF80: # C startup должен находиться в полностью загружаемой исполняемой Flash.
raise ValueError("Flash COFF entry is not loaded") # Исключает OUT со старой RAM-точкой входа или отрезанным startup.
return entry, loaded # Полное сравнение с SCI8 обнаружит конверсию другого OUT, потерю секции или подмену одного слова.
def verify_flash_release(stage, artifact, metadata, compiler, identity, required=False): # Проверяет и отдельно маркирует Flash COFF/SCI8 в staged-выпуске до любого постоянного копирования.
base = Path(artifact.replace("\\", "/")).name # Использует исходное имя артефакта CCS без директории и расширения OUT.
coff, serial = stage / (base + "_flash.out"), stage / (base + "_flash_rs.bin") # JTAG COFF и RS SCI8 являются разными формами одного отдельного Flash-варианта.
if not coff.exists() and not serial.exists() and not required: # Проекты без build_flash.py и host-фикстуры сохраняют прежний защищённый выпуск RAM.
return # Наличие обычного RAM BIN само по себе не объявляет поддержку Flash.
if not coff.is_file() or not serial.is_file(): # Сборка Flash должна выпустить оба согласованных артефакта.
raise ValueError("Incomplete Flash COFF/SCI8 release") # Не экспортирует один старый OUT вместе с новым RAM-выпуском.
verify_linker_profile(coff.with_suffix(".map"), metadata) # Flash использует физическую EEPROM для параметров и не меняет её профиль при размещении кода внутри CPU.
if verify_flash_coff(coff, compiler) != verify_embedded(serial, metadata, flash=True): # Сравнивает entry и каждое загрузочное слово официального COFF с результатом hex2000.
raise ValueError("Flash SCI8 does not match its COFF") # Отклоняет stale конверсию даже при правильных независимых SHA-файлах.
pairs = [(path, "COFF") for path in stage.rglob(coff.name)] + [(path, "SCI8-FLASH") for path in stage.rglob(serial.name)] # Включает плоские результаты и все их копии внутри текущего архива.
for path, kind in pairs: # Сначала проверяет весь исходный комплект, не меняя даже временных паспортов при первом отказе.
verify_package(path, metadata) # Общий postcompile обязан сохранить текущие version/EEPROM/SHA для каждого результата.
if path.read_bytes() != (coff if kind == "COFF" else serial).read_bytes(): # Архивированная копия должна быть побайтно тем же проверенным вариантом.
raise ValueError("Flash archive differs from staged release") # Не позволяет экспортировать чужой Flash внутри корректного RAM-архива.
with tempfile.TemporaryDirectory(prefix=".variant-", dir=stage) as directory: # Вариантные JSON нужны только для подписи разных форматов и не попадают в постоянный выпуск.
metadata_path = Path(directory) / "metadata.json" # Общий seal принимает обычный путь и не требует изменения соседнего templates.
for path, kind in pairs: # Только проверенные Flash-файлы получают явный признак исполнения и транспортный формат.
specific = dict(metadata, execution="FLASH", image_format=kind) # eeprom остаётся M95M02/AT25256B: исполнение Flash не подменяет физический тип сохраняемых параметров.
metadata_path.write_text(json.dumps(specific, ensure_ascii=False), encoding="utf-8") # Сохраняет все поля происхождения и ручную версию из общего prebuild.
identity.seal(path, metadata_path) # Переподписывает конкретный COFF либо SCI8 с явным execution=FLASH только после проверки байтов.
verify_package(path, specific) # Подтверждает согласованность готового вариантного manifest и SHA перед публикацией.
def convert(compiler, build_dir, artifact, metadata_path, stage, tools_templates): # Оборачивает общий postcompile без передачи постоянного output либо export.
script = tools_templates / "tools/firmware-build/ccs/postcompile.bat" # Общая BAT-точка остаётся в полной рабочей библиотеке инструментов.
if not script.is_file(): # Отсутствие зависимости не должно возвращать прежний выпуск как новый.
raise FileNotFoundError(script) # Указывает конкретную недостающую точку конверсии.
artifact_path = Path(artifact) # CCS допускает абсолютный OUT-базис либо имя относительно BuildDirectory.
source = Path(str(artifact_path if artifact_path.is_absolute() else build_dir / artifact_path) + ".out") # Выбирает только свежий RAM COFF текущего вызова.
with tempfile.TemporaryDirectory(prefix=".input-", dir=stage) as directory: # Общий Flash postbuild копирует дополнительные результаты рядом с входным OUT; это место тоже должно быть временным.
staged_source = Path(directory) / source.name # Не позволяет общей библиотеке публиковать непроверенный Flash OUT рядом с настоящим RAM-артефактом.
shutil.copy2(source, staged_source) # Даёт конвертору точную копию текущего OUT без изменения оригинала или его каталога.
subprocess.run(["cmd.exe", "/d", "/c", "call", str(script), "--identity", str(compiler), str(build_dir), str(staged_source.with_suffix("")), str(metadata_path), str(stage)], check=True) # И архивы, и служебные копии всех вариантов остаются временными до локальных проверок.
def publish(project, templates, tools_templates, compiler, build_dir, artifact, map_path, output, export=None, eeprom=None): # Выпускает только проверенные результаты конкретной конфигурации CCS.
project, templates, tools_templates = project.resolve(), templates.resolve(), tools_templates.resolve() # Фиксирует выбранные исходники и инструменты независимо от рабочего каталога терминала.
build_dir, compiler = build_dir.resolve(), compiler.resolve() # Общая BAT-точка получает полные пути текущих компилятора и Debug/Release.
metadata_path = build_dir / "generated/firmware_identity.json" # Использует паспорт, подготовленный до компиляции этой конфигурации.
metadata = json.loads(metadata_path.read_text(encoding="utf-8")) # Отсутствующий prebuild останавливает работу до создания выпуска.
profile = read_profile(project, templates, eeprom) # Повторно читает текущий выбор EEPROM и общий резерв параметров.
settings = json.loads((project / "firmware_identity.json").read_text(encoding="utf-8-sig")) # Сверяет product и target с действующим проектом.
expected = {"product": settings["product"], "target": settings["target"], "version": read_version(project / "firmware_version.h"), "templates_root": str(templates), **profile} # Ручной номер и аппаратный вариант обязаны совпадать с prebuild.
if any(metadata.get(key) != value for key, value in expected.items()): # Не подписывает старый OUT после изменения версии, EEPROM либо компилируемой зависимости.
raise ValueError("Configuration changed: run prebuild and rebuild before publishing") # Требует пересборку вместо подмены старых метаданных.
verify_linker_profile(map_path.resolve(), profile) # Проверяет аппаратный выбор линкера, который нельзя вывести только из Python-конфигурации.
with tempfile.TemporaryDirectory(prefix=".verified-release-", dir=build_dir) as directory: # Единственное место, куда общий postcompile пишет до подтверждения пригодности BIN.
stage = Path(directory) # Изолирует все архивы и промежуточные HEX от постоянных каталогов выпуска.
convert(compiler, build_dir, artifact, metadata_path, stage, tools_templates) # Любая ошибка TI или общей библиотеки прерывает публикацию.
image_name = Path(artifact.replace("\\", "/")).name + "_" + metadata["build_id"] + ".bin" # Повторяет штатное имя конкретного образа, не сканируя старые BIN.
image = stage / image_name # Проверяет только результат текущего вызова конвертора.
verify_embedded(image, metadata) # Обязательные проверки EEPROM, полной SCI8-таблицы и реально встроенных 12 слов.
verify_package(image, metadata) # Сверяет SHA и весь паспорт, включая физический профиль EEPROM.
for archived in stage.rglob(image_name): # Проверяет также копии внутри текущего архива перед их публикацией или export.
verify_package(archived, metadata) # Архив обязан содержать те же байты и профиль, что плоский выпуск.
spec = importlib.util.spec_from_file_location("release_identity_tools", tools_templates / "c/firmware-info/tools/identity.py") # Подключает существующий формат именования и экспорта архива без копирования реализации.
identity = importlib.util.module_from_spec(spec) # Создаёт отдельный модуль инструментов, не подменяя компилируемую зависимость паспорта.
spec.loader.exec_module(identity) # Ошибка подключения остаётся внутри временного этапа до постоянных файлов.
verify_flash_release(stage, artifact, metadata, compiler, identity, required=(build_dir.parent / "build_flash.py").is_file()) # Отдельная Flash-сборка требует собственного безопасного COFF/SCI8 и не ослабляет прежние RAM-проверки.
archive_name = identity.build_folder_name(metadata) # Сохраняет принятые имена архивов с ревизией, признаком изменений и временем UTC+3.
if not (stage / archive_name / image_name).is_file(): # Полный архив является обязательным результатом общей конверсии, а не необязательной старой папкой.
raise ValueError("Common postcompile did not produce the current build archive") # Не публикует неполный комплект без OUT, MAP и паспорта текущего выпуска.
output = output.resolve() # Постоянный каталог появляется только после всех проверок staged-комплекта.
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: # Плоские BIN/HEX и их паспорта остаются совместимыми с прежними потребителями.
shutil.copy2(item, output / item.name) # Сохраняет имена и содержимое прошедшего проверки комплекта.
verify_package(output / image_name, metadata) # Подтверждает отсутствие повреждения при копировании на постоянный диск.
if export is not None: # Копирование в каталог GUI выполняется последним и лишь для прошедшего проверки архива.
identity.export_build(output / archive_name, export.resolve(), image_name) # Использует общий экспорт уже проверенного локального выпуска.
print(f"[firmware-build] Verified {profile['eeprom']} BIN: {output / image_name}") # Показывает пригодный аппаратный профиль и точный путь конечного файла.
return output / image_name # Даёт тестам и внешним средствам однозначный результат без поиска по времени.
if __name__ == "__main__": # Импорт для host-тестов не запускает TI и не меняет каталоги выпуска.
parser = argparse.ArgumentParser(description=__doc__) # Описывает назначение защищённого postbuild в сообщениях CCS.
for argument in ("project", "templates", "tools-templates", "compiler", "build-dir", "map", "output"): # Каждый обязательный путь задаётся настройками активной конфигурации CCS.
parser.add_argument("--" + argument, type=Path, required=True) # Проверяет обязательность аргументов до любых файловых операций.
parser.add_argument("--artifact", required=True) # Получает прежний BuildArtifactFileBaseName с допустимым абсолютным путём.
parser.add_argument("--export", type=Path) # Сохраняет возможность явно отключить экспорт при ручной проверке или в тестах.
parser.add_argument("--eeprom", choices=("AT25256B", "M95M02")) # Явный CLI-профиль должен совпадать с prebuild и линкерными define.
arguments = vars(parser.parse_args()) # argparse переводит дефисы имён аргументов в подчёркивания параметров функции.
arguments["map_path"] = arguments.pop("map") # Отделяет путь карты от встроенного имени map, не меняя CLI CCS.
try: # Ошибочный образ возвращает сборке явный ненулевой код.
publish(**arguments) # Выполняет проверку и публикацию только после разбора полного набора аргументов.
except (OSError, ValueError, KeyError, TypeError, struct.error, subprocess.CalledProcessError) as error: # Включает ошибки формата, инструментов и отсутствующие файлы staged-комплекта.
parser.exit(1, f"[firmware-build] ERROR: {error}\n") # Не маскирует отказ проверок сообщением об успешном выпуске.
+107
View File
@@ -0,0 +1,107 @@
#include <stdint.h> /* Фиксирует ширину таймера и аппаратных полей eCAN независимо от разрядности host-компилятора. */
#include <stdio.h> /* Выводит точную строку нарушенного сценария вместо молчаливого неверного результата. */
#include <string.h> /* Изолирует независимые случаи очисткой модели аппаратуры и данных. */
typedef uint16_t Uint16; typedef uint32_t Uint32; /* Повторяет 16- и 32-битные аппаратные типы TI C28x. */
#define READY_FREQ 1000U /* Один такт Timer1 равен одной миллисекунде, как в действующей прошивке 166. */
#define CANPOWSE 20U /* Прежний базовый интервал обнаружения обновлений данных составляет 20 мс. */
#define ANSWER_LEN 128 /* Как в package.h, технологическая карта содержит ровно R0–R127 без дополнительного запаса для ошибочного чтения. */
#define interrupt /* Host-модель вызывает настоящий ISR как обычную функцию без атрибутов процессора TI. */
#define EALLOW ((void)0) /* Защищённый доступ аппаратных регистров отдельно проверяется настоящим компилятором TI. */
#define EDIS ((void)0) /* Модель не подменяет логику изменения битов тайм-аута mailbox. */
#define EINT ((void)0) /* Тесты явно выбирают момент аппаратного прерывания, исключая недетерминированные потоки. */
#define DINT ((void)0) /* Сценарий проверяет восстановление маски, не симулируя планировщик ОС. */
#define M_INT9 0x0100U /* Девятая группа PIE обслуживает eCAN в production-обработчике. */
#define MINT9 0x0100U /* Сохраняет аппаратную маску CPU INT9 при обработке ошибки передачи. */
#define MG96 0x00DFU /* Маска соответствует подавлению вложенного INTx6 в старом ISR проекта. */
struct REG { Uint32 all; }; /* Даёт production-коду настоящие 32-битные поля регистров eCAN. */
struct ECAN_REGS { struct REG CANTA, CANAA, CANTRR, CANTRS, CANTOC, CANTOS; Uint32 CANTSC; }; /* Моделирует только используемый жизненный цикл штатного mailbox. */
static volatile struct ECAN_REGS ECanaRegs; /* Volatile сохраняет все наблюдаемые записи production-функций при оптимизации O3. */
static struct { struct { struct REG MDH, MDL; } MBOX0; } ECanaMboxes; /* Восемь октетов передаваемого кадра наблюдаются независимо от кэша алгоритма. */
static struct { Uint32 InterruptCount; } CpuTimer1; /* Счётчик переполняется в точности через 2^32 тактов. */
static struct { struct { Uint16 all; } PIEIER9; union { unsigned all; struct { unsigned reserved:8, ACK9:1; } bit; } PIEACK; } PieCtrlRegs; /* Сохраняет реальное положение PIEACK9 в маске подтверждения IRQ. */
static Uint16 IER; static unsigned int irq_mask; /* Позволяет проверить возврат исходного состояния прерываний после короткого снимка данных. */
static int wait, CanTimeOutErrorTR, Mode, Desk, modbus[ANSWER_LEN]; /* Использует настоящий размер массива технологических слов и наблюдаемые параметры узла. */
static unsigned int Maska[2][8]; /* Каждому потоку соответствует восемь 16-битных масок разрешённых регистров. */
#define Cancount (modbus + 0x60) /* Как в package.h, R96 и R97 определяют включение двух потоков данных. */
static unsigned int __disable_interrupts(void) { unsigned int previous = irq_mask; irq_mask = 1U; return previous; } /* Имитирует сохранение исходной маски IRQ даже при вложенном чтении Timer1. */
static void __restore_interrupts(unsigned int previous) { irq_mask = previous; } /* Выявляет ошибочное безусловное разрешение прерываний отправителем. */
static void led1_toggle(void) {} /* Индикация не влияет на подтверждение доставки или период таймера. */
#include "can_telemetry_impl.h" /* Компилирует точный production-алгоритм, а не отдельную копию реализации в тесте. */
/* TIMEOUT_ISR */
#define CHECK(condition) do { if (!(condition)) { printf("FAIL line %d: %s\n", __LINE__, #condition); return 1; } } while (0) /* Проверка остаётся активной при оптимизации и показывает нарушенное ожидание. */
static Uint32 frames_hi[2048], frames_lo[2048], frames_at[2048]; static unsigned int count; /* Журнал хранит только действительно запущенные аппаратные кадры и момент их передачи. */
static void hardware_ack(void) { ECanaRegs.CANTRS.all &= ~1UL; ECanaRegs.CANTRR.all &= ~1UL; ECanaRegs.CANTA.all = 1UL; ECanaRegs.CANAA.all = 0UL; } /* Завершает именно mailbox 0 успешным аппаратным ACK. */
static void prepare(unsigned int mode, unsigned int desk) { /* Начинает независимый сценарий, включая имитацию повторного запуска программы. */
memset((void *)&ECanaRegs, 0, sizeof(ECanaRegs)); memset(&ECanaMboxes, 0, sizeof(ECanaMboxes)); memset(&PieCtrlRegs, 0, sizeof(PieCtrlRegs)); /* Сбрасывает только модель периферии между испытаниями. */
memset(modbus, 0, sizeof(modbus)); memset(Maska, 0, sizeof(Maska)); CpuTimer1.InterruptCount = 0U; count = 0U; irq_mask = 0U; /* Устанавливает известное начальное содержимое карты и таймера. */
Mode = mode; Desk = desk; Cancount[0] = 10; Cancount[1] = 101; IER = M_INT9; PieCtrlRegs.PIEIER9.all = 0x0030U; CanTimeOutErrorTR = 0; /* Периоды 10 и 101 проверяют независимость нового повтора от старых делителей. */
CAN_telemetry_init(); /* Первый снимок после каждого запуска должен отправляться независимо от содержимого старых кэш-массивов. */
} /* Аппаратный ACK в каждом сценарии задаётся явно. */
static int pump(void) { /* Выдаёт всю уже готовую очередь с ACK между кадрами, не продвигая тестовое время. */
unsigned int limit; /* Ограничивает итерации для обнаружения зависания на пустой маске или неизменном пакете. */
for (limit = 0U; limit < 512U; ++limit) { /* Полная карта из двух потоков требует значительно меньше 512 аппаратных кадров. */
CAN_telemetry_service(); /* Обслуживает реальную очередь, heartbeat и индивидуальные периоды подтверждённых данных. */
CHECK(irq_mask == 0U); /* После любого пути отправки прерывания должны иметь первоначальное состояние. */
if (!(ECanaRegs.CANTRS.all & 1UL)) return 0; /* Отсутствие нового TX означает завершённую выдачу готовых кадров. */
CHECK(count < 2048U); /* Исключает скрытое переполнение тестового журнала. */
frames_hi[count] = ECanaMboxes.MBOX0.MDH.all; frames_lo[count] = ECanaMboxes.MBOX0.MDL.all; frames_at[count++] = CpuTimer1.InterruptCount; /* Запоминает точные данные mailbox, не используя внутренний кэш production-кода как эталон. */
hardware_ack(); /* Только успешное подтверждение разрешает алгоритму подавить следующий неизменный пакет. */
} /* Бесконечное повторение выявляется как ошибка конечной модели. */
CHECK(0); /* Даже при пустой маске или неподтверждённом адресе сервис обязан возвращаться без блокировки. */
return 1; /* Сохраняет однозначный код ошибки для компилятора C. */
} /* Все вызовы pump выполняются с фиксированным временем, поэтому сравнения периодов точны. */
static unsigned int address_at(unsigned int index) { return (unsigned int)((frames_hi[index] >> 16) & 0x1FFFU); } /* Декодирует существующий маркер BALZAM независимо от внутренней реализации отправителя. */
static unsigned int count_address(unsigned int address) { unsigned int index, found = 0U; for (index = 0U; index < count; ++index) if (address_at(index) == address) ++found; return found; } /* Считает реальные кадры выбранного технологического или heartbeat-адреса. */
int main(void) { /* Проверяет требования пользователя, границы карты и ошибки аппаратного TX на настоящем коде прошивки. */
unsigned int mode, before, index; Uint32 started; /* Сценарии используют независимые ожидаемые периоды в миллисекундах. */
for (mode = 1U; mode <= 9U; ++mode) { /* Сравнение данных и повтор 1,3 с применяются ко всем аппаратным Mode, включая задатчик. */
prepare(mode, mode == 8U ? 4U : 0U); Maska[0][0] = 1U; modbus[0] = 9; modbus[1] = 12; modbus[2] = -1; CHECK(pump() == 0); /* Начальный пакет должен передаться сразу в прежнем порядке трёх слов. */
CHECK(count == 1U && frames_hi[0] == 0xE0000009UL && frames_lo[0] == 0x000CFFFFUL); /* Отрицательное 16-битное значение не портит упаковку и старший регистр. */
CpuTimer1.InterruptCount = 999U; CHECK(pump() == 0 && count == 1U); /* Неизменившиеся данные не создают лишних кадров до секундного heartbeat. */
CpuTimer1.InterruptCount = 1000U; CHECK(pump() == 0 && count == 2U && address_at(1U) == 0x100U); /* Heartbeat появляется ровно через одну секунду, независимо от обоих Cancount. */
CHECK((frames_hi[1] & 0xFFFFUL) == 1U && frames_lo[1] == ((Uint32)Mode << 16 | (unsigned int)Desk)); /* Отдельный heartbeat содержит счётчик секунд, Mode и Desk. */
CpuTimer1.InterruptCount = 1299U; CHECK(pump() == 0 && count_address(0U) == 1U); /* Технологический пакет не повторяется раньше индивидуального срока. */
CpuTimer1.InterruptCount = 1300U; CHECK(pump() == 0 && count_address(0U) == 2U); /* Неизменные данные повторяются на границе 1,3 с без искусственного изменения состояния планировщика тестом. */
CHECK(frames_at[count - 1U] == 1300U && frames_hi[count - 1U] == frames_hi[0]); /* Повторяет сами данные, а не только отдельный heartbeat. */
} /* Завершает проверку одинакового поведения всех Mode. */
prepare(5U, 0U); Maska[0][0] = 1U; Maska[1][0] = 8U; modbus[0] = 1; modbus[3] = 2; CHECK(pump() == 0 && count == 2U); /* Два потока получают отдельные пакеты и подтверждения без ожидания старых делителей. */
CpuTimer1.InterruptCount = 40U; modbus[1] = 77; CHECK(pump() == 0 && count == 3U && address_at(2U) == 0U); /* Изменение второго слова пакета отправляется до истечения 1,3 с. */
CpuTimer1.InterruptCount = 60U; modbus[2] = 88; CHECK(pump() == 0 && count == 4U && (frames_lo[3] & 0xFFFFUL) == 88U); /* Изменение третьего слова тоже обнаруживается и не подавляется кэшем первого регистра. */
CpuTimer1.InterruptCount = 1300U; CHECK(pump() == 0 && count_address(3U) == 2U && count_address(0U) == 3U); /* Независимый пакет повторяется через 1,3 с, а обновлённый пакет R0 ещё не достиг своего нового срока. */
CpuTimer1.InterruptCount = 1360U; CHECK(pump() == 0 && count_address(0U) == 4U); /* Период R0 отсчитывается заново от ACK последнего изменённого снимка. */
prepare(8U, 4U); Maska[0][1] = 1U; Maska[1][0] = 15U; ECanaRegs.CANTRS.all = 1U; ECanaMboxes.MBOX0.MDH.all = 0x12345678UL; CAN_telemetry_service(); /* Занятый внешним запросом mailbox нельзя менять или считать доставленным. */
CHECK(ECanaMboxes.MBOX0.MDH.all == 0x12345678UL && can_scan_due == 1U); /* Оба потока сохраняют готовность до физического освобождения передатчика. */
ECanaRegs.CANTRS.all = 0U; CHECK(pump() == 0 && count_address(16U) == 1U && count_address(0U) == 1U && count_address(3U) == 1U); /* После занятости не теряются R0–R2 и R3–R5 медленного потока. */
prepare(5U, 0U); Maska[0][0] = 1U; CAN_telemetry_service(); CHECK(wait && !(can_valid[0] & 1U)); /* Постановка первого пакета в TX ещё не означает успешную доставку. */
ECanaRegs.CANTA.all = 0U; ECanaRegs.CANTOS.all = 1U; ECanaRegs.CANTOC.all = 5U; CANa_reset_err(); /* Воспроизводит аппаратный timeout, пока отдельный контроль служебного mailbox тоже включён. */
CHECK(can_tx_failed && CanTimeOutErrorTR == 1 && ECanaRegs.CANTRR.all == 1U && ECanaRegs.CANTOC.all == 4U); /* ISR помечает отказ и отменяет только штатный TX без выключения служебного контроля. */
ECanaRegs.CANTOS.all &= ECanaRegs.CANTOC.all; ECanaRegs.CANTRS.all = ECanaRegs.CANTRR.all = 0U; ECanaRegs.CANAA.all = 0U; CpuTimer1.InterruptCount = 10U; CAN_telemetry_service(); /* Отмена завершена без CANAA: отсутствие этого флага не должно навсегда заблокировать очередь. */
CHECK(!wait && can_backoff && !(can_valid[0] & 1U)); /* Неудачный снимок не становится подтверждённым и остаётся кандидатом следующей попытки. */
CpuTimer1.InterruptCount = 1009U; modbus[0] = 42; CAN_telemetry_service(); CHECK(!(ECanaRegs.CANTRS.all & 1U)); /* До окончания секундной паузы после ошибки повтор не засоряет CAN. */
CpuTimer1.InterruptCount = 1010U; CHECK(pump() == 0 && count_address(0U) == 1U && (frames_hi[count - 1U] & 0xFFFFUL) == 42U); /* После паузы повторяет актуальное изменение и отдельно ожидающий heartbeat. */
prepare(5U, 0U); Maska[0][0] = 1U; CAN_telemetry_service(); ECanaRegs.CANTA.all = 0U; CpuTimer1.InterruptCount = 100U; CAN_telemetry_service(); /* Потерянный timeout IRQ страхуется реальным временем Timer1 через 100 мс. */
CHECK(can_tx_failed && ECanaRegs.CANTRR.all == 1U && CanTimeOutErrorTR == 1); /* Программная отмена не ждёт аппаратного CANAA в блокирующем цикле. */
CAN_telemetry_service(); CHECK(CanTimeOutErrorTR == 1); /* Длительная аппаратная отмена не увеличивает счётчик ошибки на каждой итерации main. */
prepare(5U, 0U); Maska[0][0] = 1U; CHECK(pump() == 0); CpuTimer1.InterruptCount = 20U; modbus[0] = 2; CAN_telemetry_service(); ECanaRegs.CANTA.all = 0U; ECanaRegs.CANTRS.all = 0U; ECanaRegs.CANAA.all = 1U; CAN_telemetry_service(); /* Изменение, отменённое после старого успешного снимка, не должно затереть подтверждённый кэш. */
CHECK(can_last_hi[0] == 0xE0000000UL && can_backoff); /* Только реально доставленный прежний пакет остаётся подтверждённым. */
prepare(5U, 0U); CHECK(pump() == 0 && count == 0U); CpuTimer1.InterruptCount = 3500U; CHECK(pump() == 0 && count == 1U && address_at(0U) == 0x100U && (frames_hi[0] & 0xFFFFU) == 3U); /* Пустые маски не зависают; после задержки main отправляется последний heartbeat без пачки пропущенных секунд. */
CHECK(pump() == 0 && count == 1U); /* Повторная итерация main не дублирует уже поставленный heartbeat. */
prepare(5U, 0U); Maska[0][0] = 1U; Cancount[0] = 0; Cancount[1] = 0; CHECK(pump() == 0 && count == 0U); CpuTimer1.InterruptCount = 1000U; CHECK(pump() == 0 && count == 1U && address_at(0U) == 0x100U); /* Отключённые потоки не отключают независимый heartbeat узла. */
prepare(5U, 0U); Maska[0][7] = 0x8000U; modbus[127] = 0x55AA; CHECK(pump() == 0 && count == 1U && frames_hi[0] == 0xE07F55AAUL && frames_lo[0] == 0U); /* Конец карты R127 дополняется нулями без чтения data[128] и data[129]. */
prepare(5U, 0U); for (index = 0U; index < 8U; ++index) Maska[0][index] = 0xFFFFU; CHECK(pump() == 0 && count == 43U); /* Плотная маска сохраняет прежние 43 трёхсловных пакета вместо лишних 128 перекрывающихся кадров. */
for (index = 0U; index < count; ++index) CHECK(address_at(index) == index * 3U); /* Первый полный снимок покрывает ровно R0, R3, ... R126 в установленном формате. */
CpuTimer1.InterruptCount = 1300U; CHECK(pump() == 0 && count_address(0U) == 2U && count_address(126U) == 2U && count == 87U); /* Готовая очередь выдаёт всю карту за период, а не один кадр маски каждые 1,3 с. */
prepare(5U, 0U); CpuTimer1.InterruptCount = 0xFFFFFF00UL; started = CpuTimer1.InterruptCount; Maska[0][0] = 1U; CHECK(pump() == 0); CpuTimer1.InterruptCount = started + 1000U; CHECK(pump() == 0 && count_address(0x100U) == 1U); /* Секундный heartbeat переживает переполнение Timer1. */
CpuTimer1.InterruptCount = started + 1300U; CHECK(pump() == 0 && count_address(0U) == 2U); /* Период повторения данных тоже корректен через 32-битное переполнение. */
prepare(5U, 0U); Maska[0][0] = 1U; CHECK(pump() == 0); before = count; can_valid[7] = 0xFFFFU; can_backoff = can_heartbeat_due = 1U; wait = 1; CAN_telemetry_init(); CHECK(pump() == 0 && count == before + 1U); /* Повторная инициализация отбрасывает грязное состояние и заново отправляет первый снимок. */
prepare(5U, 0U); Maska[0][1] = 1U; Maska[1][0] = 1U; for (index = 0U; index <= 250U; ++index) { CpuTimer1.InterruptCount = index * 20U; modbus[16] = index; CHECK(pump() == 0); } /* Непрерывное изменение быстрого потока не должно вытеснить heartbeat и неизменный пакет другого потока. */
CHECK(count_address(16U) == 251U && count_address(0U) == 4U && count_address(0x100U) == 5U); /* За пять секунд доставляются все изменения, четыре состояния R0 и пять секундных heartbeat. */
prepare(5U, 0U); Maska[0][0] = 1U; CAN_telemetry_service(); CpuTimer1.InterruptCount = 50U; hardware_ack(); CAN_telemetry_service(); /* Подтверждает первый пакет через 50 мс после запуска, а не в нулевой момент передачи. */
CpuTimer1.InterruptCount = 1349U; CHECK(CAN_send(0, modbus, 0) == 2); CpuTimer1.InterruptCount = 1350U; CHECK(CAN_send(0, modbus, 0) == 1); /* Индивидуальные 1300 мс отсчитываются от ACK, поэтому ранний повтор через 1300 мс от запуска запрещён. */
prepare(5U, 0U); ECanaRegs.CANTOC.all = 1U; ECanaRegs.CANTRS.all = 1U; CANa_reset_err(); /* Вызывает фантомный ISR без аппаратного CANTOS0 во время исправной передачи. */
CHECK(!can_tx_failed && !CanTimeOutErrorTR && ECanaRegs.CANTRR.all == 0U && ECanaRegs.CANTOC.all == 1U && PieCtrlRegs.PIEIER9.all == 0x0030U); /* Не отменяет исправный кадр и восстанавливает прежние разрешения обоих CAN IRQ. */
prepare(5U, 0U); Maska[0][0] = 1U; ECanaRegs.CANTRR.all = 1U; ECanaMboxes.MBOX0.MDH.all = 0x11223344UL; CAN_telemetry_service(); CHECK(ECanaMboxes.MBOX0.MDH.all == 0x11223344UL && !wait); /* Даже без CANTRS незавершённая аппаратная отмена запрещает перезапись mailbox. */
prepare(5U, 0U); irq_mask = 1U; CHECK(CAN_send(0, modbus, 0) == 1 && irq_mask == 1U); /* Передатчик сохраняет уже запрещённые IRQ вызывающего кода вместо безусловного разрешения. */
puts("PASS: heartbeat 1 Hz; changed data; per-packet refresh 1300 ms; all modes; masks; ACK, timeout and backoff; wraparound; dirty RAM; bounds"); /* Подтверждает проверку реального production-кода без утверждения о запуске на физической плате. */
return 0; /* Все независимые поведенческие проверки завершились успешно. */
} /* Модель не требует подключения CAN-адаптера и не отправляет команды реальному оборудованию. */
+22
View File
@@ -0,0 +1,22 @@
from pathlib import Path # Находим production-исходники относительно установленного теста либо явно выбранного проверяемого снимка.
import os # Подключаем runtime GCC только дочернему процессу теста.
import re # Извлекаем настоящий ISR с сохранением вложенных условий и без отдельной копии алгоритма ошибки.
import subprocess # Компилируем и выполняем поведенческую модель передатчика.
import sys # Разрешаем проверить изолированный снимок коммита вместо текущего рабочего дерева.
import tempfile # Создаём исполняемый файл проверки только во временном каталоге теста.
test = Path(__file__).resolve().parent # Производственная установка хранит этот файл в tests/can_telemetry.
root = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else test.parents[1] # При обычном запуске проверяет текущий проект прошивки.
source = (root / 'Source/Internal/ecan.c').read_text(encoding='utf-8') # Изменённый production-файл и русские комментарии обязаны быть корректным UTF-8.
clean = re.sub(r'/\*.*?\*/|//[^\n]*', '', source, flags=re.S) # Скобки комментариев не влияют на выделение ISR.
match = re.search(r'interrupt\s+void\s+CANa_reset_err\(void\)\s*\{', clean) # Отличаем определение обработчика от прототипа и регистрации IRQ.
assert match # Отсутствующий обработчик не считается успешной проверкой передачи.
end, depth = match.end(), 1 # Начинаем поиск после внешней открывающей скобки функции.
while depth: depth += (clean[end] == '{') - (clean[end] == '}'); end += 1 # Сохраняем все вложенные аппаратные проверки оригинала.
model = (test / 'model.c').read_text(encoding='utf-8').replace('/* TIMEOUT_ISR */', clean[match.start():end]) # Сама реализация телеметрии подключается напрямую через production-header.
compiler = os.environ.get('CC', 'C:/msys64/mingw64/bin/gcc.exe') # Поддерживаем установленный GCC и явный выбор другого совместимого компилятора.
environment = dict(os.environ); environment['PATH'] = str(Path(compiler).parent) + os.pathsep + environment.get('PATH', '') # Не меняем системный PATH пользователя или настройки CCS.
with tempfile.TemporaryDirectory(prefix='can-telemetry-166-') as directory: # Временные файлы не попадают в источник прошивки или Git-коммит.
cfile, exe = Path(directory) / 'test.c', Path(directory) / 'test.exe' # Отделяем исходник модели и её исполняемый результат.
cfile.write_text(model, encoding='utf-8') # Тестовые комментарии сохраняются в той же кодировке UTF-8.
subprocess.run([compiler, '-std=c99', '-O3', '-Wall', '-Wextra', '-Werror', '-I' + str(root / 'Source/Internal/Include'), str(cfile), '-o', str(exe)], env=environment, check=True) # Все предупреждения нового алгоритма считаются ошибками до создания коммита.
subprocess.run([str(exe)], env=environment, check=True, timeout=10) # Зависание очереди или нарушенный тайминг завершают проверку ошибкой.
+4 -31
View File
@@ -1,31 +1,4 @@
"""Проверяет тайм-аут штатного CAN на настоящих функциях проекта 167.""" # Модель не заменяет испытание аппаратного eCAN.
import os # Передаёт пути библиотек GCC только дочерним процессам.
from pathlib import Path # Находит исходники независимо от рабочего каталога запуска.
import re # Удаляет комментарии и выделяет тела проверяемых функций.
import subprocess # Запускает компилятор и проверяет поведенческий результат модели.
import tempfile # Изолирует сгенерированные файлы от образов прошивки пользователя.
import sys # Позволяет проверить отдельно сохранённую старую версию ecan.c.
root = Path(__file__).resolve().parents[2] # Корень проекта содержит рабочую реализацию eCAN.
source_path = Path(sys.argv[1]) if len(sys.argv) > 1 else root / "Source/Internal/ecan.c" # Дополнительный путь нужен для проверки чувствительности к старой ошибке.
source = re.sub(r"/\*.*?\*/|//[^\n]*", "", source_path.read_text(encoding="utf-8"), flags=re.S) # Комментарии со скобками не участвуют в выделении функций.
def function(name): # Извлекает текущий алгоритм вместо поддержания отдельной копии в тесте.
match = re.search(r"(?:interrupt\s+)?void\s+" + name + r"\([^;{}]*\)\s*\{", source) # Отличает определение от вызова и объявления.
assert match is not None, name # Исчезновение функции должно останавливать проверку.
end, depth = match.end(), 1 # Начинает поиск после открывающей скобки тела.
while depth: # Учитывает вложенные условия до закрытия внешнего блока.
depth += (source[end] == "{") - (source[end] == "}") # Считает только скобки кода без комментариев.
end += 1 # Включает найденную закрывающую скобку в результат.
return source[match.start():end] # Сохраняет все реальные обращения к регистрам и условия.
model = Path(__file__).with_name("model.inc").read_text(encoding="utf-8") # Загружает минимальное аппаратное окружение и сценарии потери ACK.
model = model.replace("/* PRODUCTION */", function("CAN_send") + "\n" + function("CANa_reset_err")) # Проверяет передачу и прерывание в одном жизненном цикле.
compiler = os.environ.get("CC", "C:/msys64/mingw64/bin/gcc.exe") # Использует установленный GCC, сохраняя возможность явно выбрать другой.
environment = dict(os.environ) # Не меняет окружение приложения и проекта CCS.
environment["PATH"] = str(Path(compiler).parent) + os.pathsep + environment.get("PATH", "") # Делает библиотеки MinGW доступными тестовой программе.
with tempfile.TemporaryDirectory(prefix="can-timeout-") as directory: # Удаляет только собственные временные файлы после завершения проверки.
c_path, exe_path = Path(directory) / "test.c", Path(directory) / "test.exe" # Разделяет исходник модели и исполняемый результат.
c_path.write_text(model, encoding="utf-8") # Сохраняет русские пояснения модели в требуемой кодировке.
subprocess.run([compiler, "-std=c99", "-O3", "-Wall", "-Wextra", "-Wno-unused-parameter", "-Werror", str(c_path), "-o", str(exe_path)], env=environment, check=True) # Проверяет реальные функции под оптимизацией и без скрытых предупреждений.
subprocess.run([str(exe_path)], env=environment, check=True, timeout=10) # Зависание и нарушенное условие делают сценарий неуспешным.
"""Проверяет аппаратный timeout и его программную страховку общими сценариями передатчика.""" # После переноса ACK-кэша отказ и секундная пауза проверяются вместе с повтором данных.
from pathlib import Path # Находит соседний тест без зависимости от текущего рабочего каталога.
import runpy # Выполняет тот же набор проверок реального production-header вместо устаревшей копии void CAN_send.
runpy.run_path(str(Path(__file__).resolve().parents[1] / "can_telemetry" / "run_test.py"), run_name="__main__") # Сохраняет прежнюю команду проверки timeout и проверяет также CANAA, фантомный IRQ и backoff.
+68
View File
@@ -0,0 +1,68 @@
"""Проверяет частоту подтверждения R3 на настоящих масках и планировщике 167 после переноса рассылки.""" # Проверка выполняется на ПК без отправки команд плате.
from pathlib import Path # Находит исходники проекта и отдельно подготовленный вариант маски.
import os # Передаёт библиотеки GCC только дочерним процессам проверки.
import re # Извлекает определения и блоки C без комментариев.
import subprocess # Компилирует и исполняет реальные алгоритмы прошивки в модели времени.
import sys # Позволяет проверить старый и исправленный файлы одинаковым сценарием.
import tempfile # Ограничивает результаты компиляции временным каталогом.
root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).resolve().parents[2] # Установленная проверка по умолчанию использует свой проект прошивки.
measure = Path(sys.argv[2]) if len(sys.argv) > 2 else root / 'Source/Internal/measure.c' # Подмена только этого файла позволяет воспроизвести ошибку до применения исправления.
def clean(path): # Удаляет комментарии перед поиском парных скобок C.
raw = path.read_bytes() # Сохраняет возможность чтения старых исходников Windows-1251.
source = raw.decode('utf-8') if _utf8(raw) else raw.decode('cp1251') # Не теряет русские комментарии исходника при выборе кодировки.
return re.sub(r'/\*.*?\*/|//[^\n]*', '', source, flags=re.S) # Скобки в пояснениях не меняют границы блока алгоритма.
def _utf8(raw): # Отличает старую Windows-кодировку от требуемого UTF-8 без замены символов.
try: # Корректный UTF-8 имеет приоритет перед совместимой однобайтовой кодировкой.
raw.decode('utf-8'); return True # Успешное декодирование разрешает чтение современного исходника.
except UnicodeDecodeError: # Старый файл будет прочитан в его исходной кодировке.
return False # Не выдаёт повреждённые комментарии за исходный текст.
def block(source, start): # Выделяет настоящий блок с вложенными условиями.
cursor, depth = source.index('{', start) + 1, 1 # Начинает после открывающей скобки внешнего блока.
while depth: # Ищет соответствующую закрывающую скобку.
depth += (source[cursor] == '{') - (source[cursor] == '}') # Учитывает только скобки очищенного C-кода.
cursor += 1 # Включает завершающую скобку в извлечённый текст.
return source[start:cursor] # Проверка не поддерживает отдельную копию алгоритма прошивки.
package = clean(root / 'Source/Internal/Include/package.h') # Значения режимов и индексов потоков берутся из текущего проекта 167.
names = ('dsk_COMM', 'dsk_BKSD', 'dsk_BKST', 'dsk_EPLT', 'dsk_SHKF', 'dsk_LOAD', 'm_FAST', 'm_SLOW') # Модели нужны только константы, используемые масками.
defines = '\n'.join(re.search(r'^#define\s+' + name + r'\s+\d+', package, re.M).group() for name in names) # Не дублирует номера плат в тестовой карте.
measure_source = clean(measure) # Выбирает проверяемую версию Init_packMask.
mask_function = block(measure_source, measure_source.index('void Init_packMask(')) # Состав быстрой и медленной рассылки полностью берётся из прошивки.
ecan = clean(root / 'Source/Internal/ecan.c') # Реальный ISR завершает ту же передачу, что и новый сервис телеметрии.
match = re.search(r'interrupt\s+void\s+CANa_reset_err\(void\)\s*\{', ecan) # Находит определение обработчика ошибок вместо его прототипа.
assert match is not None # Исчезновение обработчика должно проваливать проверку.
model = (root / 'tests/can_telemetry/model.c').read_text(encoding='utf-8').split('int main(void)')[0] # Повторно использует аппаратуру, ACK и журнал кадров общей модели нового передатчика.
model = model.replace('/* TIMEOUT_ISR */', block(ecan, match.start())) # Вставляет реальное тело обработчика без отдельной копии его алгоритма.
model += '\n' + defines + '\nstatic int Kurrent, pow_addr, Owen; /* Повторяет настройки каналов и OWEN, используемые настоящими масками версии 167. */\n' + mask_function # Берёт реальные номера плат, потоков и состав масок из текущей прошивки.
model += r'''
int main(void) { /* Измеряет свежесть R3 при реальной карте пульта и разных настройках потоков EEPROM. */
const unsigned periods[][2] = {{2,101},{10,101},{12,50},{2,0},{1,101},{1,1}}; /* Проверяет прежние настройки, включая отключённый медленный поток. */
unsigned scenario, tick, cursor, received, last, gap, worst; /* Наблюдает переданные кадры и разрыв между обновлениями R3. */
for (scenario = 0; scenario < sizeof(periods)/sizeof(periods[0]); ++scenario) { /* Проверяет независимые запуски платы. */
prepare(8U, dsk_EPLT); Init_packMask(); /* Строит настоящую карту задатчика поверх сброшенной модели CAN. */
CHECK((Maska[m_FAST][0] & 15U) == 15U && !(Maska[m_SLOW][0] & 15U)); /* R0–R3 остаются в быстром потоке при отключённом медленном. */
CHECK(Maska[m_FAST][1] == 1U && Maska[m_SLOW][6] == 0x1FFU && Maska[m_SLOW][7] == 0xE000U); /* Кнопки и параметры сохраняют состав исходной карты. */
Cancount[0] = periods[scenario][0]; Cancount[1] = periods[scenario][1]; /* Ненулевое значение теперь включает обновления по изменению и таймеру. */
cursor = received = last = worst = 0U; /* Учитывает также начальное ожидание первого подтверждения. */
for (tick = 1U; tick <= 5000U; ++tick) { /* Пять секунд включают heartbeat и обязательные повторы неизменных данных. */
CpuTimer1.InterruptCount = tick; modbus[3] = tick; CHECK(pump() == 0); /* Меняет R3 каждую миллисекунду и выдаёт готовую очередь через настоящие CAN_send и ACK. */
for (; cursor < count; ++cursor) { /* Анализирует только новые кадры mailbox. */
if (address_at(cursor) != 3U) continue; /* R3 начинает второй трёхсловный фрагмент действительной маски. */
CHECK((frames_hi[cursor] & 0xFFFFU) == tick); /* Требует свежее значение вместо старого снимка. */
gap = tick - last; if (gap > worst) worst = gap; last = tick; ++received; /* Измеряет худший интервал получения актуального R3. */
} /* Завершает анализ кадров текущего момента. */
} /* Приоритет heartbeat обрабатывается общей моделью передатчика. */
CHECK(received > 0U && worst <= CANPOWSE && tick - 1U - last <= CANPOWSE); /* При немедленном ACK интервал ограничен шагом сканирования 20 мс независимо от slow. */
CHECK(count_address(CAN_HEARTBEAT_ADDR) >= 4U); /* Обновление R3 не должно вытеснять секундный heartbeat. */
printf("PASS VPU fast=%u slow=%u: R3 max=%u ms, updates=%u\n", periods[scenario][0], periods[scenario][1], worst, received); /* Выводит измеренный интервал и число свежих кадров. */
} /* Завершает матрицу настроек EEPROM. */
return 0; /* Подтверждает программную модель без заявления об аппаратном испытании. */
} /* Завершает проверку обратной связи R3. */
''' # Использует production-маски и production-сервис вместо удалённого блока if(CanGO).
compiler = Path(os.environ.get('CC', 'C:/msys64/mingw64/bin/gcc.exe')) # Использует установленный GCC без изменения системных настроек.
environment = dict(os.environ, PATH=str(compiler.parent) + os.pathsep + os.environ.get('PATH', '')) # Добавляет runtime только дочерним процессам.
with tempfile.TemporaryDirectory(prefix='vpu-r3-rx-') as directory: # Отделяет временные результаты от образов пользователя.
source, executable = Path(directory) / 'test.c', Path(directory) / 'test.exe' # Разделяет исходник и исполняемый результат.
source.write_text(model, encoding='utf-8') # Сохраняет русские пояснения в UTF-8.
subprocess.run([str(compiler), '-std=c99', '-O3', '-Wall', '-Wextra', '-Werror', '-I' + str(root / 'Source/Internal/Include'), str(source), '-o', str(executable)], env=environment, check=True) # Компилирует настоящий заголовок при строгих предупреждениях.
subprocess.run([str(executable)], env=environment, check=True, timeout=10) # Нарушение задержки или зависание проваливает проверку.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+12
View File
@@ -3,8 +3,10 @@
#include <stdio.h>
/* Подключает <string.h>: настройки и интерфейс string. */
#include <string.h>
#define FLASH_BOOT 1 /* Проверяет дополнительный read-only запрос безопасности, который отсутствует в обычном RAM-образе. */
/* Подключает "../../Source/Internal/DAC.c": выход ЦАП и автомат защиты нагрузки с проверкой напряжения и свежести CAN. */
#include "../../Source/Internal/DAC.c"
#include "production_flash_guard.inc" /* Вызывает настоящий запрет FlashRs_Command перед CRC/erase на том же production-автомате нагрузки. */
/* Объявляет READY — данные READY; Desk — аппаратно определённый тип платы; Mode — рабочий адрес или режим платы, выбранный перемычками. Начальное значение задаётся справа от знака равенства. */
int READY = 1, Desk = 6, Mode = 5;
/* Объявляет modbus — массив технологических регистров; слово int на C28x занимает 16 бит. */
@@ -198,6 +200,16 @@ static int attempt_limit_test(void) {
/* Запускает новые стартовые сценарии и прежние регрессии автомата на программных подменах периферии. */
int main(void) {
setup(5); CHECK(flash_load_guard(0) == 0 && flash_load_guard(24) == 0x1005U); /* Probe разрешён при холодном старте, запись запрещена до штатного STOP и подтверждённого ЦАП. */
Commands = 0x80; tick(10, 1, 200); /* Поднимает реальную нагрузку через Timer1-модель с START, разрешением и ненулевым заданием. */
CHECK(load_state == LOAD_RUN && flash_load_guard(24) == 0x1005U && flash_load_guard(0) == 0); /* Активный LOAD_RUN отклоняется до Flash API; проверка возможностей не меняет рабочие выходы. */
CHECK(test_start && test_permission && !test_stop); /* Отказ загрузчика не выполняет кустарную аварийную остановку GPIO вместо штатного автомата. */
Commands = 0; tick(1, 1, 200); CHECK(flash_load_guard(24) == 0x1005U); /* Только начало STOP ещё не разрешает erase: нужно дождаться базового тока и полной выдержки. */
tick(1000, 1, 200); CHECK(Load_flash_safe() && flash_load_guard(24) == 0); /* После штатного idle, STOP длительностью не менее секунды и SPI-отправки 4 мА запись разрешается. */
CHECK(!test_start && !test_permission && test_stop && modbus[123] == DAC_min()); /* Проверяет программно подтверждённые безопасные выходы до будущего отключения Timer1. */
Commands = 0x200; CHECK(flash_load_guard(24) == 0x1005U); /* Отложенная калибровка не должна изменить аналоговое задание во время автономного программирования. */
Commands = 0; DAC_min()++; CHECK(flash_load_guard(24) == 0x1005U); /* Новая калибровка минимума требует фактической повторной SPI-отправки, а не доверия старому флагу нулевого задания. */
modbus[123] = DAC_min(); CHECK(flash_load_guard(24) == 0x1005U); /* Подмена открытого диагностического регистра не заменяет приватное подтверждение последнего отправленного кода. */
/* Объявляет out — данные out; error — ошибка; count — число; ramp — данные ramp; code — данные code. */
unsigned int out[8], error, count, ramp, code, stopped_at;
/* Сначала проверяет исправляемый штатный пуск с 6 мА, затем выполняет остальные регрессии защиты. */
@@ -0,0 +1,62 @@
/* Проверяет реальные Default_params/Load_params и защиту живого слова команд от сохранённого START в EEPROM. */
#include <assert.h> /* Делает нарушение сохранности команд, атомарности или состояния IRQ ошибкой теста. */
#include <stdio.h> /* Печатает результат самостоятельной регрессии загрузки параметров. */
#include <string.h> /* Копирует модель EEPROM и обнуляет регистры между независимыми сценариями. */
#define interrupt /* Убирает аппаратный атрибут ISR при подключении настоящих типов и констант датчиков на ПК. */
#include "../../Source/Internal/Include/filter_bat2.h" /* Предоставляет настоящий тип FILTERBAT, необходимый объявлениям measure.h. */
#include "../../Source/Internal/Include/measure.h" /* Подключает настоящие ERROR, частоты и объявления глобальных измерительных данных. */
typedef union { int all; struct { unsigned int reserved:4, bit4:1; } bit; } WORDE; /* Сохраняет адресацию слова R127 и бита калибровки4 в модели GCC. */
#include "../../Source/Internal/Include/package.h" /* Использует настоящие адреса R127, калибровок, тайм-аута и лимита пусков профиля167. */
#include "../../Source/Internal/Include/caliber.h" /* Даёт Default_params реальные заводские таблицы167 для всех режимов платы. */
#define ANSWER_LEN 128U /* Повторяет длину технологической карты без аппаратных зависимостей message.h. */
#define BALSAM167_EEPROM_PARAMS_START_WORD 0x3F7EU /* Адрес не влияет на управление: модель проверяет чтение полного блока параметров без запрета IRQ. */
int modbus[129], READY, Desk, Mode, Kalibro, Owen, Kurrent, TermoSW; /* Предоставляет рабочим функциям регистры и признаки режима платы. */
unsigned int param[129], Caliber_time; /* Предоставляет считанный EEPROM-блок и таймер калибровки из рабочего интерфейса. */
int sens_type[24]; /* Позволяет заводской инициализации обращаться к таблице типов SHKF без изменения рабочего алгоритма. */
static unsigned int irq_mask, saved_count, concurrent_command; /* Хранит исходную маску IRQ, число записей EEPROM и команду, принятую во время чтения. */
static unsigned int image[129]; /* Моделирует отдельную сохранённую EEPROM, чтобы загрузка не читала текущие команды обратно из RAM. */
static unsigned int __disable_interrupts(void) { unsigned int old = irq_mask; irq_mask = 1; return old; } /* Проверяет вложенное сохранение маски при применении RAM-настроек. */
static void __restore_interrupts(unsigned int old) { irq_mask = old; } /* Возвращает переданную вызывающим кодом маску, включая заранее запрещённые IRQ. */
static void Seeprom_read(unsigned long address, unsigned int *data, unsigned int bytes) /* Моделирует только длительное чтение EEPROM, не подменяя Default/Load. */
{ /* Начинает передачу проверенного набора параметров в рабочий буфер param. */
assert(!irq_mask && address == BALSAM167_EEPROM_PARAMS_START_WORD && bytes == 258U); /* EEPROM-драйвер нуждается в прерываниях; 129 слов занимают258 байт C28x. */
memcpy(data, image, sizeof(image)); /* Считывает в том числе сохранённый START, чтобы проверить его исключение из применения параметров. */
if (concurrent_command) Commands = concurrent_command; /* Имитирует принятие нового START/RESET по CAN во время длительного чтения EEPROM. */
} /* Заканчивает чтение при исходно разрешённых прерываниях. */
static unsigned int get_crc16(const unsigned int *data, unsigned int words) { assert(data == param && words == ANSWER_LEN); return 0x1234U; } /* Выбирает валидную ветку по контрольному слову image без повторения алгоритма загрузки. */
void Save_params(void) { assert(!irq_mask); saved_count++; } /* Проверяет, что восстановление заводских настроек не удерживает IRQ во время записи EEPROM. */
void Init_sensors_more(void) { assert(!irq_mask); } /* Проверяет освобождение короткой RAM-секции перед расчётом измерительных коэффициентов. */
#include "production_parameters.inc" /* Подключает неизменённые reset_parameter_commands, Default_params и Load_params из текущего рабочего message.c. */
static void setup(unsigned int live, unsigned int stored, unsigned int crc) /* Готовит независимую плату нагрузки и EEPROM с намеренно отличающимся словом команд. */
{ /* Сбрасывает модель, сохраняя реальное расположение регистров профиля167. */
memset(modbus, 0, sizeof(modbus)); memset(image, 0, sizeof(image)); /* Удаляет состояние предыдущего сценария только в тестовой модели. */
READY = 1; Desk = dsk_LOAD; Mode = adr_LOA1; Kalibro = Kurrent = 1; Owen = TermoSW = 0; /* Выбирает готовую плату нагрузки с заводской калибровкой167. */
Commands = live; image[127] = stored; image[126] = Mode; image[ANSWER_LEN] = crc; /* Разделяет текущую команду интерфейса и потенциально опасный сохранённый START. */
image[0x52] = 2000; image[0x7D] = 7; irq_mask = saved_count = concurrent_command = 0; /* Задаёт допустимые новые защиты и независимые счётчики модели. */
} /* Завершает подготовку до вызова настоящего рабочего кода. */
int main(void) /* Проверяет режимы RUN/STOP/RESET/FAULT через сохранение уровней, на которых работает неизменённый автомат DAC167. */
{ /* Начинает все комбинации текущих и сохранённых команд. */
unsigned int live, stored; /* Перебирает четыре сочетания START/RESET независимо от содержимого EEPROM. */
for (live = 0; live <= 0xC0U; live += 0x40U) { /* Проверяет ожидание, RESET, START и совместное удержание двух управляющих битов. */
for (stored = 0; stored <= 0xC0U; stored += 0x40U) { /* Не допускает ни отмены текущего START, ни появления START из EEPROM. */
setup(live | 0x0305U, stored, 0x1234U); Load_params(); /* Загружает корректный блок с дополнительными одноразовыми служебными командами. */
assert((unsigned int)Commands == live && LOAD_MAX_ATTEMPTS == 7 && LOAD_CAN_TIMEOUT == 2000 && !irq_mask && !saved_count); /* Сохраняет только живые START/RESET и действительно применяет новые настройки защиты. */
setup(live | 0x0305U, stored, 0); Load_params(); /* Проверяет ту же гарантию при неверной CRC и автоматическом восстановлении заводских параметров. */
assert((unsigned int)Commands == live && saved_count == 1 && LOAD_MAX_ATTEMPTS == 5 && LOAD_CAN_TIMEOUT == 2000 && !irq_mask); /* Сохранение исправленного блока EEPROM не очищает управляющие уровни нагрузки. */
} /* Завершает все варианты сохранённых команд для текущего живого уровня. */
setup(live | 0x0305U, 0, 0x1234U); Default_params(); /* Проверяет прямую команду Default без предварительного чтения EEPROM. */
assert((unsigned int)Commands == live && !irq_mask && LOAD_MAX_ATTEMPTS == 5); /* Заводской сброс сохраняет RUN/STOP-семантику, удаляя одноразовые служебные флаги. */
} /* Завершает проверку всех четырёх живых сочетаний START/RESET. */
setup(0, 0x80U, 0x1234U); concurrent_command = 0xC0U; Load_params(); /* Передаёт новый RESET/START во время чтения вместо снимка, снятого до EEPROM. */
assert(Commands == 0xC0U && !irq_mask); /* Не теряет последнюю принятую по интерфейсу команду при завершении Load. */
setup(0xC0U, 0x80U, 0x1234U); READY = 0; Load_params(); /* На холодном старте имитирует и мусор RAM, и сохранённый START EEPROM. */
assert(!Commands && !irq_mask); /* До READY ни один источник сохранённого состояния не разрешает автоматический пуск. */
setup(0xC0U, 0, 0x1234U); READY = 0; Default_params(); /* Проверяет ту же стартовую очистку в заводской ветке без EEPROM. */
assert(!Commands && !irq_mask); /* Начальные START/RESET обязательно очищены до разрешения рабочего такта платы. */
setup(0xC0U, 0x80U, 0x1234U); Desk = dsk_COMM; Kalibro = Kurrent = 0; Load_params(); /* У остальных плат сохраняет прежнее поведение очистки всех команд после загрузки. */
assert(!Commands && !irq_mask); /* Не переносит специальную семантику УМП на несвязанные типы плат. */
setup(0x2C0U, 0, 0x1234U); irq_mask = 1; reset_parameter_commands(); /* Проверяет вызов helper из уже защищённого блока применения параметров. */
assert(Commands == 0xC0U && irq_mask == 1); /* Вложенный helper сохраняет внешнюю маску IRQ и не выдаёт EINT без разрешения вызывающего кода. */
puts("PASS: production parameter loading/defaults preserve live START/RESET and reject EEPROM commands"); /* Сообщает о прохождении рабочего кода, а не копии алгоритма в модели. */
return 0; /* Возвращает успех только после всех сценариев сохранности команд и состояния IRQ. */
} /* Завершает регрессию без обращения к аппаратной плате. */
+43 -1
View File
@@ -3,6 +3,8 @@
"""Compile the production DAC.c against GPIO/IRQ mocks; no hardware writes."""
# Подключает модуль os для этой проверки или подготовки сборки.
import os
# Разбирает только границы настоящих функций message.c, сохраняя их неизменённое тело в регрессии параметров.
import re
# Импортирует необходимые имена: from pathlib import Path.
from pathlib import Path
# Подключает модуль shutil для этой проверки или подготовки сборки.
@@ -22,11 +24,51 @@ os.environ['PATH'] = str(Path(compiler).resolve().parent) + os.pathsep + os.envi
with tempfile.TemporaryDirectory(prefix='load-control-') as tmp:
# Задаёт exe по выражению справа; значение используется последующими операциями.
exe = Path(tmp) / 'test.exe'
flash = (root / 'Source/Internal/flash_rs.c').read_text(encoding='utf-8') # Проверяет настоящий запрет активной нагрузки перед переходом к Flash API.
guard = re.search(r'^static unsigned int flash_load_guard\([^\n]*\n\{[^\n]*\n[^\n]*\n\}', flash, re.M) # Извлекает короткий production-helper без переписывания его условия в тестовой модели.
assert guard, 'Production Flash load guard missing' # Переименование или удаление защиты не должно незаметно отключить тест перед erase.
(Path(tmp) / 'production_flash_guard.inc').write_text(guard.group() + '\n', encoding='utf-8') # Включает реальный helper в тот же C-модуль, который исполняет production DAC.c.
# Запускает внешний компилятор, генератор или тест; ненулевой результат проверяется параметрами вызова.
subprocess.run([compiler, '-Wall', '-Wextra', '-std=c99',
# Продолжает текущий вызов, список или выражение: '-Itests/load_control/stubs', '-ISource/Internal/Include',; порядок аргументов сохраняется.
'-Itests/load_control/stubs', '-ISource/Internal/Include',
'-Itests/load_control/stubs', '-ISource/Internal/Include', '-I' + tmp, # Добавляет только каталог извлечённого рабочего Flash-helper этой проверки.
# Задаёт 'tests/load_control/load_control_test.c', '-o', str(exe)], cwd по выражению справа; значение используется последующими операциями.
'tests/load_control/load_control_test.c', '-o', str(exe)], cwd=root, check=True)
# Запускает внешний компилятор, генератор или тест; ненулевой результат проверяется параметрами вызова.
subprocess.run([str(exe)], check=True, timeout=10)
# Читает рабочие загрузку и заводской сброс параметров для отдельной проверки живых START/RESET.
message = (root / 'Source/Internal/message.c').read_text(encoding='utf-8')
# Маскирует комментарии пробелами той же длины, чтобы фигурные скобки пояснений не влияли на поиск границ функций.
masked = re.sub(r'/\*.*?\*/|//[^\n]*', lambda match: ' ' * len(match.group()), message, flags=re.S)
# Собирает только проверяемые рабочие функции: EEPROM и GPIO заменяются моделью, сам алгоритм загрузки остаётся исходным.
functions = []
# Извлекает helper и обе ветки применения параметров независимо от форматирования вложенных блоков в исходнике.
for name in ('reset_parameter_commands', 'Default_params', 'Load_params'):
# Находит объявление функции с настоящим именем и необязательным static.
match = re.search(r'^(?:static )?void ' + name + r'\(', masked, re.M)
# Не допускает молчаливого запуска модели после удаления или переименования рабочего обработчика.
assert match, name
# Начинает подсчёт вложенных блоков с первой открывающей скобки тела найденной функции.
end, depth = masked.index('{', match.start()), 0
# Идёт до парной закрывающей скобки, сохраняя все вложенные условия и циклы рабочего кода.
while end < len(masked):
# Учитывает открывающую скобку только после удаления комментариев.
depth += masked[end] == '{'
# Учитывает соответствующую закрывающую скобку в том же уровне вложенности.
depth -= masked[end] == '}'
# Переходит к следующему символу, включая последнюю закрывающую скобку в извлекаемый текст.
end += 1
# Нулевой уровень означает завершение всей функции, а не первого вложенного if.
if depth == 0:
# Завершает поиск, чтобы не включить соседний обработчик Modbus.
break
# Копирует исходный текст функции вместе с русскими комментариями без изменения условий и присваиваний.
functions.append(message[match.start():end])
# Передаёт извлечённый production-код компилятору из временного каталога, не записывая generated-файлы в репозиторий.
(Path(tmp) / 'production_parameters.inc').write_text('\n'.join(functions), encoding='utf-8')
# Выбирает отдельный исполняемый файл, чтобы проверка параметров не подменяла прежние регрессии автомата DAC.
parameters_exe = Path(tmp) / 'parameters.exe'
# Компилирует реальную загрузку параметров с контролем IRQ, модели EEPROM и содержимого R127.
subprocess.run([compiler, '-Wall', '-Wextra', '-std=c99', '-I' + tmp, 'tests/load_control/parameter_commands_test.c', '-o', str(parameters_exe)], cwd=root, check=True)
# Выполняет комбинации живых START/RESET, сохранённых команд, неверной CRC, холодного старта и CAN во время чтения.
subprocess.run([str(parameters_exe)], check=True, timeout=10)
+36 -50
View File
@@ -23,7 +23,8 @@ static struct { struct gpio_reg GPADAT, GPBDAT; } GpioDataRegs; /* Хранит
static struct { unsigned long all; } DigErr; /* Принимает диагностическую маску посторонней ветви SHKF. */
static struct { unsigned long InterruptCount; } CpuTimer1; /* Учитывает вызовы настоящего обработчика пульта. */
int modbus[129], Desk; /* Предоставляет технологические регистры и выбирает ветвь пульта. */
unsigned int INcount0, CanPowse, CanGO; /* Хранит production-накопитель кнопки и таймер выдачи CAN. */
unsigned int INcount0; /* Сохраняет 16-битный тип накопителя кнопок из 167, соответствующий указателю er_anal. */
unsigned int CanPowse, CanGO; /* Хранит счётчик тактов выдачи CAN и признак готовности очередной передачи. */
unsigned long isMask, Lonely; /* Хранит тестовую маску и возраст последней связи. */
int isNumb, quaLamp = 6; /* Сохраняет тестовые цифры и число ламп из pulto.c. */
static unsigned int IER, mask_at_eint, entry_mask, failures, sends, nested_entries, timer_pending; /* Отслеживает разрешения прерываний, пакетные вызовы и возможность вложенного Timer1. */
@@ -71,52 +72,37 @@ static void tick(void) /* Моделирует сохранение и восс
check(mask_at_eint == (entry_mask & MINT13 & ~M_INT13), "interrupt mask"); /* Проверяет отсутствие собственного Timer1 и запрет включения ранее выключенных источников. */
check(sends >= 1 && sends <= 2, "packet count"); /* Требует штатный объём одного такта: пакет ламп и не более одного пакета очередного индикатора. */
} /* Завершает один такт индикации. */
int main(void) /* Запускает на настоящих функциях сценарий рабочих ламп, теста с помехами и возврата в работу. */
{ /* Начинает общий поведенческий сценарий. */
int i, pwm_on = 0; /* Перебирает опросы и такты; считает включённые такты рабочего PWM за один полный период. */
memset(modbus, 0, sizeof(modbus)); /* Убирает старые команды и состояния перед сценарием. */
check(sizeof(WORDE) == sizeof(modbus[0]), "register layout"); /* Подтверждает корректность production-псевдонимов в хостовой модели. */
Desk = dsk_EPLT; /* Выбирает реальную ветвь чтения TESTLAMP пульта. */
modbus[2] = 1; /* Включает рабочую лампу до нажатия TESTLAMP, как в сообщении пользователя. */
Bright[0] = maximum_bright; /* Задаёт уже включённой лампе постоянную яркость для обнаружения случайных провалов. */
entry_mask = M_INT13 | (1U << 0) | (1U << 13); /* Разрешает Timer1, группу ADC и Timer2, проверяя также маскирование равного приоритета. */
tick(); /* Получает первый рабочий пакет ламп. */
check(lamp_word == 1, "normal lamp before test"); /* Подтверждает исходное постоянное свечение до нажатия. */
GpioDataRegs.GPADAT.bit.GPIO26 = 1; /* Нажимает физическую кнопку активным высоким уровнем. */
for (i = 0; i < 1001; i++) get_Buttons(); /* Проходит реальные 1000 накопительных опросов и следующий опрос фиксации порога. */
check(bTestLamp == 1, "test activation"); /* Требует включения теста после полной выдержки. */
for (i = 0; i < 600; i++) { /* Проверяет удержание более BLINK_TIME=500 тактов без подавления штатного медленного мигания. */
unsigned long expected_mask; /* Сохраняет фазу теста перед очередным возмущением кнопки. */
GpioDataRegs.GPADAT.bit.GPIO26 = 0; /* Вводит короткий провал входа на один опрос главного цикла. */
get_Buttons(); /* Выполняет настоящий накопительный фильтр и публикацию кнопки. */
check(bTestLamp == 1, "held test"); /* Проверяет сохранение TESTLAMP вместо мгновенного возврата рабочих масок. */
timer_pending = 1; /* Допускает новый аппаратный такт во время передачи для проверки запрета повторного входа. */
tick(); /* Формирует тестовый пакет при кратком провале физического входа. */
expected_mask = isMask; /* Получает ожидаемую медленную фазу из настоящей what_is. */
check(lamp_word == expected_mask, "test packet during input glitch"); /* Исключает рабочую маску уже горящих ламп из тестового потока. */
check(!digit_sends || (digit_word == isNumb && digit_dot == 4), "test digit packet during input glitch"); /* Требует сохранения тестового значения и формата цифр во время помехи кнопки. */
check(nested_entries == 0, "nested transmission"); /* Требует единственного владельца DATA/CLK на протяжении передачи. */
timer_pending = 0; /* Убирает смоделированное событие после завершения текущего такта. */
GpioDataRegs.GPADAT.bit.GPIO26 = 1; /* Возвращает уровень удерживаемой кнопки. */
get_Buttons(); /* Восстанавливает насыщенный накопитель без смены тестового режима. */
} /* Завершает серию коротких помех при удержании. */
check(isNumb == 1111 && isMask == 0xFFFFFFFFUL, "slow test sequence"); /* После 600 миллисекунд требует единственного штатного шага what_is с периодом 500 тактов, а не ускоренного строба. */
GpioDataRegs.GPADAT.bit.GPIO26 = 0; /* Отпускает физическую кнопку на длительное время. */
for (i = 0; i < 1001; i++) get_Buttons(); /* Даёт накопителю полностью разрядиться и зафиксировать отпускание. */
check(bTestLamp == 0, "test release"); /* Проверяет, что сохранение состояния не залипает в TESTLAMP. */
tick(); /* Возвращает передачу рабочей маски после отпускания. */
check(lamp_word == 1, "normal lamp after test"); /* Восстанавливает уже включённую до теста лампу. */
cTestLamp = 1; /* Включает независимую удалённую команду теста через production-регистр. */
tick(); /* Проверяет приоритет удалённого TESTLAMP при отпущенной физической кнопке. */
check(lamp_word == isMask, "remote test"); /* Требует тестовую маску для обоих источников команды. */
cTestLamp = 0; /* Завершает удалённый тест перед проверкой рабочего PWM. */
Bright[0] = 6; /* Устанавливает шесть включённых тактов из десяти в production-периоде яркости. */
for (i = 0; i < 10; i++) { /* Проверяет один полный период PWM после выхода из теста. */
tick(); /* Даёт настоящей функции вычислить текущую фазу яркости. */
check(lamp_word == 0 || lamp_word == 1, "normal PWM packet"); /* Исключает оставшиеся тестовые биты из рабочей маски. */
pwm_on += lamp_word == 1; /* Измеряет реальное число включённых тактов production-PWM. */
} /* Завершает период обычной яркости. */
check(pwm_on == 6, "normal PWM duty"); /* Подтверждает сохранение заданной яркости 6/10 после выхода из теста. */
printf("%s TESTLAMP: held input glitches, single ISR, normal lamps, release, remote command\n", failures ? "FAIL" : "PASS"); /* Выводит проверенные свойства без заявления о физическом испытании платы. */
return failures ? 1 : 0; /* Возвращает результат всех поведенческих требований. */
} /* Завершает хостовую проверку. */
int main(void) /* Проверяет двухсекундное удержание по Timer1 и защиту стробов DATA/CLOCK от повторного входа. */
{ /* Начинает независимые сценарии физической кнопки, удалённой команды и яркости. */
int i, pwm_on = 0; /* Считает такты удержания и включённые фазы десятишагового PWM. */
memset(modbus, 0, sizeof(modbus)); /* Очищает команды и входы перед запуском production-функций. */
check(sizeof(WORDE) == sizeof(modbus[0]), "register layout"); /* Подтверждает совместимость битовых псевдонимов с массивом хостовой модели. */
Desk = dsk_EPLT; modbus[2] = 1; Bright[0] = maximum_bright; /* Выбирает пульт и заранее включает рабочую лампу с полной яркостью. */
entry_mask = M_INT13 | (1U << 0) | (1U << 13); /* Проверяет Timer1 вместе с ранее разрешёнными источниками разных приоритетов. */
tick(); check(lamp_word == 1, "normal lamp before test"); /* Проверяет рабочую индикацию до начала удержания. */
GpioDataRegs.GPADAT.bit.GPIO26 = 1; /* Нажатие TESTLAMP представлено высоким уровнем входа платы. */
for (i = 0; i < 4000; i++) get_Buttons(); /* Частый main не должен ускорять выдержку, которую теперь считает только Timer1. */
check(!bTestLamp, "polling independent"); /* Доказывает независимость порога от числа опросов главного цикла. */
for (i = 0; i < 2 * READY_FREQ - 1; i++) { tick(); check(!bTestLamp, "short test"); } /* Даже 1999 тактов при 1000 Гц ещё не разрешают местный тест. */
tick(); check(bTestLamp, "test activation"); /* Двухтысячный такт включает тест точно после двух секунд удержания. */
for (i = 0; i < 600; i++) { /* Проверяет непрерывный тест дольше периода BLINK_TIME=500 без потери защиты стробов. */
timer_pending = 1; tick(); /* Имитирует новый такт во время передачи, когда прежний ISR ещё не завершился. */
check(bTestLamp && lamp_word == isMask, "held test"); /* Требует передачу тестовой фазы вместо исходной рабочей маски. */
check(!digit_sends || (digit_word == isNumb && digit_dot == 4), "test digit packet"); /* Проверяет тестовый формат числового индикатора. */
check(nested_entries == 0, "nested transmission"); timer_pending = 0; /* Единственный владелец DATA/CLOCK исключает перемешивание пакетов. */
} /* Завершает длительное удержание физической кнопки. */
GpioDataRegs.GPADAT.bit.GPIO26 = 0; tick(); /* Отпускание сбрасывает местный тест в следующем такте Timer1. */
check(!bTestLamp && lamp_word == 1, "release"); /* Возвращает заранее включённую рабочую лампу без дополнительной задержки. */
for (i = 0; i < 3; i++) { /* Отдельные короткие нажатия суммарно превышают две секунды, но не должны включать тест. */
int held; GpioDataRegs.GPADAT.bit.GPIO26 = 1; /* Начинает очередное независимое нажатие. */
for (held = 0; held < READY_FREQ; held++) tick(); /* Удерживает вход одну секунду, то есть меньше порога. */
check(!bTestLamp, "separate presses"); /* Не позволяет сложить длительности разных нажатий. */
GpioDataRegs.GPADAT.bit.GPIO26 = 0; tick(); /* Сбрасывает накопленную выдержку отпусканием. */
} /* Завершает сценарий повторных коротких нажатий. */
cTestLamp = 1; tick(); check(lamp_word == isMask, "remote test"); /* Команда Modbus сохраняет немедленный независимый запуск теста. */
cTestLamp = 0; Bright[0] = 6; /* Возвращает рабочий режим с яркостью шесть тактов из десяти. */
for (i = 0; i < 10; i++) { tick(); check(lamp_word <= 1, "normal PWM packet"); pwm_on += lamp_word == 1; } /* Измеряет полный период реального алгоритма яркости после теста. */
check(pwm_on == 6, "normal PWM duty"); /* Подтверждает сохранение настроенной скважности рабочего сигнала. */
printf("%s TESTLAMP: 2-second hold, main independent, single ISR, release, remote command, PWM\n", failures ? "FAIL" : "PASS"); /* Указывает проверенные программные свойства без заявления об аппаратном испытании. */
return failures ? 1 : 0; /* Любое нарушенное условие завершает процесс проверки с ошибкой. */
} /* Завершает проверку кнопки и защищённой передачи индикации. */
+2 -2
View File
@@ -27,13 +27,13 @@ button = function(source("peripher.c"), "get_Buttons") # Проверяет и
pult = source("pulto.c") # Берёт текущую обработку индикации, включая реальные ветви теста и яркости.
body = "\n".join((function(source("measure.c"), "er_anal"), button, function(pult, "what_is"), function(pult, "cpu_timer1_isr_PULT"))) # Собирает функции, участвующие в переключении режима и передаче масок.
before, after = Path(__file__).with_name("model.inc").read_text(encoding="utf-8").split("/* HANDLERS */") # Помещает production-функции между аппаратными заглушками и поведенческими сценариями.
old_button, count_button = re.subn(r"(er_anal\(GpioDataRegs\.GPADAT\.bit\.GPIO26,\s*&INcount0,\s*1000,\s*)bTestLamp(\))", r"\g<1>0\2", body) # Возвращает сброс фильтра при одном провале только во временную мутацию.
old_button, count_button = re.subn(r"test_lamp_hold_ticks >= 2U \* \(unsigned int\)READY_FREQ", "test_lamp_hold_ticks >= 1U", body) # Убирает задержку только во временной модели, чтобы доказать обнаружение преждевременного включения теста.
old_mask, count_mask = re.subn(r"IER\s*&=\s*\(MINT13\s*&\s*~M_INT13\);", "IER |= MINT13;", body) # Восстанавливает ошибочное разрешение прерываний в отдельном временном варианте.
assert count_button == count_mask == 1, "Не найдены единственные места исправлений TESTLAMP" # Не позволяет мутации незаметно проверять неизменённый код.
environment = dict(os.environ) # Сохраняет окружение основного процесса.
environment["PATH"] = str(Path(args.cc).resolve().parent) + os.pathsep + environment.get("PATH", "") # Делает DLL и вспомогательные программы GCC доступными дочерним процессам.
with tempfile.TemporaryDirectory(prefix="testlamp-") as directory: # Изолирует артефакты каждой проверки от дерева прошивки.
for name, functions, failure in (("production", body, None), ("old_button", old_button, "FAIL held test"), ("old_mask", old_mask, "FAIL interrupt mask")): # Проверяет исправление и чувствительность сценариев к каждой из двух старых ошибок.
for name, functions, failure in (("production", body, None), ("old_button", old_button, "FAIL short test"), ("old_mask", old_mask, "FAIL interrupt mask")): # Проверяет задержку удержания и запрет вложенного Timer1 вместе с чувствительностью сценариев к каждой ошибке.
c_path, exe_path = Path(directory) / (name + ".c"), Path(directory) / (name + ".exe") # Разделяет временные исходники и программы трёх вариантов.
c_path.write_text(before + functions + after, encoding="utf-8") # Сохраняет модель и русские пояснения в UTF-8.
subprocess.run([args.cc, "-std=c99", "-O3", "-fno-strict-aliasing", "-Werror=implicit-function-declaration", "-I", str(ROOT / "Source/Internal/Include"), "-I", str(ROOT / "Source/External/v120/DSP2833x_common/include"), str(c_path), "-o", str(exe_path)], env=environment, check=True) # Сохраняет адреса production-псевдонимов регистров и использует реальную таблицу приоритетов TI.