You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tinySA/PROTOCOL.md

16 KiB

tinyGTC Remote Control & Screen Mirroring Protocol

This document describes the serial communication protocol used between the tinyGTC-App Windows application and a compatible device. It is intended to enable third-party device firmware to be controlled and mirrored by this application.


1. USB Device Identification

The application discovers devices on the USB bus using the Windows Setup API. A device is considered compatible when both of the following match:

Property Value
USB Vendor ID (VID) 0x0483 (STMicroelectronics)
USB Product ID (PID) 0x5741 (tinyGTC) or 0x5740 (tinySA)

The device must expose a virtual COM port (VCP).


2. Serial Port Parameters

Parameter Value
Data bits 8
Stop bits 1
Parity None
Flow control None

Timeouts configured by the host:

Timeout Value
ReadIntervalTimeout 50 ms
ReadTotalTimeoutConstant 500 ms
ReadTotalTimeoutMultiplier 10 ms/byte
WriteTotalTimeoutConstant 500 ms
WriteTotalTimeoutMultiplier 10 ms/byte

3. Initialization Sequence

Upon connecting, the host performs the following steps:

  1. Flush any data already in the device's RX buffer.
  2. Wait 100 ms.
  3. Send scpi off\r (disables SCPI mode on the device).
  4. Wait 100 ms.
  5. Send capt\r\n (requests the first full screen capture).

4. Host → Device Commands

All commands are plain ASCII text terminated with \r (carriage return). A trailing \n (line feed) is also accepted. There is no binary encoding on the host-to-device path.

Command Description
scpi off\r Disable SCPI/UART command mode on the device
capt\r Request a full screen capture (device replies asynchronously; see Section 6.1)
refresh on\r Enable automatic push of incremental screen updates
refresh rle\r Enable automatic push of incremental screen updates using rle encoding
refresh off\r Disable automatic push of incremental screen updates
touch X Y\r Simulate a touch-screen press at pixel coordinate (X, Y)
release\r Release a previous touch press

In case of a tinySA the host first sends 'refresh rle' en check if the command is understood. If not a 'refresh on' command is send and the pixel transfers will be done without rle. If the 'refresh rle' command is accepted it is assumed the tinySA will use rle for pixel transfer A tinyGTC will always use rle when transferring pixels.

Touch Coordinate System

  • Origin (0, 0) is the top-left corner of the display.
  • X increases to the right; Y increases downward.
  • Coordinates are in device pixels (not scaled), matching the display dimensions (see Section 8).
  • The host converts scaled screen-window coordinates to device pixels before sending.

Command Interaction Notes

  • touch and release are typically used together: send touch X Y\r, wait ≥100 ms, then send release\r.
  • refresh on causes the device to continuously push bulk, fill, and flip events as the screen changes.
  • refresh off stops the push; the host can still request individual full captures with capt.

5. Device → Host Protocol Overview

The device communicates back to the host using a line-based event model:

  1. The device sends a text event line terminated with \r\n.
  2. Immediately following the event line, it sends binary payload data (or no data, for lines that are purely informational).

The host reads one \r\n-terminated line, inspects it for known keywords, and then reads the binary payload appropriate to that keyword.

Event Keywords

The host performs a substring search on each received line:

Substring present in line Event type Binary payload follows?
"apt" or "ture" Full screen capture Yes see Section 6.1
"ulk" Bulk region update Yes see Section 6.2
"ill" Fill region update Yes see Section 6.3
"lip" Flip (rotation change) Yes see Section 6.4
(anything else) Informational / ignored No

Note: The keyword matching is case-sensitive and uses partial substring matching, so the device is free to include additional text in the line (e.g. "> capture\r\n" matches "apt" and "ture"; "> bulk\r\n" matches "ulk").


6. Device → Host Binary Payloads

6.1 Full Screen Capture

Trigger: Event line containing "apt" or "ture".

Payload: Exactly width × height pixels, each pixel encoded as a 2-byte little-endian word in the compact RLE pixel format described in Section 7.

  • Pixels are ordered left-to-right, top-to-bottom (row-major).
  • Rotation is fixed at 232 (standard landscape orientation) for full captures.
  • The total uncompressed pixel count is always width × height; the RLE encoding means the byte count may be less, but the pixel count is exact.

Example for a 480×320 display: the host reads exactly 480 × 320 = 153,600 decoded pixel values (each returned by one invocation of the pixel decoder in Section 7).


6.2 Bulk Region Update

Trigger: Event line containing "ulk".

Payload structure:

Bytes Type Description
2 uint16_t little-endian X left edge of the region
2 uint16_t little-endian Y top edge of the region
2 uint16_t little-endian W width of the region
2 uint16_t little-endian H height of the region
W × H × ~2 bytes compact pixel stream Pixel data in Section 7 format
  • Pixels are ordered row-by-row within the bounding rectangle.
  • The region must be fully within the display: x + w ≤ width, y + h ≤ height.
  • The host decodes exactly W × H pixels from the compact stream and writes them into the frame buffer at the correct positions, respecting the current rotation (Section 8).

