Files
templates/python/protocan/tms_firmware.py

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}")