119 lines
5.4 KiB
Python
119 lines
5.4 KiB
Python
"""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}")
|