Add DSLogic capture, signal conversion and DSView decoders
This commit is contained in:
60
tools/dsview/decoders/gate_driver_timing/README.md
Normal file
60
tools/dsview/decoders/gate_driver_timing/README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# 1SP0635 / 1SD536F2 timing decoder for DSView
|
||||
|
||||
This decoder checks driver control and status responses:
|
||||
|
||||
- `Vin1`: optional control sent to the first gate driver (legacy channel ID `vin`);
|
||||
- `Vstat`: edge acknowledgement and fault feedback returned by the driver.
|
||||
|
||||
Built-in data-sheet profiles:
|
||||
|
||||
| Profile | ACK delay (typ.) | ACK width (specified) | Long pulse |
|
||||
|---|---:|---:|---:|
|
||||
| 1SP0635 | 250 ns | 400...1050 ns | fault above 1.5 us |
|
||||
| 1SD536F2 | 380 ns | 600...1800 ns | fault above 1.8 us* |
|
||||
|
||||
`*` The 1.8 us fault threshold is a project classification choice based on the
|
||||
data-sheet ACK maximum, not a verified universal manufacturer fault threshold.
|
||||
The previously stated 1.5 us manual threshold could not be verified.
|
||||
Correlated pulses within the ACK width limits pass. Uncorrelated pulses up to
|
||||
the fault threshold are warnings; pulses above it are faults. Width and fault
|
||||
annotations are emitted only when the pulse ends.
|
||||
|
||||
The data sheets specify only a *typical* ACK delay, not minimum and maximum
|
||||
limits. DSView therefore emits a warning outside the configurable tolerance
|
||||
(default +/-100 ns). The ACK-width check uses the published min/max values.
|
||||
|
||||
## Use in SETGUI
|
||||
|
||||
The DSView build recipe packages this adapter together with the canonical
|
||||
`python/logic_analyzer/decoders/gate_timing.py` core. In the DSView tab,
|
||||
choose **Анализаторы SET → IGBT · 1SP0635 / 1SD536F2** (or search Decode for
|
||||
`IGBT 1SP/1SD`). Assign Vstat and, if available, Vin1, choose the driver profile, and set `Vstat acknowledge/fault level`
|
||||
to match the receiver circuit. The diagrams in the application manual show
|
||||
the logical event; some optical receiver circuits invert it.
|
||||
|
||||
Use at least 50 MS/s; 100 MS/s or more is recommended. Never connect a DSLogic
|
||||
digital input directly to the IGBT gate (+15/-10 V) or to a power-stage node.
|
||||
Use the isolated optical receiver logic signals and a common logic ground.
|
||||
|
||||
## Separate transistor pair analyzer
|
||||
|
||||
This decoder analyzes only Vin1/Vstat driver responses. Vin1 is optional;
|
||||
Vstat alone retains pulse/fault classification, while Vin1 enables ACK timing
|
||||
and full-cycle results. Vstat is required. The channel ID `vin` remains stable.
|
||||
|
||||
Pair analysis was moved to **Transistor pair** in the **Пользовательские**
|
||||
section. Add that decoder separately and assign Vin1/Vin2. Old pair settings
|
||||
on IGBT instances must be recreated in the new decoder. The pair checkbox,
|
||||
Vin2 assignment and pulse limits are no longer part of the driver decoder.
|
||||
|
||||
## Full pulse cycle result
|
||||
|
||||
The **Vin1 / ACK full cycle** waveform row displays OK, WARNING or FAULT
|
||||
across each complete sequence **Vin1 ON -> ACK -> Vin1 OFF -> ACK**.
|
||||
The annotation starts at Vin1 ON and ends at the end of the OFF acknowledgment.
|
||||
It is emitted only after both ACKs have been classified. A delay warning makes
|
||||
that cycle WARNING; missing ACK, invalid width or fault feedback makes it FAULT.
|
||||
A later normal cycle can be OK independently of previous cycles.
|
||||
Unfinished cycles are never marked OK. Active-low Vin1 is supported.
|
||||
The results panel retains its original layout without a large summary banner.
|
||||
Deadtime and mintime are provided by the separate Transistor pair analyzer.
|
||||
3
tools/dsview/decoders/gate_driver_timing/__init__.py
Normal file
3
tools/dsview/decoders/gate_driver_timing/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""DSView protocol decoder for SCALE gate-driver timing checks."""
|
||||
|
||||
from .pd import Decoder
|
||||
168
tools/dsview/decoders/gate_driver_timing/pd.py
Normal file
168
tools/dsview/decoders/gate_driver_timing/pd.py
Normal file
@@ -0,0 +1,168 @@
|
||||
"""DSView/libsigrokdecode front end for SCALE gate-driver timing checks."""
|
||||
|
||||
import sigrokdecode as srd
|
||||
|
||||
from common.setgui_decoders.gate_timing import TimingChecker
|
||||
|
||||
|
||||
class SamplerateError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Ann(object):
|
||||
CONTROL = 0
|
||||
DELAY_OK = 1
|
||||
WIDTH_OK = 2
|
||||
WARNING = 3
|
||||
ERROR = 4
|
||||
FAULT = 5
|
||||
CYCLE_OK = 6
|
||||
CYCLE_WARNING = 7
|
||||
CYCLE_FAULT = 8
|
||||
|
||||
|
||||
ANN_FOR_KIND = {
|
||||
'control': Ann.CONTROL,
|
||||
'delay_ok': Ann.DELAY_OK,
|
||||
'width_ok': Ann.WIDTH_OK,
|
||||
'delay_warn': Ann.WARNING,
|
||||
'width_fail': Ann.ERROR,
|
||||
'missing': Ann.ERROR,
|
||||
'orphan': Ann.WARNING,
|
||||
'fault': Ann.FAULT,
|
||||
'cycle_ok': Ann.CYCLE_OK,
|
||||
'cycle_warning': Ann.CYCLE_WARNING,
|
||||
'cycle_fault': Ann.CYCLE_FAULT,
|
||||
}
|
||||
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'gate_driver_timing'
|
||||
name = 'IGBT 1SP/1SD'
|
||||
longname = 'Power Integrations 1SP0635 / 1SD536F2 timing checker'
|
||||
desc = 'Checks driver Vin1/Vstat ACK timing, faults and full pulse cycles.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['logic']
|
||||
outputs = []
|
||||
tags = ['Clock/timing', 'Util']
|
||||
|
||||
channels = ()
|
||||
optional_channels = (
|
||||
{'id': 'vin', 'name': 'Vin1', 'desc': 'First driver control input'},
|
||||
{'id': 'vstat', 'name': 'Vstat', 'desc': 'Driver status/acknowledge output'},
|
||||
)
|
||||
|
||||
options = (
|
||||
{'id': 'profile', 'desc': 'Driver profile', 'default': '1SP0635',
|
||||
'values': ('1SP0635', '1SD536F2', 'custom')},
|
||||
{'id': 'vin_active', 'desc': 'Vin1 active level', 'default': 'high',
|
||||
'values': ('high', 'low')},
|
||||
{'id': 'vstat_active', 'desc': 'Vstat acknowledge/fault level', 'default': 'high',
|
||||
'values': ('high', 'low')},
|
||||
{'id': 'delay_tolerance_ns',
|
||||
'desc': 'Warning tolerance around typical ACK delay (ns)', 'default': 100},
|
||||
{'id': 'orphan_min_width_ns',
|
||||
'desc': 'ORPHAN minimum pulse width (ns, 0 = off)', 'default': 0},
|
||||
{'id': 'custom_delay_ns', 'desc': 'Custom typical ACK delay (ns)', 'default': 250},
|
||||
{'id': 'custom_width_min_ns', 'desc': 'Custom ACK width minimum (ns)', 'default': 400},
|
||||
{'id': 'custom_width_typ_ns', 'desc': 'Custom ACK width typical (ns)', 'default': 700},
|
||||
{'id': 'custom_width_max_ns', 'desc': 'Custom ACK width maximum (ns)', 'default': 1050},
|
||||
{'id': 'custom_fault_ns', 'desc': 'Custom fault pulse threshold (ns)', 'default': 1500},
|
||||
)
|
||||
|
||||
annotations = (
|
||||
('control', 'Vin1 edge'),
|
||||
('delay-ok', 'ACK delay pass'),
|
||||
('width-ok', 'ACK width pass'),
|
||||
('warning', 'Warning'),
|
||||
('error', 'Timing failure'),
|
||||
('fault', 'Driver fault'),
|
||||
('cycle-ok', 'Cycle OK'),
|
||||
('cycle-warning', 'Cycle WARNING'),
|
||||
('cycle-fault', 'Cycle FAULT'),
|
||||
)
|
||||
|
||||
annotation_rows = (
|
||||
('cycle', 'Vin1 / ACK full cycle', (Ann.CYCLE_OK, Ann.CYCLE_WARNING, Ann.CYCLE_FAULT)),
|
||||
('vin', 'Vin1', (Ann.CONTROL,)),
|
||||
('measurements', 'Measurements', (Ann.DELAY_OK, Ann.WIDTH_OK)),
|
||||
('checks', 'Warnings / failures', (Ann.WARNING, Ann.ERROR, Ann.FAULT)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
# Состояние принадлежит одному запуску декодера. Сброс не должен
|
||||
# переносить незавершённый кадр или частоту из предыдущей записи.
|
||||
def reset(self):
|
||||
self.samplerate = None
|
||||
self.checker = None
|
||||
|
||||
# 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)
|
||||
|
||||
# Адаптер переводит настройки DSView в параметры общего TimingChecker.
|
||||
# Пороговые алгоритмы остаются в templates/python/logic_analyzer/decoders.
|
||||
def _make_checker(self):
|
||||
custom = {
|
||||
'ack_delay_typ_ns': float(self.options['custom_delay_ns']),
|
||||
'ack_width_min_ns': float(self.options['custom_width_min_ns']),
|
||||
'ack_width_typ_ns': float(self.options['custom_width_typ_ns']),
|
||||
'ack_width_max_ns': float(self.options['custom_width_max_ns']),
|
||||
'fault_threshold_ns': float(self.options['custom_fault_ns']),
|
||||
}
|
||||
return TimingChecker(
|
||||
self.samplerate,
|
||||
profile=self.options['profile'],
|
||||
delay_tolerance_ns=float(self.options['delay_tolerance_ns']),
|
||||
vin_active_high=self.options['vin_active'] == 'high',
|
||||
vstat_active_high=self.options['vstat_active'] == 'high',
|
||||
orphan_min_width_ns=self.options.get('orphan_min_width_ns', 0),
|
||||
custom=custom, cycle_results=True)
|
||||
|
||||
def _put_events(self, events):
|
||||
for event in events:
|
||||
ann = ANN_FOR_KIND[event['kind']]
|
||||
self.put(event['start'], max(event['start'], event['end']),
|
||||
self.out_ann, [ann, [event['text'], event['short']]])
|
||||
|
||||
def decode(self):
|
||||
if not self.samplerate:
|
||||
raise SamplerateError('Cannot decode without samplerate.')
|
||||
self.checker = self._make_checker()
|
||||
has_vin1 = self.has_channel(0)
|
||||
if not self.has_channel(1):
|
||||
raise ValueError('Connect Vstat for driver response analysis.')
|
||||
while True:
|
||||
conditions = [{0: 'e'}, {1: 'e'}] if has_vin1 else [{1: 'e'}]
|
||||
timer_bit = 1 << len(conditions)
|
||||
deadline = self.checker.next_deadline()
|
||||
if deadline is not None:
|
||||
conditions.append({'skip': max(1, deadline - self.samplenum)})
|
||||
|
||||
# DSView mutates and returns the same native tuple on every wait.
|
||||
# Release that tuple before waiting again (PyTuple_SetItem requires
|
||||
# exclusive ownership); tuple(...) would not make a copy.
|
||||
pins = list(self.wait(conditions))
|
||||
vin, vstat = pins[:2]
|
||||
now = self.samplenum
|
||||
# Both channels may transition at the same sample; preserve both.
|
||||
if has_vin1 and self.matched & 0b001:
|
||||
self._put_events(self.checker.on_control_edge(now, vin))
|
||||
if self.matched & (0b010 if has_vin1 else 0b001):
|
||||
self._put_events(self.checker.on_status_edge(now, vstat))
|
||||
if deadline is not None and self.matched & timer_bit:
|
||||
self._put_events(self.checker.expire(now))
|
||||
|
||||
def end(self):
|
||||
if self.checker is not None:
|
||||
self._put_events(self.checker.finish_cycles(self.samplenum))
|
||||
1
tools/dsview/decoders/pm35_uart/__init__.py
Normal file
1
tools/dsview/decoders/pm35_uart/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .pd import Decoder
|
||||
82
tools/dsview/decoders/pm35_uart/pd.py
Normal file
82
tools/dsview/decoders/pm35_uart/pd.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import sigrokdecode as srd
|
||||
from common.setgui_decoders.pm35_uart import PM35Parser
|
||||
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'pm35_uart'
|
||||
name = 'PM35 UART'
|
||||
longname = 'PM35 / TMS320F28335 terminal'
|
||||
desc = 'MODBUS 03/06 register traffic and CRC16.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['uart']
|
||||
outputs = ['pm35']
|
||||
tags = ['Embedded/industrial']
|
||||
options = (
|
||||
{'id': 'rx_role', 'desc': 'RX carries', 'default': 'response', 'values': ('request', 'response', 'auto')},
|
||||
{'id': 'tx_role', 'desc': 'TX carries', 'default': 'request', 'values': ('request', 'response', 'auto')},
|
||||
{'id': 'baudrate', 'desc': 'UART baudrate (RTU gap detection)', 'default': 115200},
|
||||
{'id': 'bits_per_char', 'desc': 'Bits per UART character including start/stop/parity', 'default': 10},
|
||||
)
|
||||
annotations = (('frame', 'Frame'), ('error', 'Error'))
|
||||
annotation_rows = (('frames', 'Frames', (0,)), ('errors', 'Errors', (1,)))
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
# Состояние принадлежит одному запуску декодера. Сброс не должен
|
||||
# переносить незавершённый кадр или частоту из предыдущей записи.
|
||||
def reset(self):
|
||||
self.parsers = {}
|
||||
self.last = {}
|
||||
self.samplerate = None
|
||||
|
||||
# DSView передаёт время в номерах отсчётов. Частота нужна общему ядру
|
||||
# для перевода тайм-аутов и измерений в физические единицы.
|
||||
def metadata(self, key, value):
|
||||
if key == srd.SRD_CONF_SAMPLERATE:
|
||||
self.samplerate = value
|
||||
|
||||
# Выходы регистрируются в жизненном цикле sigrok, а не в конструкторе.
|
||||
# Аннотации предназначены для дорожек, структурированные события — для стека.
|
||||
def start(self):
|
||||
if self.options['baudrate'] <= 0 or self.options['bits_per_char'] <= 0:
|
||||
raise ValueError('Baudrate and bits per character must be positive')
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
self.out_python = self.register(srd.OUTPUT_PYTHON)
|
||||
|
||||
def emit(self, events, direction):
|
||||
for ss, es, result, error in events:
|
||||
self.put(ss, es, self.out_ann, [1 if error else 0,
|
||||
[('RX', 'TX')[direction] + ' ' + (error or result['summary'])]])
|
||||
if result:
|
||||
result['direction'] = direction
|
||||
self.put(ss, es, self.out_python, ['FRAME', result])
|
||||
|
||||
def decode(self, ss, es, data):
|
||||
kind, direction, payload = data
|
||||
if direction not in (0, 1):
|
||||
return
|
||||
parser = self.parsers.setdefault(direction, PM35Parser(self.options[('rx_role', 'tx_role')[direction]]))
|
||||
if kind in ('INVALID STOPBIT', 'INVALID STARTBIT', 'PARITY ERROR', 'BREAK'):
|
||||
self.emit(parser.flush(), direction)
|
||||
self.put(ss, es, self.out_ann, [1, [kind]])
|
||||
return
|
||||
if kind != 'FRAME':
|
||||
return
|
||||
if not payload[1]:
|
||||
self.emit(parser.flush(), direction)
|
||||
self.put(ss, es, self.out_ann, [1, ['Invalid UART frame']])
|
||||
return
|
||||
# Граница MODBUS определяется паузой 3,5 символа. bits_per_char включает
|
||||
# все биты символа; эта настройка должна соответствовать базовому UART.
|
||||
if self.samplerate and direction in self.last:
|
||||
gap = self.samplerate * 3.5 * self.options['bits_per_char'] / self.options['baudrate']
|
||||
if ss - self.last[direction] >= gap:
|
||||
self.emit(parser.flush(), direction)
|
||||
self.last[direction] = es
|
||||
if not 0 <= payload[0] <= 255:
|
||||
self.emit(parser.flush(), direction)
|
||||
self.put(ss, es, self.out_ann, [1, ['Configure UART for 8 data bits']])
|
||||
return
|
||||
self.emit(parser.feed(payload[0], ss, es), direction)
|
||||
1
tools/dsview/decoders/set_can/__init__.py
Normal file
1
tools/dsview/decoders/set_can/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .pd import Decoder
|
||||
66
tools/dsview/decoders/set_can/pd.py
Normal file
66
tools/dsview/decoders/set_can/pd.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import sigrokdecode as srd
|
||||
from common.setgui_decoders.set_can import Reassembler, legacy
|
||||
|
||||
|
||||
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 __init__(self):
|
||||
self.reset()
|
||||
|
||||
# Состояние принадлежит одному запуску декодера. Сброс не должен
|
||||
# переносить незавершённый кадр или частоту из предыдущей записи.
|
||||
def reset(self):
|
||||
self.reassembler = Reassembler()
|
||||
self.samplerate = None
|
||||
|
||||
# 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)]])
|
||||
1
tools/dsview/decoders/set_uart/__init__.py
Normal file
1
tools/dsview/decoders/set_uart/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .pd import Decoder
|
||||
89
tools/dsview/decoders/set_uart/pd.py
Normal file
89
tools/dsview/decoders/set_uart/pd.py
Normal file
@@ -0,0 +1,89 @@
|
||||
import sigrokdecode as srd
|
||||
from common.setgui_decoders.set_uart import StreamParser
|
||||
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'set_uart'
|
||||
name = 'SET UART'
|
||||
longname = 'SET v2 / SETGUI v1 / ProtoCAN bridge'
|
||||
desc = 'SET frames, fields and CRC over UART.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['uart']
|
||||
outputs = ['set']
|
||||
tags = ['Embedded/industrial']
|
||||
options = (
|
||||
{'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},
|
||||
)
|
||||
annotations = (('frame', 'Frame'), ('payload', 'Payload'), ('error', 'Error'))
|
||||
annotation_rows = (('frames', 'Frames', (0,)), ('payload', 'Payload', (1,)), ('errors', 'Errors', (2,)))
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
# Состояние принадлежит одному запуску декодера. Сброс не должен
|
||||
# переносить незавершённый кадр или частоту из предыдущей записи.
|
||||
def reset(self):
|
||||
self.parsers = {}
|
||||
self.last = {}
|
||||
self.samplerate = None
|
||||
|
||||
# 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 emit(self, events, direction):
|
||||
for ss, es, frame, error in events:
|
||||
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:
|
||||
# Imported only at decode time: both packages are installed together.
|
||||
from common.setgui_decoders.set_can import legacy
|
||||
try:
|
||||
application = legacy(frame['can_id'], frame['payload'])
|
||||
frame['application'] = application
|
||||
self.put(ss, es, self.out_ann, [0, [application['summary']]])
|
||||
except ValueError as exc:
|
||||
self.put(ss, es, self.out_ann, [2, [str(exc)]])
|
||||
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 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 kind in ('INVALID STOPBIT', 'INVALID STARTBIT', 'PARITY ERROR', 'BREAK'):
|
||||
self.emit(parser.flush(), direction)
|
||||
self.put(ss, es, self.out_ann, [2, [kind]])
|
||||
return
|
||||
if kind != 'FRAME':
|
||||
return
|
||||
if not payload[1]:
|
||||
self.emit(parser.flush(), direction)
|
||||
self.put(ss, es, self.out_ann, [2, ['Invalid UART frame']])
|
||||
return
|
||||
gap = float(self.options['gap_ms'])
|
||||
if self.samplerate and gap > 0 and direction in self.last and ss - self.last[direction] > self.samplerate * gap / 1000:
|
||||
self.emit(parser.flush(), direction)
|
||||
self.last[direction] = es
|
||||
if not 0 <= payload[0] <= 255:
|
||||
self.emit(parser.flush(), direction)
|
||||
self.put(ss, es, self.out_ann, [2, ['Configure UART for 8 data bits']])
|
||||
return
|
||||
self.emit(parser.feed(payload[0], ss, es), direction)
|
||||
26
tools/dsview/decoders/transistor_pair/README.md
Normal file
26
tools/dsview/decoders/transistor_pair/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Transistor pair
|
||||
|
||||
Independent two-channel analyzer. Assign Vin1 and Vin2; Vstat is not used.
|
||||
Available in DSView's **Пользовательские** section as **Transistor pair**.
|
||||
|
||||
- Minimum active ON pulse duration (mintime), independently per input.
|
||||
- Minimum inactive OFF duration, independently per input.
|
||||
- Deadtime from Vin1 OFF to Vin2 ON and vice versa, with independent minimums.
|
||||
- Active overlap is a fault, regardless of the deadtime settings.
|
||||
- Period, frequency and active duty cycle for complete cycles on each input.
|
||||
- Independent active-high/active-low polarity for both inputs.
|
||||
|
||||
All limits are nanoseconds. Zero disables the corresponding lower limit;
|
||||
equality passes. Simultaneous handovers measure zero deadtime. These settings
|
||||
check measured pulses and do not debounce or filter the source waveform.
|
||||
|
||||
Measurements occupy separate waveform rows. Timing violations appear in the
|
||||
FAULT row. Unknown edges outside the capture are not fabricated; partial
|
||||
pulses do not produce full pulse widths or full-cycle frequency/duty values.
|
||||
|
||||
Python: `PairTimingChecker` in `logic_analyzer.decoders.transistor_pair`.
|
||||
Offline API: `analyze_capture(..., dict(mode='Transistor pair', channel=0,
|
||||
vin2_channel=1, vin1_mintime_ns=100, deadtime_12_ns=50))`.
|
||||
|
||||
For Vin/Vstat ACK checks and full driver-cycle OK/WARNING/FAULT results use
|
||||
the separate **IGBT 1SP/1SD** decoder.
|
||||
1
tools/dsview/decoders/transistor_pair/__init__.py
Normal file
1
tools/dsview/decoders/transistor_pair/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .pd import Decoder
|
||||
88
tools/dsview/decoders/transistor_pair/pd.py
Normal file
88
tools/dsview/decoders/transistor_pair/pd.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""Two-input transistor pair analyzer, independent of driver feedback."""
|
||||
import sigrokdecode as srd
|
||||
from common.setgui_decoders.transistor_pair import PairTimingChecker
|
||||
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'transistor_pair'
|
||||
name = 'Transistor pair'
|
||||
longname = 'Transistor pair: mintime / deadtime / overlap'
|
||||
desc = 'Checks ON/OFF pulse limits, deadtime, overlap, frequency and duty.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['logic']
|
||||
outputs = []
|
||||
tags = ['Clock/timing', 'Util']
|
||||
channels = (
|
||||
{'id': 'vin1', 'name': 'Vin1', 'desc': 'First transistor command'},
|
||||
{'id': 'vin2', 'name': 'Vin2', 'desc': 'Second transistor command'},
|
||||
)
|
||||
options = (
|
||||
{'id': 'vin1_active', 'desc': 'Vin1 active level', 'default': 'high', 'values': ('high', 'low')},
|
||||
{'id': 'vin2_active', 'desc': 'Vin2 active level', 'default': 'high', 'values': ('high', 'low')},
|
||||
{'id': 'vin1_mintime_ns', 'desc': 'Vin1 minimum ON time (ns)', 'default': 0},
|
||||
{'id': 'vin2_mintime_ns', 'desc': 'Vin2 minimum ON time (ns)', 'default': 0},
|
||||
{'id': 'vin1_minoff_ns', 'desc': 'Vin1 minimum OFF time (ns)', 'default': 0},
|
||||
{'id': 'vin2_minoff_ns', 'desc': 'Vin2 minimum OFF time (ns)', 'default': 0},
|
||||
{'id': 'deadtime_12_ns', 'desc': 'Minimum deadtime Vin1 -> Vin2 (ns)', 'default': 0},
|
||||
{'id': 'deadtime_21_ns', 'desc': 'Minimum deadtime Vin2 -> Vin1 (ns)', 'default': 0},
|
||||
)
|
||||
annotations = (
|
||||
('vin1-on', 'Vin1 ON duration'), ('vin2-on', 'Vin2 ON duration'),
|
||||
('deadtime', 'Deadtime'), ('failure', 'FAULT'),
|
||||
('vin1-off', 'Vin1 OFF duration'), ('vin2-off', 'Vin2 OFF duration'),
|
||||
('vin1-period', 'Vin1 period / frequency / duty'),
|
||||
('vin2-period', 'Vin2 period / frequency / duty'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('deadtime', 'Deadtime', (2,)),
|
||||
('faults', 'FAULT: overlap / timing', (3,)),
|
||||
('vin1', 'Vin1 mintime / OFF', (0, 4)),
|
||||
('vin2', 'Vin2 mintime / OFF', (1, 5)),
|
||||
('pwm1', 'Vin1 period / frequency / duty', (6,)),
|
||||
('pwm2', 'Vin2 period / frequency / duty', (7,)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.samplerate = None
|
||||
self.checker = None
|
||||
|
||||
def metadata(self, key, value):
|
||||
if key == srd.SRD_CONF_SAMPLERATE:
|
||||
self.samplerate = value
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
|
||||
def _put_events(self, events):
|
||||
for event in events:
|
||||
kind = event['kind']
|
||||
channel = event.get('channel', 1) - 1
|
||||
ann = (3 if kind in ('mintime_fail', 'deadtime_fail', 'off_fail', 'overlap') else
|
||||
2 if kind == 'deadtime' else channel if kind == 'mintime_ok' else
|
||||
4 + channel if kind == 'off_time' else 6 + channel)
|
||||
self.put(event['start'], event['end'], self.out_ann, [ann, [event['text'], event['short']]])
|
||||
|
||||
def decode(self):
|
||||
if not self.samplerate:
|
||||
raise ValueError('Cannot decode without samplerate.')
|
||||
if not self.has_channel(0) or not self.has_channel(1):
|
||||
raise ValueError('Connect both Vin1 and Vin2.')
|
||||
numeric = {name: self.options[name] for name in (
|
||||
'vin1_mintime_ns', 'vin2_mintime_ns', 'vin1_minoff_ns', 'vin2_minoff_ns',
|
||||
'deadtime_12_ns', 'deadtime_21_ns')}
|
||||
self.checker = PairTimingChecker(self.samplerate,
|
||||
self.options['vin1_active'] == 'high', self.options['vin2_active'] == 'high', **numeric)
|
||||
# Do not retain DSView's mutable native wait tuple.
|
||||
pins = list(self.wait({}))
|
||||
self._put_events(self.checker.update(self.samplenum, *pins))
|
||||
while True:
|
||||
pins = list(self.wait([{0: 'e'}, {1: 'e'}]))
|
||||
self._put_events(self.checker.update(self.samplenum, *pins))
|
||||
|
||||
def end(self):
|
||||
if self.checker is not None:
|
||||
self._put_events(self.checker.finish(self.samplenum))
|
||||
Reference in New Issue
Block a user