6.3 Fill Region Update

Trigger: Event line containing "ill".

Payload structure:

Bytes Type Description
2 uint16_t little-endian X
2 uint16_t little-endian Y
2 uint16_t little-endian W
2 uint16_t little-endian H
2 uint16_t big-endian Fill color (standard RGB565, MSB first)
2 raw bytes End marker: 0x00 0x40
  • The host fills the entire W × H rectangle with the given color.
  • The end marker bytes are 0x00 0x40 (this is the byte-swapped representation of RUN_END = 0x4000).

6.4 Flip (Rotation Change)

Trigger: Event line containing "lip".

Payload structure:

Bytes Type Description
2 uint16_t little-endian X
2 uint16_t little-endian Y
2 uint16_t little-endian W
2 uint16_t little-endian H
2 uint16_t little-endian New rotation value
2 raw bytes End marker: 0x00 0x40
  • The rotation value changes the mapping from incoming pixel data to the frame buffer (see Section 8).
  • The end marker bytes are 0x00 0x40.

7. Compact Pixel Encoding (Inline RLE)

All pixel streams (full captures and bulk regions) use the same compact 2-byte-per-word encoding. Each 2-byte word encodes one color value plus a repeat count, packing both into the bits of a standard 16-bit RGB565 word.

7.1 Bit Layout

The 16-bit word is received little-endian (low byte first over the wire):

Bit:   [15][14][13][12][11][10][ 9][ 8][ 7][ 6][ 5][ 4][ 3][ 2][ 1][ 0]
Role:    c   c   c   B   B   B   c   c   R   R   R   c   c   G   G   G
  • Uppercase = color bits: B (blue, 3 bits), R (red, 3 bits), G (green, 3 bits)
  • Lowercase c = repeat-count bits (7 bits total)

Mask constants:

REPEAT_MASK = 0xe318   (bits [15:13], [9:8], [4:3])
COLOR_MASK  = 0x1ce7   (bits [12:10], [7:5], [2:0])

7.2 Decoding Algorithm

Function ReadPixel() → rgb565_pixel:

    // If there are pending repeats, return the stored color
    if remaining > 0:
        remaining -= 1
        return current_color

    // Read 2 bytes little-endian from the serial stream
    byte0, byte1 ← read 2 bytes
    word ← byte0 | (byte1 << 8)

    // Extract 7-bit repeat count from the interleaved count bits
    remaining ← ((word & 0xe000) >> 9)    // bits [15:13] → count bits [6:4]
               | ((word & 0x0300) >> 6)    // bits [9:8]   → count bits [3:2]
               | ((word & 0x0018) >> 3)    // bits [4:3]   → count bits [1:0]
    // remaining is now 0..127

    // Fill the count bit positions with 1s to complete the RGB565 word
    word |= 0xe318

    // Byte-swap to produce standard big-endian RGB565
    pixel ← ((word & 0x00ff) << 8) | ((word & 0xff00) >> 8)

    current_color ← pixel
    return pixel

Pixel delivery: the first call delivers the pixel once; subsequent calls deliver the same pixel remaining more times without reading from the stream. The total delivery count per encoded word is 1 + remaining (i.e. 1 to 128).

7.3 Color Fidelity Note

The encoding trades 7 color LSBs for the run-length count, so each channel carries only 3 significant bits out of the RGB565 5/6/5 bit allocation. The reconstruction forces the count-bit positions to 1, so the decoded RGB565 color has certain bits always set (0xe318 after the byte-swap), resulting in slight color quantization compared to an uncompressed RGB565 stream. The host applies no further correction.

7.4 Encoding a Pixel Word (for device firmware implementors)

Function EncodePixel(rgb565_pixel, repeat_count) → 2 bytes:
    // repeat_count: 0 = send once, 127 = send 128 times
    // rgb565_pixel is standard RGB565 (big-endian convention)

    // Byte-swap to match wire format
    word ← ((rgb565_pixel & 0x00ff) << 8) | ((rgb565_pixel & 0xff00) >> 8)

    // Clear the count bit positions and insert count
    word &= COLOR_MASK   // 0x1ce7  keep only color bits
    word |= ( (((repeat_count) << 9) & 0xe000)    // count bits [6:4] → [15:13]
             |(((repeat_count) << 6) & 0x0300)    // count bits [3:2] → [9:8]
             |(((repeat_count) << 3) & 0x0018) )  // count bits [1:0] → [4:3]

    // Transmit little-endian
    byte0 ← word & 0xff
    byte1 ← (word >> 8) & 0xff
    send byte0, byte1

