Добавить протокол Altera Logic и общие клиенты прошивки
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
"""Переносимые модули и тонкая Python-обёртка SETProtocol."""
|
||||
|
||||
# Consumers may supply additional platform ports from their pinned templates
|
||||
# checkout. The selected primary checkout wins; missing modules can coexist.
|
||||
from pkgutil import extend_path
|
||||
__path__ = extend_path(__path__, __name__)
|
||||
|
||||
from .native import (
|
||||
NativeCore, NativeCoreUnavailable, NativeFrame, NativeGuiFrame,
|
||||
NativeGuiParser, NativeParser, NativeProtocol, NativeProtocolUnavailable,
|
||||
|
||||
123
python/protocan/can_boot.py
Normal file
123
python/protocan/can_boot.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""Legacy ProtoCAN Boot client ported from Gui_Android CanFirmwareProtocol.kt."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import struct
|
||||
|
||||
from .protocan import ProtoCanId
|
||||
from .transport import build_frame
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CanBootTarget:
|
||||
device_type: int = 0
|
||||
device: int = 13
|
||||
product_type: int = 0x166
|
||||
hardware_min: int = 0
|
||||
hardware_max: int = 255
|
||||
session_id: int = 1
|
||||
|
||||
def __post_init__(self):
|
||||
if not 0 <= self.device_type <= 7 or not 0 <= self.device <= 15:
|
||||
raise ValueError("Адрес ProtoCAN: Device Type 0…7, Device 0…15")
|
||||
if not 0 <= self.product_type <= 65535:
|
||||
raise ValueError("Product Type вне диапазона u16")
|
||||
if not 0 <= self.hardware_min <= self.hardware_max <= 255:
|
||||
raise ValueError("Неверный диапазон аппаратных ревизий")
|
||||
if not 1 <= self.session_id <= 255:
|
||||
raise ValueError("Session ID должен быть 1…255")
|
||||
|
||||
|
||||
class CanBootTransfer:
|
||||
"""Windowed 8-byte blocks; control 0x9, slots 0xA/0xB, status 0xC."""
|
||||
def __init__(self, image, target: CanBootTarget):
|
||||
if not 0 < len(image.data) <= 512 * 1024:
|
||||
raise ValueError("ProtoCAN Boot: размер образа 1…512 КиБ")
|
||||
if not 0 <= image.version <= 0xFFFFFFFF:
|
||||
raise ValueError("Версия образа вне диапазона u32")
|
||||
self.image, self.target = image, target
|
||||
self.stage = "enter"
|
||||
self.next_block = 0
|
||||
self.slot = None
|
||||
self.finished = False
|
||||
self._window_end = 0
|
||||
|
||||
@property
|
||||
def percent(self):
|
||||
return min(100, self.next_block * 8 * 100 // len(self.image.data))
|
||||
|
||||
def _frame(self, kind, body, data=b""):
|
||||
t = self.target
|
||||
return build_frame(ProtoCanId.build(1, 0, t.device_type, t.device, kind, body), data, to_can=True)
|
||||
|
||||
def _control(self, command, data=b""):
|
||||
return [self._frame(9, self.target.session_id << 8 | command, data)]
|
||||
|
||||
def start(self):
|
||||
return self._control(2)
|
||||
|
||||
def abort(self):
|
||||
return self._control(10)
|
||||
|
||||
def accepts(self, frame):
|
||||
p = ProtoCanId.parse(frame.can_id)
|
||||
t = self.target
|
||||
return (not frame.to_can and frame.ide and not frame.rtr and not frame.is_error
|
||||
and p.msg_type == 12 and p.pm == 1 and p.device_type == t.device_type
|
||||
and p.device == t.device and p.body >> 8 == t.session_id and len(frame.data) == 8)
|
||||
|
||||
def _window(self):
|
||||
total = (len(self.image.data) + 7) // 8
|
||||
self._window_end = min(total, self.next_block + 16)
|
||||
return [self._frame(10 + self.slot, i, self.image.data[i * 8:(i + 1) * 8].ljust(8, b"\xff"))
|
||||
for i in range(self.next_block, self._window_end)]
|
||||
|
||||
def handle_status(self, frame):
|
||||
if not self.accepts(frame):
|
||||
return [], ""
|
||||
command = ProtoCanId.parse(frame.can_id).body & 255
|
||||
expected_command = {"enter": 2, "image": 3, "compat": 4, "erase": 5,
|
||||
"data": 0, "verify": 6, "commit": 7, "reboot": 9}.get(self.stage)
|
||||
if command != expected_command:
|
||||
return [], ""
|
||||
status, slot, expected = struct.unpack_from("<BBH", frame.data)
|
||||
if status == 1:
|
||||
return [], "Загрузчик занят"
|
||||
if status not in (0, 8) or (status == 8 and self.stage != "data"):
|
||||
raise RuntimeError(f"ProtoCAN Boot: ошибка 0x{status:02X}")
|
||||
if self.stage == "enter":
|
||||
self.stage = "image"
|
||||
return self._control(3, struct.pack("<II", len(self.image.data), self.image.crc32)), "Передача метаданных"
|
||||
if self.stage == "image":
|
||||
self.stage = "compat"
|
||||
t = self.target
|
||||
return self._control(4, struct.pack("<HBBI", t.product_type, t.hardware_min, t.hardware_max, self.image.version)), "Проверка совместимости"
|
||||
if self.stage == "compat":
|
||||
if slot not in (0, 1):
|
||||
raise RuntimeError("ProtoCAN Boot: неверный слот")
|
||||
self.slot, self.stage = slot, "erase"
|
||||
return self._control(5), "Стирание неактивного слота"
|
||||
if self.stage in ("erase", "data"):
|
||||
total = (len(self.image.data) + 7) // 8
|
||||
# The final index of a 512 KiB image wraps the u16 status field.
|
||||
if self.stage == "data" and expected == 0 and self._window_end == 65536 and status == 0:
|
||||
expected = 65536
|
||||
limit = total if self.stage == "erase" else self._window_end
|
||||
if expected > limit:
|
||||
raise RuntimeError("ProtoCAN Boot: подтверждён непереданный блок")
|
||||
if self.stage == "data" and status == 0 and expected < self.next_block:
|
||||
return [], ""
|
||||
self.next_block = expected
|
||||
if expected == total:
|
||||
self.stage = "verify"
|
||||
return self._control(6), "Проверка CRC32"
|
||||
self.stage = "data"
|
||||
return self._window(), "Передача блоков ProtoCAN Boot"
|
||||
if self.stage == "verify":
|
||||
self.stage = "commit"
|
||||
return self._control(7), "Активация образа"
|
||||
if self.stage == "commit":
|
||||
self.stage = "reboot"
|
||||
return self._control(9), "Перезапуск прибора"
|
||||
self.stage, self.finished = "finished", True
|
||||
return [], "Прошивка ProtoCAN Boot завершена"
|
||||
@@ -131,9 +131,11 @@ MSGTYPE_RU = {
|
||||
#: но не присваивает приборам имён.
|
||||
DEVICE_TYPE_NAMES = {
|
||||
0x0: "Верхний уровень",
|
||||
0x6: "Логические анализаторы",
|
||||
}
|
||||
|
||||
DEVICE_ADDRESS_NAMES = {
|
||||
(0x6, 0xE): "Altera Logic",
|
||||
(0x7, 0xD): "configurator",
|
||||
}
|
||||
|
||||
|
||||
239
python/protocan/setp_firmware.py
Normal file
239
python/protocan/setp_firmware.py
Normal file
@@ -0,0 +1,239 @@
|
||||
"""SETProtocol v2 firmware client over segmented classic CAN."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import struct
|
||||
from dataclasses import dataclass
|
||||
from time import perf_counter
|
||||
|
||||
from setprotocol.can import CanAddress, CanFrame, CanReassembler, segment
|
||||
from setprotocol.core import (
|
||||
Capabilities,
|
||||
DeviceInfo,
|
||||
Feature,
|
||||
FirmwareBegin,
|
||||
FirmwareFlag,
|
||||
Frame as SetFrame,
|
||||
FrameFlag,
|
||||
MessageType,
|
||||
SetProtocolError,
|
||||
Status,
|
||||
build_frame,
|
||||
decode_datagram,
|
||||
decode_response,
|
||||
encode_firmware_data,
|
||||
)
|
||||
|
||||
from . import transport as tr
|
||||
from typing import Any
|
||||
|
||||
FirmwareImage = Any
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CanFirmwareTarget:
|
||||
"""Address and compatibility settings for a SETProtocol v2 target."""
|
||||
|
||||
node_id: int
|
||||
device_class: int
|
||||
hardware_min: int = 0
|
||||
hardware_max: int = 0xFF
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not 0 <= self.node_id <= 0xFF:
|
||||
raise ValueError("SETP node ID вне диапазона 0..255")
|
||||
if not 0 <= self.device_class <= 0xFFFF:
|
||||
raise ValueError("Device class вне диапазона u16")
|
||||
if not 0 <= self.hardware_min <= self.hardware_max <= 0xFFFFFFFF:
|
||||
raise ValueError("Диапазон hardware version задан неверно")
|
||||
|
||||
|
||||
class CanFirmwareTransfer:
|
||||
"""Stop-and-wait SETP v2 firmware transaction for a classic CAN link."""
|
||||
|
||||
HOST_NODE = 0
|
||||
CHANNEL = 1
|
||||
DEFAULT_BLOCK_SIZE = 64
|
||||
|
||||
def __init__(self, image: FirmwareImage, target: CanFirmwareTarget) -> None:
|
||||
if not image.data:
|
||||
raise ValueError("Образ прошивки пуст")
|
||||
if len(image.data) > 512 * 1024:
|
||||
raise ValueError("BALZAM поддерживает образ не более 512 КиБ")
|
||||
self.image = image
|
||||
self.target = target
|
||||
self.offset = 0
|
||||
self.block_size = self.DEFAULT_BLOCK_SIZE
|
||||
self.stage = "ping"
|
||||
self.finished = False
|
||||
self._sequence = 0
|
||||
self._expected_sequence = 0
|
||||
self._expected_type = 0
|
||||
self._response: SetFrame | None = None
|
||||
self._reassembler = CanReassembler()
|
||||
|
||||
def _request(self, message_type: int, payload: bytes = b"") -> list[tr.Frame]:
|
||||
self._sequence = (self._sequence + 1) & 0xFFFF
|
||||
if self._sequence == 0:
|
||||
self._sequence = 1
|
||||
self._expected_sequence = self._sequence
|
||||
self._expected_type = int(message_type)
|
||||
packet = build_frame(
|
||||
SetFrame(
|
||||
message_type=message_type,
|
||||
sequence=self._sequence,
|
||||
payload=payload,
|
||||
flags=FrameFlag.ACK_REQUIRED | FrameFlag.PRIORITY,
|
||||
source=self.HOST_NODE,
|
||||
destination=self.target.node_id,
|
||||
)
|
||||
)
|
||||
address = CanAddress(
|
||||
destination=self.target.node_id,
|
||||
source=self.HOST_NODE,
|
||||
priority=1,
|
||||
channel=self.CHANNEL,
|
||||
)
|
||||
return [tr.build_frame(item.can_id, item.data, to_can=True)
|
||||
for item in segment(packet, address)]
|
||||
|
||||
def start(self) -> list[tr.Frame]:
|
||||
return self._request(MessageType.PING)
|
||||
|
||||
def abort(self) -> list[tr.Frame]:
|
||||
return self._request(MessageType.FW_ABORT)
|
||||
|
||||
def accepts(self, frame: tr.Frame) -> bool:
|
||||
if frame.to_can or not frame.ide:
|
||||
return False
|
||||
try:
|
||||
address = CanAddress.unpack(frame.can_id)
|
||||
except SetProtocolError:
|
||||
return False
|
||||
if address.source != self.target.node_id or address.destination != self.HOST_NODE:
|
||||
return False
|
||||
try:
|
||||
packet = self._reassembler.feed(
|
||||
CanFrame(frame.can_id, frame.data), int(perf_counter() * 1000)
|
||||
)
|
||||
if packet is None:
|
||||
return False
|
||||
response = decode_datagram(packet)
|
||||
except SetProtocolError:
|
||||
return False
|
||||
if (
|
||||
not response.flags & FrameFlag.RESPONSE
|
||||
or response.source != self.target.node_id
|
||||
or response.destination != self.HOST_NODE
|
||||
or response.sequence != self._expected_sequence
|
||||
or response.message_type != self._expected_type
|
||||
):
|
||||
return False
|
||||
self._response = response
|
||||
return True
|
||||
|
||||
def _begin(self) -> list[tr.Frame]:
|
||||
begin = FirmwareBegin(
|
||||
image_size=len(self.image.data),
|
||||
image_crc32=self.image.crc32,
|
||||
image_version=self.image.version,
|
||||
base_address=self.image.base_address,
|
||||
slot=0,
|
||||
block_size=self.block_size,
|
||||
sha256=bytes.fromhex(self.image.sha256),
|
||||
flags=FirmwareFlag.RESUME | FirmwareFlag.ERASE_SLOT,
|
||||
)
|
||||
self.stage = "begin"
|
||||
return self._request(MessageType.FW_BEGIN, begin.encode())
|
||||
|
||||
def _next_data_or_end(self) -> tuple[list[tr.Frame], str]:
|
||||
if self.offset >= len(self.image.data):
|
||||
self.stage = "end"
|
||||
payload = struct.pack(
|
||||
"<II32s", len(self.image.data), self.image.crc32,
|
||||
bytes.fromhex(self.image.sha256),
|
||||
)
|
||||
return self._request(MessageType.FW_END, payload), "Проверка CRC32 и SHA-256"
|
||||
self.stage = "data"
|
||||
data = self.image.data[self.offset : self.offset + self.block_size]
|
||||
return (
|
||||
self._request(MessageType.FW_DATA, encode_firmware_data(self.offset, data)),
|
||||
"Передача блоков SETProtocol v2",
|
||||
)
|
||||
|
||||
def handle_status(self, _frame: tr.Frame) -> tuple[list[tr.Frame], str]:
|
||||
response, self._response = self._response, None
|
||||
if response is None:
|
||||
return [], ""
|
||||
status, body = decode_response(response)
|
||||
if status != Status.OK:
|
||||
try:
|
||||
name = Status(status).name
|
||||
except ValueError:
|
||||
name = "0x%04X" % status
|
||||
raise RuntimeError(name)
|
||||
|
||||
if self.stage == "ping":
|
||||
if len(body) != 4:
|
||||
raise RuntimeError("PING: неверная длина ответа")
|
||||
self.stage = "device_info"
|
||||
return self._request(MessageType.DEVICE_INFO), "Чтение информации об устройстве"
|
||||
|
||||
if self.stage == "device_info":
|
||||
info = DeviceInfo.decode(body)
|
||||
if self.target.device_class and info.device_class != self.target.device_class:
|
||||
raise RuntimeError(
|
||||
"device class 0x%04X вместо 0x%04X"
|
||||
% (info.device_class, self.target.device_class)
|
||||
)
|
||||
if not self.target.hardware_min <= info.hardware_version <= self.target.hardware_max:
|
||||
raise RuntimeError("hardware version устройства вне разрешённого диапазона")
|
||||
self.stage = "capabilities"
|
||||
return self._request(MessageType.CAPABILITIES), "Проверка возможностей устройства"
|
||||
|
||||
if self.stage == "capabilities":
|
||||
capabilities = Capabilities.decode(body)
|
||||
if not capabilities.features & Feature.FIRMWARE:
|
||||
raise RuntimeError("устройство не объявило поддержку firmware update")
|
||||
self.block_size = min(
|
||||
self.DEFAULT_BLOCK_SIZE,
|
||||
capabilities.max_payload - 12,
|
||||
)
|
||||
if self.block_size <= 0:
|
||||
raise RuntimeError("устройство объявило слишком маленький CAN firmware MTU")
|
||||
return self._begin(), "Начало SETProtocol v2 firmware session"
|
||||
|
||||
if self.stage == "begin":
|
||||
if len(body) != 4:
|
||||
raise RuntimeError("FW_BEGIN: неверный next_offset")
|
||||
self.offset = int.from_bytes(body, "little")
|
||||
if self.offset > len(self.image.data):
|
||||
raise RuntimeError("FW_BEGIN: next_offset за пределами образа")
|
||||
return self._next_data_or_end()
|
||||
|
||||
if self.stage == "data":
|
||||
if len(body) != 4:
|
||||
raise RuntimeError("FW_DATA: неверный next_offset")
|
||||
next_offset = int.from_bytes(body, "little")
|
||||
expected = min(self.offset + self.block_size, len(self.image.data))
|
||||
if next_offset != expected:
|
||||
raise RuntimeError(
|
||||
"FW_DATA: подтверждён offset %d вместо %d" % (next_offset, expected)
|
||||
)
|
||||
self.offset = next_offset
|
||||
return self._next_data_or_end()
|
||||
|
||||
if self.stage == "end":
|
||||
if len(body) != 4 or int.from_bytes(body, "little") != len(self.image.data):
|
||||
raise RuntimeError("FW_END: устройство не подтвердило полный образ")
|
||||
self.stage = "activate"
|
||||
return self._request(MessageType.FW_ACTIVATE), "Активация образа"
|
||||
|
||||
if self.stage == "activate":
|
||||
self.finished = True
|
||||
return [], "Прошивка по CAN (SETProtocol v2) завершена"
|
||||
return [], ""
|
||||
|
||||
@property
|
||||
def percent(self) -> int:
|
||||
return min(100, int(self.offset * 100 / len(self.image.data)))
|
||||
47
python/protocan/stm32_boot.py
Normal file
47
python/protocan/stm32_boot.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""Packets and constants for the STM32 system-memory UART bootloader (AN3155)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
ACK = 0x79
|
||||
NACK = 0x1F
|
||||
SYNC = b"\x7f"
|
||||
|
||||
|
||||
def command(code: int) -> bytes:
|
||||
"""Encode an AN3155 command and its one's-complement checksum."""
|
||||
if not 0 <= code <= 0xFF:
|
||||
raise ValueError("Код команды вне диапазона байта")
|
||||
return bytes((code, code ^ 0xFF))
|
||||
|
||||
|
||||
def address(value: int) -> bytes:
|
||||
"""Encode a big-endian 32-bit address followed by XOR checksum."""
|
||||
if not 0 <= value <= 0xFFFFFFFF:
|
||||
raise ValueError("Адрес вне диапазона u32")
|
||||
raw = value.to_bytes(4, "big")
|
||||
return raw + bytes((raw[0] ^ raw[1] ^ raw[2] ^ raw[3],))
|
||||
|
||||
|
||||
def write_payload(data: bytes) -> bytes:
|
||||
"""Encode one Write Memory payload (1..256 bytes)."""
|
||||
if not 1 <= len(data) <= 256:
|
||||
raise ValueError("Блок STM32 должен содержать от 1 до 256 байт")
|
||||
count = len(data) - 1
|
||||
checksum = count
|
||||
for value in data:
|
||||
checksum ^= value
|
||||
return bytes((count,)) + data + bytes((checksum,))
|
||||
|
||||
|
||||
MASS_ERASE = b"\xff\x00"
|
||||
|
||||
|
||||
def erase_pages_payload(pages: list[int]) -> bytes:
|
||||
"""Encode the standard Erase Memory page list used by STM32F1."""
|
||||
if not pages or len(pages) > 256 or any(not 0 <= page <= 0xFF for page in pages):
|
||||
raise ValueError("Список страниц STM32 должен содержать 1..256 номеров")
|
||||
body = bytes((len(pages) - 1, *pages))
|
||||
checksum = 0
|
||||
for value in body:
|
||||
checksum ^= value
|
||||
return body + bytes((checksum,))
|
||||
118
python/protocan/tms_firmware.py
Normal file
118
python/protocan/tms_firmware.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""BALZAM/PM67 firmware protocol, ported from Gui_Android Tms2812Protocol.
|
||||
|
||||
Addresses count 16-bit words; transfer lengths count bytes.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import struct
|
||||
|
||||
from protocan.tms2812 import crc16_modbus, build_upload_request, decode_upload_reply
|
||||
|
||||
CMD_LOAD = 51
|
||||
CMD_UPLOAD = 52
|
||||
CMD_TFLASH = 55
|
||||
CMD_INITLOAD = 58
|
||||
CMD_EXTEND = 60
|
||||
|
||||
|
||||
def packet(controller: int, command: int, payload: bytes) -> bytes:
|
||||
raw = bytes((controller, command)) + payload
|
||||
return raw + struct.pack("<H", crc16_modbus(raw))
|
||||
|
||||
|
||||
def init_load(controller: int, ram: int, size: int) -> bytes:
|
||||
return packet(controller, CMD_INITLOAD, struct.pack("<II", ram, size))
|
||||
|
||||
|
||||
def load_data(controller: int, data: bytes) -> bytes:
|
||||
return packet(controller, CMD_LOAD, data)
|
||||
|
||||
|
||||
def tflash(controller: int, ram: int, flash: int, size: int) -> bytes:
|
||||
return packet(controller, CMD_TFLASH, struct.pack("<III", ram, flash, size))
|
||||
|
||||
|
||||
def extend(controller: int, first: int, second: int, size: int, code: int, board: int) -> bytes:
|
||||
return packet(controller, CMD_EXTEND, struct.pack("<IIIBB", first, second, size, code, board))
|
||||
|
||||
|
||||
def extend_result(raw: bytes) -> tuple[int, int, int]:
|
||||
return struct.unpack_from("<III", raw, 2)
|
||||
|
||||
|
||||
def normalize_reply(raw: bytes, controller: int, command: int, size: int) -> bytes | None:
|
||||
"""Accept omitted reserved tail bytes only after the receive settle interval."""
|
||||
tail = 4 if command == CMD_UPLOAD and size > 6 else 2
|
||||
if not size - tail <= len(raw) <= size or raw[:2] != bytes((controller, command)):
|
||||
return None
|
||||
full = raw.ljust(size, b"\0")
|
||||
if tail == 4:
|
||||
try:
|
||||
decode_upload_reply(full, controller, size - 8)
|
||||
except ValueError:
|
||||
return None
|
||||
elif crc16_modbus(full[:-4]) != int.from_bytes(full[-4:-2], "little"):
|
||||
return None
|
||||
return full
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TmsTarget:
|
||||
controller: int = 10
|
||||
ram: int = 0x000A0000
|
||||
flash: int = 0x00110000
|
||||
block_size: int = 256
|
||||
kind: str = "tms"
|
||||
board: int = 0
|
||||
load_only: bool = False
|
||||
|
||||
def validate(self, size: int) -> None:
|
||||
if not 0 <= self.controller <= 255:
|
||||
raise ValueError("Адрес контроллера должен быть 0…255")
|
||||
if not 256 <= self.block_size <= 65534 or self.block_size % 2:
|
||||
raise ValueError("Блок LOAD должен быть чётным, 256…65534 байт")
|
||||
if size <= 0:
|
||||
raise ValueError("Образ пуст")
|
||||
words = (size + 1) // 2
|
||||
if not 0 <= self.ram <= 0xFFFFFFFF or self.ram + words > 0x100000000:
|
||||
raise ValueError("Диапазон RAM выходит за пределы uint32")
|
||||
if self.kind not in ("tms", "spartan2e", "spartan6"):
|
||||
raise ValueError("Неизвестный протокол прошивки TMS")
|
||||
if not 0 <= self.board <= 255 or not 0 <= self.flash <= 0xFFFFFFFF:
|
||||
raise ValueError("Неверный адрес платы или памяти")
|
||||
if not self.load_only and self.kind == "tms" and (not 0x00100000 <= self.flash < 0x00180000 or self.flash + words > 0x00180000):
|
||||
raise ValueError("Flash должна находиться в диапазоне слов 0x00100000…0x00180000")
|
||||
|
||||
|
||||
def programming_steps(data: bytes, target: TmsTarget):
|
||||
"""Yield requests; send each validated response back into the generator."""
|
||||
t = target
|
||||
t.validate(len(data))
|
||||
for offset in range(0, len(data), t.block_size):
|
||||
block = data[offset:offset + t.block_size]
|
||||
ram = t.ram + offset // 2
|
||||
yield init_load(t.controller, ram, len(block)), 6, 1500, "CMD_INITLOAD", int(offset * 85 / len(data))
|
||||
yield load_data(t.controller, block), 6, 3000, "CMD_LOAD", int(offset * 85 / len(data))
|
||||
if t.load_only:
|
||||
return
|
||||
if t.kind != "tms":
|
||||
code = 6 if t.kind == "spartan2e" else 10
|
||||
response = yield extend(t.controller, t.ram, t.flash, len(data), code, t.board), 18, 600000, "CMD_EXTEND: запись платы", 85
|
||||
words, error, repeats = extend_result(response)
|
||||
if error or words != (len(data) + 1) // 2:
|
||||
raise ValueError(f"Ошибка записи платы: код {error}, обработано {words} слов, повторов {repeats}")
|
||||
if t.kind == "spartan2e":
|
||||
response = yield extend(t.controller, t.ram, t.flash, len(data), 17, t.board), 18, 600000, "CMD_EXTEND: проверка EEPROM", 95
|
||||
words, error, repeats = extend_result(response)
|
||||
if error or words != (len(data) + 1) // 2:
|
||||
raise ValueError(f"Проверка EEPROM не пройдена: код {error}, обработано {words} слов")
|
||||
return
|
||||
yield tflash(t.controller, t.ram, t.flash, len(data)), 6, 180000, "CMD_TFLASH: запись Flash", 85
|
||||
for offset in range(0, len(data), 256):
|
||||
block = data[offset:offset + 256]
|
||||
count = (len(block) + 1) & ~1
|
||||
response = yield build_upload_request(t.controller, t.flash + offset // 2, count), count + 8, 30000, "CMD_UPLOAD: проверка Flash", 90 + int(offset * 10 / len(data))
|
||||
actual = decode_upload_reply(response, t.controller, count)[:len(block)]
|
||||
if actual != block:
|
||||
raise ValueError(f"Проверка Flash не пройдена: блок по адресу слова 0x{t.flash + offset // 2:08X}")
|
||||
Reference in New Issue
Block a user