]>
Commit | Line | Data |
---|---|---|
92ee3b28 GS |
1 | ## |
2 | ## This file is part of the libsigrokdecode project. | |
3 | ## | |
4 | ## Copyright (C) 2016 Rudolf Reuter <reuterru@arcor.de> | |
5 | ## Copyright (C) 2017 Marcus Comstedt <marcus@mc.pp.se> | |
6 | ## Copyright (C) 2019 Gerhard Sittig <gerhard.sittig@gmx.net> | |
7 | ## | |
8 | ## This program is free software; you can redistribute it and/or modify | |
9 | ## it under the terms of the GNU General Public License as published by | |
10 | ## the Free Software Foundation; either version 2 of the License, or | |
11 | ## (at your option) any later version. | |
12 | ## | |
13 | ## This program is distributed in the hope that it will be useful, | |
14 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | ## GNU General Public License for more details. | |
17 | ## | |
18 | ## You should have received a copy of the GNU General Public License | |
19 | ## along with this program; if not, see <http://www.gnu.org/licenses/>. | |
20 | ## | |
21 | ||
22 | # This file was created from earlier implementations of the 'gpib' and | |
23 | # the 'iec' protocol decoders. It combines the parallel and the serial | |
24 | # transmission variants in a single instance with optional inputs for | |
25 | # maximum code re-use. | |
26 | ||
27 | # TODO | |
28 | # - Extend annotations for improved usability. | |
29 | # - Keep talkers' data streams on separate annotation rows? Is this useful | |
30 | # here at the GPIB level, or shall stacked decoders dispatch these? May | |
31 | # depend on how often captures get inspected which involve multiple peers. | |
32 | # - Make serial bit annotations optional? Could slow down interactive | |
33 | # exploration for long captures (see USB). | |
34 | # - Move the inlined Commodore IEC peripherals support to a stacked decoder | |
35 | # when more peripherals get added. | |
36 | # - SCPI over GPIB may "represent somewhat naturally" here already when | |
37 | # text lines are a single run of data at the GPIB layer (each line between | |
38 | # the address spec and either EOI or ATN). So a stacked SCPI decoder may | |
39 | # only become necessary when the text lines' content shall get inspected. | |
40 | ||
41 | import sigrokdecode as srd | |
42 | from common.srdhelper import bitpack | |
43 | ||
44 | ''' | |
45 | OUTPUT_PYTHON format for stacked decoders: | |
46 | ||
47 | General packet format: | |
48 | [<ptype>, <addr>, <pdata>] | |
49 | ||
50 | This is the list of <ptype>s and their respective <pdata> values: | |
51 | ||
52 | Raw bits and bytes at the physical transport level: | |
53 | - 'IEC_BIT': <addr> is not applicable, <pdata> is the transport's bit value. | |
54 | - 'GPIB_RAW': <addr> is not applicable, <pdata> is the transport's | |
55 | byte value. Data bytes are in the 0x00-0xff range, command/address | |
56 | bytes are in the 0x100-0x1ff range. | |
57 | ||
58 | GPIB level byte fields (commands, addresses, pieces of data): | |
59 | - 'COMMAND': <addr> is not applicable, <pdata> is the command's byte value. | |
60 | - 'LISTEN': <addr> is the listener address (0-30), <pdata> is the raw | |
61 | byte value (including the 0x20 offset). | |
62 | - 'TALK': <addr> is the talker address (0-30), <pdata> is the raw byte | |
63 | value (including the 0x40 offset). | |
64 | - 'SECONDARY': <addr> is the secondary address (0-31), <pdata> is the | |
65 | raw byte value (including the 0x60 offset). | |
66 | - 'MSB_SET': <addr> as well as <pdata> are the raw byte value (including | |
67 | the 0x80 offset). This usually does not happen for GPIB bytes with ATN | |
68 | active, but was observed with the IEC bus and Commodore floppy drives, | |
69 | when addressing channels within the device. | |
70 | - 'DATA_BYTE': <addr> is the talker address (when available), <pdata> | |
71 | is the raw data byte (transport layer, ATN inactive). | |
72 | ||
73 | Extracted payload information (peers and their communicated data): | |
74 | - 'TALK_LISTEN': <addr> is the current talker, <pdata> is the list of | |
75 | current listeners. These updates for the current "connected peers" | |
76 | are sent when the set of peers changes, i.e. after talkers/listeners | |
77 | got selected or deselected. Of course the data only covers what could | |
78 | be gathered from the input data. Some controllers may not explicitly | |
79 | address themselves, or captures may not include an early setup phase. | |
80 | - 'TALKER_BYTES': <addr> is the talker address (when available), <pdata> | |
81 | is the accumulated byte sequence between addressing a talker and EOI, | |
82 | or the next command/address. | |
83 | - 'TALKER_TEXT': <addr> is the talker address (when available), <pdata> | |
84 | is the accumulated text sequence between addressing a talker and EOI, | |
85 | or the next command/address. | |
86 | ''' | |
87 | ||
88 | class ChannelError(Exception): | |
89 | pass | |
90 | ||
91 | def _format_ann_texts(fmts, **args): | |
92 | if not fmts: | |
93 | return None | |
94 | return [fmt.format(**args) for fmt in fmts] | |
95 | ||
96 | _cmd_table = { | |
97 | # Command codes in the 0x00-0x1f range. | |
98 | 0x01: ['Go To Local', 'GTL'], | |
99 | 0x04: ['Selected Device Clear', 'SDC'], | |
100 | 0x05: ['Parallel Poll Configure', 'PPC'], | |
101 | 0x08: ['Global Execute Trigger', 'GET'], | |
102 | 0x09: ['Take Control', 'TCT'], | |
103 | 0x11: ['Local Lock Out', 'LLO'], | |
104 | 0x14: ['Device Clear', 'DCL'], | |
105 | 0x15: ['Parallel Poll Unconfigure', 'PPU'], | |
106 | 0x18: ['Serial Poll Enable', 'SPE'], | |
107 | 0x19: ['Serial Poll Disable', 'SPD'], | |
108 | # Unknown type of command. | |
109 | None: ['Unknown command 0x{cmd:02x}', 'command 0x{cmd:02x}', 'cmd {cmd:02x}', 'C{cmd_ord:c}'], | |
110 | # Special listener/talker "addresses" (deselecting previous peers). | |
111 | 0x3f: ['Unlisten', 'UNL'], | |
112 | 0x5f: ['Untalk', 'UNT'], | |
113 | } | |
114 | ||
115 | def _is_command(b): | |
116 | # Returns a tuple of booleans (or None when not applicable) whether | |
117 | # the raw GPIB byte is: a command, an un-listen, an un-talk command. | |
118 | if b in range(0x00, 0x20): | |
119 | return True, None, None | |
120 | if b in range(0x20, 0x40) and (b & 0x1f) == 31: | |
121 | return True, True, False | |
122 | if b in range(0x40, 0x60) and (b & 0x1f) == 31: | |
123 | return True, False, True | |
124 | return False, None, None | |
125 | ||
126 | def _is_listen_addr(b): | |
127 | if b in range(0x20, 0x40): | |
128 | return b & 0x1f | |
129 | return None | |
130 | ||
131 | def _is_talk_addr(b): | |
132 | if b in range(0x40, 0x60): | |
133 | return b & 0x1f | |
134 | return None | |
135 | ||
136 | def _is_secondary_addr(b): | |
137 | if b in range(0x60, 0x80): | |
138 | return b & 0x1f | |
139 | return None | |
140 | ||
141 | def _is_msb_set(b): | |
142 | if b & 0x80: | |
143 | return b | |
144 | return None | |
145 | ||
146 | def _get_raw_byte(b, atn): | |
147 | # "Decorate" raw byte values for stacked decoders. | |
08198247 | 148 | return b | 0x100 if atn else b |
92ee3b28 GS |
149 | |
150 | def _get_raw_text(b, atn): | |
151 | return ['{leader}{data:02x}'.format(leader = '/' if atn else '', data = b)] | |
152 | ||
153 | def _get_command_texts(b): | |
154 | fmts = _cmd_table.get(b, None) | |
155 | known = fmts is not None | |
156 | if not fmts: | |
157 | fmts = _cmd_table.get(None, None) | |
158 | if not fmts: | |
159 | return known, None | |
160 | return known, _format_ann_texts(fmts, cmd = b, cmd_ord = ord('0') + b) | |
161 | ||
162 | def _get_address_texts(b): | |
163 | laddr = _is_listen_addr(b) | |
164 | taddr = _is_talk_addr(b) | |
165 | saddr = _is_secondary_addr(b) | |
166 | msb = _is_msb_set(b) | |
167 | fmts = None | |
168 | if laddr is not None: | |
169 | fmts = ['Listen {addr:d}', 'L {addr:d}', 'L{addr_ord:c}'] | |
170 | addr = laddr | |
171 | elif taddr is not None: | |
172 | fmts = ['Talk {addr:d}', 'T {addr:d}', 'T{addr_ord:c}'] | |
173 | addr = taddr | |
174 | elif saddr is not None: | |
175 | fmts = ['Secondary {addr:d}', 'S {addr:d}', 'S{addr_ord:c}'] | |
176 | addr = saddr | |
177 | elif msb is not None: # For IEC bus compat. | |
178 | fmts = ['Secondary {addr:d}', 'S {addr:d}', 'S{addr_ord:c}'] | |
179 | addr = msb | |
180 | return _format_ann_texts(fmts, addr = addr, addr_ord = ord('0') + addr) | |
181 | ||
182 | def _get_data_text(b): | |
183 | # TODO Move the table of ASCII control characters to a common location? | |
184 | # TODO Move the "printable with escapes" logic to a common helper? | |
185 | _control_codes = { | |
186 | 0x00: 'NUL', | |
187 | 0x01: 'SOH', | |
188 | 0x02: 'STX', | |
189 | 0x03: 'ETX', | |
190 | 0x04: 'EOT', | |
191 | 0x05: 'ENQ', | |
192 | 0x06: 'ACK', | |
193 | 0x07: 'BEL', | |
194 | 0x08: 'BS', | |
195 | 0x09: 'TAB', | |
196 | 0x0a: 'LF', | |
197 | 0x0b: 'VT', | |
198 | 0x0c: 'FF', | |
199 | 0x0d: 'CR', | |
200 | 0x0e: 'SO', | |
201 | 0x0f: 'SI', | |
202 | 0x10: 'DLE', | |
203 | 0x11: 'DC1', | |
204 | 0x12: 'DC2', | |
205 | 0x13: 'DC3', | |
206 | 0x14: 'DC4', | |
207 | 0x15: 'NAK', | |
208 | 0x16: 'SYN', | |
209 | 0x17: 'ETB', | |
210 | 0x18: 'CAN', | |
211 | 0x19: 'EM', | |
212 | 0x1a: 'SUB', | |
213 | 0x1b: 'ESC', | |
214 | 0x1c: 'FS', | |
215 | 0x1d: 'GS', | |
216 | 0x1e: 'RS', | |
217 | 0x1f: 'US', | |
218 | } | |
219 | # Yes, exclude 0x7f (DEL) here. It's considered non-printable. | |
220 | if b in range(0x20, 0x7f) and b not in ('[', ']'): | |
221 | return '{:s}'.format(chr(b)) | |
222 | elif b in _control_codes: | |
223 | return '[{:s}]'.format(_control_codes[b]) | |
224 | # Use a compact yet readable and unambigous presentation for bytes | |
225 | # which contain non-printables. The format that is used here is | |
226 | # compatible with 93xx EEPROM and UART decoders. | |
227 | return '[{:02x}]'.format(b) | |
228 | ||
229 | ( | |
230 | PIN_DIO1, PIN_DIO2, PIN_DIO3, PIN_DIO4, | |
231 | PIN_DIO5, PIN_DIO6, PIN_DIO7, PIN_DIO8, | |
232 | PIN_EOI, PIN_DAV, PIN_NRFD, PIN_NDAC, | |
233 | PIN_IFC, PIN_SRQ, PIN_ATN, PIN_REN, | |
234 | PIN_CLK, | |
235 | ) = range(17) | |
236 | PIN_DATA = PIN_DIO1 | |
237 | ||
238 | ( | |
239 | ANN_RAW_BIT, ANN_RAW_BYTE, | |
240 | ANN_CMD, ANN_LADDR, ANN_TADDR, ANN_SADDR, ANN_DATA, | |
241 | ANN_EOI, | |
242 | ANN_TEXT, | |
243 | # TODO Want to provide one annotation class per talker address (0-30)? | |
244 | ANN_IEC_PERIPH, | |
245 | ANN_WARN, | |
246 | ) = range(11) | |
247 | ||
248 | ( | |
249 | BIN_RAW, | |
250 | BIN_DATA, | |
251 | # TODO Want to provide one binary annotation class per talker address (0-30)? | |
252 | ) = range(2) | |
253 | ||
254 | class Decoder(srd.Decoder): | |
255 | api_version = 3 | |
256 | id = 'ieee488' | |
257 | name = 'IEEE-488' | |
bca4fea3 | 258 | longname = 'IEEE-488 GPIB/HPIB/IEC' |
92ee3b28 GS |
259 | desc = 'IEEE-488 General Purpose Interface Bus (GPIB/HPIB or IEC).' |
260 | license = 'gplv2+' | |
261 | inputs = ['logic'] | |
262 | outputs = ['ieee488'] | |
263 | tags = ['PC', 'Retro computing'] | |
264 | channels = ( | |
265 | {'id': 'dio1' , 'name': 'DIO1/DATA', | |
266 | 'desc': 'Data I/O bit 1, or serial data'}, | |
267 | ) | |
268 | optional_channels = ( | |
269 | {'id': 'dio2' , 'name': 'DIO2', 'desc': 'Data I/O bit 2'}, | |
270 | {'id': 'dio3' , 'name': 'DIO3', 'desc': 'Data I/O bit 3'}, | |
271 | {'id': 'dio4' , 'name': 'DIO4', 'desc': 'Data I/O bit 4'}, | |
272 | {'id': 'dio5' , 'name': 'DIO5', 'desc': 'Data I/O bit 5'}, | |
273 | {'id': 'dio6' , 'name': 'DIO6', 'desc': 'Data I/O bit 6'}, | |
274 | {'id': 'dio7' , 'name': 'DIO7', 'desc': 'Data I/O bit 7'}, | |
275 | {'id': 'dio8' , 'name': 'DIO8', 'desc': 'Data I/O bit 8'}, | |
276 | {'id': 'eoi', 'name': 'EOI', 'desc': 'End or identify'}, | |
277 | {'id': 'dav', 'name': 'DAV', 'desc': 'Data valid'}, | |
278 | {'id': 'nrfd', 'name': 'NRFD', 'desc': 'Not ready for data'}, | |
279 | {'id': 'ndac', 'name': 'NDAC', 'desc': 'Not data accepted'}, | |
280 | {'id': 'ifc', 'name': 'IFC', 'desc': 'Interface clear'}, | |
281 | {'id': 'srq', 'name': 'SRQ', 'desc': 'Service request'}, | |
282 | {'id': 'atn', 'name': 'ATN', 'desc': 'Attention'}, | |
283 | {'id': 'ren', 'name': 'REN', 'desc': 'Remote enable'}, | |
284 | {'id': 'clk', 'name': 'CLK', 'desc': 'Serial clock'}, | |
285 | ) | |
286 | options = ( | |
287 | {'id': 'iec_periph', 'desc': 'Decode Commodore IEC bus peripherals details', | |
288 | 'default': 'no', 'values': ('no', 'yes')}, | |
962983f3 GS |
289 | {'id': 'delim', 'desc': 'Payload data delimiter', |
290 | 'default': 'eol', 'values': ('none', 'eol')}, | |
92ee3b28 GS |
291 | ) |
292 | annotations = ( | |
293 | ('bit', 'IEC bit'), | |
294 | ('raw', 'Raw byte'), | |
295 | ('cmd', 'Command'), | |
296 | ('laddr', 'Listener address'), | |
297 | ('taddr', 'Talker address'), | |
298 | ('saddr', 'Secondary address'), | |
299 | ('data', 'Data byte'), | |
300 | ('eoi', 'EOI'), | |
301 | ('text', 'Talker text'), | |
302 | ('periph', 'IEC bus peripherals'), | |
e144452b | 303 | ('warning', 'Warning'), |
92ee3b28 GS |
304 | ) |
305 | annotation_rows = ( | |
306 | ('bits', 'IEC bits', (ANN_RAW_BIT,)), | |
307 | ('raws', 'Raw bytes', (ANN_RAW_BYTE,)), | |
308 | ('gpib', 'Commands/data', (ANN_CMD, ANN_LADDR, ANN_TADDR, ANN_SADDR, ANN_DATA,)), | |
309 | ('eois', 'EOI', (ANN_EOI,)), | |
310 | ('texts', 'Talker texts', (ANN_TEXT,)), | |
311 | ('periphs', 'IEC peripherals', (ANN_IEC_PERIPH,)), | |
e144452b | 312 | ('warnings', 'Warnings', (ANN_WARN,)), |
92ee3b28 GS |
313 | ) |
314 | binary = ( | |
315 | ('raw', 'Raw bytes'), | |
316 | ('data', 'Talker bytes'), | |
317 | ) | |
318 | ||
319 | def __init__(self): | |
320 | self.reset() | |
321 | ||
322 | def reset(self): | |
323 | self.curr_raw = None | |
324 | self.curr_atn = None | |
325 | self.curr_eoi = None | |
326 | self.latch_atn = None | |
327 | self.latch_eoi = None | |
328 | self.accu_bytes = [] | |
329 | self.accu_text = [] | |
330 | self.ss_raw = None | |
331 | self.es_raw = None | |
332 | self.ss_eoi = None | |
333 | self.es_eoi = None | |
334 | self.ss_text = None | |
335 | self.es_text = None | |
336 | self.last_talker = None | |
337 | self.last_listener = [] | |
338 | self.last_iec_addr = None | |
339 | self.last_iec_sec = None | |
340 | ||
341 | def start(self): | |
342 | self.out_ann = self.register(srd.OUTPUT_ANN) | |
343 | self.out_bin = self.register(srd.OUTPUT_BINARY) | |
344 | self.out_python = self.register(srd.OUTPUT_PYTHON) | |
345 | ||
346 | def putg(self, ss, es, data): | |
347 | self.put(ss, es, self.out_ann, data) | |
348 | ||
349 | def putbin(self, ss, es, data): | |
350 | self.put(ss, es, self.out_bin, data) | |
351 | ||
352 | def putpy(self, ss, es, ptype, addr, pdata): | |
353 | self.put(ss, es, self.out_python, [ptype, addr, pdata]) | |
354 | ||
355 | def emit_eoi_ann(self, ss, es): | |
356 | self.putg(ss, es, [ANN_EOI, ['EOI']]) | |
357 | ||
358 | def emit_bin_ann(self, ss, es, ann_cls, data): | |
359 | self.putbin(ss, es, [ann_cls, bytes(data)]) | |
360 | ||
361 | def emit_data_ann(self, ss, es, ann_cls, data): | |
362 | self.putg(ss, es, [ann_cls, data]) | |
363 | ||
364 | def emit_warn_ann(self, ss, es, data): | |
365 | self.putg(ss, es, [ANN_WARN, data]) | |
366 | ||
367 | def flush_bytes_text_accu(self): | |
368 | if self.accu_bytes and self.ss_text is not None and self.es_text is not None: | |
369 | self.emit_bin_ann(self.ss_text, self.es_text, BIN_DATA, bytearray(self.accu_bytes)) | |
370 | self.putpy(self.ss_text, self.es_text, 'TALKER_BYTES', self.last_talker, bytearray(self.accu_bytes)) | |
371 | self.accu_bytes = [] | |
372 | if self.accu_text and self.ss_text is not None and self.es_text is not None: | |
373 | text = ''.join(self.accu_text) | |
374 | self.emit_data_ann(self.ss_text, self.es_text, ANN_TEXT, [text]) | |
375 | self.putpy(self.ss_text, self.es_text, 'TALKER_TEXT', self.last_talker, text) | |
376 | self.accu_text = [] | |
377 | self.ss_text = self.es_text = None | |
378 | ||
962983f3 GS |
379 | def check_extra_flush(self, b): |
380 | # Optionally flush previously accumulated runs of payload data | |
381 | # according to user specified conditions. | |
382 | if self.options['delim'] == 'none': | |
383 | return | |
384 | if not self.accu_bytes: | |
385 | return | |
386 | ||
387 | # This implementation exlusively handles "text lines", but adding | |
388 | # support for more variants here is straight forward. | |
389 | # | |
390 | # Search for the first data byte _after_ a user specified text | |
391 | # line termination sequence was seen. The termination sequence's | |
392 | # alphabet may be variable, and the sequence may span multiple | |
393 | # data bytes. We accept either CR or LF, and combine the CR+LF | |
394 | # sequence to strive for maximum length annotations for improved | |
395 | # readability at different zoom levels. It's acceptable that this | |
396 | # implementation would also combine multiple line terminations | |
397 | # like LF+LF. | |
398 | term_chars = (10, 13) | |
399 | is_eol = b in term_chars | |
400 | had_eol = self.accu_bytes[-1] in term_chars | |
401 | if had_eol and not is_eol: | |
402 | self.flush_bytes_text_accu() | |
403 | ||
92ee3b28 GS |
404 | def handle_ifc_change(self, ifc): |
405 | # Track IFC line for parallel input. | |
406 | # Assertion of IFC de-selects all talkers and listeners. | |
407 | if ifc: | |
408 | self.last_talker = None | |
409 | self.last_listener = [] | |
8e796681 | 410 | self.flush_bytes_text_accu() |
92ee3b28 GS |
411 | |
412 | def handle_eoi_change(self, eoi): | |
413 | # Track EOI line for parallel and serial input. | |
414 | if eoi: | |
415 | self.ss_eoi = self.samplenum | |
416 | self.curr_eoi = eoi | |
417 | else: | |
418 | self.es_eoi = self.samplenum | |
419 | if self.ss_eoi and self.latch_eoi: | |
420 | self.emit_eoi_ann(self.ss_eoi, self.es_eoi) | |
421 | self.es_text = self.es_eoi | |
422 | self.flush_bytes_text_accu() | |
423 | self.ss_eoi = self.es_eoi = None | |
424 | self.curr_eoi = None | |
425 | ||
426 | def handle_atn_change(self, atn): | |
427 | # Track ATN line for parallel and serial input. | |
428 | self.curr_atn = atn | |
429 | if atn: | |
430 | self.flush_bytes_text_accu() | |
431 | ||
432 | def handle_iec_periph(self, ss, es, addr, sec, data): | |
433 | # The annotation is optional. | |
434 | if self.options['iec_periph'] != 'yes': | |
435 | return | |
436 | # Void internal state. | |
437 | if addr is None and sec is None and data is None: | |
438 | self.last_iec_addr = None | |
439 | self.last_iec_sec = None | |
440 | return | |
441 | # Grab and evaluate new input. | |
442 | _iec_addr_names = { | |
443 | # TODO Add more items here. See the "Device numbering" section | |
444 | # of the https://en.wikipedia.org/wiki/Commodore_bus page. | |
445 | 8: 'Disk 0', | |
446 | 9: 'Disk 1', | |
447 | } | |
448 | _iec_disk_range = range(8, 16) | |
449 | if addr is not None: | |
450 | self.last_iec_addr = addr | |
451 | name = _iec_addr_names.get(addr, None) | |
452 | if name: | |
453 | self.emit_data_ann(ss, es, ANN_IEC_PERIPH, [name]) | |
454 | addr = self.last_iec_addr # Simplify subsequent logic. | |
455 | if sec is not None: | |
456 | # BEWARE! The secondary address is a full byte and includes | |
457 | # the 0x60 offset, to also work when the MSB was set. | |
458 | self.last_iec_sec = sec | |
459 | subcmd, channel = sec & 0xf0, sec & 0x0f | |
460 | channel_ord = ord('0') + channel | |
461 | if addr is not None and addr in _iec_disk_range: | |
462 | subcmd_fmts = { | |
463 | 0x60: ['Reopen {ch:d}', 'Re {ch:d}', 'R{ch_ord:c}'], | |
464 | 0xe0: ['Close {ch:d}', 'Cl {ch:d}', 'C{ch_ord:c}'], | |
465 | 0xf0: ['Open {ch:d}', 'Op {ch:d}', 'O{ch_ord:c}'], | |
466 | }.get(subcmd, None) | |
467 | if subcmd_fmts: | |
468 | texts = _format_ann_texts(subcmd_fmts, ch = channel, ch_ord = channel_ord) | |
469 | self.emit_data_ann(ss, es, ANN_IEC_PERIPH, texts) | |
470 | sec = self.last_iec_sec # Simplify subsequent logic. | |
471 | if data is not None: | |
472 | if addr is None or sec is None: | |
473 | return | |
474 | # TODO Process data depending on peripheral type and channel? | |
475 | ||
476 | def handle_data_byte(self): | |
962983f3 GS |
477 | if not self.curr_atn: |
478 | self.check_extra_flush(self.curr_raw) | |
92ee3b28 GS |
479 | b = self.curr_raw |
480 | texts = _get_raw_text(b, self.curr_atn) | |
481 | self.emit_data_ann(self.ss_raw, self.es_raw, ANN_RAW_BYTE, texts) | |
482 | self.emit_bin_ann(self.ss_raw, self.es_raw, BIN_RAW, b.to_bytes(1, byteorder='big')) | |
483 | self.putpy(self.ss_raw, self.es_raw, 'GPIB_RAW', None, _get_raw_byte(b, self.curr_atn)) | |
484 | if self.curr_atn: | |
485 | ann_cls = None | |
486 | upd_iec = False, | |
487 | py_type = None | |
488 | py_peers = False | |
489 | is_cmd, is_unl, is_unt = _is_command(b) | |
490 | laddr = _is_listen_addr(b) | |
491 | taddr = _is_talk_addr(b) | |
492 | saddr = _is_secondary_addr(b) | |
493 | msb = _is_msb_set(b) | |
494 | if is_cmd: | |
495 | known, texts = _get_command_texts(b) | |
496 | if not known: | |
497 | warn_texts = ['Unknown GPIB command', 'unknown', 'UNK'] | |
498 | self.emit_warn_ann(self.ss_raw, self.es_raw, warn_texts) | |
499 | ann_cls = ANN_CMD | |
500 | py_type, py_addr = 'COMMAND', None | |
501 | if is_unl: | |
502 | self.last_listener = [] | |
503 | py_peers = True | |
504 | if is_unt: | |
505 | self.last_talker = None | |
506 | py_peers = True | |
507 | if is_unl or is_unt: | |
508 | upd_iec = True, None, None, None | |
509 | elif laddr is not None: | |
510 | addr = laddr | |
511 | texts = _get_address_texts(b) | |
512 | ann_cls = ANN_LADDR | |
513 | py_type, py_addr = 'LISTEN', addr | |
514 | if addr == self.last_talker: | |
515 | self.last_talker = None | |
516 | self.last_listener.append(addr) | |
517 | upd_iec = True, addr, None, None | |
518 | py_peers = True | |
519 | elif taddr is not None: | |
520 | addr = taddr | |
521 | texts = _get_address_texts(b) | |
522 | ann_cls = ANN_TADDR | |
523 | py_type, py_addr = 'TALK', addr | |
524 | if addr in self.last_listener: | |
525 | self.last_listener.remove(addr) | |
526 | self.last_talker = addr | |
527 | upd_iec = True, addr, None, None | |
528 | py_peers = True | |
529 | elif saddr is not None: | |
530 | addr = saddr | |
531 | texts = _get_address_texts(b) | |
532 | ann_cls = ANN_SADDR | |
533 | upd_iec = True, None, b, None | |
534 | py_type, py_addr = 'SECONDARY', addr | |
535 | elif msb is not None: | |
536 | # These are not really "secondary addresses", but they | |
537 | # are used by the Commodore IEC bus (floppy channels). | |
538 | texts = _get_address_texts(b) | |
539 | ann_cls = ANN_SADDR | |
540 | upd_iec = True, None, b, None | |
541 | py_type, py_addr = 'MSB_SET', b | |
542 | if ann_cls is not None and texts is not None: | |
543 | self.emit_data_ann(self.ss_raw, self.es_raw, ann_cls, texts) | |
544 | if upd_iec[0]: | |
545 | self.handle_iec_periph(self.ss_raw, self.es_raw, upd_iec[1], upd_iec[2], upd_iec[3]) | |
546 | if py_type: | |
547 | self.putpy(self.ss_raw, self.es_raw, py_type, py_addr, b) | |
548 | if py_peers: | |
549 | self.last_listener.sort() | |
550 | self.putpy(self.ss_raw, self.es_raw, 'TALK_LISTEN', self.last_talker, self.last_listener) | |
551 | else: | |
552 | self.accu_bytes.append(b) | |
553 | text = _get_data_text(b) | |
554 | if not self.accu_text: | |
555 | self.ss_text = self.ss_raw | |
556 | self.accu_text.append(text) | |
557 | self.es_text = self.es_raw | |
558 | self.emit_data_ann(self.ss_raw, self.es_raw, ANN_DATA, [text]) | |
559 | self.handle_iec_periph(self.ss_raw, self.es_raw, None, None, b) | |
560 | self.putpy(self.ss_raw, self.es_raw, 'DATA_BYTE', self.last_talker, b) | |
561 | ||
562 | def handle_dav_change(self, dav, data): | |
563 | if dav: | |
564 | # Data availability starts when the flag goes active. | |
565 | self.ss_raw = self.samplenum | |
566 | self.curr_raw = bitpack(data) | |
567 | self.latch_atn = self.curr_atn | |
568 | self.latch_eoi = self.curr_eoi | |
569 | return | |
570 | # Data availability ends when the flag goes inactive. Handle the | |
571 | # previously captured data byte according to associated flags. | |
572 | self.es_raw = self.samplenum | |
573 | self.handle_data_byte() | |
574 | self.ss_raw = self.es_raw = None | |
575 | self.curr_raw = None | |
576 | ||
577 | def inject_dav_phase(self, ss, es, data): | |
578 | # Inspection of serial input has resulted in one raw byte which | |
579 | # spans a given period of time. Pretend we had seen a DAV active | |
580 | # phase, to re-use code for the parallel transmission. | |
581 | self.ss_raw = ss | |
582 | self.curr_raw = bitpack(data) | |
583 | self.latch_atn = self.curr_atn | |
584 | self.latch_eoi = self.curr_eoi | |
585 | self.es_raw = es | |
586 | self.handle_data_byte() | |
587 | self.ss_raw = self.es_raw = None | |
588 | self.curr_raw = None | |
589 | ||
590 | def invert_pins(self, pins): | |
591 | # All lines (including data bits!) are low active and thus need | |
592 | # to get inverted to receive their logical state (high active, | |
593 | # regular data bit values). Cope with inputs being optional. | |
594 | return [1 - p if p in (0, 1) else p for p in pins] | |
595 | ||
596 | def decode_serial(self, has_clk, has_data_1, has_atn, has_srq): | |
597 | if not has_clk or not has_data_1 or not has_atn: | |
598 | raise ChannelError('IEC bus needs at least ATN and serial CLK and DATA.') | |
599 | ||
600 | # This is a rephrased version of decoders/iec/pd.py:decode(). | |
601 | # SRQ was not used there either. Magic numbers were eliminated. | |
602 | ( | |
603 | STEP_WAIT_READY_TO_SEND, | |
604 | STEP_WAIT_READY_FOR_DATA, | |
605 | STEP_PREP_DATA_TEST_EOI, | |
606 | STEP_CLOCK_DATA_BITS, | |
607 | ) = range(4) | |
608 | step_wait_conds = ( | |
609 | [{PIN_ATN: 'f'}, {PIN_DATA: 'l', PIN_CLK: 'h'}], | |
610 | [{PIN_ATN: 'f'}, {PIN_DATA: 'h', PIN_CLK: 'h'}, {PIN_CLK: 'l'}], | |
611 | [{PIN_ATN: 'f'}, {PIN_DATA: 'f'}, {PIN_CLK: 'l'}], | |
612 | [{PIN_ATN: 'f'}, {PIN_CLK: 'e'}], | |
613 | ) | |
614 | step = STEP_WAIT_READY_TO_SEND | |
615 | bits = [] | |
616 | ||
617 | while True: | |
618 | ||
619 | # Sample input pin values. Keep DATA/CLK in verbatim form to | |
620 | # re-use 'iec' decoder logic. Turn ATN to positive logic for | |
621 | # easier processing. The data bits get handled during byte | |
622 | # accumulation. | |
623 | pins = self.wait(step_wait_conds[step]) | |
624 | data, clk = pins[PIN_DATA], pins[PIN_CLK] | |
625 | atn, = self.invert_pins([pins[PIN_ATN]]) | |
626 | ||
627 | if self.matched[0]: | |
628 | # Falling edge on ATN, reset step. | |
629 | step = STEP_WAIT_READY_TO_SEND | |
630 | ||
631 | if step == STEP_WAIT_READY_TO_SEND: | |
632 | # Don't use self.matched[1] here since we might come from | |
633 | # a step with different conds due to the code above. | |
634 | if data == 0 and clk == 1: | |
635 | # Rising edge on CLK while DATA is low: Ready to send. | |
636 | step = STEP_WAIT_READY_FOR_DATA | |
637 | elif step == STEP_WAIT_READY_FOR_DATA: | |
638 | if data == 1 and clk == 1: | |
639 | # Rising edge on DATA while CLK is high: Ready for data. | |
640 | ss_byte = self.samplenum | |
641 | self.handle_atn_change(atn) | |
642 | if self.curr_eoi: | |
643 | self.handle_eoi_change(False) | |
644 | bits = [] | |
645 | step = STEP_PREP_DATA_TEST_EOI | |
646 | elif clk == 0: | |
647 | # CLK low again, transfer aborted. | |
648 | step = STEP_WAIT_READY_TO_SEND | |
649 | elif step == STEP_PREP_DATA_TEST_EOI: | |
650 | if data == 0 and clk == 1: | |
651 | # DATA goes low while CLK is still high, EOI confirmed. | |
652 | self.handle_eoi_change(True) | |
653 | elif clk == 0: | |
654 | step = STEP_CLOCK_DATA_BITS | |
655 | ss_bit = self.samplenum | |
656 | elif step == STEP_CLOCK_DATA_BITS: | |
657 | if self.matched[1]: | |
658 | if clk == 1: | |
659 | # Rising edge on CLK; latch DATA. | |
660 | bits.append(data) | |
661 | elif clk == 0: | |
662 | # Falling edge on CLK; end of bit. | |
663 | es_bit = self.samplenum | |
664 | self.emit_data_ann(ss_bit, es_bit, ANN_RAW_BIT, ['{:d}'.format(bits[-1])]) | |
665 | self.putpy(ss_bit, es_bit, 'IEC_BIT', None, bits[-1]) | |
666 | ss_bit = self.samplenum | |
667 | if len(bits) == 8: | |
668 | es_byte = self.samplenum | |
669 | self.inject_dav_phase(ss_byte, es_byte, bits) | |
670 | if self.curr_eoi: | |
671 | self.handle_eoi_change(False) | |
672 | step = STEP_WAIT_READY_TO_SEND | |
673 | ||
674 | def decode_parallel(self, has_data_n, has_dav, has_atn, has_eoi, has_srq): | |
675 | ||
676 | if False in has_data_n or not has_dav or not has_atn: | |
677 | raise ChannelError('IEEE-488 needs at least ATN and DAV and eight DIO lines.') | |
678 | has_ifc = self.has_channel(PIN_IFC) | |
679 | ||
680 | # Capture data lines at the falling edge of DAV, process their | |
681 | # values at rising DAV edge (when data validity ends). Also make | |
682 | # sure to start inspection when the capture happens to start with | |
683 | # low signal levels, i.e. won't include the initial falling edge. | |
684 | # Scan for ATN/EOI edges as well (including the trick which works | |
685 | # around initial pin state). | |
686 | # Map low-active physical transport lines to positive logic here, | |
687 | # to simplify logical inspection/decoding of communicated data, | |
688 | # and to avoid redundancy and inconsistency in later code paths. | |
689 | waitcond = [] | |
690 | idx_dav = len(waitcond) | |
691 | waitcond.append({PIN_DAV: 'l'}) | |
692 | idx_atn = len(waitcond) | |
693 | waitcond.append({PIN_ATN: 'l'}) | |
694 | idx_eoi = None | |
695 | if has_eoi: | |
696 | idx_eoi = len(waitcond) | |
697 | waitcond.append({PIN_EOI: 'l'}) | |
698 | idx_ifc = None | |
699 | if has_ifc: | |
700 | idx_ifc = len(waitcond) | |
701 | waitcond.append({PIN_IFC: 'l'}) | |
702 | while True: | |
703 | pins = self.wait(waitcond) | |
704 | pins = self.invert_pins(pins) | |
705 | ||
706 | # BEWARE! Order of evaluation does matter. For low samplerate | |
707 | # captures, many edges fall onto the same sample number. So | |
708 | # we process active edges of flags early (before processing | |
709 | # data bits), and inactive edges late (after data got processed). | |
710 | if idx_ifc is not None and self.matched[idx_ifc] and pins[PIN_IFC] == 1: | |
711 | self.handle_ifc_change(pins[PIN_IFC]) | |
712 | if idx_eoi is not None and self.matched[idx_eoi] and pins[PIN_EOI] == 1: | |
713 | self.handle_eoi_change(pins[PIN_EOI]) | |
714 | if self.matched[idx_atn] and pins[PIN_ATN] == 1: | |
715 | self.handle_atn_change(pins[PIN_ATN]) | |
716 | if self.matched[idx_dav]: | |
717 | self.handle_dav_change(pins[PIN_DAV], pins[PIN_DIO1:PIN_DIO8 + 1]) | |
718 | if self.matched[idx_atn] and pins[PIN_ATN] == 0: | |
719 | self.handle_atn_change(pins[PIN_ATN]) | |
720 | if idx_eoi is not None and self.matched[idx_eoi] and pins[PIN_EOI] == 0: | |
721 | self.handle_eoi_change(pins[PIN_EOI]) | |
722 | if idx_ifc is not None and self.matched[idx_ifc] and pins[PIN_IFC] == 0: | |
723 | self.handle_ifc_change(pins[PIN_IFC]) | |
724 | ||
725 | waitcond[idx_dav][PIN_DAV] = 'e' | |
726 | waitcond[idx_atn][PIN_ATN] = 'e' | |
727 | if has_eoi: | |
728 | waitcond[idx_eoi][PIN_EOI] = 'e' | |
729 | if has_ifc: | |
730 | waitcond[idx_ifc][PIN_IFC] = 'e' | |
731 | ||
732 | def decode(self): | |
733 | # The decoder's boilerplate declares some of the input signals as | |
734 | # optional, but only to support both serial and parallel variants. | |
735 | # The CLK signal discriminates the two. For either variant some | |
736 | # of the "optional" signals are not really optional for proper | |
737 | # operation of the decoder. Check these conditions here. | |
738 | has_clk = self.has_channel(PIN_CLK) | |
739 | has_data_1 = self.has_channel(PIN_DIO1) | |
740 | has_data_n = [bool(self.has_channel(pin) for pin in range(PIN_DIO1, PIN_DIO8 + 1))] | |
741 | has_dav = self.has_channel(PIN_DAV) | |
742 | has_atn = self.has_channel(PIN_ATN) | |
743 | has_eoi = self.has_channel(PIN_EOI) | |
744 | has_srq = self.has_channel(PIN_SRQ) | |
745 | if has_clk: | |
746 | self.decode_serial(has_clk, has_data_1, has_atn, has_srq) | |
747 | else: | |
748 | self.decode_parallel(has_data_n, has_dav, has_atn, has_eoi, has_srq) |