Repository navigation
Added
-
Protocol decoding:
scope.decodeand five MCP tools. The fourBUS<n>
decoders and their nine protocols - Parallel, RS232, SPI, I2C, I2S, LIN, CAN,
FlexRay, MIL-STD-1553 - are now typed, withget_decode,set_decode,
read_decode_eventsand a threshold get/set pair. There is noDECodecommand in
this firmware: it calls the subsystemBUS<n>and tags all 134 of its commands
decode<n>. The four buses' definitions are byte-identical apart from that tag, so
the tables are written once.Six things about it are not what the programming guide says, all measured on an
MSO5074 (firmware 00.01.03.03.00):SPI:MISO:POLarityandSPI:MOSI:POLarityare one setting, sharing command
id 8517: writing MISO left MOSI reading the new value. LikewiseBAUDand
BUSerper protocol. Both are published as a single field, since two fields that
silently overwrite each other are worse than one.FORMat,EVENt:FORMatandEVENt:VIEWchange only the screen. With all
three set toDEC,BUS1:DATA?still answered in hex, andEVENt:VIEW PAYLoad
returned the same columns asPACKets.- A protocol must keep one source live: with the partner already
OFF, both
BUS2:RS232:TX OFFandBUS3:SPI:MISO:SOURce OFFare refused-200. So a patch
moving a protocol from one line to the other is legal as a whole and illegal in
declaration order, and the write switching a source on is now reordered ahead of
the one switching its partner off - the same treatment the trigger's lower/upper
level pairs get. - The parallel per-bit assignment needs the
USERpreset: under a fixed preset
PARallel:SOURceis refused-200rather than ignored, whileBITXis accepted. - The three event-table settings -
EVENt,EVENt:FORMatandEVENt:VIEW- are
instrument-wide, not per bus, one setting each behind four command paths.
GivingBUS1:EVENt:VIEWPACKetsandBUS2:EVENt:VIEWPAYLoadleft both
readingPAYLoad;BUS1:EVENt ONwithBUS2:EVENt OFFleft both off. They stay
onDecodeStatebecause reading any bus yields the true shared value, but two
buses can never disagree and setting one moves them all.FORMat,LABel,
POSitionandDISPlayare genuinely per bus, checked the same way. EVENt:VIEWis only writable while the event table is on. With it off the
write is discarded in both directions with an empty error queue - sixteen trials
across SPI, RS232, I2C and LIN, no disagreement. SoEVENtis now written before
the view it gates, and a view that could not land is refused up front rather than
silently dropped. This is the defect that made a post-*RSTrebuild quietly come
back withPACKetsafter asking forPAYLoad.
-
read_decode_events/Decode.events.BUS<n>:DATA?is parsed into typed
rows. Its label isDECODE<n>- the bus number, not the decode type the guide's
example shows - so nothing in the payload says which protocol produced it and the
mode is read separately. Values are always hexadecimal, and the columns are
protocol-specific and taken from the reply's own header rather than a table here:
SPI answersMISO/MISOErr/MOSI/MOSIErr, RS232Tx/Rx/Data/Err, I2C
W/R/Addr/Data/ACK. Recorded payloads for three protocols are goldens, so the
parser needs no instrument.The query does not fail cleanly - it stays silent for the whole timeout and only
then queues-220- so three measured causes are turned into[state]errors: an
undisplayed bus (checked first, one query), a decoder that has produced nothing at
all (a SPI bus with an idle clock refused; pointing it at a real clock made the same
query answer 16 951 bytes), and modePARallel, which refused unconditionally even
displayed, with its width set and reading a real 1 kHz clock. -
Digital waveform capture:
acquire_logicandcapture_logic. Digital lines can
now be read as waveforms, one retained capture per line holding its own 0/1 samples -
sofind_waveform_edgesat threshold 0.5 gives a line's transition timestamps and a
summary's mean is its duty cycle. The waveform store and every retained-read tool
took them unchanged.TRACereturns two bytes per sample, one per pod, not the one-byte-per-pod-group
the guide describes:preamble.pointsis twice the sample count and the selected
source changes nothing, withD0,D1andD8returning identical blocks. So one
transfer carries every line, and asking for eight costs one transfer rather than
eight. Reading it as one byte per sample made every duty come out at exactly half its
NORMalvalue, which is how the layout was found:D4is held high throughout and
read 0.5, and reads 1.0 after de-interleaving.RAWis refused for digital sources, because it kills the instrument:WAVeform: DATA?in RAW mode with a digital source took down the VXI-11 service twice, at 20 MPt
and at 125 points, each time needing a physical power cycle. Not a size effect, and
the preamble answers normally first, so it cannot be discovered by asking.A
TRACeblock often does not span the displayed window, which is the one way a
logic read differs from an analog one in what the data means. The instrument caps it
at 500,000 samples - 1,000,000 bytes - while leavingx_incrementat the analog
record's sample interval, so a window needingNsamples comes back covering
min(500_000, N) / Nof it: a prefix that starts atx_originand stops early, with
nothing in the reply admitting it. Measured on a stopped MSO5074 atAUTOmemory
depth, against an analog read of the same window:window analog span/window logic span/window 100 ns - 50 us 1.000 1.000 100 us 1.000 0.625 1 ms 1.000 0.500 10 ms 1.000 0.500 100 ms 1.000 0.500 The
0.625row is the one that says what is happening rather than merely that
something is: analog returned 800,000 samples for that window and logic 500,000, and
500000/800000 is exactly 0.625. A block spanning the whole window at twice the
reportedx_increment- the other reading of a byte-countedpoints, and the one
that would mean every capped logic timestamp is half its true value - gives 1.000 on
every row, so it is excluded by measurement rather than by argument. Two further
checks pin it: sweeping memory depth 10 kPt to 10 MPt at a fixed timebase left
coverage flat at 0.500 andx_incrementflat at 5 ns while the rate moved 2 MSa/s to
2 GSa/s, sox_incrementis a decimated display interval rather than the acquisition
one; and against a 33-byte 115200 8N1 message triggered on its first byte in a 5 s
window, the 2.865 ms burst sat at sample 20,000 and spanned 567 samples (2.835 ms),
where twice the interval puts it at sample 10,000, which measured idle.The missing span is not fetchable -
WAVeform:STARtpastpointswraps to the start
of the block and returns the same bytes, and asking for twicepointsis refused
-200- soacquire_logicreads the timebase once per transfer and reports a short
block as a scope warning naming the fraction it holds. Half a record does not look
like an error; it looks like a signal that stops. -
Logic analyzer channel groups.
Digital.define_group,delete_group,
set_group_display,group_displayedandautosort, published as
configure_digital_groupandautosort_digital. Group membership is not readable -
LA:GROup:APPendis write-only and the aliases that would read it declare the
unresolvable origin:LA::GROup- andAPPendaccumulates: appendingD5,D6to
GROup1and displaying it switched onD5,D6andD7. So membership is
stated rather than added to, and the delete-then-append that makes it deterministic
happens inside the call.LA:AUTosortis an ENUM of"0"/"1"rather than the BOOL
it reads as, so a Python bool is mapped here rather than refused by the formatter.Also measured: displaying a group cascades to its members, deleting one ungroups
without hiding, andLA:DISPlay?answers immediately for lines, groups and pods
alike - unlikeLA:DIGital:POSition?, so it needs no hang avoidance. -
DigitalState.deskew_s, read-only.LA:TCALibrateiscalibrationrisk, so a
typed setter would reach it without the permission that gatesscpi_execute, making
the risk policy depend on which tool you pick. The query is not classified at all.Three more things about the analyzer are documented rather than fixed, because
nothing here can fix them:- A line's own
enabledflag is not gated by the master switch, so it does not
mean the line is on screen. After*RSTthe master switch is off while all sixteen
lines reportenabledtrue and answer the position query. A caller has to read
both, and there is no query for the conjunction. line_positioninserts rather than swaps: writing a position another line
holds pushes that line and everything above it up one slot, so movingD3to 0
whileD0-D2sat at 0-2 left them at 1-3. Reordering is a sequence of
insertions; a loop assigning each line its target will not converge.- Whether labels are drawn cannot be reached over SCPI at all. The
LA:
subsystem has no such command, and the one switch describing itself as "the display
of all the labels",REFerence:LABel:ENABle, is the analog and reference one -
measured reading 0 while the logic labels were on screen. So a reset instrument
stores every label written and drawsD0-D15instead, with the read-back
agreeing with the write and the screen disagreeing with both. Only the front panel
(LA -> Label -> Display) reaches it, which the MCP server's raw UI lane can drive.
- A line's own
Changed
-
TRACeon a running scope is documented as returning a partial window. It has
always been described as "the displayed window at acquisition resolution", and on a
stopped scope it is: an analog read spanned the window exactly at every timebase
from 10 us/div to 50 ms/div. Running, the same reads came back as short prefixes -
10% of the window from 100 us/div through 10 ms/div, 20% at 50 ms/div - because a
sweep that has not finished has not been drawn, and this mode reads what is drawn.
The preamble describes the prefix truthfully, so nothing is wrong with the record;
it just is not the whole window, and nothing said so. -
A command that times out is now diagnosed rather than guessed at. This
instrument answers a message it rejects with an error-queue entry and no reply,
so a refused command used to cost the full VISA timeout and was then reported as
[timeout] ... the scope is not triggering- the wrong cause, pointing at the
wrong fix. The error queue is now read once after any timed-out command, which
the instrument itself answers definitively: entries mean the command was refused
and it is reported as[command], naming the entries and the command, while an
empty queue is reported as[timeout]as before and now says the queue was
empty, which is positive evidence for the acquisition explanation rather than the
absence of any. Verified on an MSO5074 (firmware 01.03.03.00):BUS1:DATA?, a
query whose firmware definition understates its parameters, now reports
-220,"Lack of paramater"instead of blaming the trigger.ScopeTimeoutErroris correspondingly narrower - it now asserts that the scope
had no complaint - so a caller that treats every timeout as "not acquiring" keeps
being right, and one that used to see a timeout for a refused command now sees a
ScopeCommandErrorcarryingerrorsandcommand.The read costs one round trip, only on a path that has already paid for a
timeout, and it leaves the queue genuinely empty afterwards - so the following
checked write no longer needs a pre-drain to avoid inheriting the blame.That refusal is now its own type,
ScopeSilentCommandError, a subclass of
ScopeCommandErrorso every existing handler keeps catching it. Two callers need
the distinction.measure()degrades it per request like the timeout it replaced,
reportingavailabilityrefusedwith the queue entries in the note - without
it, one refused request discarded every result the batch had already collected. And
the MCP setter envelope raises it instead of reportingok=false: it can come from
a setter's read-back query rather than from any write, and by then each write's own
error queue has already been checked, so reporting it as a refused write blamed the
wrong command and dropped the explanation the envelope has no field for. The
write-only envelope behindrun,stop,single, the raw-UI tools and
clear_measurement_itemsstill reports it asok=false, because those operations
have no read-back to have gone silent and drain the queue before writing, so the
entries are theirs.availabilitygainsrefusedalongsidetimeout. Both are silence on the wire,
separated by the error queue afterwards, and the fixes are opposite:timeoutmeans
provide a signal,refusedmeans ask for something else. -
Waveform transfers are about ten times faster, because nothing was setting
pyvisa's read buffer. It defaults to 20 KiB and pyvisa loops until the reply
ends, so the read count was set by the buffer rather than by the reply: a
250,000-point window cost ~13 reads through the whole pyvisa stack and a 20 MPt
capture ~1000. The default is now 1 MiB. Measured through
Scope.waveform.inspecton an MSO5074, before and after: a 4 MPtRAWread went
from 9.69 s to 1.01 s and a 1 MPtTRACeread from 6.17 s to 1.01 s, so the
full-depth 20 MPt transfer that took 48.5 s takes about five seconds.What made those iterations expensive is specific to the message transports:
VXI-11 serves each one with adevice_readRPC, so a 20 KiB buffer means ~50
network round trips per megabyte. A byte stream is indifferent - measured over a
raw socket, a static 200,000-byte block took 0.045 s at 20 KiB and 0.049 s at
1 MiB, every read byte-identical - so the setting is applied to both framings for
uniformity rather than because it helps there.Neither thing that could make a large buffer cost something does. A short reply is
unaffected (13.9 ms perCHANnel1:SCALe?at 20 KiB against 12.9 ms at 8 MiB,
because VXI-11 answers on end-of-message rather than on a full buffer), and peak
RSS was 51.2 MiB at every combination tried. It is a property of the link rather
than of the instrument, so it is configurable:chunk_bytes=onScope.connect
andScopeSession.create,--chunk-bytes/PYMSO5000_CHUNK_BYTESon the
server. -
The waveform transfer window is 1,000,000 points, and is a granularity knob
rather than the instrument limit it was documented as. It was 250,000 on the
stated grounds that this is "the documented per-transfer maximum for
:WAVeform:DATA?in RAW mode" and that asking for more returns a short block.
Neither is true of an MSO5000: its programming guide gives the range of
:WAVeform:STARtand:WAVeform:STOPas "RAW: 1 to the current maximum memory
depth", and its own worked example reads 120,000 points in one transfer. 250,000
is the DS1000Z series' limit. Measured on an MSO5074, single transfers of 250,001
through the full 20,000,000-point memory depth each returned exactly the count
requested with an empty error queue and statistics identical to the windowed read.What actually bounds the window is that progress callbacks and cancellation checks
happen once per window. 1,000,000 keeps 20 checkpoints on a 20 MPt capture and 200
at this instrument's deepest. The latter was checked with only CH1 enabled: a full
200,000,000-pointRAWinspection returned the exact count through all 200 windows
in 40.07 s with no restoration warnings.TRACeis the exception: an MSO5074
reports exactly 1,000,000TRACepoints, so that read is a single window and cannot
be cancelled mid-transfer - buying a checkpoint by halving the window would cost
more time than it could save, since the same read took 0.66 s in one window and
1.29 s in two.capture_waveformandinspect_waveformadvertise the new cost, and
PROGRESS_INTERVAL_Snow genuinely throttles progress notifications where before
it suppressed almost nothing.