"""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(" bytes: return packet(controller, CMD_INITLOAD, struct.pack(" 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(" bytes: return packet(controller, CMD_EXTEND, struct.pack(" tuple[int, int, int]: return struct.unpack_from(" 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}")