A Saleae Logic 2 protocol analyzer for decoding HDLC/SDLC frames, with support for external clock synchronization used in NEMA TS2 and ATC traffic signal communications.
The official Saleae HDLC analyzer does not work for NEMA TS2 / ATC SDLC traffic. It only supports an internal baud-rate-derived sampling clock, which causes progressive drift on true synchronous protocols like SDLC where data must be sampled on edges of an external clock signal provided by the controller. This drift produces garbled frames, wrong CRC checks, and phantom abort sequences -- making the stock analyzer unusable for traffic signal fieldwork.
This plugin was built from the ground up to solve that problem. It adds external clock sampling, NRZ encoding support (NEMA TS2 single-ended RS-485 probing uses NRZ, not NRZI as standard SDLC assumes), and configurable data inversion to handle the opposite polarity between Tx and Rx on the "+" wire.
- External Clock Mode — A new "Bit Synchronous (External Clock)" transmission mode that samples the data line on edges of a user-specified external clock channel, eliminating sampling drift on true synchronous protocols like SDLC
- NRZ / NRZI Data Encoding — Choose between NRZI (transition-based, standard SDLC) and NRZ (direct line level). NEMA TS2 signals probed single-ended on the RS-485
+wire use NRZ encoding - Data Polarity Inversion — Handle inverted RS-485 polarity when probing single-ended. On NEMA TS2, TxData+ and RxData+ have opposite polarity and require different inversion settings
- Full HDLC frame decoding — Address (basic/extended), Control (modulo 8/128/32768/2G), Information, and FCS fields
- CRC verification — CRC-8, CRC-16-CCITT, and CRC-32 with pass/fail indication
- CSV export — Export decoded frames as timestamped CSV
- Build the analyzer (see below)
- Open Saleae Logic 2
- Go to Preferences (Ctrl+,) > Custom Low Level Analyzers
- Add the directory containing the built DLL (e.g.
build/Analyzers/Release) - Restart Logic 2
- The analyzer appears as "NEMA/ATC SDLC"
| Setting | Options | Description |
|---|---|---|
| SDLC Data | Any digital channel | The data channel carrying the HDLC/SDLC serial bit stream. |
| Clock | Any digital channel / None | External clock channel. Required when using "Bit Synchronous (External Clock)" mode; leave as "None" for other modes. |
| Baud Rate | 1 - 6,000,000 | Bit rate in symbols per second. Used only by the internal clock modes ("Bit Synchronous" and "Byte Asynchronous") to derive sampling timing. Ignored in External Clock mode. |
| Transmission Mode | Bit Synchronous / Bit Synchronous (External Clock) / Byte Asynchronous | See Transmission Modes below. |
| Clock Edge | Rising Edge / Falling Edge | Which clock edge to sample data on. Only applies to External Clock mode. For NEMA TS2, use Rising Edge (see below). |
| Data Encoding | NRZI / NRZ | NRZI = transition-based (1=no transition, 0=transition). NRZ = direct line level (1=HIGH, 0=LOW). NEMA TS2 single-ended probing uses NRZ. |
| Invert Data | No / Yes | Invert data line polarity. Needed when probing one side of an RS-485 differential pair. For NEMA TS2, Tx and Rx require different inversion settings (see below). |
| Address Field | Basic / Extended | Basic = single 8-bit address. Extended = variable-length address where the LSB of each byte indicates whether more address bytes follow (LSB = 1 continues, LSB = 0 terminates). NEMA TS2 uses Basic. |
| Control Field | Basic - Modulo 8 / Extended - Modulo 128 / Extended - Modulo 32768 / Extended - Modulo 2147483648 | Size of the control field: Basic = 1 byte (8-bit sequence numbers), Extended = 2, 4, or 8 bytes for larger sequence number spaces. NEMA TS2 uses Basic (Modulo 8). |
| FCS Type | CRC-8 / CRC-16-CCITT / CRC-32 | Frame Check Sequence algorithm for error detection. NEMA TS2 uses CRC-16-CCITT. |
- Bit Synchronous - Standard HDLC bit-oriented framing with NRZI encoding and zero-bit stuffing after 5 consecutive 1-bits. Uses an internal sampling clock derived from the specified baud rate. Best for captures where no external clock signal is available.
- Bit Synchronous (External Clock) - Bit-stuffing framing with configurable NRZ/NRZI encoding, sampling on edges of a physical clock signal. Eliminates sampling drift on true synchronous links such as NEMA TS2 / ATC SDLC.
- Byte Asynchronous - Byte-oriented framing with start/stop bits and byte-stuffing (escape sequences). Used by protocols that run HDLC over asynchronous serial links (e.g., PPP).
Connect the Saleae logic analyzer to the TS2 Type AB (EIA-485) port:
| TS2 Port Signal | Pin | Logic Analyzer | Direction |
|---|---|---|---|
| TxData+ | 1 | Digital Channel 0 | Controller -> Field Device |
| TxClock+ | 3 | Digital Channel 3 | Clock for Tx |
| RxData+ | 5 | Digital Channel 1 | Field Device -> Controller |
| RxClock+ | 7 | Digital Channel 2 | Clock for Rx |
| GND | 2, 4, 6, 8 | GND | -- |
Set up two analyzer instances -- one for Tx (command frames) and one for Rx (response frames).
Tx (Command Frame):
Rx (Response Frame):
![]() |
|
Note: The only difference between Tx and Rx is Invert Data. TxData+ and RxData+ have opposite RS-485 polarity when probed single-ended on the
+wire.
- NEMA TS2 operates at 153,600 bps (153.6 kbps)
- ATC operates at 614,400 bps (614.4 kbps)
Note: When using External Clock mode, the baud rate setting is ignored -- timing is derived entirely from the clock signal. The baud rate only matters for the internal clock modes.
Both NEMA TS2 and ATC use Rising Edge sampling. Per the TS2 specification, "the receiving station shall read the data ... when the voltage potential of the Tx Clock+ line makes a positive going transition with respect to the Tx Clock-line." The clock's positive-going (rising) transition occurs at the midpoint of each bit cell. ATC follows the same convention.
If decoding produces garbled output, try switching to Falling Edge -- some signal conditioners or level translators may invert the clock polarity.
- A C++23-compatible compiler (Visual Studio 2022, GCC 13+, or Clang 16+)
- CMake 3.13+
- git
mkdir build && cd build
cmake ..
cmake --build .
mkdir build && cd build
cmake ..
cmake --build .
mkdir build && cd build
cmake .. -A x64
cmake --build . --config Release
The built analyzer DLL will be at build/Analyzers/Release/sdlc_analyzer.dll.
Written in C++23 with modern patterns:
src/
types.h Core types, enums, pattern matching, monadic guard, CRTP utilities
crc.h Compile-time CRC engine template (CRC-8, CRC-16-CCITT, CRC-32)
settings.h/cpp User-facing configuration with fold-expression serialization
analyzer.h/cpp Frame decoder with CRTP mixins (FrameEmitter, BitDecoder)
results.h/cpp Display text rendering and CSV export
sim.h/cpp Synthetic waveform generator for simulation mode
Key design patterns:
| Pattern | Where | Purpose |
|---|---|---|
| CRTP | FrameEmitter<D>, BitDecoder<D> |
Static polymorphism for frame buffering and NRZ/NRZI decoding |
| Compile-time CRC | CrcEngine<Bits,V,Poly,Init,Xout,Reflect> |
Table generation at compile time via constexpr lambda |
| Pattern matching | match(), match_or(), select() |
Replaces switch/if-else chains with declarative dispatch |
| Monadic guard | guard(flag) >> [&]{...} |
Short-circuit chaining on abort conditions |
| Fold expressions | load_all(), save_all(), chain() |
Variadic archive serialization and export pipelines |
| Constexpr lambdas | fcs_bytes, classify, ctrl_bytes |
Enum dispatch tables as first-class values |
- ISO/IEC 13239:2002 - High-level Data Link Control (HDLC) Procedures
- NEMA TS 2 - Traffic Controller Assemblies with NTCIP Requirements
- ITE ATC 5201/5301 - Advanced Transportation Controller Standards
- Saleae Analyzer SDK - https://github.com/saleae/AnalyzerSDK
- Saleae Protocol Analyzer SDK documentation - https://github.com/saleae/SampleAnalyzer/blob/master/docs/Analyzer_API.md
MIT License - Copyright (c) 2026 Wuping Xin


