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

56 lines
8.9 KiB
Python

import ctypes # Вызывает настоящие MCU-валидатор и writer на изолированной модели Flash, не используя оборудование.
import importlib.util # Подключает контракт FAB1 из выбранной зависимости templates без изменения окружения GUI.
import os # Передаёт процессу GCC путь к его runtime DLL в Windows.
from pathlib import Path # Все файлы испытания находятся в собственном временном каталоге.
import struct # Формирует SCI8 с известными адресами и проверяемыми повреждениями.
import subprocess # Компилирует production-функции host-компилятором без обращения к JTAG.
import tempfile # Автоматически очищает только временные исходники и DLL текущего испытания.
ROOT = Path(__file__).resolve().parents[2] # Корень содержит production flash_rs.c и исходники host-модели.
TEMPLATES = ROOT.parent / 'newProject/SETGUI/third_party/templates' # Использует ту же реализацию FAB1, что компилируемая прошивка.
PORT = TEMPLATES / 'c/protocan-boot/ports/tms320f28335' # Включает общий C-валидатор запуска постоянного загрузчика.
GCC = Path('C:/msys64/mingw64/bin/gcc.exe') # Установленный GCC предоставляет независимый от TI host-компилятор.
os.environ['PATH'] = str(GCC.parent) + os.pathsep + os.environ['PATH'] # Добавляет runtime только в среду этого тестового процесса.
spec = importlib.util.spec_from_file_location('flash_contract', TEMPLATES / 'python/protocan/f28335_flash.py') # Загружает общий Python-подписыватель описания.
contract = importlib.util.module_from_spec(spec) # Не подменяет установленный пакет protocan у пользователя.
spec.loader.exec_module(contract) # Ошибка контракта останавливает проверку вместо использования старого образа.
def block(address, words): # Формирует стандартный адресный блок TI SCI8 в октетах.
return struct.pack('<HHH', len(words), address >> 16, address & 65535) + struct.pack('<' + 'H' * len(words), *words) # Размер и адрес выражены словами C28x; каждое слово little endian.
entry = contract.APP_DATA # Точка входа расположена после фиксированного описания приложения в секторе H.
loaded = {entry: 0x1234, entry + 1: 0x5678, entry + 12: 0xAABB, entry + 13: 0xCCDD} # Разрыв между секциями проверяет включение стёртых слов 0xFFFF в CRC.
signed = contract.descriptor(entry, loaded) # Python формирует тот же FAB1, который C-загрузчик проверяет перед запуском.
header = b'\xaa\x08' + bytes(16) + struct.pack('<HH', entry >> 16, entry & 65535) # Сохраняет штатный SCI8 header и адрес безопасного startup.
valid = header + block(contract.APP_START, struct.unpack('<16H', signed)) + block(entry, [0x1234, 0x5678]) + block(entry + 12, [0xAABB, 0xCCDD]) + bytes(2) # Три блока отдельно проверяют отложенную публикацию описания после данных.
source = (ROOT / 'Source/Internal/flash_rs.c').read_text(encoding='utf-8') # Читает текущий production writer со всеми защитами адресов.
helpers = source[source.index('#pragma CODE_SECTION(byte_at'):source.index('#pragma CODE_SECTION(reply_status')] # Декодирование RS и callback виртуального Flash остаются производственными.
validator = source[source.index('#pragma CODE_SECTION(validate_image'):source.index('#pragma CODE_SECTION(program_image')] # Извлекает настоящую проверку до первого erase.
writer = source[source.index('#pragma CODE_SECTION(program_image'):source.index('void FlashRs_Command')] # Извлекает настоящий порядок erase, program, verify и последней публикации FAB1.
with tempfile.TemporaryDirectory(prefix='protected-f28335-') as directory: # Никакие промежуточные файлы не заменяют рабочую прошивку.
directory = Path(directory) # Разрешает все пути относительно одного изолированного запуска.
(directory / 'production.inc').write_text(helpers + validator + writer, encoding='utf-8') # Русские комментарии production-функций сохраняются в UTF-8.
library = directory / 'writer.dll' # Динамическая библиотека содержит только модель аппаратуры и настоящий MCU-код.
subprocess.run([str(GCC), '-shared', '-std=c99', '-Wno-unknown-pragmas', '-Wno-int-to-pointer-cast', '-I' + str(PORT), '-I' + str(directory), str(ROOT / 'tests/protected_boot/rs_program_model.c'), '-o', str(library)], check=True) # Не вызывает TI, программатор или аппаратные драйверы.
native = ctypes.CDLL(str(library)) # Загружает только созданную библиотеку этой проверки.
native.check_image.argtypes = [ctypes.POINTER(ctypes.c_uint16), ctypes.c_uint32] # Каждая физическая октета передаётся отдельным словом модели.
native.check_image.restype = ctypes.c_uint16 # Сохраняет коды предварительной проверки MCU.
native.check_program.argtypes = [ctypes.POINTER(ctypes.c_uint16), ctypes.c_uint32, ctypes.c_uint] # Последний параметр задаёт прерываемую стадию записи.
native.check_program.restype = ctypes.c_uint16 # Ненулевой результат API не превращается в успех модели.
for payload, expected in [(valid, 0), (valid[:-2], 0x1002), (valid + bytes(2), 0x1002), (valid[:-4] + b'\0\0' + bytes(2), 0x1002)]: # Проверяет полный файл, обрезание, хвост и нарушение CRC данных.
octets = (ctypes.c_uint16 * len(payload))(*payload) # Исключает зависимость от host-char при вызове производственного C-кода.
assert native.check_image(octets, len(payload)) == expected # Python-подпись и MCU-проверка должны согласовываться на реальном контракте.
for address in (0x2FFFFF, 0x338000, 0x33FFF6, 0x33FFF8, 0x340000, 0x380400): # Проверяет сектор A, аппаратный boot, CSM, OTP и обе внешние границы.
payload = valid[:-2] + block(address, [1, 2]) + bytes(2) # Добавляет запрещённую секцию к иначе полностью правильному приложению.
octets = (ctypes.c_uint16 * len(payload))(*payload) # Передаёт исходные октеты в неизменённый MCU-валидатор.
assert native.check_image(octets, len(payload)) == 0x1002 # Ни один запрещённый адрес не допускает erase.
octets = (ctypes.c_uint16 * len(valid))(*valid) # Одни и те же правильные данные проходят все стадии испытания writer.
for fault, expected in ((0, 0), (1, 22), (2, 22), (3, 22)): # Обрыв моделируется на каждой порции двух секций данных и на публикации FAB1.
assert native.check_program(octets, len(valid), fault) == expected # Boot сохраняется побитно, а частичная Flash не проходит проверку запуска.
unload = ctypes.WinDLL('kernel32').FreeLibrary # Освобождает DLL перед удалением её временного каталога в Windows.
unload.argtypes, unload.restype = [ctypes.c_void_p], ctypes.c_int # Передаёт полный размер дескриптора библиотеки.
assert unload(native._handle) # Закрывает только библиотеку текущего испытания.
print('PASS: production FAB1 CRC, protected sector A, RS erase/program/verify and power-loss stages') # Подтверждает проверки модели, не выдавая их за физическое испытание платы.