Files
Andrey 05be38a04a Обновить Flash/CAN, версии сборок и управление периферией
Добавить защищённое обновление приложения через RS и CAN с сохранением
загрузчика, проверкой CRC, восстановлением передачи и защитой EEPROM.
Подключить общий процесс сборки C2000 и автоматический счётчик версии
для согласованных RAM, Flash, CAN и паспортов бинарных образов.

Уточнить запуск ЦАП, обработку измерений и параметров нагрузки,
начальную индикацию и документацию регистров GAS.
Дополнить модели аппаратуры и изолировать заголовки CAN в тестах.

Проверка: 26 тестов выпуска, 4 теста восстановления CAN и восемь
программных моделей CAN, Flash, EEPROM, ЦАП, параметров и TESTLAMP.
2026-11-06 16:11:11 +03:00

85 lines
16 KiB
Python

"""Сквозная проверка SETGUI → CAN → staging EEPROM → внутренняя Flash F28335.""" # Использует production-ядро и writer, подменяя только физическую память.
import ctypes # Передаёт кадры реального Python-клиента с точными размерами C28x wire-октетов.
import os # Выбирает host-компилятор и каталог runtime DLL только для текущего процесса.
from pathlib import Path # Находит исходники и клиент относительно этого репозитория.
import struct # Формирует независимые тестовые SCI8 с адресами в словах C28x.
import subprocess # Компилирует аппаратную модель с настоящим CAN-портом и ядром.
import sys # Подключает ту же библиотеку protocan, что SETGUI.
import tempfile # Изолирует DLL текущего испытания без изменения прошивок платы.
from types import SimpleNamespace # Предоставляет клиенту неизменяемые данные, версию и CRC образа.
import zlib # Независимо вычисляет транспортную CRC32 тестовых файлов.
ROOT = Path(__file__).resolve().parents[2] # Корень текущей прошивки 167 содержит тестовую аппаратную модель.
TEMPLATES = Path(os.environ.get("TEMPLATES_ROOT", ROOT.parent / "newProject/SETGUI/third_party/templates")) # Выбирает ту же зависимость, что действующий CCS-проект.
CORE = TEMPLATES / "c/protocan-boot" # Общая библиотека содержит аппаратный CAN-порт и защищённый Flash writer.
CC = os.environ.get("CC", "C:/msys64/ucrt64/bin/gcc.exe") # Допускает явный выбор установленного GCC.
os.environ["PATH"] = str(Path(CC).parent) + os.pathsep + os.environ["PATH"] # Делает runtime DLL GCC доступными компилятору и загружаемой модели.
sys.path.insert(0, str(TEMPLATES / "python")) # Использует production-клиент без копирования его автомата.
from protocan.can_boot import CanBootTarget, CanBootTransfer # Проверяет согласованность адресов, сессий, окон и новой команды выбора памяти.
from protocan.f28335_flash import APP_START, APP_DATA, descriptor # Создаёт FAB1 общим инструментом выпуска приложения.
from protocan.protocan import ProtoCanId # Извлекает команду из реального 29-битного идентификатора.
from protocan.transport import build_frame # Возвращает аппаратный ответ через штатный транспортный codec.
def section(address, words): # Размещает размер, адрес TI и слова одной секции в SCI8.
return struct.pack("<HHH", len(words), address >> 16, address & 65535) + struct.pack("<" + "H" * len(words), *words) # Адрес передаёт старшее слово первым, каждый октет слова — младшим вперёд.
entry = APP_DATA # Тестовый entry указывает на реально загруженное слово приложения.
loaded = {APP_DATA + i: 0x1234 + i for i in range(300)} # Несколько порций записи проверяют порядок данных и отложенного FAB1.
loaded.update({APP_DATA + 700 + i: 0x5678 + i for i in range(10)}) # Промежуток между секциями должен остаться стёртым и войти в итоговую CRC.
fab = struct.unpack("<16H", descriptor(entry, loaded)) # Заголовок вычисляется по будущему физическому содержимому Flash.
valid = b"\xaa\x08" + bytes(16) + struct.pack("<HH", entry >> 16, entry & 65535) + section(APP_DATA + 700, list(loaded.values())[300:]) + section(APP_START, list(fab) + list(loaded.values())[:300]) + bytes(2) # Обратный порядок секций проверяет независимость MCU-валидатора от порядка блоков SCI8.
ram = b"\xaa\x08" + bytes(16) + struct.pack("<HH", 0, 0x8000) + section(0x8000, [1, 2, 3, 4, 5]) + bytes(2) # Корректный RAM SCI8 обязан сохранить прежний маршрут EEPROM A/B.
with tempfile.TemporaryDirectory(prefix="can-flash-") as directory: # Все результаты модели удаляются после освобождения DLL.
library = Path(directory) / "flash_transfer.dll" # Модель не открывает физические CAN, COM или JTAG.
subprocess.run([CC, "-std=c99", "-O2", "-Wall", "-Wextra", "-Werror", "-Wno-misleading-indentation", "-Wno-error=unused-variable", "-DPCAN_BOOT_WIDE_BYTES", "-shared", "-I" + str(CORE), "-I" + str(CORE / "tests/f28335_stubs"), "-I" + str(CORE / "ports/tms320f28335"), str(CORE / "src/pcan_boot.c"), str(CORE / "ports/tms320f28335/f28335_boot_store.c"), str(ROOT / "tests/can_boot/flash_transfer_model.c"), "-o", str(library)], check=True) # Линкует настоящий parser, store, CAN callbacks и Flash writer в одну исполнимую модель.
native = ctypes.CDLL(str(library)); octets = ctypes.c_uint * 8 # C28x хранит каждый wire-октет в младших восьми битах 16-битного char.
native.model_frame.argtypes = [ctypes.c_uint32, ctypes.POINTER(ctypes.c_uint), ctypes.c_uint, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint)] # Сохраняет точную передачу 29-битного ID и восьми октетов.
native.model_flash_check.argtypes = [ctypes.POINTER(ctypes.c_uint), ctypes.c_uint] # Неподходящие файлы передаются MCU-валидатору wide-октетами без зависимости от host-char.
def control(command, payload=b"", session=1, device=8): # Проверяет защиту новой команды теми же production CAN-фильтрами, что полная передача.
reply_id, reply = ctypes.c_uint32(), octets() # Результат берётся из фактического mailbox 4.
count = native.model_frame(ProtoCanId.build(1, 0, 1, device, 9, session << 8 | command), octets(*payload.ljust(8, b"\0")), len(payload), ctypes.byref(reply_id), reply) # Формирует адресный управляющий кадр с заданными сессией и DLC.
return reply[0] if count else None # Отсутствие ответа на чужой адрес отличается от ошибки собственного кадра.
def transfer(payload, memory="flash", corrupt=False, crc=None): # Выполняет автомат SETGUI до успешного REBOOT либо точного отказа MCU.
client = CanBootTransfer(SimpleNamespace(data=payload, version=0x01010002, crc32=zlib.crc32(payload) if crc is None else crc), CanBootTarget(device_type=1, device=8, product_type=0x2833, hardware_min=1, hardware_max=1, memory=memory)) # Использует адрес задатчика 1/8 и аппаратную совместимость F28335.
pending = client.start() # Начинает ENTER_BOOT, а для Flash затем требует отдельный ACK выбора памяти.
while pending: # Следующее окно появляется только после соответствующего production-статуса.
requests, pending = pending, [] # Сохраняет текущие кадры до обработки ответов.
for frame in requests: # Передаёт кадры окна тем же порядком, что CAN-адаптер SETGUI.
if corrupt and ProtoCanId.parse(frame.can_id).body & 255 == 7 and ProtoCanId.parse(frame.can_id).msg_type == 9: native.model_flash_corrupt() # Повреждает staging строго после VERIFY и до COMMIT.
reply_id, reply = ctypes.c_uint32(), octets() # Выделяет память под реальный ответ mailbox 4.
if native.model_frame(frame.can_id, octets(*frame.data.ljust(8, b"\0")), len(frame.data), ctypes.byref(reply_id), reply): # Production-порт может не отвечать на промежуточные кадры окна.
outgoing, _ = client.handle_status(build_frame(reply_id.value, bytes(reply[:]), to_can=False)) # Преобразует восемь wide-октетов в восемь wire-байтов и передаёт production-статус клиенту.
pending.extend(outgoing) # Добавляет следующее окно либо VERIFY/COMMIT/REBOOT.
assert client.finished # Отсутствие ответа не считается успешной прошивкой.
try: # Освобождает DLL даже при неуспешной регрессии.
native.model_flash_reset(0); assert control(16, b"\x01") == 10 # SELECT_MEMORY до ENTER_BOOT отклоняется как неверная сессия.
assert control(2) == 0 and control(16, b"\x01", session=2) == 10 # Чужая сессия не может поменять память открытого загрузчика.
assert control(16, b"\x01", device=5) is None and control(16) == 5 # Чужой PERIPH не отвечает, а отсутствующий октет выбора получает INVALID_SIZE.
assert control(16, b"\x02") == 2 and control(16, b"\x01") == 0 # Неизвестная память отклоняется, явная внутренняя Flash подтверждается.
assert control(3, struct.pack("<II", len(valid), zlib.crc32(valid))) == 0 and control(16, b"\x00") == 12 # После первых метаданных запрещает менять назначение обратно в EEPROM.
assert not native.model_flash_erase_count() and native.model_flash_intact() # Проверки команд и метаданных не начинают разрушительную операцию Flash.
invalid = [ram, valid[:-2], valid + bytes(2), valid[:-4] + bytes(4)] # Проверяет RAM-адреса, отсутствие терминатора, лишний хвост и повреждённые данные при правильной транспортной CRC.
invalid += [valid[:-2] + section(address, [1, 2]) + bytes(2) for address in (0x2FFFFF, 0x338000, 0x33FFF6, 0x33FFF8, 0x380400, APP_DATA)] # Запрещает сектор A, аппаратный boot, CSM, OTP, внешние адреса и пересечение секций.
for payload in invalid: # Эти файлы проверяются непосредственно MCU, даже если штатный SETGUI отвергает их раньше.
native.model_flash_reset(0); assert not native.model_flash_check((ctypes.c_uint * len(payload))(*payload), len(payload)) # Ни правильный CRC CAN, ни наличие FAB1 не разрешают структурно неверный образ.
assert not native.model_flash_erase_count() and native.model_flash_intact() # Отказ обязан оставить внутреннюю Flash и параметры неизменными.
native.model_flash_reset(0); transfer(ram, "eeprom") # Прежний клиент записывает RAM-образ в EEPROM без новой команды SELECT_MEMORY.
assert native.model_select() == 0 and native.model_confirm(0) # Создаёт подтверждённый резерв A для следующего обновления Flash.
native.model_reopen(); transfer(valid) # Обновляет внутреннюю Flash, используя B только как временный staging.
assert native.model_flash_valid() and native.model_flash_intact() and native.model_select() == 0 # Сохранены FAB1/CRC, boot, параметры и активный EEPROM-резерв A.
for fault in (1, 2, 3): # Проверяет отказ erase, обрыв обычных данных и обрыв публикации FAB1.
native.model_flash_reset(fault) # Каждый сценарий получает независимую исходную память и сессию.
try: transfer(valid) # Ошибка аппаратной записи должна пройти до Python-клиента как отказ COMMIT.
except RuntimeError: pass # Ожидаемый отрицательный статус не подменяется успехом передачи CAN.
else: raise AssertionError("Ошибка Flash ошибочно подтверждена") # Любой успешный REBOOT после отказа проваливает проверку.
assert not native.model_flash_valid() and native.model_flash_intact() # Частичный образ не запускается, а постоянный boot и параметры сохраняются.
for corrupt, crc in ((True, None), (False, zlib.crc32(valid) ^ 1)): # Проверяет изменение EEPROM после VERIFY и неверную транспортную CRC CAN.
native.model_flash_reset(0) # Стирание Flash должно остаться недоступным в обоих случаях.
try: transfer(valid, corrupt=corrupt, crc=crc) # Использует правильный SCI8, меняя только независимую причину отказа.
except RuntimeError: pass # MCU обязан явно отклонить повреждение.
else: raise AssertionError("Повреждённый образ ошибочно принят") # Непроверенный образ не может пройти COMMIT.
assert not native.model_flash_erase_count() and native.model_flash_intact() # Доказывает обнаружение ошибки до первого erase.
sample = os.environ.get("CAN_FLASH_IMAGE") # Позволяет дополнительно проверить текущий реальный TI SCI8 Flash BIN.
if sample: native.model_flash_reset(0); transfer(Path(sample).read_bytes()); assert native.model_flash_valid() and native.model_flash_intact() # Проверяет весь реальный выпуск тем же клиентом и writer.
finally: # Windows не позволяет удалить загруженную DLL без явного освобождения.
unload = ctypes.WinDLL("kernel32").FreeLibrary; unload.argtypes = [ctypes.c_void_p]; unload.restype = ctypes.c_int # Освобождает только дескриптор текущей аппаратной модели.
assert unload(native._handle) # Завершает использование временной DLL до удаления её каталога.
print("PASS: SETGUI CAN -> EEPROM staging -> internal Flash, protected boot, fallback, CRC and interrupted programming") # Подтверждает модель протокола и памяти, не выдавая её за стендовый тест.