Add DSLogic capture, signal conversion and DSView decoders
This commit is contained in:
@@ -4,10 +4,12 @@ from __future__ import annotations
|
||||
from bisect import bisect_left, bisect_right
|
||||
from dataclasses import dataclass
|
||||
import heapq
|
||||
from itertools import groupby
|
||||
import math
|
||||
|
||||
from .files import ImportCancelled
|
||||
from .decoders.gate_timing import TimingChecker
|
||||
from .decoders.transistor_pair import PairTimingChecker
|
||||
from .decoders.set_uart import StreamParser
|
||||
from .decoders.pm35_uart import PM35Parser
|
||||
from .decoders.set_can import legacy, Reassembler
|
||||
@@ -84,7 +86,7 @@ def uart_frames(channel, start, end, baud, parity='none', stops=1, inverted=Fals
|
||||
continue
|
||||
finish = time + bits * bit
|
||||
if finish > end + bit * 1e-6:
|
||||
yield time, end, None, 'Неполный UART-байт в конце диапазона'
|
||||
yield time, end, None, 'Неполный UART-байт в конце диапазона'
|
||||
break
|
||||
read = lambda pos: channel.level_at(time + pos * bit) ^ inverted
|
||||
if read(0.5):
|
||||
@@ -94,10 +96,10 @@ def uart_frames(channel, start, end, baud, parity='none', stops=1, inverted=Fals
|
||||
value = sum(v << i for i, v in enumerate(values))
|
||||
error = None
|
||||
if parity != 'none' and read(9.5) != ((sum(values) + (parity == 'odd')) & 1):
|
||||
error = 'Ошибка чётности UART'
|
||||
error = 'Ошибка чётности UART'
|
||||
stop_start = 9 + (parity != 'none')
|
||||
if any(read(stop_start + i + 0.5) != 1 for i in range(stops)):
|
||||
error = 'Ошибка стопового бита UART / BREAK'
|
||||
error = 'Ошибка стопового бита UART / BREAK'
|
||||
yield time, finish, value, error
|
||||
index = bisect_left(edges, finish - bit * 1e-6, index + 1)
|
||||
|
||||
@@ -142,7 +144,7 @@ class _CanBits:
|
||||
break
|
||||
sample = boundary + self.sample_point * self.bit
|
||||
if sample >= self.end:
|
||||
raise EOFError('Неполный CAN-кадр в конце диапазона')
|
||||
raise EOFError('Неполный CAN-кадр в конце диапазона')
|
||||
self.last_sample = sample
|
||||
self.position += 1
|
||||
return self.channel.level_at(sample) ^ self.inverted
|
||||
@@ -151,7 +153,7 @@ class _CanBits:
|
||||
if self.run == 5:
|
||||
value = self.raw()
|
||||
if value == self.previous:
|
||||
raise ValueError('CAN: ошибка bit stuffing / error frame')
|
||||
raise ValueError('CAN: ошибка bit stuffing / error frame')
|
||||
self.previous, self.run = value, 1
|
||||
value = self.raw()
|
||||
self.run = self.run + 1 if value == self.previous else 1
|
||||
@@ -181,34 +183,34 @@ def can_frames(channel, start, end, bitrate, inverted=False, sample_point=.7, ca
|
||||
frame, error = None, None
|
||||
try:
|
||||
if reader.take(1):
|
||||
raise ValueError('CAN: неверный SOF')
|
||||
raise ValueError('CAN: неверный SOF')
|
||||
ident = reader.take(11)
|
||||
rtr, extended = reader.take(1), reader.take(1)
|
||||
if extended:
|
||||
if rtr != 1:
|
||||
raise ValueError('CAN: неверный SRR')
|
||||
raise ValueError('CAN: неверный SRR')
|
||||
ident = (ident << 18) | reader.take(18)
|
||||
rtr = reader.take(1)
|
||||
if reader.take(2):
|
||||
raise ValueError('CAN FD / reserved bits не поддерживаются')
|
||||
raise ValueError('CAN FD / reserved bits не поддерживаются')
|
||||
elif reader.take(1):
|
||||
raise ValueError('CAN FD / reserved bit не поддерживается')
|
||||
raise ValueError('CAN FD / reserved bit не поддерживается')
|
||||
dlc = reader.take(4)
|
||||
if dlc > 8:
|
||||
raise ValueError('CAN: поддерживается classic DLC 0…8')
|
||||
raise ValueError('CAN: поддерживается classic DLC 0…8')
|
||||
data = bytes(reader.take(8) for _ in range(0 if rtr else dlc))
|
||||
expected = can_crc(reader.bits)
|
||||
received = reader.take(15)
|
||||
if reader.run == 5:
|
||||
if reader.raw() == reader.previous:
|
||||
raise ValueError('CAN: неверный последний stuff bit')
|
||||
raise ValueError('CAN: неверный последний stuff bit')
|
||||
if reader.raw() != 1:
|
||||
raise ValueError('CAN: неверный CRC delimiter')
|
||||
raise ValueError('CAN: неверный CRC delimiter')
|
||||
ack = reader.raw() == 0
|
||||
if reader.raw() != 1 or any(reader.raw() != 1 for _ in range(7)):
|
||||
raise ValueError('CAN: неверный ACK delimiter / EOF')
|
||||
raise ValueError('CAN: неверный ACK delimiter / EOF')
|
||||
if received != expected:
|
||||
raise ValueError('CAN CRC15: получено %04X, ожидается %04X' % (received, expected))
|
||||
raise ValueError('CAN CRC15: получено %04X, ожидается %04X' % (received, expected))
|
||||
frame = dict(ident=ident, extended=bool(extended), remote=bool(rtr),
|
||||
data=data, dlc=dlc, ack=ack)
|
||||
except (ValueError, EOFError) as exc:
|
||||
@@ -222,14 +224,14 @@ def analyze_capture(capture, options, start=None, end=None, progress=lambda n: N
|
||||
start = capture.start if start is None else max(capture.start, start)
|
||||
end = capture.end if end is None else min(capture.end, end)
|
||||
if end <= start:
|
||||
raise ValueError('Выберите непустой интервал анализа.')
|
||||
raise ValueError('Выберите непустой интервал анализа.')
|
||||
mode = options['mode']
|
||||
channel = capture.channels[options.get('channel', 0)]
|
||||
events = []
|
||||
last_progress = -1
|
||||
limit = options.get('max_events', 50000)
|
||||
if limit <= 0:
|
||||
raise ValueError('Лимит результатов должен быть положительным.')
|
||||
raise ValueError('Лимит результатов должен быть положительным.')
|
||||
|
||||
def emit(a, b, kind, text, details=''):
|
||||
if len(events) >= limit:
|
||||
@@ -260,8 +262,10 @@ def analyze_capture(capture, options, start=None, end=None, progress=lambda n: N
|
||||
if mode in ('1SP0635', '1SD536F2'):
|
||||
other = options.get('status_channel', 1)
|
||||
if other == options.get('channel', 0):
|
||||
raise ValueError('Vin и Vstat должны быть разными каналами.')
|
||||
raise ValueError('Vin1 и Vstat должны быть разными каналами.')
|
||||
status = capture.channels[other]
|
||||
if options.get('pair_analysis'):
|
||||
raise ValueError('Use the Transistor pair analyzer for Vin1/Vin2 timing.')
|
||||
checker = TimingChecker(1e9, mode, options.get('tolerance_ns', 100),
|
||||
not options.get('vin_low', False), not options.get('status_low', False))
|
||||
# The pure checker works in integer ticks. Offset before rounding
|
||||
@@ -272,7 +276,7 @@ def analyze_capture(capture, options, start=None, end=None, progress=lambda n: N
|
||||
for item in items:
|
||||
text = item['text']
|
||||
if item['kind'] == 'fault':
|
||||
text += ' · аварийная обратная связь; превышение тока не подтверждено'
|
||||
text += ' · аварийная обратная связь; превышение тока не подтверждено'
|
||||
emit(start + item['start'] / 1e9, start + item['end'] / 1e9, item['kind'], text)
|
||||
|
||||
def transitions(ch, which):
|
||||
@@ -281,21 +285,48 @@ def analyze_capture(capture, options, start=None, end=None, progress=lambda n: N
|
||||
yield ch.edges[i], which, ch.initial ^ ((i + 1) & 1)
|
||||
|
||||
if bool(status.level_at(start)) == checker.vstat_active_high:
|
||||
emit(start, start, 'orphan', 'Vstat активен в начале диапазона: начало импульса не записано')
|
||||
for time, which, level in heapq.merge(transitions(channel, 0), transitions(status, 1)):
|
||||
emit(start, start, 'orphan', 'Vstat активен в начале диапазона: начало импульса не записано')
|
||||
streams = [transitions(channel, 0), transitions(status, 1)]
|
||||
for time, which, level in heapq.merge(*streams):
|
||||
report(time)
|
||||
timing(checker.expire(tick(time)))
|
||||
timing(checker.on_control_edge(tick(time), level) if which == 0 else checker.on_status_edge(tick(time), level))
|
||||
timing(checker.on_control_edge(tick(time), level) if which == 0
|
||||
else checker.on_status_edge(tick(time), level))
|
||||
timing(checker.expire(tick(end)))
|
||||
if checker.status_start is not None:
|
||||
emit(start + checker.status_start / 1e9, end, 'incomplete', 'Vstat: импульс не завершён в диапазоне')
|
||||
emit(start + checker.status_start / 1e9, end, 'incomplete', 'Vstat: импульс не завершён в диапазоне')
|
||||
for pending in checker.pending:
|
||||
emit(start + pending['sample'] / 1e9, end, 'incomplete', 'Vin: диапазон закончился до тайм-аута ACK')
|
||||
emit(start + pending['sample'] / 1e9, end, 'incomplete', 'Vin1: диапазон закончился до тайм-аута ACK')
|
||||
elif mode == 'Transistor pair':
|
||||
second = options.get('vin2_channel', 1)
|
||||
if second == options.get('channel', 0):
|
||||
raise ValueError('Vin1 and Vin2 must be different channels.')
|
||||
vin2 = capture.channels[second]
|
||||
numeric = {name: options.get(name, 0) for name in (
|
||||
'vin1_mintime_ns', 'vin2_mintime_ns', 'vin1_minoff_ns', 'vin2_minoff_ns',
|
||||
'deadtime_12_ns', 'deadtime_21_ns')}
|
||||
checker = PairTimingChecker(1e9, not options.get('vin_low', False),
|
||||
not options.get('vin2_low', False), **numeric)
|
||||
levels = [channel.level_at(start), vin2.level_at(start)]
|
||||
checker.update(0, *levels)
|
||||
def edges(ch, which):
|
||||
for index in range(bisect_right(ch.edges, start), bisect_right(ch.edges, end)):
|
||||
yield ch.edges[index], which, ch.initial ^ ((index + 1) & 1)
|
||||
def emit_pair(items):
|
||||
for item in items:
|
||||
emit(start + item['start'] / 1e9, start + item['end'] / 1e9,
|
||||
item['kind'], item['text'])
|
||||
for time, group in groupby(heapq.merge(edges(channel, 0), edges(vin2, 1)), key=lambda e: e[0]):
|
||||
report(time)
|
||||
for _, which, level in group:
|
||||
levels[which] = level
|
||||
emit_pair(checker.update(round((time - start) * 1e9), *levels))
|
||||
emit_pair(checker.finish(round((end - start) * 1e9)))
|
||||
elif mode in ('UART', 'SET UART', 'PM35 UART'):
|
||||
baud = options.get('baudrate', 115200)
|
||||
parity, stops = options.get('parity', 'none'), options.get('stops', 1)
|
||||
if not math.isfinite(baud) or baud <= 0 or parity not in ('none', 'even', 'odd') or stops not in (1, 2):
|
||||
raise ValueError('Некорректные настройки UART.')
|
||||
raise ValueError('Некорректные настройки UART.')
|
||||
parser = (StreamParser(options.get('protocol', 'auto')) if mode == 'SET UART' else
|
||||
PM35Parser(options.get('role', 'response')) if mode == 'PM35 UART' else None)
|
||||
gap = (3.5 * (9 + (parity != 'none') + stops) / baud if mode == 'PM35 UART'
|
||||
@@ -320,7 +351,7 @@ def analyze_capture(capture, options, start=None, end=None, progress=lambda n: N
|
||||
bitrate = options.get('baudrate', 1000000)
|
||||
sample_point = options.get('sample_point', 70) / 100
|
||||
if not math.isfinite(bitrate) or bitrate <= 0 or not .1 <= sample_point <= .95:
|
||||
raise ValueError('Некорректный битрейт / точка выборки CAN.')
|
||||
raise ValueError('Некорректный битрейт / точка выборки CAN.')
|
||||
reassembler = Reassembler()
|
||||
for a, b, frame, error in can_frames(channel, start, end, bitrate, options.get('inverted', False), sample_point, cancel):
|
||||
report(a)
|
||||
@@ -328,7 +359,7 @@ def analyze_capture(capture, options, start=None, end=None, progress=lambda n: N
|
||||
emit(a, b, 'error', error)
|
||||
# Never bridge an invalid/missing physical frame.
|
||||
for pending in reassembler.pending.values():
|
||||
emit(pending['start'], b, 'incomplete', 'SET CAN: сборка прервана ошибкой шины')
|
||||
emit(pending['start'], b, 'incomplete', 'SET CAN: сборка прервана ошибкой шины')
|
||||
reassembler.pending.clear()
|
||||
continue
|
||||
ident, data = frame['ident'], frame['data']
|
||||
@@ -347,9 +378,9 @@ def analyze_capture(capture, options, start=None, end=None, progress=lambda n: N
|
||||
except ValueError as exc:
|
||||
emit(a, b, 'error', str(exc))
|
||||
for pending in reassembler.pending.values():
|
||||
emit(pending['start'], end, 'incomplete', 'Неполная сборка SET CAN в конце диапазона')
|
||||
emit(pending['start'], end, 'incomplete', 'Неполная сборка SET CAN в конце диапазона')
|
||||
else:
|
||||
raise ValueError('Неизвестный анализатор: ' + mode)
|
||||
raise ValueError('Неизвестный анализатор: ' + mode)
|
||||
except OverflowError:
|
||||
truncated = True
|
||||
_cancel(cancel)
|
||||
|
||||
Reference in New Issue
Block a user