Files
templates/python/protocan/tms_firmware.py

126 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Последовательность обновления BALZAM/ПМ67 через команды TMS. Адрес задаётся в 16-битных
# словах, а размер передаваемого блока — в байтах; смешение этих единиц смещает запись даже
# при корректной контрольной сумме.
"""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
from protocan.tms_terminal import build_fixed_request, validate_response
CMD_LOAD = 51
CMD_UPLOAD = 52
CMD_TFLASH = 55
CMD_INITLOAD = 58
CMD_EXTEND = 60
def packet(controller: int, command: int, payload: bytes) -> bytes:
return build_fixed_request(controller, command, payload)
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
else:
try:
validate_response(raw, controller, command, size, True)
except ValueError:
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}")