Добавить очереди операций PBUS/SBUS, общую консоль, настройки Candle и SLCAN, декодеры DSView и обработку плотных графиков. Обновить протокол Бальзам и проверки общих компонентов.
173 lines
23 KiB
Python
173 lines
23 KiB
Python
"""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] # Следующий таймер сравнивается с последним реальным уровнем.
|