Decoding the GPIB Status Byte and Serial Poll Errors
A GPIB instrument signals every asynchronous condition — a completed sweep, a full output buffer, a latched command error — through a single 8-bit Status Byte returned by a serial poll, and reading those bits wrong sends a control loop chasing the wrong fault. This guide decodes the IEEE 488.1/488.2 Status Byte bit by bit, follows the summary chain into the Standard Event Status Register, and maps each bit to a recovery branch, extending the numeric-code work in the parent Error Code Categorization collection from the Serial, USB, and GPIB Communication Workflows reference.
Scope: Status-Byte Bits, Not Error-Queue Text
This page is deliberately about the hardware status register, not the ASCII error queue. Its sibling, Categorizing SCPI error codes for automated recovery, begins once you already hold a SYST:ERR? string; this one begins one layer earlier, at the raw byte a serial poll returns before any text query is issued. The two meet at exactly one bit: the Status Byte tells you whether an error exists and which register summarised it, and only then do you spend a slow text query to read the code. Polling the byte first is what keeps a fast acquisition loop from issuing SYST:ERR? on every cycle.
The technique assumes an IEEE 488.2 instrument reached over GPIB (or USB-TMC, which carries the same status model) through a VISA Resource Manager session, using pyvisa>=1.13 on Python 3.10+. A serial poll is a bus-level operation — the controller asserts ATN and reads one byte directly, no command string sent — so it works even when the instrument’s input parser is wedged and a text query would time out. That property is exactly why the Status Byte is the fault channel of last resort, and why decoding it correctly matters more than any other read on the bus.
Mechanism: The Status Byte, SRE Mask, and the ESR Summary Chain
The Status Byte Register (STB) packs eight condition summaries into one byte. Bit 6 is special: on a serial poll it reads as RQS (Requested Service, a latch set when the instrument asserted SRQ), while the same bit read via *STB? reads as MSS (Master Summary Status, the live OR of all enabled summary bits). Bits 4 and 5 are fixed by 488.2 as MAV (Message Available) and ESB (Event Status Bit summary); the remaining bits 0–3 and 7 are device-defined, and SCPI conventionally assigns bit 3 to the Questionable summary, bit 2 to the error/event queue, and bit 7 to the Operation summary.
Decoding is a pure bit test — the set of raised conditions is the set of bit positions whose mask is present in the byte:
The service-request logic is a masked reduction. The Service Request Enable register (SRE) selects which summary bits are allowed to pull SRQ; bit 6 is not maskable and is excluded. MSS — and therefore the RQS latch sampled when SRQ fired — is the logical OR of the enabled bits:
The consequence is that RQS and MSS are the same physical bit read two different ways: a serial poll returns RQS and clears it as a side effect, whereas *STB? returns MSS and leaves it untouched. Treating them as interchangeable is the single most common decoding bug on GPIB, and the failure modes below return to it.
The ESB bit at position 5 is not a leaf — it is a summary of the Standard Event Status Register (ESR), read with *ESR?. The ESR carries the genuinely diagnostic bits: PON (power-on), CME (command error), EXE (execution error), DDE (device-dependent error), QYE (query error), and OPC (operation complete). So a raised ESB is an instruction: go read *ESR? to learn what kind of event occurred. The bit-to-action map below is the whole decoding contract on one page.
| Bit | Mask | Mnemonic / meaning | Recovery action |
|---|---|---|---|
| 7 | 0x80 |
Device-defined (SCPI: Operation Status summary) | Read STAT:OPER? if enabled; usually informational |
| 6 | 0x40 |
RQS (serial poll) / MSS (*STB?) — service requested |
Dispatch SRQ handler; RQS auto-clears on this poll |
| 5 | 0x20 |
ESB — Standard Event summary | Read *ESR? and classify the event bits |
| 4 | 0x10 |
MAV — message available in output buffer | Read the pending response before issuing new queries |
| 3 | 0x08 |
Device-defined (SCPI: Questionable Status summary) | Read STAT:QUES?; treat as data-integrity warning |
| 2 | 0x04 |
Device-defined (SCPI: error/event queue not empty) | Drain SYST:ERR? and classify the numeric codes |
| 1 | 0x02 |
Device-defined (vendor-specific) | Consult vendor manual; default to fail-safe |
| 0 | 0x01 |
Device-defined (vendor-specific) | Consult vendor manual; default to fail-safe |
The Status Byte reduced to actions: bits 4–6 are fixed by 488.2 and drive the three real recovery branches; a raised ESB (bit 5) is a redirect to *ESR?, and bit 6 clears itself the moment a serial poll reads it.
Production Decoder and SRQ-Driven Serial-Poll Loop
The implementation has two halves. decode_status_byte is a pure function — no I/O, fully testable — that turns a byte into structured flags. The event loop then waits on SRQ, performs one serial poll, decodes it, and routes each raised summary bit to its handler, reading *ESR? only when ESB is set so the slow query is spent exactly when it is diagnostic.
from __future__ import annotations
import enum
import logging
from dataclasses import dataclass
import pyvisa
logger = logging.getLogger(__name__)
# Fixed IEEE 488.2 Status Byte masks; bits 0-3 and 7 are device-defined.
STB_MAV = 0x10 # bit 4: message available in output buffer
STB_ESB = 0x20 # bit 5: Standard Event Status summary
STB_RQS = 0x40 # bit 6: RQS (serial poll) / MSS (*STB?)
# Standard Event Status Register (*ESR?) bit masks.
ESR_OPC = 0x01 # operation complete
ESR_QYE = 0x04 # query error
ESR_DDE = 0x08 # device-dependent error
ESR_EXE = 0x10 # execution error
ESR_CME = 0x20 # command error
ESR_PON = 0x80 # power-on
class Recovery(enum.Enum):
NONE = "none"
READ_OUTPUT = "read_output" # MAV set: drain the pending response
CLASSIFY_EVENT = "classify" # ESB set: read *ESR?, route the event
HALT = "halt" # unrecoverable device/command event
@dataclass(frozen=True)
class StatusFlags:
raw: int
rqs: bool # service requested (True only on a serial poll read)
esb: bool # a Standard Event summarised into the STB
mav: bool # output buffer has a message waiting
device_bits: tuple[int, ...] # raised device-defined positions (0-3, 7)
def decode_status_byte(stb: int) -> StatusFlags:
"""Decode an 8-bit GPIB Status Byte into structured summary flags.
``stb`` is the raw byte from a serial poll (RQS semantics on bit 6) or
from ``*STB?`` (MSS semantics). Raises ValueError for out-of-range input
so a corrupt bus read never silently decodes as an empty status.
"""
if not 0 <= stb <= 0xFF:
raise ValueError(f"Status byte out of range: {stb!r}")
device = tuple(b for b in (0, 1, 2, 3, 7) if stb & (1 << b))
return StatusFlags(
raw=stb,
rqs=bool(stb & STB_RQS),
esb=bool(stb & STB_ESB),
mav=bool(stb & STB_MAV),
device_bits=device,
)
def classify_event(esr: int) -> Recovery:
"""Route a Standard Event Status Register byte to a recovery disposition."""
if esr & (ESR_DDE | ESR_EXE | ESR_CME | ESR_QYE):
# A real command/execution/device/query event was latched.
return Recovery.HALT
if esr & ESR_PON:
return Recovery.HALT # instrument power-cycled mid-session
return Recovery.NONE # OPC-only or benign
def service_request_loop(
inst: pyvisa.resources.MessageBasedResource, poll_budget: int = 32
) -> list[Recovery]:
"""React to one SRQ: serial-poll, decode, and route each raised summary.
Serial-polls the instrument (clearing RQS), decodes the byte once, then
reads the output buffer for MAV and ``*ESR?`` for ESB. Bounded by
``poll_budget`` so a re-arming fault cannot spin the loop forever.
"""
actions: list[Recovery] = []
for _ in range(poll_budget):
stb = inst.read_stb() # bus serial poll; RQS auto-clears here
flags = decode_status_byte(stb)
if not flags.rqs:
break # no outstanding service request
if flags.mav:
pending = inst.read() # drain the buffer before new queries
logger.info("Drained pending response: %r", pending)
actions.append(Recovery.READ_OUTPUT)
if flags.esb:
esr = int(inst.query("*ESR?")) # reading *ESR? clears the ESR
action = classify_event(esr)
logger.warning("ESB set, *ESR?=0x%02X -> %s", esr, action.name)
actions.append(action)
if flags.device_bits:
logger.warning("Device-defined STB bits raised: %s", flags.device_bits)
else:
logger.error("SRQ did not clear within %d serial polls", poll_budget)
return actions
Enabling SRQ at all requires arming both the ESR and the STB: inst.write("*ESE 60; *SRE 32") unmasks the CME/EXE/DDE/QYE event bits (0x3C) into ESB, then unmasks ESB (0x20) into the service request. Without that mask setup, read_stb() returns a byte whose ESB never rises no matter how many events latch. Pair the bounded poll_budget with the retry ceilings in Timeout Handling & Retry Logic so a re-arming hardware fault degrades gracefully instead of pinning the bus.
Validating the Decode Against Known Status Bytes
Prove the pure decoder before trusting it on the bus. Assert the canonical single-bit bytes and a realistic composite, then confirm the RQS latch is what a live serial poll actually reports:
# Canonical single-bit status bytes.
assert decode_status_byte(0x40).rqs is True # RQS only: service requested
assert decode_status_byte(0x10).mav is True # MAV only: response waiting
assert decode_status_byte(0x20).esb is True # ESB only: check *ESR?
assert decode_status_byte(0x00).device_bits == () # clean status
# Composite: RQS + ESB + MAV asserted together (0x70).
flags = decode_status_byte(0x70)
assert (flags.rqs, flags.esb, flags.mav) == (True, True, True)
# Device-defined SCPI summaries (bit 3 QUES, bit 2 EAV) surface verbatim.
assert decode_status_byte(0x0C).device_bits == (2, 3)
# Event routing: a latched command error must halt, a bare OPC must not.
assert classify_event(ESR_CME) is Recovery.HALT
assert classify_event(ESR_OPC) is Recovery.NONE
On a live rig, provoke the bits deliberately. A pending query response raises MAV — read *STB? and confirm 0x10 before reading the buffer. Send a bad header such as VOLT:BOGUS, and after *ESE 60; *SRE 32 the instrument should assert SRQ, a serial poll should return bit 6 set, and *ESR? should read 0x20 (CME). The definitive live indicator is that a second immediate serial poll returns with bit 6 clear: RQS is a latch, and the first poll consumed it. If you standardise these event masks across a mixed fleet, fold the mnemonics into the numeric taxonomy in Categorizing SCPI error codes for automated recovery.
Failure Modes Specific to Status-Byte Decoding
These faults are unique to reading the register, distinct from the numeric-code faults its sibling covers.
RQS auto-clears on read, so a second poll misses the request. A serial poll returns RQS and clears the latch in the same operation. Code that polls once to log the byte and polls again to branch on it reads bit 6 clear the second time and concludes no service was requested. Decode the single byte you already have; never re-poll to re-check RQS. If you genuinely need the live master summary without consuming the latch, read *STB? (MSS semantics) instead of issuing a second serial poll.
ESB set but *ESR? left unread latches the summary forever. The ESR is a latching register cleared only by reading *ESR? (or *CLS). If your loop sees ESB and dispatches to recovery without actually reading *ESR?, the underlying event bit stays set, ESB stays summarised into every subsequent Status Byte, and each poll re-triggers the event branch on a fault that was handled minutes ago. Reading *ESR? in the handler — as the loop above does — is what breaks the latch; treat a persistently stuck ESB as proof the read was skipped.
Polling the wrong bus address returns another instrument’s status. A serial poll is addressed to a specific GPIB primary address. If the resource string or the address in a multi-drop chain is wrong, read_stb() succeeds and returns a perfectly valid byte — belonging to the wrong device — so you recover a fault the target never had while its real SRQ stays asserted. Confirm every session’s address through the VISA Resource Manager, and when several instruments share one controller, serialise polls through an Async Command Queuing System so two SRQ handlers never poll the bus concurrently.
Mixing 488.1 RQS and 488.2 MSS semantics on bit 6. Bit 6 means two different things depending on how it was read: RQS (a latch, cleared on serial poll) versus MSS (the live OR, never cleared). Logic that reads *STB?, sees bit 6 set, and assumes a new service request will loop forever, because MSS stays set as long as any enabled summary bit is true. Reserve serial poll (read_stb()) for edge-triggered SRQ handling where the clear-on-read latch is the point, and reserve *STB? for level-triggered status inspection where you want the current picture without disturbing it.
Once decoded, a Status Byte becomes the cheap trigger that gates the expensive text queries: MAV tells you a response is worth reading, ESB tells you an event is worth classifying, and only then does the numeric-code machinery in the parent Error Code Categorization collection run against SYST:ERR?.
Related
- Error Code Categorization — the parent framework where decoded status bits feed the three-severity recovery model.
- Categorizing SCPI error codes for automated recovery — the numeric-code classifier the ESB bit hands off to once you read
SYST:ERR?. - VISA Resource Manager Setup — session and address allocation that a serial poll targets.
- Timeout Handling & Retry Logic — the bounded ceilings that keep an SRQ poll loop from spinning on a re-arming fault.
- Async Command Queuing Systems — serialising serial polls so concurrent SRQ handlers never collide on the bus.
← Back to Error Code Categorization
References
- IEEE 488.2-1992 Standard Codes, Formats, Protocols and Common Commands — the Status Byte, SRE, and Standard Event Status Register model.
- PyVISA resource attributes and
read_stb— the serial-poll and status-byte API used above.