Based on:
- UT8803E Programming Manual V1.1 (UNI-T)
- UT8803E User Manual (UNI-T)
- UNI-T SDK V2.3 (headers, examples, uci.dll)
- CP2110 Datasheet (Silicon Labs)
- AN434: CP2110/4 Interface Specification (Silicon Labs)
Confidence levels:
- [KNOWN] -- documented in official UNI-T programming manual
- [VENDOR] -- confirmed by analyzing UNI-T's official SDK/software
- [DEDUCED] -- logical inferences from available evidence
- [UNVERIFIED] -- requires real device testing to confirm
From the programming manual support model table:
| Parameter | Value | Source |
|---|---|---|
| USB VID | 0x10C4 | Programming manual |
| USB PID | 0xEA80 | Programming manual |
| USB Class | HID | Programming manual: [T:HID] |
| Bridge Chip | CP2110 | Same VID/PID as UT61E+ CP2110 |
UNI-T kept the Silicon Labs default VID/PID. The same VID/PID is used by both the UT8802/UT8803 and the UT61E+. Device discrimination must be done at the application layer.
From AN434 (same as UT61E+):
UART Data Transfer (Interrupt Transfers):
- Report IDs 0x01 through 0x3F carry UART data
- Report ID = byte count (1-63 data bytes)
- Byte 0 = Report ID, bytes 1-63 = UART data
Device Configuration (Feature Reports):
- 0x41: Get/Set UART Enable
- 0x43: Set Purge FIFOs
- 0x50: Get/Set UART Config (baud rate, data bits, parity, stop bits)
The Ghidra decompilation of uci.dll reveals the exact UART configuration
sent to the CP2110 via feature report 0x50. In FUN_1001d460 (the
CP2110 HID initialization function):
// FUN_1001d460 — CP2110 UART init for UT8802/UT8803
local_24[0] = 0x141; // Feature report 0x41: enable UART (0x01)
HidD_SetFeature(param_2, local_24, 2); // Send 2-byte enable report
local_20 = 0x25000050; // Bytes: 0x50, 0x00, 0x00, 0x25
local_1c = 0x3000080; // Bytes: 0x80, 0x00, 0x03, 0x00
local_18 = 0; // Byte: 0x00
HidD_SetFeature(param_2, &local_20, 9); // Send 9-byte UART config reportThe feature report 0x50 bytes decode per AN434 as:
| Byte | Value | Meaning |
|---|---|---|
| 0 | 0x50 | Report ID: Set UART Config |
| 1-4 | 0x00 0x00 0x25 0x80 | Baud rate: 0x00002580 = 9600 (big-endian) |
| 5 | 0x00 | Parity: None |
| 6 | 0x03 | Flow control: No flow control (0x03) |
| 7 | 0x00 | Data bits: 8 (0x00 = 8 bits per AN434) |
| 8 | 0x00 | Stop bits: 1 (short stop bit) |
This is identical to the UT61E+ configuration.
Correction (2026-06 review): no trigger byte on the CP2110 path.
The 0x5A byte previously attributed to this init function is not there:
FUN_1001d460 (uci_dll_decompiled.txt:24031-24085) sends only the
feature reports and sets the input buffer count — it performs no UART
write. The only FUN_1002a4d0(handle, 0x5a, 1000) call in uci.dll is
in FUN_1001d360 (lines 23981-24027), the QinHeng/CH9325 2400-baud
init path used for other meters (UT803/UT804 class). The UT8803
streams without any trigger. Whether a stray 0x5A is harmless to a
UT8803 is untested on hardware.
| Parameter | Value | Confidence |
|---|---|---|
| Baud rate | 9600 bps | [VENDOR] |
| Data bits | 8 | [VENDOR] |
| Parity | None | [VENDOR] |
| Stop bits | 1 (short) | [VENDOR] |
| Flow control | None | [VENDOR] |
From FUN_1001d460 in uci.dll (CP2110 HID path for UT8802/UT8803):
- Open HID device matching VID 0x10C4, PID 0xEA80
- Send feature report 0x41: Enable UART (
[0x41, 0x01]) - Send feature report 0x50: Configure 9600/8N1 (
[0x50, 0x00, 0x00, 0x25, 0x80, 0x00, 0x03, 0x00, 0x00]) - Set HID input buffer count to 64 (
HidD_SetNumInputBuffers(0x40))
(No trigger byte — see the §1.3 correction; the 0x5A belongs to the CH9325 init path for other meters.)
Note: There is a separate function FUN_1001d2b0 that sends different
feature report values (0x34b0000 for the report payload) — this
appears to be for the QinHeng HID models (VID 0x1A86), not the CP2110
models.
The programming manual groups these models by transport:
CP2110 HID (VID 0x10C4, PID 0xEA80):
- UT8802 / UT8802N
- UT8803 / UT8803N
QinHeng HID (VID 0x1A86, PID 0xE008):
- UT632 / UT632N
- UT803 / UT803N
- UT804 / UT804N
Serial port (9600 8N1):
- UT805A / UT805N
All share the UCI protocol layer and data?;/disp?; commands.
The UT8803 communicates through UNI-T's UCI (United Communication
Interface) SDK. Unlike the UT61E+ which has a bare binary protocol,
the UT8803's wire protocol is abstracted by the uci.dll library.
API usage pattern (from SDK examples and programming manual):
// 1. Open connection
u_session session;
u_status r = uci_Open(
"[C:DM][D:T8803][T:HID][PID:0xea80][VID:0x10c4]",
&session, 2000);
// 2. Read display data (DMFRM struct)
DMFRM dfrm;
r = uci_ReadX(session, "disp?;", 2000,
(unsigned char*)&dfrm, sizeof(dfrm));
// 3. Read raw measurement value (double)
double value;
r = uci_ReadX(session, "data?;", 2000,
(unsigned char*)&value, sizeof(value));
// 4. Close
uci_Close(session);| Command | Response Type | Size | Description |
|---|---|---|---|
data?; |
double |
8 bytes | Raw measurement value |
disp?; |
struct DMFRM |
56+ bytes | Display strings, values, and flags |
data?; returns only the numeric measurement value as an IEEE 754
double-precision float. The return value from uci_ReadX encodes the
status (>0 = status code with mode/range information).
disp?; returns the full display state including text
representation, numeric values, and a 64-bit flags word encoding mode,
range, unit, and status information.
The UCI library internally uses a framed binary protocol to communicate
with the meter over the CP2110 UART. Ghidra decompilation of uci.dll
revealed the UT8803 parser function (at address 0x1001e5f0), which
fully specifies the wire protocol:
+------+------+----------+------+------------------+----------+----------+
| 0xAB | 0xCD | mode_hi | 0x02 | payload (17 B) | chk_hi | chk_lo |
+------+------+----------+------+------------------+----------+----------+
byte 0 byte 1 byte 2 byte 3 bytes 4-20 byte 19 byte 20
- Header:
0xAB 0xCD-- same as UT61E+ [VENDOR] - Byte 2: High byte of the mode word. The parser reads bytes 2-3 as
a 16-bit
shortvalue (param_2[1]whereparam_2isshort *), but only byte 3 is checked (== 0x02). Byte 2 is part of the same word; its low byte contributes to(byte)param_2[2]which is byte offset 4 (the mode byte). The actual role of byte 2 itself is not directly consumed in parsing -- it is included in the checksum but its value is not independently validated. [VENDOR] - Byte 3: Response type
0x02(measurement data) [VENDOR] - Minimum frame size: 0x15 = 21 bytes [VENDOR]
- Checksum: At bytes 19-20 (offsets 0x13-0x14) [VENDOR]
The frame parser discriminates UT8802 vs UT8803 by the first bytes of the payload:
| First Byte(s) | Model | Evidence |
|---|---|---|
0xAC |
UT8802 | *_Memory == -0x54 (line 25275) |
0xAB 0xCD |
UT8803 | *_Memory == -0x55 && _Memory[1] == -0x33 (line 25290) |
The UT8803 response thus begins with AB CD as a header, while the
UT8802 uses a single-byte 0xAC header. [VENDOR]
The UT8803 checksum is an alternating-byte sum -- different from the UT61E+'s simple sequential sum:
// Decompiled checksum (simplified):
ushort sum_even = 0; // sum of even-offset bytes
ushort sum_odd = 0; // sum of odd-offset bytes
for (int i = 0; i < 0x12; i += 2) {
sum_even += data[i]; // bytes 0, 2, 4, 6, 8, 10, 12, 14, 16
sum_odd += data[i + 1]; // bytes 1, 3, 5, 7, 9, 11, 13, 15, 17
}
ushort extra = 0;
if (i < 0x13) extra = data[i]; // byte 18 if present
ushort computed = extra + sum_even + sum_odd;
ushort received = (data[0x13] << 8) | data[0x14]; // bytes 19-20 big-endian
if (computed != received) → checksum errorThe checksum covers bytes 0 through 18 (19 bytes), stored big-endian at bytes 19-20. [VENDOR]
From the parser at 0x1001e5f0 (decompiled UT8803 parser):
| Offset | Field | Notes |
|---|---|---|
| 0-1 | Header | 0xAB 0xCD |
| 2 | (frame byte) | Part of param_2[1] word; not independently consumed |
| 3 | Type | 0x02 for measurement response |
| 4 | Mode | (byte)param_2[2] → mode byte (raw, max 0x16) |
| 5 | Range | *(byte*)((int)param_2 + 5) → has 0x30 prefix (mask: - 0x30, max 6) |
| 6 | (included in checksum) | Not directly accessed by the parser. Included in the alternating-byte checksum sum but no field extraction. Likely reserved or padding. |
| 7-11 | Display | 5 raw bytes, appended to string buffer via FUN_1001fce0 (buffer append, no transformation) |
| 12-13 | (included in checksum) | Not accessed by the parser. An earlier reading put the *(byte*)((int)param_2 + 0x11) access here, but offset 0x11 is byte 17 |
| 14 | Flags | Low byte of param_2[7]: bit 0 = HOLD (D31), bit 1 = Over-range (D18; previously misread as unused), bit 2 = OL (D7), bit 3 = Sign (D19). Bits 4-7 are not read |
| 15 | Flags | High byte of param_2[7]: bit 0 = REL (D30), bit 1 = AUTO, inverted (D6), bit 2 = Error (high D0). Bits 3-7 are not read |
| 16 | Flags | *(byte*)(param_2 + 8): bit 0 = MIN (D29), bit 1 = MAX (D28), bits 2-3 = ScalePos (D24-D27). Bits 4-7 are not read |
| 17 | Flags | *(byte*)((int)param_2 + 0x11): bits 0-1 = inductance test frequency (below), bit 2 = TestMode, serial/parallel (high D1). Bits 3-7 are not read |
| 18 | Flags | (char)param_2[9]: bit 0 = DiodeLR (high D3), bit 1 = DiodeRL (high D2). Bits 2-7 are not read |
| 19-20 | Checksum | 16-bit BE, alternating-byte sum |
The flag-byte bits are extracted at uci_dll_decompiled.txt:24985-25013; the "D" numbers are the status-word bits they feed (§3.2).
Mode byte values: 0x00 through 0x16 (max 22 values), checked
at line 25014: if (0x16 < bVar1) → reject. This corresponds to the
23-entry UT8803 position coding table. [VENDOR]
Range byte: Has a 0x30 prefix, masked with - 0x30, max value
6 after masking. Same encoding as UT61E+. [VENDOR]
Display value parsing: The function FUN_1001fce0 is a buffer
append function, not a byte transformation. It appends raw bytes to
a dynamically-growing buffer (checking capacity, reallocating via
FUN_1001a5a0 if needed). The display bytes at offsets 7-11 are
copied verbatim into the string buffer. After accumulation,
FUN_1017f410 converts the string to a float value. The display bytes
are therefore raw values (not ASCII with 0x30 prefix like the
range byte). [VENDOR]
Flag bit construction: The parser builds a 32-bit status word
(at param_4 + 0x14) by combining extracted bits from the flag bytes.
The format string at line 25091 confirms the bit layout:
"ACDC:%s,dotpos:%d,fun:%d,isauto:%d,ismax:%d,ismin:%d,ishold:%d,isrel:%d,isOL:%d"
The debug format string extracts these fields from the status word:
fun= bits 0-3 (uVar9 & 0xf) — functional codingACDC= bits 4-5 (uVar9 >> 4 & 3) — AC/DC statusisauto= bit 6 (uVar9 >> 6 & 1) — auto rangeisOL= bit 7 (uVar9 >> 7 & 1) — overloaddotpos= bits 24-27 (uVar9 >> 0x18 & 0xf) — decimal point positionismax= bit 28 (uVar9 >> 0x1c & 1) — MAX modeismin= bit 29 (uVar9 >> 0x1d & 1) — MIN modeisrel= bit 30 (uVar9 >> 0x1e & 1) — REL modeishold= bit 31 (uVar9 >> 0x1f) — HOLD mode
This exactly matches the Flags word layout documented in the programming manual (section 3.2). [VENDOR]
Raw-byte → status-word mapping (resolved 2026-04-19): A second
Ghidra pass traced each status-word bit back to a frame byte by
following the intermediate local_1e, local_1f, local_21,
local_27, local_28, local_29, local_1d assignments from their
write sites (lines 24985-25013) and then counting the accumulated
shifts in the status-word construction at lines 25027-25031. Resolved
mapping:
| Status-word bit | Flag | Source byte | Source bit | Notes |
|---|---|---|---|---|
| D6 | AUTO | frame byte 15 | bit 1 | inverted (vendor: bVar17 = local_16 == '\0') |
| D7 | OL (overload) | frame byte 14 | bit 2 | local_1e = bVar4 >> 2 & 1 |
| D19 | Sign (minus) | frame byte 14 | bit 3 | vendor negates the parsed float when set: *param_4 = *param_4 * DAT_101bde08 (line 25041) |
| D28 | MAX | frame byte 16 | bit 1 | local_29 via bVar12 >> 1 |
| D29 | MIN | frame byte 16 | bit 0 | local_21 via (uVar14 >> 8) & 1 |
| D30 | REL | frame byte 15 | bit 0 | local_1f via the byte-15 bit-0 chain at line 24997 |
| D31 | HOLD | frame byte 14 | bit 0 | local_27 via uVar9 & 0xffffff01 at line 24990 |
D15 (LowBat) and D17 (Under) are present in the programming-manual status-word layout but are not populated by this parser — consistent with the UT8803 being an AC-mains bench meter. [VENDOR]
High-byte flags (param_4 + 3): The parser also constructs a
separate byte at param_4 + 0x18 (offset 24 from param_4 base):
*(uint *)(param_4 + 3) =
(((((((bVar1==0x10)*2 | (bVar1==0xf))*2 | (bVar1==0xd))*2 |
(bVar1==0xc))*2 | local_15)*2 | local_25)*2 | local_20)*2 |
local_26 ...This encodes mode-specific flags by checking if the mode byte equals
specific inductance/capacitance sub-measurement modes (0x0C=IndQ,
0x0D=IndR, 0x0F=CapD, 0x10=CapR), corresponding to the high 32-bit
flags D4-D7 (IndQ, IndR, CapD, CapR) documented in the programming
manual. The four locals fill D3-D0 from the flag bytes: local_15 =
DiodeLR (byte 18 bit 0), local_25 = DiodeRL (byte 18 bit 1),
local_20 = TestMode (byte 17 bit 2), local_26 = Error (byte 15
bit 2) (lines 25018-25021). [VENDOR]
The parser checks for inductance modes (0x0B through 0x10) and extracts a 2-bit field (frame byte 17 bits 0-1) determining the test frequency:
0→ "100Hz"1→ "1kHz"
This corresponds to the UT8803's inductance test at 100Hz and 1kHz frequencies. [VENDOR]
The Ghidra decompilation reveals that the UT8803 does not use
text-based SCPI commands on the wire. The UCI library translates the
text API commands (data?;, disp?;) into a binary polling
mechanism:
No initialization trigger (corrected, 2026-06 review): the
FUN_1002a4d0(handle, 0x5a, 1000) call previously described here is in
the CH9325 init path (FUN_1001d360), not the CP2110 path used by the
UT8803 — see §1.3. The UT8803 receives no host bytes at all.
Read loop: The FUN_1001f170 function implements the frame read
loop. It calls FUN_1002a380 which calls FUN_1002a260 to perform
HID reads (with 0x32 = 50ms individual timeouts). The function reads
chunks from the HID device, accumulates them in a buffer, then calls
the parser (FUN_1001e5f0 for UT8803, FUN_1001e0a0 for UT8802)
when enough data has been received. There is no evidence of the
host sending a per-measurement request command — the meter
streams data continuously, unprompted.
Frame dispatch: The function FUN_1001eb30 is the frame
discriminator. It scans the received HID data (which arrives in 64-byte
HID reports), reassembles complete frames by looking for 0xAB 0xCD
(UT8803) or 0xAC (UT8802) headers, then dispatches to the appropriate
parser.
The :DISPlay:DATA? string: This string appears in the
PLAIN-TEXT transport handler (FUN_10038300 area, line 45478), which
is used for text-based instruments (oscilloscopes, signal generators).
The DMM parser path (FUN_1001e5f0) does not use SCPI commands.
| Aspect | Details | Confidence |
|---|---|---|
| Trigger command | None (0x5A was a misattribution — §1.3) | [VENDOR] |
| Communication model | Meter streams continuously, unprompted | [VENDOR] |
| Frame reassembly | HID reports concatenated, header-delimited | [VENDOR] |
| Per-measurement command | None — no request per sample | [VENDOR] |
The UT8803 uses a streaming model, not a polled model:
- Host opens HID device and configures CP2110 UART (9600/8N1)
- Meter sends 21-byte measurement frames continuously (no trigger — see §1.3 correction)
- UCI library's
FUN_1001f170reads HID reports in a loop, reassembles frames, and parses each complete frame - The
uci_ReadX("data?;")anduci_ReadX("disp?;")calls simply read the most recently parsed measurement from internal state
This differs from the UT61E+ polled model where each measurement
requires a request command (AB CD 03 5E 01 D9). The UT8803 streams
at approximately 2-3 Hz (matching the user manual's stated refresh
rate).
struct DMFRM {
TCHAR MainDisp[20]; // Main display character string (20 chars)
TCHAR AuxDisp[20]; // Secondary display character string (20 chars)
double MainValue; // Main display numeric value (8 bytes, IEEE 754)
double AuxValue; // Secondary display numeric value (8 bytes)
unsigned long long Flags; // Flag bits (8 bytes, 64 bits)
};Size: In ASCII mode, TCHAR = char (1 byte), total = 20+20+8+8+8 =
64 bytes. In Unicode mode, TCHAR = wchar_t (2 bytes), total =
40+40+8+8+8 = 104 bytes.
Note: The UT805A/UT805N buffer area can be set to 8 or 16 bytes
for main and secondary display. Other models use 1 double (8 bytes)
for data?;.
The 64-bit Flags field encodes all status information. Bit extraction
uses the macro:
#define Bits(_status, _offset, _mask) ((_status >> _offset) & _mask)| Bits | Mask | Field | Description |
|---|---|---|---|
| D0-D3 | 0xF | FuncCode | Functional coding (mode category) |
| D4-D5 | 0x3 | ACDC | AC&DC status: 0=OFF, 1=AC, 2=DC, 3=AC+DC |
| D6 | 0x1 | AutoRange | 1=Auto range active |
| D7 | 0x1 | OverLoad | 1=Overload (OL) |
| D8-D11 | 0xF | UnitType | Physical unit type (V/A/ohm/Hz/C/F/F/hFE/%) |
| D12-D14 | 0x7 | UnitMag | Physical unit magnitude (n/u/m/std/K/M/G) |
| D15 | 0x1 | LowBat | 1=Low battery |
| D16 | 0x1 | USBComm | 1=USB communication active |
| D17 | 0x1 | Under | 1=Under range |
| D18 | 0x1 | Over | 1=Over range |
| D19 | 0x1 | Minus | 1=Display shows minus sign |
| D20-D23 | 0xF | Position | Position coding (model-specific) |
| D24-D27 | 0xF | ScalePos | Scaling position (starts from 1) |
| D28 | 0x1 | MAX | 1=MAX mode active (UT8802/UT8803) |
| D29 | 0x1 | MIN | 1=MIN mode active (UT8802/UT8803) |
| D30 | 0x1 | REL | 1=REL (relative) mode active (UT8802/UT8803) |
| D31 | 0x1 | HOLD | 1=Data hold active (UT8802/UT8803) |
| Bits | Mask | Field | Description |
|---|---|---|---|
| D0 | 0x1 | Error | 1=Current data error or display error |
| D1 | 0x1 | TestMode | 1=Serial (SEL), 0=Parallel (PAL) |
| D2 | 0x1 | DiodeRL | 1=Diode/thyristor direction right-to-left valid |
| D3 | 0x1 | DiodeLR | 1=Diode/thyristor direction left-to-right valid |
| D4 | 0x1 | IndQ | 1=Inductance quality element (Q) measurement |
| D5 | 0x1 | IndR | 1=Equivalent resistance measurement |
| D6 | 0x1 | CapD | 1=Capacitance loss element (D) measurement |
| D7 | 0x1 | CapR | 1=Capacitance equivalent resistance measurement |
| D8-D15 | 0xFF | FuncPos | Functional position coding (UT8803-specific) |
| D16-D31 | 0xFFFF | Hold2 | Extended hold field |
These abstract measurement categories apply to all models:
| Code | Function | Notes |
|---|---|---|
| 0 | Voltage | Combined with ACDC bits for DCV/ACV/ACV+DCV |
| 1 | Resistance (OHM) | |
| 2 | Diode | |
| 3 | Continuity | |
| 4 | Capacitance | |
| 5 | Frequency (FREQ) | |
| 6 | Temperature Fahrenheit | |
| 7 | Temperature Centigrade | |
| 8 | Triode hFE | |
| 9 | Current | Combined with ACDC bits for DCI/ACI |
| 10 | % (4-20mA) | |
| 11 | Duty ratio | |
| 12 | Thyristor (SCR) | |
| 13 | Inductance | Including Q and R sub-measurements |
Resolving AC/DC variants: The functional coding does not distinguish between AC and DC for voltage and current. Use the ACDC field (D4-D5) to determine:
- DCV: FuncCode=0 (Voltage), ACDC=2 (DC)
- ACV: FuncCode=0 (Voltage), ACDC=1 (AC)
- ACV+DCV: FuncCode=0 (Voltage), ACDC=3 (AC+DC)
- DCI: FuncCode=9 (Current), ACDC=2 (DC)
- ACI: FuncCode=9 (Current), ACDC=1 (AC)
These model-specific position codes identify the exact measurement
mode and range position on the dial. Available only from the disp?;
command's high 32 bits.
| Code | Position |
|---|---|
| 0 | AC voltage (ACV) |
| 1 | DC voltage (DCV) |
| 2 | AC current microampere (ACuA) |
| 3 | AC current milliampere (AC mA) |
| 4 | AC current standard (AC A) |
| 5 | DC current microampere (DCuA) |
| 6 | DC current milliampere (DC mA) |
| 7 | DC current standard (DC A) |
| 8 | Resistance (OHM) |
| 9 | Continuity |
| 10 | Diode (DIODE) |
| 11 | Inductance (L) |
| 12 | Inductance quality factor (Q) |
| 13 | Inductance equivalent resistance (R) |
| 14 | Capacitance (C) |
| 15 | Capacitance loss factor (D) |
| 16 | Capacitance equivalent resistance (R) |
| 17 | Triode hFE |
| 18 | Thyristor (SCR) |
| 19 | Temperature Centigrade |
| 20 | Temperature Fahrenheit |
| 21 | Frequency (FREQ) |
| 22 | Duty ratio |
For comparison, the UT8802 uses different codes that encode both function and range:
| Code | Position |
|---|---|
| 0x01 | DC voltage 200mV |
| 0x03 | DC voltage 2V |
| 0x04 | DC voltage 20V |
| 0x05 | DC voltage 200V |
| 0x06 | DC voltage 1000V |
| 0x09 | AC voltage 2V |
| 0x0A | AC voltage 20V |
| 0x0B | AC voltage 200V |
| 0x0C | AC voltage 750V |
| 0x0D | DC current microampere 200uA |
| 0x0E | DC current milliampere 2mA |
| 0x10 | AC current milliampere 2mA |
| 0x11 | DC current milliampere 20mA |
| 0x12 | DC current milliampere 200mA |
| 0x13 | AC current milliampere 20mA |
| 0x14 | AC current milliampere 200mA |
| 0x16 | DC current standard 2A |
| 0x18 | AC current standard 20A |
| 0x19 | Resistance 200 ohm |
| 0x1A | Resistance 2k ohm |
| 0x1B | Resistance 20k ohm |
| 0x1C | Resistance 200k ohm |
| 0x1D | Resistance 2M ohm |
| 0x1F | Resistance 200M ohm |
| 0x22 | Duty |
| 0x23 | Diode |
| 0x24 | Continuity |
| 0x25 | hFE |
| 0x27 | Capacitance nF |
| 0x28 | Capacitance uF |
| 0x29 | Capacitance mF |
| 0x2A | SCR |
| 0x2B | Frequency Hz |
| 0x2C | Frequency kHz |
| 0x2D | Frequency MHz |
| Code | Unit | Symbol |
|---|---|---|
| 0 | Voltage | V |
| 1 | Current | A |
| 2 | Resistance | ohm |
| 3 | Frequency | Hz |
| 4 | Centigrade | C |
| 5 | Fahrenheit | F |
| 6 | RPM(rpm)hold | rpm |
| 7 | Capacitance | F |
| 8 | Triode hFE | beta |
| 9 | Percentage | % |
| 0xF | No display | (none) |
| Code | Prefix | Multiplier |
|---|---|---|
| 0 | n (nano) | 10^-9 |
| 1 | u (micro) | 10^-6 |
| 2 | m (milli) | 10^-3 |
| 3 | (standard) | 1 |
| 4 | K (kilo) | 10^3 |
| 5 | M (mega) | 10^6 |
| 6 | G (giga) | 10^9 |
| Code | Status |
|---|---|
| 0 | OFF |
| 1 | AC |
| 2 | DC |
| 3 | AC+DC |
| Range | Resolution | Full Scale |
|---|---|---|
| 600mV | 0.1mV | 5999 |
| 6V | 1mV | 5999 |
| 60V | 10mV | 5999 |
| 600V | 100mV | 5999 |
| 1000V | 1V | 1000 |
| Range | Resolution | Max |
|---|---|---|
| 600mV | 0.1mV | 600 |
| 6V | 1mV | 6 |
| 60V | 10mV | 60 |
| 600V | 100mV | 600 |
| 750V | 1V | 750 |
| Range | Resolution |
|---|---|
| 600uA | 0.1uA |
| 6mA | 1uA |
| 60mA | 10uA |
| 600mA | 100uA |
| 10A | 10mA |
| Range | Resolution |
|---|---|
| 600uA-6mA | 0.1-10uA |
| 60mA-600mA | 100uA |
| 10A | 10mA |
| Range | Resolution |
|---|---|
| 600 ohm | 0.1 ohm |
| 6k ohm | 1 ohm |
| 60k ohm | 10 ohm |
| 600k ohm | 100 ohm |
| 6M ohm | 1k ohm |
| 60M ohm | 10k ohm |
| Range | Resolution |
|---|---|
| 6nF | 1pF |
| 60nF | 10pF |
| 600nF | 100pF |
| 6uF | 1nF |
| 60uF | 10nF |
| 600uF | 100nF |
| 6mF | 1uF |
| Range | Resolution |
|---|---|
| 600uH | 0.1uH |
| 6mH | 1uH |
| 60mH | 10uH |
| 600mH | 100uH |
| 6H | 1mH |
| 60H | 10mH |
| 100H | 100mH |
| Range | Resolution |
|---|---|
| 600Hz | 0.1Hz |
| 6kHz | 1Hz |
| 60kHz | 10Hz |
| 600kHz | 100Hz |
| 6MHz | 1kHz |
| 20MHz | 10kHz |
| Range | Resolution |
|---|---|
| -40C to 1000C | 1C |
| -40F to 1832F | 1F |
| Aspect | UT61E+ | UT8803 |
|---|---|---|
| Protocol abstraction | Raw binary protocol | UCI SDK layer over binary protocol |
| USB bridge | CP2110 (0x10C4/0xEA80) | CP2110 (same VID/PID) |
| Command format | Binary: AB CD 03 cmd chk_hi chk_lo |
None — streaming, no host bytes |
| Response format | 19-byte binary frame | DMFRM struct (64-104 bytes) or 8-byte double |
| Mode encoding | Single mode byte (0x00-0x19) | 4-bit FuncCode + 2-bit ACDC + 8-bit Position |
| Range encoding | Single byte with 0x30 prefix | Embedded in Position code or separate |
| Flag encoding | 3 separate bytes (3 bits each) | Single 64-bit word with documented bit fields |
| Display value | 7 ASCII bytes, parse as float | 20-char string + IEEE 754 double |
| Meter type | Handheld (22000 counts) | Bench (6000 counts) |
| Extra features | NCV, LoZ, LPF, Peak Min/Max | Inductance L/Q/R, Capacitance C/D/R, SCR, SER/PAL |
| Software model | Qt app with plugin DLLs | UCI SDK-based application |
| Communication model | Polled (1 request per measurement) | Streaming (continuous, no trigger) |
| Baud rate | 9600 (confirmed) | 9600 (confirmed from uci.dll feature report 0x50) |
The raw wire protocol has been sufficiently reverse-engineered from the uci.dll decompilation to implement a direct driver:
Implementation steps:
- Open HID device matching VID 0x10C4, PID 0xEA80
- Send feature report 0x41: Enable UART (
[0x41, 0x01]) - Send feature report 0x50: Configure 9600/8N1
(
[0x50, 0x00, 0x00, 0x25, 0x80, 0x00, 0x03, 0x00, 0x00]) - Read HID interrupt reports continuously (no trigger byte)
- Reassemble 21-byte frames by finding
0xAB 0xCDheaders - Validate checksum (alternating-byte sum, BE at bytes 19-20)
- Parse mode (byte 4), range (byte 5 - 0x30), display (bytes 7-11), and flags (bytes 14-18)
Note: The :DISPlay:DATA? string in uci.dll is used by the
SCPI/text transport handler for oscilloscopes and signal generators,
not by the DMM binary parser. The UT8803 DMM uses a pure binary
protocol.
Given flags = 0x2013345 (from programming manual example, 50.23 Hz):
Low 32 bits: 0x02013345
D0-D3 = 0x5 = Frequency
D4-D5 = 0x0 = OFF (no AC/DC for frequency)
D6 = 0x0 = Not auto range (manual shows 0 here)
D7 = 0x0 = No overload
D8-D11 = 0x3 = Hz (Frequency)
D12-D14 = 0x3 = standard (no prefix)
D15 = 0x0 = Battery OK
D16 = 0x1 = USB communication active
D17 = 0x0 = Not under
D18 = 0x0 = Not over
D19 = 0x0 = No minus sign
D20-D23 = 0x0 = Position 0
D24-D27 = 0x2 = Scaling position 2
| Aspect | Status | Source |
|---|---|---|
| VID 0x10C4, PID 0xEA80 | [KNOWN] | Programming manual |
| USB HID interface | [KNOWN] | Programming manual |
| UCI SDK communication model | [KNOWN] | Programming manual + SDK |
data?; command (returns double) |
[KNOWN] | Programming manual |
disp?; command (returns DMFRM) |
[KNOWN] | Programming manual |
| DMFRM struct layout | [KNOWN] | Programming manual |
| 64-bit Flags word (all bit fields) | [KNOWN] | Programming manual |
| Functional coding table (14 functions) | [KNOWN] | Programming manual |
| UT8803 position coding (23 positions) | [KNOWN] | Programming manual |
| UT8802 position coding (range-specific) | [KNOWN] | Programming manual |
| Physical unit type/magnitude tables | [KNOWN] | Programming manual |
| AC/DC status coding | [KNOWN] | Programming manual |
| Models sharing protocol | [KNOWN] | Programming manual |
| UT805A uses serial (not HID) | [KNOWN] | Programming manual |
| Display: 6000 counts (5999 max) | [KNOWN] | User manual |
| All measurement ranges | [KNOWN] | User manual |
| Separate UT8802/UT8803 parsers in uci.dll | [VENDOR] | String extraction |
| Checksummed framing under UCI | [VENDOR] | String: Data check sum error! |
| Frame-based transport | [VENDOR] | String: [Frame] messages |
| HID API used directly (HidD_*) | [VENDOR] | String extraction |
| Frame header: AB CD (same as UT61E+) | [VENDOR] | Ghidra: parser condition *param_2 != -0x3255 |
| Byte 3 = 0x02 for measurement response | [VENDOR] | Ghidra: byte[3] != '\x02' |
| Frame size: 21 bytes | [VENDOR] | Ghidra: param_3 < 0x15 |
| Checksum: alternating-byte sum, BE at bytes 19-20 | [VENDOR] | Ghidra: checksum loop |
| Mode byte at offset 4, raw (0x00-0x16) | [VENDOR] | Ghidra: bVar1, 0x16 < bVar1 |
| Range byte at offset 5, 0x30 prefix | [VENDOR] | Ghidra: bVar5 - 0x30, 6 < bVar5 |
| UT8802 uses different header (0xAC, 8-byte frames) | [VENDOR] | Ghidra: discriminator |
| Inductance test freq: 0=100Hz, 1=1kHz | [VENDOR] | Ghidra: mode 0x0B-0x10 branch |
| Baud rate 9600 | [VENDOR] | Ghidra: feature report 0x50 with 0x00002580 |
| CP2110 init sequence (full) | [VENDOR] | Ghidra: FUN_1001d460 — 0x41, 0x50, SetNumInputBuffers (no UART write) |
| No trigger command (0x5A is CH9325-path only) | [VENDOR] | Ghidra: FUN_1002a4d0(param_2, 0x5a, 1000) only in FUN_1001d360 |
| Streaming model (not polled) | [VENDOR] | Ghidra: read loop with no per-measurement send |
| Display bytes: raw passthrough (no 0x30 prefix) | [VENDOR] | Ghidra: FUN_1001fce0 is buffer append |
| Flag byte-to-status-word mapping | [VENDOR] | Ghidra: bit shifts + format string verification |
| Byte 6: unused by parser (reserved/padding) | [VENDOR] | Ghidra: no access to byte offset 6 |
| Flag bytes 14-18: bits read per byte (§2.3); bytes 12-13 not read | [VENDOR] | Ghidra: bit extraction in FUN_1001e5f0 |
:DISPlay:DATA? is for oscilloscopes, not DMMs |
[VENDOR] | Ghidra: only in PLAIN-TEXT handler path |
| Maximum sampling rate | [UNVERIFIED] | Manual says 2-3 Hz refresh |
Answer: The UT8803 does not use per-measurement command encoding. Instead:
- The host sends nothing (the 0x5A trigger previously documented here belongs to the CH9325 init path — §1.3)
- The meter sends 21-byte binary frames at ~2-3 Hz
- The UCI library reads these frames in a loop;
uci_ReadX("data?;")anduci_ReadX("disp?;")return the most recent parsed measurement - There is no SCPI text protocol for DMMs — the
:DISPlay:DATA?string in uci.dll is part of the oscilloscope/signal generator handler, not the DMM path [VENDOR]
Answer: The meter streams continuously, with no trigger. The Ghidra decompilation shows:
FUN_1001d460performs no UART write (0x5A correction in §1.3)FUN_1001f170implements a read loop callingFUN_1002a380(which calls HID read) repeatedly, without sending any data between reads- The loop accumulates bytes and calls the parser when a complete frame is available
- There is no send operation in the read loop — only receives
The UCI API presents this as a polled interface (
uci_ReadXwith timeout), but the underlying transport is streaming. [VENDOR]
Answer: 9600 baud, confirmed from the Ghidra decompilation.
The function FUN_1001d460 constructs CP2110 feature report 0x50
with bytes [0x50, 0x00, 0x00, 0x25, 0x80, 0x00, 0x03, 0x00, 0x00].
Bytes 1-4 encode the baud rate as 0x00002580 = 9600 in big-endian,
matching the AN434 specification for CP2110 UART configuration.
The rest specifies 8N1 with no flow control. [VENDOR]
Answer: Two levels:
UCI API level (documented in programming manual):
data?;: 8-byte IEEE 754 double (raw measurement value)disp?;: DMFRM struct (MainDisp[20] + AuxDisp[20] + MainValue(8) + AuxValue(8) + Flags(8) = 64 bytes in ASCII mode)
Raw wire protocol (from Ghidra decompilation of uci.dll):
- 21-byte frames:
AB CD [byte2] 02 [mode] [range] [byte6] [disp x5] [flags0 x2] [flags1 x2] [flags2 x2] [flags3] [chk_hi] [chk_lo] - Mode byte at offset 4 (raw, 0x00-0x16)
- Range byte at offset 5 (has 0x30 prefix, mask with
- 0x30, max 6) - Byte 6: not accessed by parser (reserved/padding, included in checksum)
- Display bytes at offsets 7-11 (raw values, appended directly to string buffer — no 0x30 prefix stripping)
- Bytes 12-13: not accessed by parser (included in checksum)
- Bytes 14-18: status flags (hold, OL, sign, auto, min/max, inductance test frequency, diode direction; bit map in §2.3)
- Alternating-byte checksum at offsets 19-20
The UCI library parses these raw 21-byte frames and constructs the DMFRM struct and double value returned to the application. [VENDOR]
Answer: All models in the programming manual share the UCI protocol layer: UT632, UT803, UT804, UT805A, UT8802, UT8803 (and their N variants). They differ in:
- Transport: HID (0x1A86 for UT632/803/804, 0x10C4 for UT8802/8803) vs serial (UT805A)
- Position coding tables (model-specific)
- High 32 bits of Flags (UT8802/UT8803 only)
- Feature sets (D/Q/R sub-measurements only on UT8803)
[KNOWN]