Расширить обмен по шинам, CAN-адаптеры и обработку графиков
Добавить очереди операций PBUS/SBUS, общую консоль, настройки Candle и SLCAN, декодеры DSView и обработку плотных графиков. Обновить протокол Бальзам и проверки общих компонентов.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
"""Декодер параллельной шины 96-контактного интерфейса.""" # Описание пакета доступно загрузчику декодеров DSView.
|
||||
from .pd import Decoder # Экспорт класса нужен штатному загрузчику libsigrokdecode.
|
||||
@@ -0,0 +1,140 @@
|
||||
"""PBus по ParallelBusSlave_ver_2_0.vhd; Python 3.6+, sigrok API 3.""" # Времена выборки определяются ACK аппаратного ведомого.
|
||||
import sigrokdecode as srd # DSView предоставляет цифровые отсчёты и выходные аннотации.
|
||||
|
||||
|
||||
# CE ограничивает транзакцию, три перехода ACK подтверждают адрес, данные и их инверсию.
|
||||
class Decoder(srd.Decoder): # Протокол работает непосредственно с захватом логических уровней.
|
||||
api_version = 3 # API ожидания событий поддерживается libsigrokdecode4DSL.
|
||||
id = 'plates_pbus' # Имя пакета не пересекается со штатным parallel.
|
||||
name = 'Plates PBus' # Название в списке протоколов DSView.
|
||||
longname = 'Plates 96-pin PBus' # Название связывает декодер с интерфейсом плат.
|
||||
desc = 'PBus reads with address complement, ACK and inverted data checks.' # Текущий HDL реализует внешнее чтение PBus.
|
||||
license = 'gplv3+' # Допустимый идентификатор лицензии libsigrokdecode.
|
||||
inputs = ['logic'] # Входные данные — цифровые каналы анализатора.
|
||||
outputs = ['plates_pbus'] # Структурированные результаты доступны следующему уровню.
|
||||
tags = ['Embedded/industrial'] # Категория промышленного обмена.
|
||||
channels = ( # Обязательные сигналы не подменяются нулём при отсутствии захвата.
|
||||
{'id': 'ce', 'name': 'PBCE', 'desc': 'Active-low transaction enable'}, # Индекс 0: CE=0 разрешает обмен согласно HDL.
|
||||
{'id': 'ack', 'name': 'PBACK', 'desc': 'Address / data / inverse acknowledge'}, # Индекс 1: переходы 0,1,0 задают три фазы.
|
||||
) + tuple({'id': 'd%d' % i, 'name': 'D%d' % i, 'desc': 'PBDATA bit %d' % i} for i in range(16)) # Индексы 2…17 соответствуют физическим разрядам D0…D15.
|
||||
optional_channels = ( # Для выборки достаточно ACK, поэтому тактовая линия необязательна.
|
||||
{'id': 'clk', 'name': 'PBCLK', 'desc': 'Optional phase validation clock'}, # Индекс 18: данные ожидаются при CLK=0, инверсия — при CLK=1.
|
||||
) # При наличии CLK проверяется согласованность фаз с текущим HDL.
|
||||
annotations = ( # Числовые типы совместимы с форматом аннотаций DSView.
|
||||
('1', 'device', 'Device'), # Индекс 0: старшая тетрада восьмибитного адреса.
|
||||
('2', 'register', 'Register'), # Индекс 1: младшая тетрада адреса.
|
||||
('3', 'data', 'Data'), # Индекс 2: прочитанное 16-битное значение.
|
||||
('4', 'check', 'Checks'), # Индекс 3: инверсная защита адреса и ответа.
|
||||
('5', 'frame', 'Transaction'), # Индекс 4: итог полного или оборванного обращения.
|
||||
('1000', 'warning', 'Warnings'), # Индекс 5: ошибки адреса, ответа, фаз и отсутствующих ACK.
|
||||
) # Индекс класса в put() отличается от числового типа в первом поле.
|
||||
annotation_rows = ( # Раздельные строки сохраняют читаемость при перекрытии полей.
|
||||
('device', 'Device', (0,)), # Устройство и регистр передаются одновременно.
|
||||
('register', 'Register', (1,)), # Поэтому регистр располагается на своей строке.
|
||||
('data', 'Data', (2,)), # Данные видны рядом с проверкой инверсной копии.
|
||||
('checks', 'Checks', (3,)), # Результаты проверок не теряются за итогом кадра.
|
||||
('frames', 'Transactions', (4,)), # Итог содержит адрес, значение и состояние обмена.
|
||||
('warnings', 'Warnings', (5,)), # Ошибки выделяются штатным типом DSView.
|
||||
) # Все поля выводятся без зависимости от карт регистров PlatesConf.
|
||||
|
||||
# Экземпляр декодера начинает без активной транзакции.
|
||||
def __init__(self): # Вызывается загрузчиком протоколов.
|
||||
self.reset() # Повторный проход использует такой же сброс.
|
||||
|
||||
# Незавершённое состояние предыдущего захвата не переносится в следующий.
|
||||
def reset(self): # Сбрасывает только внутренний автомат.
|
||||
self.packet = None # Словарь операции появляется на спаде CE.
|
||||
self.phase = None # Ожидаемая фаза: address, data, inverse или done.
|
||||
self.ss = 0 # Начало транзакции задаётся в отсчётах захвата.
|
||||
self.phase_start = 0 # Начало текущего ожидания нужно для границ аннотаций.
|
||||
|
||||
# Аннотации и структурированные кадры регистрируются до обработки сигналов.
|
||||
def start(self): # Вызывается средой DSView при запуске декодирования.
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN) # Текстовые поля отображаются над захватом.
|
||||
self.out_python = self.register(srd.OUTPUT_PYTHON) # Пакеты сохраняют сырые значения и результаты проверок.
|
||||
|
||||
# Каждая аннотация привязана к фактическим границам текущей фазы.
|
||||
def annotate(self, start, end, kind, text): # kind — индекс annotations, а не числовой тип DSView.
|
||||
self.put(start, max(start, end), self.out_ann, [kind, [text]]) # Одноточечные переходы также допустимы.
|
||||
|
||||
# Порядок назначения каналов DSView задаёт значение разрядов без перестановки байтов.
|
||||
def word(self, pins): # На входе полный набор сигналов текущего отсчёта.
|
||||
return sum(pins[2 + i] << i for i in range(16)) # D0 имеет вес 1, D15 — 32768; результат беззнаковый.
|
||||
|
||||
# Ошибка добавляется и в выходной пакет, и в видимую строку предупреждений.
|
||||
def warning(self, text, end): # end обозначает отсчёт обнаружения ошибки.
|
||||
self.packet['errors'].append(text) # Причины остаются доступны потребителю Python-выхода.
|
||||
self.annotate(self.phase_start, end, 5, text) # Предупреждение относится к текущей фазе обмена.
|
||||
|
||||
# Адрес находится в старшем байте; младший должен быть его инверсией согласно HDL.
|
||||
def address(self, word, end): # Выборка выполняется на первом низком ACK либо при закрытии запроса без ACK.
|
||||
address = word >> 8 # Ровно восемь бит делятся на два четырёхбитных адреса.
|
||||
self.packet.update(dev=address >> 4, reg=address & 15, address_word=word, address_ok=((word & 255) == (address ^ 255))) # Маска 255 ограничивает инверсию одним байтом.
|
||||
self.annotate(self.ss, end, 0, 'DEV 0x%X' % self.packet['dev']) # Старшая тетрада определяет устройство 0…15.
|
||||
self.annotate(self.ss, end, 1, 'REG 0x%X' % self.packet['reg']) # Младшая тетрада определяет регистр 0…15.
|
||||
self.annotate(self.ss, end, 3, 'ADDR 0x%04X %s' % (word, 'OK' if self.packet['address_ok'] else 'BAD')) # Видна и исходная инверсная копия адреса.
|
||||
if not self.packet['address_ok']: # Несогласованность байтов не маскируется правильными данными ответа.
|
||||
self.warning('Address complement mismatch', end) # На исправном ведомом такой адрес не должен получать ACK.
|
||||
|
||||
# Итог формируется даже при обрыве, но неполная операция никогда не считается успешной.
|
||||
def emit_frame(self, end, complete): # complete означает получение всех трёх фаз ACK.
|
||||
self.packet['complete'] = complete # Полнота отделена от результата проверок содержимого.
|
||||
self.packet['ok'] = complete and not self.packet['errors'] # Любая ошибка проверки остаётся явной.
|
||||
value = self.packet['value'] # При отсутствии фазы данных значение остаётся None.
|
||||
text = 'READ %X:%X = %s %s' % (self.packet['dev'], self.packet['reg'], '?' if value is None else '0x%04X' % value, 'OK' if self.packet['ok'] else 'BAD / incomplete') # Формат не выдаёт предположение за данные шины.
|
||||
self.annotate(self.ss, end, 4, text) # Итог покрывает весь интервал обращения.
|
||||
self.put(self.ss, end, self.out_python, ['FRAME', self.packet]) # Даже повторное чтение того же значения остаётся отдельной операцией.
|
||||
|
||||
# Возврат CE либо конец захвата закрывает незаконченное ожидание ACK.
|
||||
def close(self, end): # Завершённая операция уже выведена на ACK инверсии и не дублируется.
|
||||
if self.packet is not None and self.phase != 'done': # Пустой захват или уже завершённый кадр не создают ошибок.
|
||||
if self.phase == 'address': # При отсутствии первого ACK сохраняем хотя бы запрос на спаде CE.
|
||||
self.address(self.packet['address_word'], end) # Это наблюдавшийся адрес, а не восстановление по ответу.
|
||||
self.warning('Incomplete PBus: missing %s ACK' % self.phase, end) # Сообщение различает отсутствие адресного ответа, данных и инверсии.
|
||||
self.emit_frame(end, False) # Частичные данные доступны с complete=False.
|
||||
self.packet = None # После закрытия CE предыдущая операция больше не активна.
|
||||
self.phase = None # Новая операция должна начаться новым спадом CE.
|
||||
|
||||
# Последний незакрытый запрос виден и при обрыве самого файла захвата.
|
||||
def end(self): # Метод вызывается DSView после последнего блока данных.
|
||||
self.close(getattr(self, 'last_samplenum', getattr(self, 'samplenum', 0))) # Используем последнюю известную границу захвата.
|
||||
|
||||
# Состояния автомата соответствуют аппаратной последовательности ACK 0 -> 1 -> 0.
|
||||
def decode(self): # Ожидание событий завершается самой средой декодирования.
|
||||
if not all(self.has_channel(i) for i in range(18)): # Для полного слова обязательны CE, ACK и все 16 разрядов данных.
|
||||
raise ValueError('PBus requires PBCE, PBACK and all D0..D15 channels') # Отсутствующий разряд не заменяется ложным нулём.
|
||||
have_clock = self.has_channel(18) # PBCLK улучшает диагностику, но не нужен для выборки по ACK.
|
||||
previous = self.wait({}) # Начало посреди активного CE игнорируется до следующей полной операции.
|
||||
while True: # Изменения самих данных не вызывают лишние пробуждения декодера.
|
||||
pins = self.wait([{0: 'e'}, {1: 'e'}]) # Фазы ограничиваются только CE и ACK, а данные читаются в точке подтверждения.
|
||||
now = self.samplenum # Все границы аннотаций задаются в отсчётах.
|
||||
if pins[0]: # CE=1 завершает текущий обмен независимо от уровня ACK.
|
||||
self.close(now) # Незаконченная фаза становится явной ошибкой.
|
||||
else: # Только низкий CE разрешает принимать ACK ведомого.
|
||||
if previous[0]: # Спад CE начинает новую операцию даже при одновременном переходе ACK.
|
||||
self.ss = self.phase_start = now # Начало фазы адреса совпадает с разрешением обмена.
|
||||
self.packet = {'bus': 'PBus', 'operation': 'read', 'dev': None, 'reg': None, 'address_word': self.word(pins), 'address_ok': None, 'value': None, 'inverse': None, 'inverse_ok': None, 'errors': []} # Пропущенные фазы имеют явное значение None.
|
||||
self.phase = 'address' # Первым ожидается низкий адресный ACK.
|
||||
falling = previous[1] == 1 and pins[1] == 0 # ACK=0 подтверждает адрес или инверсную копию в зависимости от фазы.
|
||||
rising = previous[1] == 0 and pins[1] == 1 # ACK=1 подтверждает прямые данные ответа.
|
||||
if self.packet is not None and self.phase == 'address' and falling: # Первый спад ACK фиксирует защищённый адрес.
|
||||
self.address(self.word(pins), now) # Адрес читается после готовности приёмника, а не по произвольному фронту CLK.
|
||||
self.phase, self.phase_start = 'data', now # Следующий ACK должен стать высоким при готовности данных.
|
||||
elif self.packet is not None and self.phase == 'data' and rising: # Высокий ACK отмечает прямое слово ответа.
|
||||
self.packet['value'] = self.word(pins) # Сохраняется всё 16-битное значение PBus.
|
||||
self.annotate(self.phase_start, now, 2, 'DATA 0x%04X' % self.packet['value']) # Число показывается в шестнадцатеричном виде с ведущими нулями.
|
||||
if have_clock and pins[18] != 0: # В HDL TransmitData выдаёт ACK только при низком PBCLK.
|
||||
self.warning('Data ACK while PBCLK is not low', now) # Ошибка фазы помогает выявить неверное подключение либо нарушение обмена.
|
||||
self.phase, self.phase_start = 'inverse', now # Далее ожидается низкий ACK инверсного слова.
|
||||
elif self.packet is not None and self.phase == 'inverse' and falling: # Последний спад ACK завершает проверяемый обмен.
|
||||
inverse = self.word(pins) # Инверсия выбирается отдельно от прямых данных.
|
||||
valid = inverse == (self.packet['value'] ^ 65535) # Маска 65535 ограничивает инверсию ровно 16 разрядами HDL.
|
||||
self.packet.update(inverse=inverse, inverse_ok=valid) # Сохраняем и сырое слово, и результат проверки.
|
||||
self.annotate(self.phase_start, now, 3, 'INVERSE 0x%04X %s' % (inverse, 'OK' if valid else 'BAD')) # Проверяем содержимое, а не только наличие третьего ACK.
|
||||
if not valid: # Повреждённый ответ не становится успешным из-за полного числа фаз.
|
||||
self.warning('Data complement mismatch', now) # Ошибка относится к данным ответа.
|
||||
if have_clock and pins[18] != 1: # В HDL TransmitCheck выдаёт ACK при высоком PBCLK.
|
||||
self.warning('Inverse ACK while PBCLK is not high', now) # Проверка включается только при назначении необязательного канала.
|
||||
self.emit_frame(now, True) # Итог доступен сразу после третьего подтверждения.
|
||||
self.phase = 'done' # Возврат CE в единицу не должен дублировать кадр.
|
||||
previous = pins # Следующий переход сравнивается с фактическими последними уровнями CE и ACK.
|
||||
@@ -0,0 +1,2 @@
|
||||
"""Декодер последовательной шины 96-контактного интерфейса.""" # Описание пакета доступно загрузчику декодеров DSView.
|
||||
from .pd import Decoder # Экспорт класса нужен штатному загрузчику libsigrokdecode.
|
||||
@@ -0,0 +1,172 @@
|
||||
"""SBus по SerialBusSlave_ver_2_0.vhd; Python 3.6+, sigrok API 3.""" # Формат берётся из HDL, без зависимости от приложения SETGUI.
|
||||
import math # Округление длительностей вверх исключает преждевременное срабатывание таймеров.
|
||||
import sigrokdecode as srd # Среда DSView предоставляет входные отсчёты и выходные аннотации.
|
||||
|
||||
|
||||
# CRC повторяет параллельные присваивания HDL, включая четыре завершающие единицы.
|
||||
def frame_crc(direction, address, value): # В расчёт входят направление, восьмибитный адрес и 16 бит данных.
|
||||
word = (direction << 24) | (address << 16) | value # Поля образуют 25 бит в порядке передачи от старшего к младшему.
|
||||
crc = 0 # Начальное состояние регистра CRC равно x"0" в SerialBusSlave_ver_2_0.vhd.
|
||||
for bit in [((word >> shift) & 1) for shift in range(24, -1, -1)] + [1] * 4: # После 25 бит HDL подаёт четыре единицы.
|
||||
c3, c2, c1, c0 = [(crc >> shift) & 1 for shift in (3, 2, 1, 0)] # Все новые разряды используют прежнее состояние CRC.
|
||||
crc = ((c2 ^ c3) << 3) | ((c1 ^ c0) << 2) | (c0 << 1) | (bit ^ c1) # Это точная обратная связь HDL, а не стандартный CRC-4.
|
||||
return crc # На провод передаются четыре бита результата, начиная с бита 3.
|
||||
|
||||
|
||||
# Декодер разделяет кадры по паузе любого уровня CLK и выбирает линию данных по направлению.
|
||||
class Decoder(srd.Decoder): # Базовый класс реализован встроенным модулем DSView.
|
||||
api_version = 3 # API 3 позволяет ждать фронтов и временных интервалов без перебора всех отсчётов.
|
||||
id = 'plates_sbus' # Уникальное имя не пересекается со штатными декодерами.
|
||||
name = 'Plates SBus' # Короткое название отображается при выборе протокола.
|
||||
longname = 'Plates 96-pin SBus' # Уточнение отличает шину от других последовательных протоколов.
|
||||
desc = 'SBus: direction, device, register, data, CRC and write ACK.' # Краткое описание доступно в диалоге DSView.
|
||||
license = 'gplv3+' # Это допустимый идентификатор лицензии для libsigrokdecode.
|
||||
inputs = ['logic'] # Декодер получает цифровые сигналы анализатора.
|
||||
outputs = ['plates_sbus'] # Python-пакеты можно использовать в следующем уровне декодирования.
|
||||
tags = ['Embedded/industrial'] # Категория соответствует промышленной шине плат.
|
||||
channels = ( # Направления линий указаны относительно мастера ТМС.
|
||||
{'id': 'clk', 'name': 'SBCLK', 'desc': 'Serial clock'}, # CLK задаёт границы битов и паузы между кадрами.
|
||||
{'id': 'tx', 'name': 'SBDATAIN', 'desc': 'TMS -> plates'}, # На этой линии находятся запрос и данные записи.
|
||||
{'id': 'rx', 'name': 'SBDATAOUT', 'desc': 'Plates -> TMS'}, # На этой линии находятся данные чтения и активный низкий ACK записи.
|
||||
) # Все три линии обязательны для проверки обоих направлений.
|
||||
options = ( # Времена задаются в микросекундах и пересчитываются по частоте захвата.
|
||||
{'id': 'idle_us', 'desc': 'Frame gap (us)', 'default': 3.6}, # В текущем HDL COUNT_RESET=180 при 50 МГц: 180/50=3,6 мкс.
|
||||
{'id': 'ack_us', 'desc': 'Write ACK sampling delay (us)', 'default': 0.5}, # Полмикросекунды после CRC оставляют запас для логики проверки до сброса по паузе.
|
||||
) # Параметры можно изменить для реального захвата другой аппаратуры.
|
||||
annotations = ( # Первое поле задаёт числовой тип DSView; индекс кортежа используется в put().
|
||||
('1', 'direction', 'Direction'), # Индекс 0: чтение или запись со стороны ТМС.
|
||||
('2', 'device', 'Device'), # Индекс 1: адрес устройства 0…15.
|
||||
('3', 'register', 'Register'), # Индекс 2: адрес регистра 0…15.
|
||||
('4', 'data', 'Data'), # Индекс 3: сырое 16-битное значение без выдуманных единиц.
|
||||
('5', 'crc', 'CRC'), # Индекс 4: принятая и рассчитанная контрольные суммы.
|
||||
('6', 'ack', 'Write ACK'), # Индекс 5: подтверждение записи, отдельное от проверки CRC.
|
||||
('7', 'frame', 'Transaction'), # Индекс 6: итог операции целиком.
|
||||
('1000', 'warning', 'Warnings'), # Индекс 7: CRC, отказ и незавершённые кадры.
|
||||
) # Числовой тип 1000 соответствует предупреждениям в DSView.
|
||||
annotation_rows = ( # Параллельные строки позволяют видеть поля и итог кадра одновременно.
|
||||
('fields', 'Fields', (0, 1, 2, 3)), # Неперекрывающиеся поля запроса и данных.
|
||||
('checks', 'CRC / ACK', (4, 5)), # Независимые проверки целостности и ответа платы.
|
||||
('frames', 'Transactions', (6,)), # Сводная строка удобна для поиска обращений.
|
||||
('warnings', 'Warnings', (7,)), # Ошибки видны независимо от масштаба остальных строк.
|
||||
) # Индексы соответствуют annotations выше.
|
||||
|
||||
# Новый экземпляр должен начинаться без синхронизации с границей кадра.
|
||||
def __init__(self): # Загрузчик создаёт экземпляр при добавлении декодера.
|
||||
self.reset() # Единый сброс используется также при повторном декодировании.
|
||||
|
||||
# Сбрасываем только состояние захвата, не значения обязательных входных каналов.
|
||||
def reset(self): # Вызывается средой при начале нового прохода.
|
||||
self.samplerate = None # Частота захвата будет передана через metadata().
|
||||
self.bits = [] # Элементы содержат номер отсчёта и значение бита текущего кадра.
|
||||
self.pending = None # Завершённая запись ждёт отдельного момента выборки ACK.
|
||||
self.ack_at = None # Номер отсчёта выборки ACK существует только при pending.
|
||||
|
||||
# Выходы регистрируются после сброса до обработки первых отсчётов.
|
||||
def start(self): # API DSView вызывает этот метод перед decode().
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN) # Текстовые поля и предупреждения рисуются над захватом.
|
||||
self.out_python = self.register(srd.OUTPUT_PYTHON) # Структурированные кадры сохраняют независимые CRC и ACK.
|
||||
|
||||
# Частота нужна для отделения кадров по реальному времени, а не количеству битов.
|
||||
def metadata(self, key, value): # Среда передаёт метаданные захвата независимо от фронтов.
|
||||
if key == srd.SRD_CONF_SAMPLERATE: # Другие метаданные не влияют на разбор шины.
|
||||
self.samplerate = value # Единица измерения — отсчёты в секунду.
|
||||
|
||||
# Обёртка сохраняет допустимый порядок границ даже для одноточечной аннотации.
|
||||
def annotate(self, start, end, kind, text): # kind — индекс annotations, text — содержимое над сигналом.
|
||||
self.put(start, max(start, end), self.out_ann, [kind, [text]]) # Одноточечные поля направления допустимы API.
|
||||
|
||||
# Поля передаются старшим битом вперёд; срез не включает правую границу.
|
||||
def field(self, start, end): # Параметры задают позиции битов внутри 29-битного кадра.
|
||||
value = 0 # Аккумулятор формируется заново для каждого поля.
|
||||
for _, bit in self.bits[start:end]: # Номер отсчёта не влияет на числовое значение.
|
||||
value = (value << 1) | bit # Следующий бит занимает младший разряд после сдвига.
|
||||
return value # Значение сохраняется без знакового преобразования.
|
||||
|
||||
# Итог записи отделяет хороший CRC от ACK; ACK не доказывает применение команды CPU1.
|
||||
def emit_frame(self, packet, end): # packet уже содержит все поля и результаты доступных проверок.
|
||||
ack = packet['ack'] # Для чтения и необследованного ответа записи значение остаётся None.
|
||||
suffix = '' if packet['operation'] == 'read' else ' ACK ' + ('?' if ack is None else ('OK' if ack else 'HIGH')) # Высокий уровень не различает NACK и отсутствие платы.
|
||||
status = 'OK' if packet['crc_ok'] else 'BAD' # Целостность CRC показывается независимо от подтверждения.
|
||||
text = '%s %X:%X = 0x%04X CRC %s%s' % (packet['operation'].upper(), packet['dev'], packet['reg'], packet['value'], status, suffix) # Адреса и данные удобны для сверки с регистрами проекта.
|
||||
self.annotate(self.bits[0][0], end, 6, text) # Сводная аннотация покрывает весь кадр и ожидание ACK.
|
||||
self.put(self.bits[0][0], end, self.out_python, ['FRAME', packet]) # Структурированный выход не теряет повторные одинаковые команды.
|
||||
|
||||
# Бит принимается только по нарастающему фронту, когда ответ ведомого уже установлен после спада.
|
||||
def receive_bit(self, pins): # pins имеет порядок CLK, запрос ТМС, ответ платы.
|
||||
index = len(self.bits) # Первый бит — направление, следующие восемь — адрес.
|
||||
if index >= 29: # Кадр содержит 1+8+16+4 бита; лишние такты до паузы не становятся новым запросом.
|
||||
return # Синхронизация нового кадра требует паузы на CLK.
|
||||
source = 1 if index < 9 or self.bits[0][1] else 2 # Заголовок и запись идут от ТМС; данные чтения и CRC — от платы.
|
||||
self.bits.append((self.samplenum, pins[source])) # Сохраняем точное положение каждого принятого бита.
|
||||
count = len(self.bits) # Поле выводится сразу после последнего составляющего бита.
|
||||
if count == 1: # Направление 0 означает чтение, 1 означает запись в HDL.
|
||||
self.annotate(self.samplenum, self.samplenum, 0, 'WRITE' if self.bits[0][1] else 'READ') # Операция указана относительно ТМС.
|
||||
for size, begin, kind, label, width in ((5, 1, 1, 'DEV', 1), (9, 5, 2, 'REG', 1), (25, 9, 3, 'DATA', 4)): # Границы определены ширинами адреса и данных HDL.
|
||||
if count == size: # Одно поле выводится только один раз за кадр.
|
||||
self.annotate(self.bits[begin][0], self.samplenum, kind, '%s 0x%0*X' % (label, width, self.field(begin, size))) # Нулевые старшие разряды данных сохраняются.
|
||||
if count != 29: # Проверка CRC возможна только при полном кадре.
|
||||
return # Неполные кадры будут отмечены по паузе или концу захвата.
|
||||
direction, address, value = self.field(0, 1), self.field(1, 9), self.field(9, 25) # Восьмибитный адрес делится на устройство и регистр.
|
||||
actual, expected = self.field(25, 29), frame_crc(direction, address, value) # CRC рассчитывается над теми же данными, которые видны на проводе.
|
||||
packet = {'bus': 'SBus', 'operation': 'write' if direction else 'read', 'dev': address >> 4, 'reg': address & 15, 'value': value, 'crc': actual, 'crc_expected': expected, 'crc_ok': actual == expected, 'ack': None} # Неизвестное подтверждение не подменяется успешным.
|
||||
self.annotate(self.bits[25][0], self.samplenum, 4, 'CRC %X / %X %s' % (actual, expected, 'OK' if actual == expected else 'BAD')) # Оба значения помогают диагностировать ошибку захвата.
|
||||
if actual != expected: # Ошибка видна и в отдельной строке предупреждений.
|
||||
self.annotate(self.bits[25][0], self.samplenum, 7, 'CRC mismatch') # Повреждённый кадр всё равно выводится для диагностики.
|
||||
if direction: # Запись подтверждается после проверки CRC ведомым.
|
||||
self.pending = packet # Выход кадра откладывается до выборки ACK.
|
||||
self.ack_at = self.samplenum + self.ack_samples # Задержка отсчитывается от последнего бита CRC.
|
||||
else: # У чтения после CRC нет отдельного подтверждения ведомого.
|
||||
self.emit_frame(packet, self.samplenum) # Полное чтение доступно без ожидания межкадровой паузы.
|
||||
|
||||
# Частичный заголовок не выдаётся за достоверную операцию.
|
||||
def close_partial(self, end): # Вызывается на паузе и при завершении захвата.
|
||||
if self.pending is not None: # Захват может закончиться до заданного времени выборки ACK.
|
||||
self.annotate(self.bits[-1][0], end, 7, 'Write ACK not sampled: capture ended') # Наблюдений недостаточно для вывода о подтверждении.
|
||||
self.emit_frame(self.pending, end) # Полные данные сохраняются с ack=None.
|
||||
self.pending = None # Повторный end() не должен дублировать кадр.
|
||||
if 0 < len(self.bits) < 29: # Полный кадр уже выведен и не считается обрывом.
|
||||
self.annotate(self.bits[0][0], end, 7, 'Incomplete SBus frame: %d/29 bits' % len(self.bits)) # Число бит помогает найти место потери синхронизации.
|
||||
self.bits = [] # Следующая операция начинает собственный независимый буфер.
|
||||
|
||||
# end() поддерживает диагностику обрыва даже без следующего фронта или полной паузы.
|
||||
def end(self): # DSView передаёт last_samplenum после последнего блока захвата.
|
||||
self.close_partial(getattr(self, 'last_samplenum', getattr(self, 'samplenum', 0))) # Резервная граница полезна в средах без last_samplenum.
|
||||
|
||||
# Ожидаем фронты и ближайший таймер; тихий участок захвата не требует перебора отсчётов.
|
||||
def decode(self): # Бесконечное ожидание завершается самой средой libsigrokdecode.
|
||||
if not self.samplerate or not all(self.has_channel(i) for i in range(3)): # Для времени и обоих направлений нужны частота захвата и три сигнала.
|
||||
raise ValueError('SBus requires samplerate, SBCLK, SBDATAIN and SBDATAOUT') # Ошибка настройки отличается от повреждённого кадра.
|
||||
idle_us, ack_us = self.options['idle_us'], self.options['ack_us'] # Настройки относятся к временной шкале захвата.
|
||||
if not math.isfinite(idle_us) or not math.isfinite(ack_us) or not 0 < ack_us < idle_us: # ACK должен наблюдаться раньше сброса при паузе.
|
||||
raise ValueError('Require finite 0 < ACK delay < frame gap') # Неверные интервалы не должны создавать ложную диагностику.
|
||||
gap = max(1, int(math.ceil(self.samplerate * idle_us / 1000000.0))) # Миллион переводит микросекунды в секунды.
|
||||
self.ack_samples = max(1, int(math.ceil(self.samplerate * ack_us / 1000000.0))) # Округление вверх обеспечивает минимум один отсчёт ожидания.
|
||||
if self.ack_samples >= gap: # При грубой дискретизации разные времена могут попасть в один отсчёт.
|
||||
raise ValueError('Samplerate too low to separate ACK sampling and frame gap') # Такой захват не позволяет выполнить заданную временную проверку.
|
||||
pins = self.wait({}) # Первый отсчёт задаёт уровень CLK без выдуманного фронта.
|
||||
previous, last_edge, armed = pins[0], self.samplenum, False # Начало посреди кадра игнорируется до наблюдаемой паузы.
|
||||
while True: # Каждый проход обрабатывает фронт или один ближайший таймер.
|
||||
deadlines = [] if armed else [last_edge + gap] # После подтверждённой паузы достаточно дождаться следующего фронта.
|
||||
if self.pending is not None: # Отдельный таймер ACK не зависит от дальнейших тактов CLK.
|
||||
deadlines.append(self.ack_at) # Последний CRC может быть последним тактом в захвате.
|
||||
conditions = [{0: 'e'}] # Учитываются оба фронта, поскольку сброс HDL отсчитывается от любого изменения CLK.
|
||||
if deadlines: # Таймер задаётся относительно текущего положения декодера.
|
||||
conditions.append({'skip': max(1, min(deadlines) - self.samplenum)}) # Минимум один отсчёт исключает ожидание в той же точке.
|
||||
pins = self.wait(conditions) # Значения линий доступны и при срабатывании временного условия.
|
||||
now = self.samplenum # Номер отсчёта общий для фронтов и таймеров.
|
||||
if self.pending is not None and now >= self.ack_at: # ACK измеряется после CRC, а не на его последнем фронте.
|
||||
self.pending['ack'] = pins[2] == 0 # В HDL низкий SBDATAOUT подтверждает запись; высокий означает отказ либо отсутствие ответа.
|
||||
self.annotate(self.bits[-1][0], now, 5, 'ACK' if self.pending['ack'] else 'ACK HIGH (NACK / no response)') # Уровень ответа не скрывается даже при плохом CRC.
|
||||
if not self.pending['ack']: # Отсутствие низкого ACK является отдельной проблемой обмена.
|
||||
self.annotate(self.bits[-1][0], now, 7, 'No low write ACK') # Это не доказательство конкретной причины отказа.
|
||||
self.emit_frame(self.pending, now) # Повторные записи выводятся отдельными кадрами.
|
||||
self.pending = None # Таймер подтверждения больше не нужен.
|
||||
if not armed and now - last_edge >= gap: # Пауза допустима как при высоком, так и при низком CLK.
|
||||
self.close_partial(now) # Незавершённый предыдущий кадр отмечается до повторной синхронизации.
|
||||
armed = True # Первый следующий нарастающий фронт начинает заголовок.
|
||||
if pins[0] != previous: # Срабатывание одного таймера не должно создавать фиктивный бит.
|
||||
if pins[0] and (armed or self.bits): # После паузы начинаем кадр; внутри кадра продолжаем сбор.
|
||||
armed = False # До следующей длинной паузы новый заголовок запрещён.
|
||||
self.receive_bit(pins) # Бит выбирается с нужной линии с учётом направления.
|
||||
last_edge = now # Любой фронт перезапускает измерение неподвижного CLK.
|
||||
previous = pins[0] # Следующий таймер сравнивается с последним реальным уровнем.
|
||||
@@ -1,71 +1,70 @@
|
||||
# Сегментация SET v2 и интерпретация CAN-данных зависят от выбранного слоя. Адрес и номер
|
||||
# сегмента должны проверяться до объединения данных; classic CAN-пакет ограничен восемью
|
||||
# байтами. Этот адаптер переводит события libsigrokdecode в аннотации DSView; координаты
|
||||
# выборок пересчитываются с учётом частоты захвата.
|
||||
import sigrokdecode as srd # API sigrok передаёт кадры базового CAN-декодера и принимает аннотации.
|
||||
from common.setgui_decoders.set_can import Reassembler # Существующий сборщик проверяет сегментацию и тайм-аут SET v2.
|
||||
from common.setgui_decoders.template_protocols import CAN_PROFILES, LEGACY_PROJECTS, decode_can # Профили и каталоги определяются общими библиотеками templates.
|
||||
|
||||
import sigrokdecode as srd
|
||||
from common.setgui_decoders.set_can import Reassembler, legacy
|
||||
class Decoder(srd.Decoder): # Стековый анализатор добавляет прикладные дорожки к физическому CAN.
|
||||
api_version = 3 # Версия интерфейса соответствует поставляемому libsigrokdecode4DSL.
|
||||
id = 'set_can' # Стабильный ID сохраняет совместимость записей и меню SETGUI.
|
||||
name = 'SET CAN' # Короткое имя используется в дорожках и диалоге выбора.
|
||||
longname = 'SET CAN / ProtoCAN / Balsam / UMP / DEIF / legacy' # Подпись перечисляет подключённые семейства протоколов.
|
||||
desc = 'Templates protocols, application fields, registers and SET v2 reassembly.' # Описывает результат прикладного разбора.
|
||||
license = 'gplv2+' # Лицензия адаптера соответствует остальным декодерам SET.
|
||||
inputs = ['can'] # Источник — базовый декодер CAN, а не сырые логические уровни.
|
||||
outputs = ['set'] # Тип структурированного выхода сохранён для совместимости стека.
|
||||
tags = ['Embedded/industrial'] # Категория для штатного каталога DSView.
|
||||
options = ( # Несовместимые карты адресов выбираются явно.
|
||||
{'id': 'protocol', 'desc': 'Protocol / протокол', 'default': 'protocan', 'values': CAN_PROFILES}, # Прежнее значение по умолчанию сохраняет настройки существующих записей.
|
||||
{'id': 'project', 'desc': 'Legacy CAN project / проект', 'default': LEGACY_PROJECTS[0], 'values': LEGACY_PROJECTS}, # Проект применяется в режиме legacy.
|
||||
{'id': 'ump_map', 'desc': 'UMP map / карта УМП', 'default': 'gas167', 'values': ('gas167', 'assigned')}, # Локальная карта отделена от назначенных глобальных GAS ID.
|
||||
{'id': 'ump_node', 'desc': 'UMP node / номер УМП', 'default': 1, 'values': (1, 2)}, # Локальные адреса 167/v1 не содержат номер УМП.
|
||||
) # Неприменимые к профилю настройки игнорируются.
|
||||
annotations = (('frame', 'Frame'), ('payload', 'Payload'), ('error', 'Error'), ('field', 'Field'), ('register', 'Register')) # Первые три индекса сохраняют прежние дорожки.
|
||||
annotation_rows = (('frames', 'Frames', (0,)), ('payload', 'Payload', (1,)), ('errors', 'Errors', (2,)), ('fields', 'Fields', (3,)), ('registers', 'Registers', (4,))) # Поля и регистры получают самостоятельные строки.
|
||||
|
||||
def __init__(self): # Каждый экземпляр имеет независимое состояние разбора.
|
||||
self.reset() # Незавершённые сегменты не переходят между записями.
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'set_can'
|
||||
name = 'SET CAN'
|
||||
longname = 'SET v2 CAN / ProtoCAN / Balsam'
|
||||
desc = 'Application fields and segmented SET packets over classic CAN.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['can']
|
||||
outputs = ['set']
|
||||
tags = ['Embedded/industrial']
|
||||
options = ({'id': 'protocol', 'desc': 'CAN protocol (select explicitly)',
|
||||
'default': 'protocan', 'values': ('protocan', 'set-v2', 'balsam')},)
|
||||
annotations = (('frame', 'Frame'), ('payload', 'Payload'), ('error', 'Error'))
|
||||
annotation_rows = (('frames', 'Frames', (0,)), ('payload', 'Payload', (1,)), ('errors', 'Errors', (2,)))
|
||||
def reset(self): # Перезапуск освобождает память сборщика.
|
||||
self.reassembler = Reassembler() # Машина ограничивает число параллельных пакетов и проверяет порядок сегментов.
|
||||
self.samplerate = None # Частота поступает событием metadata.
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
def metadata(self, key, value): # Метаданные задают шкалу времени сборки пакетов.
|
||||
if key == srd.SRD_CONF_SAMPLERATE: # Прочие метаданные не влияют на тайм-ауты.
|
||||
self.samplerate = value # Частота выражена в отсчётах в секунду.
|
||||
|
||||
# Состояние принадлежит одному запуску декодера. Сброс не должен
|
||||
# переносить незавершённый кадр или частоту из предыдущей записи.
|
||||
def reset(self):
|
||||
self.reassembler = Reassembler()
|
||||
self.samplerate = None
|
||||
def start(self): # Выходы регистрируются в штатной фазе запуска sigrok.
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN) # Аннотации предназначены для дорожек и поиска DSView.
|
||||
self.out_python = self.register(srd.OUTPUT_PYTHON) # Словарь полей доступен последующим слоям стека.
|
||||
|
||||
# DSView передаёт время в номерах отсчётов. Частота нужна общему ядру
|
||||
# для перевода тайм-аутов и измерений в физические единицы.
|
||||
def metadata(self, key, value):
|
||||
if key == srd.SRD_CONF_SAMPLERATE:
|
||||
self.samplerate = value
|
||||
|
||||
# Выходы регистрируются в жизненном цикле sigrok, а не в конструкторе.
|
||||
# Аннотации предназначены для дорожек, структурированные события — для стека.
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
self.out_python = self.register(srd.OUTPUT_PYTHON)
|
||||
|
||||
def decode(self, ss, es, data):
|
||||
frame_type, ident, rtr, dlc, payload = data
|
||||
if frame_type != 'extended' or rtr == 'remote':
|
||||
return
|
||||
mode = self.options['protocol']
|
||||
if not 0 <= dlc <= 8 or len(payload) != dlc:
|
||||
events = [(ss, es, None, 'Invalid classic CAN DLC')]
|
||||
# Сегментированный SET v2 требует времени для тайм-аута сборки.
|
||||
# Идентификатор, порядок сегментов и CRC проверяет общее ядро.
|
||||
elif mode == 'set-v2':
|
||||
if not self.samplerate:
|
||||
raise ValueError('SET CAN requires samplerate for reassembly timeout')
|
||||
events = self.reassembler.feed(ident, payload, ss, es, es * 1000.0 / self.samplerate)
|
||||
else:
|
||||
try:
|
||||
result = legacy(ident, payload, mode)
|
||||
events = [(ss, es, result, None)] if result else []
|
||||
except ValueError as exc:
|
||||
events = [(ss, es, None, str(exc))]
|
||||
for start, end, result, error in events:
|
||||
self.put(start, end, self.out_ann, [2 if error else 0, [error or result['summary']]])
|
||||
if result:
|
||||
result['can_id'] = ident
|
||||
self.put(start, end, self.out_python, ['FRAME', result])
|
||||
if events and payload:
|
||||
self.put(ss, es, self.out_ann, [1, [' '.join('%02X' % b for b in payload)]])
|
||||
def decode(self, ss, es, data): # Границы входного CAN-кадра выражены в отсчётах.
|
||||
frame_type, ident, rtr, dlc, payload = data # Кортеж соответствует выходу штатного CAN-декодера.
|
||||
mode = self.options['protocol'] # Профиль определяет формат ID и полезной нагрузки.
|
||||
if frame_type not in ('extended', 'standard') or rtr == 'remote': # RTR не содержит прикладных данных.
|
||||
return # Такие кадры остаются в базовых дорожках CAN.
|
||||
if frame_type == 'standard' and mode != 'deif': # DEIF использует STD CANopen, остальные подключённые протоколы — EXT.
|
||||
return # Предотвращает интерпретацию 11-битного ID как адреса ProtoCAN.
|
||||
if not 0 <= dlc <= 8 or len(payload) != dlc: # Classic CAN ограничивает нагрузку восемью байтами и требует совпадения длины с DLC.
|
||||
events = [(ss, es, None, 'Invalid classic CAN DLC')] # Повреждённый кадр показывается как ошибка.
|
||||
elif mode == 'set-v2' or mode == 'auto' and ident >> 24 == 0x12: # Авто распознаёт зарезервированный префикс SET v2 без угадывания проекта.
|
||||
if not self.samplerate: # Частота нужна для корректного тайм-аута сборки.
|
||||
raise ValueError('SET CAN requires samplerate for reassembly timeout') # Движок получает явную ошибку конфигурации.
|
||||
events = self.reassembler.feed(ident, payload, ss, es, es * 1000.0 / self.samplerate) # Перевод отсчётов в миллисекунды соответствует контракту сборщика.
|
||||
else: # Несегментированные протоколы обрабатывают каждый кадр независимо.
|
||||
try: # Ошибка одного кадра не должна прекращать разбор записи.
|
||||
result = decode_can(ident, payload, mode, extended=frame_type == 'extended', ump_map=self.options['ump_map'], ump_node=self.options['ump_node'], project=self.options['project']) # Подключает текущий парсер templates и выбранную карту.
|
||||
events = [(ss, es, result, None)] if result else [] # Чужой ID не создаёт ложных событий выбранного проекта.
|
||||
except ValueError as exc: # Проверки DLC, диапазонов и форматов возвращают диагностический текст.
|
||||
events = [(ss, es, None, str(exc))] # Следующий кадр продолжит обрабатываться.
|
||||
for start, end, result, error in events: # Сборщик может выдать несколько событий за один кадр.
|
||||
self.put(start, end, self.out_ann, [2 if error else 0, [error or result['summary']]]) # Сводка попадает на дорожку Frames либо Errors.
|
||||
if result: # Подробности выдаются для распознанного сообщения.
|
||||
for title, value in result.get('fields', []): # Названия и единицы определены общим парсером.
|
||||
self.put(start, end, self.out_ann, [3, ['%s: %s' % (title, value)]]) # Поля доступны для просмотра и поиска.
|
||||
for address, value in result.get('registers') or []: # Отсутствующая регистровая нагрузка отличается от пустого запроса.
|
||||
self.put(start, end, self.out_ann, [4, ['R%04X = %04X (%d)' % (address, value, value)]]) # Адрес и слово показаны в HEX, значение также в десятичной системе.
|
||||
for warning in result.get('warnings', []): # Замечания общего парсера сохраняются вместе с корректными полями.
|
||||
self.put(start, end, self.out_ann, [2, [warning]]) # Предупреждения видны в строке ошибок.
|
||||
result['can_id'] = ident # ID остаётся доступным последующим слоям.
|
||||
self.put(start, end, self.out_python, ['FRAME', result]) # Передаёт полную модель события.
|
||||
if events and payload: # Сырые байты позволяют сопоставить расшифровку с исходным CAN.
|
||||
self.put(ss, es, self.out_ann, [1, [' '.join('%02X' % byte for byte in payload)]]) # Порядок байтов совпадает с захватом.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import sigrokdecode as srd
|
||||
from common.setgui_decoders.set_uart import StreamParser
|
||||
from common.setgui_decoders.template_protocols import LEGACY_PROJECTS # Проекты CAN внутри моста выбираются из того же каталога templates.
|
||||
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
@@ -21,9 +22,13 @@ class Decoder(srd.Decoder):
|
||||
{'id': 'protocol', 'desc': 'Protocol', 'default': 'auto',
|
||||
'values': ('auto', 'v2', 'v1', 'bridge')},
|
||||
{'id': 'gap_ms', 'desc': 'Reset incomplete frame after gap (ms, 0 disables)', 'default': 100},
|
||||
{'id': 'can_protocol', 'desc': 'Bridge CAN protocol', 'default': 'protocan', 'values': ('protocan', 'auto', 'devboard', 'balsam', 'ump-protocan', 'deif', 'legacy')}, # Профиль применяется к прикладным кадрам, переданным транспортом AA55.
|
||||
{'id': 'project', 'desc': 'Bridge legacy CAN project', 'default': LEGACY_PROJECTS[0], 'values': LEGACY_PROJECTS}, # Исторический проект выбирается явно, поскольку его ID могут пересекаться с другими форматами.
|
||||
{'id': 'ump_map', 'desc': 'Bridge UMP map', 'default': 'gas167', 'values': ('gas167', 'assigned')}, # Разделяет текущую локальную карту и назначенные глобальные GAS ID.
|
||||
{'id': 'ump_node', 'desc': 'Bridge UMP node', 'default': 1, 'values': (1, 2)}, # Номер УМП задаётся отдельно от локального адреса регистра.
|
||||
)
|
||||
annotations = (('frame', 'Frame'), ('payload', 'Payload'), ('error', 'Error'))
|
||||
annotation_rows = (('frames', 'Frames', (0,)), ('payload', 'Payload', (1,)), ('errors', 'Errors', (2,)))
|
||||
annotations = (('frame', 'Frame'), ('payload', 'Payload'), ('error', 'Error'), ('field', 'Field')) # Индексы прежних аннотаций сохранены, подробные поля добавлены в конец.
|
||||
annotation_rows = (('frames', 'Frames', (0,)), ('payload', 'Payload', (1,)), ('errors', 'Errors', (2,)), ('fields', 'Fields', (3,))) # Поля прикладного парсера видны на отдельной дорожке.
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
@@ -52,27 +57,37 @@ class Decoder(srd.Decoder):
|
||||
prefix = ('RX', 'TX')[direction] + ' '
|
||||
self.put(ss, es, self.out_ann, [2 if error else 0, [prefix + (error or frame['summary'])]])
|
||||
if frame:
|
||||
if frame['protocol'] == 'ProtoCAN bridge' and frame['flags'] & 1 and not frame['flags'] & 10:
|
||||
if frame['protocol'] == 'ProtoCAN bridge' and not frame['flags'] & 10: # Флаги RTR и ERR исключают прикладные данные; STD допускается для профиля DEIF.
|
||||
# Imported only at decode time: both packages are installed together.
|
||||
from common.setgui_decoders.set_can import legacy
|
||||
from common.setgui_decoders.template_protocols import decode_can # Мост использует те же прикладные парсеры, что анализатор SET CAN.
|
||||
try:
|
||||
application = legacy(frame['can_id'], frame['payload'])
|
||||
application = decode_can(frame['can_id'], frame['payload'], self.options['can_protocol'], extended=bool(frame['flags'] & 1), project=self.options['project'], ump_map=self.options['ump_map'], ump_node=self.options['ump_node']) # Флаг IDE и выбранные настройки передаются общему парсеру без изменения wire-данных.
|
||||
frame['application'] = application
|
||||
self.put(ss, es, self.out_ann, [0, [application['summary']]])
|
||||
if application: # Выбранный профиль может не распознать чужой CAN ID.
|
||||
self.put(ss, es, self.out_ann, [0, [application['summary']]]) # Прикладная сводка дополняет сведения о транспортном кадре моста.
|
||||
for title, value in application.get('fields', []): # Детали CAN внутри UART берутся из того же результата templates.
|
||||
self.put(ss, es, self.out_ann, [3, ['%s: %s' % (title, value)]]) # Поля доступны без отдельного импорта записи в CAN-монитор.
|
||||
except ValueError as exc:
|
||||
self.put(ss, es, self.out_ann, [2, [str(exc)]])
|
||||
for title, value in frame.get('fields', []): # Стековые UART-профили могут возвращать готовые прикладные поля.
|
||||
self.put(ss, es, self.out_ann, [3, [prefix + '%s: %s' % (title, value)]]) # Префикс RX/TX сохраняет направление каждого значения.
|
||||
frame['direction'] = direction
|
||||
self.put(ss, es, self.out_python, ['FRAME', frame])
|
||||
if frame['payload']:
|
||||
self.put(ss, es, self.out_ann, [1, [prefix + ' '.join('%02X' % b for b in frame['payload'])]])
|
||||
|
||||
def make_parser(self): # Фабрика позволяет использовать ту же UART-границу с другим переносимым потоковым парсером.
|
||||
return StreamParser(self.options['protocol']) # Каждый экземпляр хранит байты только одного направления.
|
||||
|
||||
def decode(self, ss, es, data):
|
||||
kind, direction, payload = data
|
||||
if direction not in (0, 1):
|
||||
return
|
||||
# RX и TX имеют независимые парсеры: перемежающиеся байты двух направлений
|
||||
# нельзя объединять в один прикладной кадр.
|
||||
parser = self.parsers.setdefault(direction, StreamParser(self.options['protocol']))
|
||||
if direction not in self.parsers: # В отличие от setdefault, проверка не создаёт и не отбрасывает парсер на каждом UART-событии.
|
||||
self.parsers[direction] = self.make_parser() # Парсер создаётся один раз на направление и сохраняет незавершённый кадр.
|
||||
parser = self.parsers[direction] # RX и TX продолжают использовать независимые буферы.
|
||||
if kind in ('INVALID STOPBIT', 'INVALID STARTBIT', 'PARITY ERROR', 'BREAK'):
|
||||
self.emit(parser.flush(), direction)
|
||||
self.put(ss, es, self.out_ann, [2, [kind]])
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from .pd import Decoder # Экспортирует стандартную точку входа анализатора libsigrokdecode.
|
||||
@@ -0,0 +1,18 @@
|
||||
from set_uart.pd import Decoder as SetUartDecoder # Общий адаптер уже разделяет RX/TX, учитывает паузы и отклоняет ошибки UART.
|
||||
from common.setgui_decoders.tms_uart import LAYOUTS, TmsStreamParser, tms # Подключает существующий парсер и каталог раскладок templates.
|
||||
|
||||
class Decoder(SetUartDecoder): # Использует проверенную UART-границу вместо второй реализации обработки байтов sigrok.
|
||||
id = 'tms_uart' # Отдельный ID позволяет одновременно анализировать SET и исторический терминальный обмен.
|
||||
name = 'TMS UART' # Короткое имя отображается в меню и дорожках.
|
||||
longname = 'TMS terminal / TMS_TO_TERMINAL' # Полное имя соответствует структуре действующего протокола терминала.
|
||||
desc = 'Templates TMS terminal frames, analog values, discrete bits and CRC.' # Подсказка перечисляет доступные поля расшифровки.
|
||||
options = ( # Параметры раскладки повторяют возможности общего парсера терминала.
|
||||
{'id': 'layout', 'desc': 'Layout / раскладка', 'default': tms.DEFAULT_PRESET, 'values': LAYOUTS}, # Готовые раскладки поступают из единого каталога templates.
|
||||
{'id': 'digit_bytes', 'desc': 'Custom discrete bytes / байт дискретов', 'default': 38}, # В custom задаётся длина дискретной части в байтах.
|
||||
{'id': 'analog_count', 'desc': 'Custom analog words / слов аналогов', 'default': 34}, # В custom задаётся число знаковых 16-битных аналоговых слов.
|
||||
{'id': 'crc', 'desc': 'CRC variant', 'default': tms.DEFAULT_CRC, 'values': tuple(tms.CRC_VARIANTS)}, # Неподтверждённые CRC кадры общий парсер не выдаёт.
|
||||
{'id': 'gap_ms', 'desc': 'Reset after gap (ms, 0 disables)', 'default': 100}, # Пауза 100 мс по умолчанию отделяет незавершённые посылки.
|
||||
) # Опции протокола SET здесь не требуются.
|
||||
|
||||
def make_parser(self): # Базовый адаптер создаёт отдельный парсер для каждого направления UART.
|
||||
return TmsStreamParser(self.options['layout'], self.options['digit_bytes'], self.options['analog_count'], self.options['crc']) # Общая модель проверяет диапазоны и рассчитывает длину кадра.
|
||||
Reference in New Issue
Block a user