133 lines
6.6 KiB
Python
133 lines
6.6 KiB
Python
"""Протокол кольцевого логгера УМП 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])
|