Extract reusable device protocols, ports and logic analyzers from SETGUI

This commit is contained in:
2026-09-23 20:11:37 +03:00
parent 80ba17d77d
commit 795a1279b1
63 changed files with 10181 additions and 6 deletions

View File

@@ -0,0 +1,132 @@
"""Протокол кольцевого логгера УМП v2.
Технологические регистры 0..127 не изменены. Расширение 0x1000 сначала
проверяется по сигнатуре и версии: старая прошивка игнорировала старший
байт адреса, поэтому диагностическое чтение безопасно попадёт в 0..15,
но управляющие записи до успешной проверки запрещены в панели.
CRC и проверка ответов остаются в общей библиотеке протокола ПМ35.
"""
from dataclasses import dataclass
from protocan import periph28335 as protocol
BASE = 0x1000
DATA = 0x1010
LIVE = 0x1100
EMU_CONTROL = 0x1200
FAST_CAPABILITY = 0x1220
FAST_DATA = 0x2000
WORDS = 40
CAPACITY = 64000 // (2 * WORDS)
FIELDS = (
('off_trigger', 'Событие отключения', 2, 8),
('post_truncated', 'Усечение постинтервала', 2, 9),
('start', 'Команда пуск УМП', 3, 7),
('reset', 'Команда сброс УМП', 3, 6),
('omega', 'Вход OMEGA', 4, 0),
('start_in', 'Вход Start_1', 4, 1),
('res_in', 'Вход Res_in', 4, 2),
('start_out', 'Выход START_DPTB', 5, 0),
('stop_out', 'Выход STOP_DPTB', 5, 1),
('res_out', 'Выход RES_OUT_2', 5, 2),
('impulse', 'Остаток импульса, мс', 6, None),
('wake', 'Инициализация ЦАП, тики ЦАП', 7, None),
('dac', 'Код ЦАП', 8, None),
('current', 'Расчётное задание тока, мА', 9, None),
('sensor0', 'Измерение 0', 10, None),
('sensor1', 'Измерение 1', 11, None),
('sensor2', 'Измерение 2', 12, None),
('sensor3', 'Измерение 3', 13, None),
('error0', 'Ошибки 0 (маска)', 14, None),
('error1', 'Ошибки 1 (маска)', 15, None),
('error2', 'Ошибки 2 (маска)', 16, None),
('error3', 'Ошибки 3 (маска)', 17, None),
('can_header', 'CAN адрес/маска', 18, None),
('can0', 'CAN слово 0', 19, None),
('can1', 'CAN слово 1', 20, None),
('can2', 'CAN слово 2', 21, None),
('can_count', 'Количество CAN RX (mod 65536)', 22, None),
('can_timeout', 'Таймауты CAN TX (mod 65536)', 23, None),
('commands', 'Команды (маска)', 3, None),
('go', 'Начальная уставка, мА', 24, None),
('stop', 'Конечная уставка, мА', 25, None),
('dac_min', 'Код калибровки 4 мА', 26, None),
('dac_max', 'Код калибровки 20 мА', 27, None),
('dac_cal', 'Калибровочный код ЦАП', 28, None),
('dac04', 'DAC_04 (регистр)', 29, None),
('dac20', 'DAC_20 (регистр)', 30, None),
('ramp', 'Счётчик разгона', 31, None),
('duration', 'Длительность разгона, тики ЦАП', 32, None),
('effective_go', 'Начальная уставка после ограничений, мА', 33, None),
('effective_stop', 'Конечная уставка после ограничений, мА', 34, None),
('position', 'Положение разгона (kod)', 35, None),
('adc0', 'АЦП канал 0', 36, None),
('adc1', 'АЦП канал 1', 37, None),
('adc2', 'АЦП канал 2', 38, None),
('adc3', 'АЦП канал 3', 39, None),
)
def request(controller: int, function: int, address: int, value: int) -> bytes:
if not 0 <= controller <= 255 or not 0 <= value <= 65535:
raise ValueError('Недопустимый адрес контроллера или значение')
if function == 3:
valid = ((address == BASE and value == 16) or
(address == DATA and 1 <= value <= 120 and value % WORDS == 0) or (address == LIVE and value == 124) or (address == EMU_CONTROL and value == 4)
or (address == FAST_CAPABILITY and value == 4)
or (FAST_DATA <= address < FAST_DATA + CAPACITY * WORDS
and (address - FAST_DATA) % WORDS == 0
and 1 <= value <= 120 and value % WORDS == 0
and address + value <= FAST_DATA + CAPACITY * WORDS))
else:
valid = function == 6 and (address in (BASE, BASE + 1, BASE + 2, BASE + 3) or
(address == EMU_CONTROL + 2 and value in (0, 1)))
if not valid:
raise ValueError('Недопустимый запрос логгера УМП')
return protocol.with_crc(bytes((controller, function)) + address.to_bytes(2, 'big') + value.to_bytes(2, 'big'))
@dataclass(frozen=True)
class Status:
state: int
count: int
period: int
mode: int
frequency: int
generation: int
archive_count: int
post_ms: int
live_state: int
@classmethod
def decode(cls, words):
if (len(words) != 16 or words[0:2] != (0x554D, 2) or
words[4] != WORDS or words[5] != CAPACITY):
raise ValueError('Прошивка не поддерживает логгер УМП v2')
if words[9] != 6:
raise ValueError('Логгер УМП доступен только на плате нагрузки dsk_LOAD')
if words[2] not in (0, 1, 2, 3) or words[3] > CAPACITY or not 1 <= words[6] <= 1000 or words[11] != 1000 or words[13] > CAPACITY or words[14] > 1000 or words[15] not in (0, 1, 3):
raise ValueError('Повреждён статус логгера')
return cls(words[2], words[3], words[6], words[10], words[11], *words[12:16])
def sample(words, frequency=1000):
if len(words) != WORDS or frequency <= 0:
raise ValueError('Неверная запись логгера')
result = {'time_ms': (words[0] | words[1] << 16) * 1000 / frequency, 'event': words[2]}
for key, _label, index, bit in FIELDS:
value = words[index]
if bit is not None:
value = (value >> bit) & 1
elif 10 <= index <= 13 and value >= 32768:
value -= 65536
# Физические единицы приводим здесь, одинаково для CSV и графика.
if key in ('current', 'effective_go', 'effective_stop'):
value /= 10.0
result[key] = value
return result
def emulator_enabled(words):
if len(words) != 4 or tuple(words[:2]) != (0x454D, 1) or words[2] not in (0, 1):
raise ValueError('Плата не поддерживает управление эмуляцией STM')
return bool(words[2])