Important: When encoding, first byte-swap the pixel (so it is stored in the same non-standard orientation that the receiver expects), then clear color bits, then insert the count. The color bits stored in the wire format are the 3 MSBs of each channel from the original RGB565 value (after the byte-swap operation).


8. Rotation / Coordinate Mapping

The rotation value (set by flip events) controls how bulk pixel data is written into the frame buffer.

Rotation value Meaning Pixel mapping
232 Standard landscape framebuffer[(y + row) * width + (x + col)] = pixel (row-major, direct)
136 Rotated (portrait) destX = y + row; destY = height (x + col); framebuffer[destY * width + destX] = pixel

The full-screen capture and fill operations always use rotation 232 regardless of the current rotation state.


9. Supported Device Screen Sizes

The host must know the display dimensions in advance (configured via command-line argument or defaulting to tinyGTC size). The device does not announce its dimensions over the protocol.

Device Width (pixels) Height (pixels)
tinyGTC (default) 480 320
tinyGTC Ultra 480 320
tinySA Ultra 480 320
NanoVNA-H4 480 320
tinySA 320 240
NanoVNA-H 320 240

10. Frame Buffer Format

The host maintains an in-memory frame buffer of width × height 16-bit RGB565 values (stored as uint32_t but only the lower 16 bits are used). The host converts to 32-bit BGRA for display:

R = ((rgb565 & 0xF800) >> 11) << 3   // 5-bit red  → 8-bit
G = ((rgb565 & 0x07E0) >> 5)  << 2   // 6-bit green → 8-bit
B =  (rgb565 & 0x001F)        << 3   // 5-bit blue  → 8-bit

Optional invert mode: R = 255 R, G = 255 G, B = 255 B.


11. Connection Management

  • The host auto-discovers the first matching USB VCP at startup.
  • Connection health is checked every 1000 ms via ClearCommError; hardware errors (CE_BREAK, CE_FRAME, CE_OVERRUN, CE_RXOVER, CE_TXFULL) indicate lost connection.
  • On disconnect, the host waits 500 ms then attempts to re-open and re-initialize the device.
  • On re-connection, the host does not automatically re-enable refresh on; the device resumes the capture stream via capt.
  • On clean disconnect, the host sends refresh off\r before closing the port.

12. Protocol State Machine Summary

HOST                                          DEVICE
 |                                               |
 |── scpi off\r ──────────────────────────────►  |  (disable SCPI)
 |── capt\r\n ─────────────────────────────────► |  (request first frame)
 |                                               |
 | ◄──────────── "> capture\r\n" ────────────── |  (event line)
 | ◄──── width×height pixels (compact RLE) ───── |  (pixel stream)
 |                                               |
 |  [user enables auto-refresh]                  |
 |── refresh on\r ──────────────────────────────► |
 |                                               |
 | ◄──────────── "> bulk\r\n" ────────────────── |  (dirty region)
 | ◄─── 8-byte header + W×H pixels ────────────  |
 |                                               |
 | ◄──────────── "> fill\r\n" ────────────────── |  (solid fill)
 | ◄─── 8-byte header + 2-byte color + 2-byte end marker ─── |
 |                                               |
 | ◄──────────── "> flip\r\n" ────────────────── |  (rotation)
 | ◄─── 8-byte header + 2-byte rotation + 2-byte end marker ─|
 |                                               |
 |  [user clicks on mirrored screen]             |
 |── touch 123 87\r ────────────────────────────► |
 |  [wait ~100ms]                                |
 |── release\r ─────────────────────────────────► |
 |                                               |
 |── refresh off\r ─────────────────────────────► |  (on disconnect)

13. Implementing a Compatible Device

To make a device controllable by this application, the firmware must:

  1. Enumerate as a USB CDC VCP with VID=0x0483, appropriate PID, and bus-reported device description containing "tinyGTC Command port".

  2. Accept the serial commands in Section 4 on the VCP UART at 115200 8N1.

  3. On scpi off\r: disable any SCPI mode and prepare for the remote protocol.

  4. On capt\r: send the event line "> capture\r\n" (or any line containing "apt" or "ture"), immediately followed by the full pixel stream (Section 6.1) using the compact pixel encoding (Section 7).

  5. For incremental updates (when refresh on is active): for each display region that changes, send one of:

    • A line containing "ulk" + 8-byte header + pixel stream (Section 6.2) for arbitrary content.
    • A line containing "ill" + 8-byte header + fill color + end marker (Section 6.3) for solid-color fills.
    • A line containing "lip" + 8-byte header + rotation + end marker (Section 6.4) when the display rotation changes.
  6. On touch X Y\r: simulate a touchscreen press at the given coordinates.

  7. On release\r: release the simulated touch.

Powered by TurnKey Linux.