Перенести улучшения 166 и добавить сборку для M95M02 и Flash
Восстановить телеметрию CAN, обработку TESTLAMP и сохранение команд при загрузке параметров. Выбрать M95M02 основным профилем, добавить самостоятельную Flash-сборку и проверять образы до публикации. Проверки: хостовые тесты, реальные сборки TI C2000, контроль COFF/SCI8 и защита записи при активной нагрузке.
This commit is contained in:
@@ -58,3 +58,7 @@
|
||||
/Bin/**/logger_channels.png
|
||||
# Снимки предпросмотра защиты используются только при визуальной проверке интерфейса.
|
||||
/Bin/**/gui_preview.png
|
||||
# Локальный комплект проверенных RAM/Flash-образов и их паспортов создаётся из исходников перед выпуском.
|
||||
/Bin/Final167/
|
||||
# Временные объекты отдельной Flash-сборки зависят от установленного TI toolchain и не входят в исходники.
|
||||
/Bin/Flash167/
|
||||
|
||||
+11
-9
@@ -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
@@ -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.
|
||||
|
||||
## Проверки и ограничения
|
||||
|
||||
|
||||
@@ -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 она не возвращает.
|
||||
|
||||
|
||||
@@ -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-адреса, включая этот блок, отклоняются с кодом недопустимого адреса.
|
||||
|
||||
|
||||
@@ -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). Каждое слово
|
||||
содержит сначала старший байт.
|
||||
|
||||
@@ -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.
|
||||
//===========================================================================
|
||||
*/
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
@@ -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[];
|
||||
|
||||
|
||||
@@ -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: выбранный блок включается в текущий вариант прошивки. */
|
||||
|
||||
@@ -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. */
|
||||
@@ -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] в диапазон не входит. */
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
@@ -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 длина. */
|
||||
|
||||
@@ -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.
|
||||
@@ -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. */
|
||||
@@ -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
@@ -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. */
|
||||
|
||||
@@ -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
@@ -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); число. */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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) # Запускает только компиляцию/линковку; программирование устройства отсутствует.
|
||||
@@ -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))) # Показывает выбранный профиль вместе с прежними полями идентификации.
|
||||
|
||||
@@ -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") # Не маскирует отказ проверок сообщением об успешном выпуске.
|
||||
@@ -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-адаптера и не отправляет команды реальному оборудованию. */
|
||||
@@ -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) # Зависание очереди или нарушенный тайминг завершают проверку ошибкой.
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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. */
|
||||
} /* Завершает регрессию без обращения к аппаратной плате. */
|
||||
@@ -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
@@ -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; /* Любое нарушенное условие завершает процесс проверки с ошибкой. */
|
||||
} /* Завершает проверку кнопки и защищённой передачи индикации. */
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user