Near-Infrared Spectrometer Serial Command Protocol

Overview

Pynect near-infrared spectrometer modules and spectrometers support low-level command communication over a USB virtual serial port (HID). Mastering the serial command protocol allows you to interact with the device directly without using an SDK, making it suitable for embedded system development, custom host-software development and similar scenarios. This article describes the data frame structure, checksum mechanism and common operating commands of serial communication.


1. Data Frame Structure

Each command consists of a fixed-format data frame (packet) with the following structure:

Member Start index Bytes Description
start bytes 0 4 Frame start marker: 0x41 0x42 0x43 0x44 (ASCII "ABCD")
checksum 4 4 Checksum (excluding start/trailer bytes)
flag 8 1 Flag byte, fixed 0xC0
sequence byte 9 1 Sequence number (used to match requests and responses)
length 10 2 Data length = bytes of command(2) + data(N)
command 12 2 Command code
data 14 N Command payload data (may be empty)
trailer bytes end 4 Frame end marker: 0x44 0x43 0x42 0x41 (ASCII "DCBA")

Example frame (configure scan parameters):

41 42 43 44         ← start bytes
5D 00 00 00         ← checksum
C0                  ← flag
00                  ← sequence byte
1E 00               ← length = 30
18 02               ← command (NNO_CMD_CONFIG_SCAN)
04 04 02 00 ...     ← data (28 bytes)
44 43 42 41         ← trailer bytes

2. Checksum Calculation

The checksum is used to verify the integrity of data transmission. It is calculated by summing all bytes from the flag to the end of the data (excluding the start bytes and trailer bytes) and taking the low 4 bytes. After receiving the data, the receiver recomputes the checksum and compares it with the checksum field in the frame; a mismatch indicates a transmission error.


3. Common Operating Commands

3.1 Configure Scan Parameters (NNO_CMD_CONFIG_SCAN)

Command code: 0x18 0x02

Configures the spectrometer's scan parameters, including integration time, gain, scan mode and more. After sending, the device returns an identical command frame as confirmation.

Example send frame:

41 42 43 44 5D 00 00 00 C0 00 1E 00 18 02
[28 bytes config data]
44 43 42 41

3.2 Perform Scan (NNO_CMD_PERFORM_SCAN)

Command code: 0x16 0x02

Triggers one spectrum scan. This command has no data field (length = 2).

Send frame:

41 42 43 44 DA 00 00 00 C0 00 02 00 16 02 44 43 42 41

Return frame: The device returns a scan data packet whose data contains wavelength, intensity and other information.

3.3 Get Scan Status (NNO_CMD_SCAN_GET_STATUS)

Command code: 0x19 0x02

Queries whether the scan is complete. The returned data payload:

Return value Meaning
0x01 Scan complete, data ready
empty Still scanning; continue polling

Send frame:

41 42 43 44 DD 00 00 00 C0 00 02 00 19 02 44 43 42 41

3.4 Get Returned Data Length (NNO_CMD_FILE_GET_READSIZE)

Command code: 0x2D 0x00

Reads the total length (bytes) of the data packet returned by the current scan, for subsequent chunked reading.

3.5 Get Scan Data (NNO_CMD_FILE_GET_DATA)

Command code: 0x2E 0x00

Reads the complete scan data packet. The returned data contains the following information:

Field Description
header_version Protocol version number
serial_number Device serial number (8-byte ASCII)
adc_data_length Total ADC data length
pga Current gain multiplier
scan_config Scan configuration parameters
wavelength[ ] Wavelength array (864 double values)
intensity[ ] Intensity array (864 int values)
length Number of valid data points (usually 228)

4. Scan Data Reading Workflow

The complete scan and data-reading workflow is as follows:

1. Send CONFIG_SCAN       → configure scan parameters
2. Send PERFORM_SCAN      → trigger a scan
3. Poll GET_STATUS        → wait for the scan to complete (data[0] == 0x01)
4. Send GET_READSIZE      → get the data packet length
5. Send GET_DATA          → read the complete data packet
6. Parse wavelength[] and intensity[] → obtain the spectral data

5. Integration Time and Pixel Width Conversion

Integration time (exposure time)

The integration time index starts from 0, and its relationship to the real time is:

index Integration time
0 0.635 ms
1 1.27 ms
2 2.54 ms
3 5.08 ms
... doubles each step

Pixel width (width_px)

The pattern width is calculated in DMD pixels (px). For the standard wavelength optical engine (900-1700 nm):

$$1\text{ px} \approx 1.1709\text{ nm} = \frac{800\text{ nm}}{854\text{ px} \times 80\%}$$

width_px Corresponding spectral width
5 px ≈ 5.85 nm
6 px ≈ 7.03 nm
7 px ≈ 8.20 nm

For extended-wavelength optical engines (1350-2150 nm / 1600-2400 nm), 1 px ≈ 1 nm.


6. FAQ

Structure Alignment

When parsing data packets in C/C++ development, pay attention to byte alignment of structures. The following structure contains members of different sizes (uint8_t, uint16_t, char[ ]), so the compiler may automatically insert padding bytes that cause parsing offsets:

struct ScanConfig {
    uint8_t  scan_type;
    uint16_t scanConfigIndex;      // may be at offset 2 instead of offset 1
    char     serial_number[8];
    char     config_name[40];
    uint16_t num_repeats;
    uint8_t  num_sections;
};

Solution: Use #pragma pack(1) or __attribute__((packed)) to force 1-byte alignment, or manually parse the offset positions field by field instead of relying on sizeof.


This article was compiled by Pynect.