Добавить SCI и J-Link и обновить общие порты устройств
Подключить профили прошивки из JSON и подготовку Python для CCS/Keil. Добавить SCI-программатор F28335 и поддержку STM32 через J-Link. Обновить Linux-консоль, CAN485 и Modbus и проверки общих портов.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
"""Эталонные обмены SCI ROM и TI Flash Kernel A без настоящей платы.""" # Проверки не открывают COM и не вызывают внешнюю программу C2Prog.
|
||||
import struct # Формирует независимые тестовые SCI8-векторы с явно заданными словами и адресами.
|
||||
import pytest # Проверяет отказ при повреждениях и граничные размеры порций ядра TI.
|
||||
from protocan.c2000_sci import Session, decode, validate, ulitco_enter # Испытывает настоящий ctypes-порт общего C99-ядра.
|
||||
|
||||
|
||||
def sci8(address, words): # Готовит минимальную адресную таблицу для эталонных данных теста.
|
||||
pointer = struct.pack("<HH", address >> 16, address & 65535) # SCI8 хранит старшее слово адреса перед младшим.
|
||||
return b"\xaa\x08" + bytes(16) + pointer + struct.pack("<H", len(words)) + pointer + struct.pack("<" + "H" * len(words), *words) + bytes(2) # Вектор содержит ключ, резерв, entry, одну секцию и терминатор.
|
||||
|
||||
|
||||
@pytest.mark.parametrize("words", [1, 1024, 1025, 2048, 2050]) # Границы проверяют обычную порцию, остаток и дополнительный нулевой ACK официального TI kernel.
|
||||
def test_complete_echo_and_checksum_transcript(words): # Фактические ответы подтверждают порядок и неизменность всего приложения.
|
||||
kernel, image = sci8(0x9000, [0x1234, 0x5678]), sci8(0x328000, [0xA55A] * words) # RAM-ядро и Flash занимают разные разрешённые области.
|
||||
session, received, pauses, zero_acks = Session(kernel, image), bytearray(), [], 0 # Наблюдения не используют внутреннюю структуру C-контекста.
|
||||
for _ in range(len(kernel) + words + 100): # Конечный предел выявляет зацикливание автомата.
|
||||
state, tx, expected, delay = session.next() # Проверяет настоящий сформированный C-пакет.
|
||||
if state == 0: break # Успех допустим лишь после отправки нулевого терминатора приложения.
|
||||
if state == 2: pauses.append(delay); continue # Внешний транспорт выполняет только заданную алгоритмом задержку.
|
||||
if expected == 1: session.accept(tx) # ROM и autobaud должны вернуть точное эхо отправленного октета.
|
||||
else: # Kernel A подтверждает сумму октетов после erase/program, без побайтового эха приложения.
|
||||
received.extend(tx) # Снимок подтверждает отсутствие пропущенных, повторённых либо изменённых октетов SCI8.
|
||||
if expected: session.accept(struct.pack("<H", sum(tx) & 65535)) # Независимый эталон суммы следует опубликованному исходнику TI SendCheckSum.
|
||||
if not tx: zero_acks += 1 # Пустой остаток TI подтверждается отдельным нулевым словом.
|
||||
else: pytest.fail("Автомат SCI не завершился") # Отсутствие терминального перехода считается ошибкой протокола.
|
||||
assert bytes(received) == image # MCU получил ровно исходный Flash SCI8 без переименования или подмены содержимого.
|
||||
assert pauses == [3000, 3000] and session.progress == 100 # Порядок ожиданий совпадает с TI f05_DownloadImage.
|
||||
assert zero_acks == int(words > 1024 and words % 1024 == 0) # Обрабатывает особенность исходного TI Kernel A на кратной порции.
|
||||
|
||||
|
||||
def test_corrupt_echo_and_checksum_stop_transfer(): # Повреждённое подтверждение не должно разрешать следующий пакет.
|
||||
kernel, image = sci8(0x9000, [1]), sci8(0x328000, [2]) # Минимальные векторы отделяют проверку ответа от больших файлов.
|
||||
session = Session(kernel, image); session.next() # Первый пакет требует эхо ASCII A.
|
||||
with pytest.raises(ValueError, match="не совпали"): session.accept(b"B") # Чужой байт autobaud не принимается как связь с ROM.
|
||||
session = Session(kernel, image) # Независимый сеанс проверяет повреждение двухоктетной суммы Flash.
|
||||
while True: # Доходит до первой операции erase/header без использования внутренних фаз автомата.
|
||||
state, tx, expected, _ = session.next() # Паузы не требуют аппаратного ответа.
|
||||
if state == 2: continue # Номер стадии остаётся внутри C-ядра.
|
||||
if expected == 1: session.accept(tx) # Корректный ROM позволяет загрузить RAM-ядро.
|
||||
else: break # Первый двухоктетный ответ должен подтвердить заголовок приложения.
|
||||
with pytest.raises(ValueError, match="не совпали"): session.accept(b"\xff\xff") # Неверная сумма не разрешает программирование следующей секции.
|
||||
|
||||
|
||||
def test_ascii_decode_memory_ranges_and_monitor_command(): # Проверяет переносимость форматов и аппаратные границы до открытия порта.
|
||||
image = sci8(0x328000, [0x1234]) # Обычный Flash-вектор не пересекает CSM.
|
||||
ascii_image = b"\x02\n" + " ".join(f"{value:02X}" for value in image).encode("ascii") + b"\n\x03\n" # Формат соответствует выводу TI hex2000 -boot -sci8 -a.
|
||||
assert decode(ascii_image) == image and validate(image) == (0x328000, 4) # Сектор C имеет маску 0x0004 в TI Flash API.
|
||||
with pytest.raises(ValueError): decode(ascii_image[:-2]) # Усечённый ETX не должен оставить частично декодированное ядро.
|
||||
with pytest.raises(ValueError): validate(sci8(0x33FFF8, [0x1234])) # Область CSM-паролей запрещена независимо от имени файла.
|
||||
with pytest.raises(ValueError): validate(sci8(0x8000, [0x1234])) # RAM-образ не может стать Flash-приложением из-за выбранного loader.
|
||||
assert ulitco_enter() == bytes([247, 213, 255, 93, 0, 0, 1, 0, 93, 1]) # Эталон совпадает с десятиоктетным Monitor-запросом FLASH_NUM=93 текущей прошивки UlitCo.
|
||||
@@ -0,0 +1,99 @@
|
||||
"""Проверки адресованной прошивки и защиты данных без подключения реального MCU.""" # Подмена касается только файлов снимков, production-план остаётся настоящим.
|
||||
from dataclasses import replace # Позволяет воспроизводить неверные настройки и адрес BIN независимо от формы.
|
||||
from pathlib import Path # Тесты создают все входы только во временном каталоге pytest.
|
||||
import struct # Создаёт корректные Cortex-M3-векторы и проверяет настоящий формат manifest UKSVEP.
|
||||
import zlib # Независимо вычисляет ожидаемый CRC32 приложения.
|
||||
import pytest # Параметры покрывают ошибки до первой аппаратной записи.
|
||||
from firmware_image.core import HexSegment, parse_intel_hex # Канонический parser проверяет сформированный снимок для Commander.
|
||||
from set_devices.firmware import FirmwareImage # Испытывает тот же тип образа, который передаёт SETGUI.
|
||||
from set_devices.stm32_jtag import JLinkOptions, FLASH_BASE, UKS_APP, UKS_PARAMS, UKS_META, prepare_image, encode_hex, programming_script, start_script, verify_readback # Проверяет публичную переносимую реализацию templates.
|
||||
from set_devices import stm32_jtag as module # Позволяет изолировать поиск установленного программатора от настоящего PATH Windows.
|
||||
|
||||
|
||||
def app_image(tmp_path, extra=()): # Создаёт небольшой корректный app HEX и необязательные отдельные диапазоны.
|
||||
data = struct.pack('<II', 0x2000C000, UKS_APP + 9) + bytes(range(64)) # MSP соответствует 48-КиБ SRAM; Thumb Reset_Handler находится внутри payload.
|
||||
path = tmp_path / 'app.hex' # Исходный файл не содержит служебного manifest, как обычный postbuild приложения.
|
||||
path.write_text(encode_hex([HexSegment(UKS_APP, data), *extra]), encoding='utf-8') # Реальный Intel HEX проходит штатную загрузку FirmwareImage.
|
||||
return FirmwareImage.load(path) # Позволяет проверять также сохранность выбора и отказ при изменении файла.
|
||||
|
||||
|
||||
def test_app_only_gets_production_manifest_and_sparse_snapshot(tmp_path): # JTAG app-only обязан остаться запускаемым постоянным загрузчиком.
|
||||
image, options = app_image(tmp_path), JLinkOptions() # Использует проверенный профиль UKSVEP STM32F103RC.
|
||||
plan = prepare_image(image, options) # Production-план автоматически добавляет запись активации приложения.
|
||||
assert [(part.start, len(part.data)) for part in plan] == [(UKS_APP, 72), (UKS_META, 24)] # Параметры и разрыв не превращаются в данные для записи.
|
||||
magic, size, crc, nsize, ncrc, product = struct.unpack('<6I', plan[-1].data) # Формат должен читаться настоящим boot_port.c без адаптации.
|
||||
assert (magic, size, crc, nsize, ncrc, product) == (0x554B5356, 72, zlib.crc32(image.data), ~72 & 0xFFFFFFFF, ~zlib.crc32(image.data) & 0xFFFFFFFF, 0x5606) # Проверяет взаимные инверсии и аппаратный Product UKSVEP.
|
||||
assert tuple(parse_intel_hex(encode_hex(plan))) == plan # Снимок Commander сохраняет точные адреса и байты обоих диапазонов.
|
||||
assert not any(part.start <= UKS_PARAMS <= part.end for part in plan) # Страница настроек отсутствует в адресованных data-записях.
|
||||
|
||||
|
||||
def test_factory_image_with_boot_and_manifest_is_not_flattened(tmp_path): # Полный install HEX не должен стереть промежуточную страницу параметров.
|
||||
image = app_image(tmp_path, [HexSegment(FLASH_BASE, b'boot' * 64)]) # Адрес загрузчика отделён от приложения разрывом.
|
||||
plan = prepare_image(image, JLinkOptions()) # Сначала получает правильный manifest для готового установочного комплекта.
|
||||
image.path.write_text(encode_hex(plan), encoding='utf-8') # Моделирует опубликованный boot+app+manifest HEX.
|
||||
assert prepare_image(FirmwareImage.load(image.path), JLinkOptions()) == plan # Повторная подготовка не добавляет FF и не меняет готовый manifest.
|
||||
|
||||
|
||||
@pytest.mark.parametrize('address', [UKS_PARAMS - 1, UKS_PARAMS, UKS_PARAMS + 2047]) # Касание любого края защищённой страницы запрещено.
|
||||
def test_parameter_page_overlap_is_rejected(tmp_path, address): # Ошибка выявляется до обращения к J-Link.
|
||||
image = app_image(tmp_path, [HexSegment(address, b'xx')]) # Два байта проверяют также вход в страницу с предыдущего адреса.
|
||||
with pytest.raises(ValueError, match='страницу параметров'): prepare_image(image, JLinkOptions()) # Полный дамп нельзя случайно записать вместо установочного разреженного HEX.
|
||||
|
||||
|
||||
def test_stale_image_and_relocated_hex_are_rejected(tmp_path): # Выбор и подтверждение должны относиться к тем же байтам и адресам.
|
||||
image = app_image(tmp_path) # Сохраняет исходный снимок формы до вмешательства в файл.
|
||||
with pytest.raises(ValueError, match='адрес HEX'): prepare_image(replace(image, base_address=UKS_APP + 4), JLinkOptions()) # Явный адрес HEX нельзя переопределять как адрес BIN.
|
||||
image.path.write_text(encode_hex([HexSegment(UKS_APP, image.data + b'changed')]), encoding='utf-8') # Моделирует пересборку файла после выбора пользователем.
|
||||
with pytest.raises(ValueError, match='изменён'): prepare_image(image, JLinkOptions()) # До повторного выбора аппаратное программирование запрещено.
|
||||
|
||||
|
||||
def test_bin_needs_address_and_accepts_another_stm32(tmp_path): # Общий режим не применяет специальные адреса и manifest UKSVEP.
|
||||
path = tmp_path / 'raw.bin' # BIN не содержит собственной карты памяти.
|
||||
path.write_bytes(bytes(range(64))) # Для обычного MCU не навязывается структура приложения UKSVEP.
|
||||
image, options = FirmwareImage.load(path), JLinkOptions(device='STM32G474VE', flash_kib=512, interface='SWD', uksvep=False) # Использует другую физическую карту STM32.
|
||||
with pytest.raises(ValueError, match='объём Flash'): prepare_image(image, options) # Нулевой адрес BIN не означает RAM или неявное начало Flash.
|
||||
assert prepare_image(replace(image, base_address=FLASH_BASE), options) == (HexSegment(FLASH_BASE, image.data),) # Явный корректный адрес создаёт ровно один диапазон без постороннего manifest.
|
||||
|
||||
|
||||
@pytest.mark.parametrize('changes', [{'device': 'STM32F103RC\ng'}, {'serial': '123 -erase'}, {'interface': 'UART'}, {'speed_khz': 0}, {'flash_kib': 0}, {'device': 'STM32F407VE'}]) # Проверяет интерфейс, размеры, профиль и внедрение дополнительных команд.
|
||||
def test_invalid_target_is_rejected(changes): # Поля будущего command file валидируются единым контрактом.
|
||||
with pytest.raises(ValueError): replace(JLinkOptions(), **changes).arguments() # Ошибка не требует создания процесса и не открывает программатор.
|
||||
|
||||
|
||||
def test_readback_rejects_partial_write_and_lost_parameters(tmp_path): # Ложный нулевой exit code не должен привести к запуску испорченного MCU.
|
||||
options = JLinkOptions() # Сохранность параметров проверяется для конкретного UKSVEP-профиля.
|
||||
plan = prepare_image(app_image(tmp_path), options) # Использует реальный production-план с manifest.
|
||||
before = bytes((index * 7) & 255 for index in range(256 * 1024)) # Полный backup содержит не-FF настройки, чтобы их потеря была заметной.
|
||||
after = bytearray(before) # Моделирует неизменённые области между реально записанными диапазонами.
|
||||
for part in plan: after[part.start - FLASH_BASE:part.start - FLASH_BASE + len(part.data)] = part.data # Применяет только адресованные данные плана.
|
||||
verify_readback(plan, options, before, after) # Полная корректная запись разрешает отдельный этап reset/run.
|
||||
after[UKS_PARAMS - FLASH_BASE] ^= 1 # Воспроизводит потерю одного бита существующих параметров.
|
||||
with pytest.raises(ValueError, match='параметров'): verify_readback(plan, options, before, after) # Сохранность protected page проверяется по аппаратным снимкам.
|
||||
after[UKS_PARAMS - FLASH_BASE] ^= 1 # Восстанавливает параметры для отдельной проверки ошибки программирования приложения.
|
||||
after[UKS_APP - FLASH_BASE + 8] ^= 1 # Повреждает один реально записываемый байт приложения.
|
||||
with pytest.raises(ValueError, match='Ошибка проверки'): verify_readback(plan, options, before, after) # Запуск запрещён даже при совпавшей длине обоих SaveBin.
|
||||
with pytest.raises(ValueError, match='полный backup'): verify_readback(plan, options, before, b'') # Преждевременный выход Commander с кодом ноль не считается успехом.
|
||||
|
||||
|
||||
def test_scripts_separate_programming_from_reset_run(tmp_path): # Архитектура не должна запускать ядро до проверки на хосте.
|
||||
options = JLinkOptions(serial='603000869') # Явный серийный номер должен сохраняться отдельным аргументом.
|
||||
program, run = programming_script(tmp_path / 'folder with spaces', options), start_script(options) # Пути с пробелами должны быть корректно заключены в кавычки.
|
||||
assert program.index('before.bin') < program.index('loadfile') < program.index('after.bin') # Backup выполняется до первой записи, readback — после неё.
|
||||
assert '\ng\n' not in program and '\ng\n' in run and 'loadfile' not in run # Отдельный проверенный этап содержит только reset/служебные регистры/run.
|
||||
assert 'erase' not in program.lower() # Не вводит массовое стирание через новую общую реализацию.
|
||||
assert options.arguments()[:2] == ['-USB', '603000869'] # Выбранный J-Link не заменяется другим подключённым адаптером.
|
||||
assert '-JTAGConf' in options.arguments() and '-1,-1' in options.arguments() # Batch не должен остановиться на интерактивном запросе цепочки JTAG.
|
||||
|
||||
|
||||
def test_hex_encoder_handles_unaligned_64k_boundary(): # Граничный адрес внутри сегмента не должен переполнить 16-битное поле записи.
|
||||
segments = (HexSegment(0x0800FFF9, bytes(range(40))),) # Невыровненный старт пересекает окно 64 КиБ внутри первой порции.
|
||||
assert tuple(parse_intel_hex(encode_hex(segments))) == segments # Независимый C-parser подтверждает корректную смену Extended Linear Address.
|
||||
|
||||
|
||||
def test_autodetection_does_not_select_java_jlink(monkeypatch): # Windows PATH может раньше SEGGER содержать одноимённый инструмент JDK.
|
||||
java = 'C:/jdk/bin/JLink.exe' # Имя совпадает без учёта регистра с программатором SEGGER.
|
||||
monkeypatch.setattr(module.shutil, 'which', lambda name: java) # Установленный в системе J-Link не участвует в этой проверке.
|
||||
monkeypatch.setattr(Path, 'is_file', lambda path: path.as_posix() in (java, 'C:/jdk/bin/java.exe')) # Моделирует только bin JDK и отсутствие Keil/SEGGER.
|
||||
assert module.find_jlink() == '' # Общая форма должна запросить настоящий executable вместо запуска Java.
|
||||
monkeypatch.setattr(Path, 'is_file', lambda path: path.as_posix() in (java, 'C:/jdk/bin/java.exe', 'C:/Keil_v5_41/ARM/Segger/JLink.exe')) # Добавляет именно установленный SEGGER, не меняя неоднозначный PATH.
|
||||
assert module.find_jlink().replace('\\', '/') == 'C:/Keil_v5_41/ARM/Segger/JLink.exe' # Проверенный каталог Keil получает приоритет над JDK.
|
||||
Reference in New Issue
Block a user