Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save Muhammad-Yunus/0a54e58c25b47192ad8ebc2d7d2fa545 to your computer and use it in GitHub Desktop.

Select an option

Save Muhammad-Yunus/0a54e58c25b47192ad8ebc2d7d2fa545 to your computer and use it in GitHub Desktop.
https://www.youtube.com/shorts/goCs0tH-d78
github - kaiaai/LDS
Your LIDAR uses the Delta-2A / 3irobotics protocol (same as LDS08RR in the kaiaai/LDS repo), NOT the Neato XV11 protocol.
Key issues with the old code:
- Start byte is 0xAA, not 0xFA. The 0xFA bytes you were seeing are just random data/distance values
- Packet size is variable (typically 72 bytes: header says packet_length=70 + 2 for the start/length fields), not a fixed 42 bytes
- You were reading misaligned slices of the actual data stream
Protocol structure (confirmed from your data matching AA 00 46 01 61 AD 00 3E):
┌────────┬───────────────────┬──────────────────┬─────────────────────────────┐
│ Offset │ Field │ Your data │ Meaning │
├────────┼───────────────────┼──────────────────┼─────────────────────────────┤
│ 0 │ Start byte │ AA │ Packet start │
├────────┼───────────────────┼──────────────────┼─────────────────────────────┤
│ 1-2 │ Packet length │ 00 46 (70) │ Total - 2 │
├────────┼───────────────────┼──────────────────┼─────────────────────────────┤
│ 3 │ Protocol version │ 01 │ Always 0x01 │
├────────┼───────────────────┼──────────────────┼─────────────────────────────┤
│ 4 │ Packet type │ 61 │ Always 0x61 │
├────────┼───────────────────┼──────────────────┼─────────────────────────────┤
│ 5 │ Data type │ AD │ RPM + measurements │
├────────┼───────────────────┼──────────────────┼─────────────────────────────┤
│ 6-7 │ Data length │ 00 3E (62) │ n_samples = (62-5)/3 = 19 │
├────────┼───────────────────┼──────────────────┼─────────────────────────────┤
│ 8 │ Scan freq ×20 │ 85/86 │ ~4.25 Hz │
├────────┼───────────────────┼──────────────────┼─────────────────────────────┤
│ 9-10 │ Offset angle ×100 │ varies │ Signed, BE │
├────────┼───────────────────┼──────────────────┼─────────────────────────────┤
│ 11-12 │ Start angle ×100 │ varies │ e.g. 08 CA = 22.50° │
├────────┼───────────────────┼──────────────────┼─────────────────────────────┤
│ 13+ │ Samples (×19) │ 3 bytes each │ [quality, dist_hi, dist_lo] │
├────────┼───────────────────┼──────────────────┼─────────────────────────────┤
│ last 2 │ Checksum │ sum of all bytes │ Big-endian │
└────────┴───────────────────┴──────────────────┴─────────────────────────────┘
Each measurement sample: distance_mm = (dist_hi<<8 | dist_lo) * 0.25
The updated script syncs on 0xAA, validates the header constants and checksum, decodes all 19 samples per packet with proper angles,
and plots a polar scan like your previous version. Try running it and let me know if the data looks correct!
@Muhammad-Yunus

Copy link
Copy Markdown
Author

buka sample code examples/all_lidars_lds02rr_esp32.ino

comment #define XIAOMI_LDS02RR
uncomment #define _3IROBOTIX_DELTA_2A_115200

pin nya sama

@Muhammad-Yunus

Copy link
Copy Markdown
Author

Python XIAOMI_LDS02RR Visualizer (watch https://www.youtube.com/shorts/goCs0tH-d78)

import serial
import struct
import math
import matplotlib.pyplot as plt

PORT = "COM9"
BAUD = 115200

# Delta-2A / 3irobotics LDS protocol (used by Xiaomi MVXVC01-JG LDS)
START_BYTE = 0xAA
PROTOCOL_VERSION = 0x01
PACKET_TYPE = 0x61
DATA_TYPE_RPM_AND_MEAS = 0xAD
DATA_TYPE_RPM_ONLY = 0xAE
HEADER_SIZE = 13
PACKETS_PER_SCAN = 16


def validate_checksum(pkt):
    data_sum = sum(pkt[:-2]) & 0xFFFF
    chk_hi, chk_lo = pkt[-2], pkt[-1]
    expected = (chk_hi << 8) | chk_lo
    expected_with_self = (expected + chk_hi + chk_lo) & 0xFFFF
    return (data_sum + chk_hi + chk_lo) & 0xFFFF == expected_with_self


def decode_packet(pkt):
    """Decode a Delta-2A LIDAR packet. Returns list of (angle_deg, distance_mm, quality)."""
    data_type = pkt[5]
    data_length = (pkt[6] << 8) | pkt[7]
    scan_freq_x20 = pkt[8]
    start_angle_x100 = (pkt[11] << 8) | pkt[12]

    scan_freq_hz = scan_freq_x20 * 0.05
    start_angle = start_angle_x100 * 0.01

    if data_type != DATA_TYPE_RPM_AND_MEAS:
        return [], scan_freq_hz, start_angle

    n_samples = (data_length - 5) // 3
    angle_step = 360.0 / (PACKETS_PER_SCAN * n_samples)

    points = []
    for i in range(n_samples):
        offset = HEADER_SIZE + i * 3
        if offset + 3 > len(pkt) - 2:
            break
        quality = pkt[offset]
        dist_x4 = (pkt[offset + 1] << 8) | pkt[offset + 2]
        distance_mm = dist_x4 * 0.25
        angle = start_angle + i * angle_step
        points.append((angle, distance_mm, quality))

    return points, scan_freq_hz, start_angle


ser = serial.Serial(PORT, BAUD)
buffer = bytearray()

# Plotting setup
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111, projection='polar')

all_angles = []
all_dists = []
last_start_angle = -1

print("Listening for LIDAR packets on", PORT, "...")

while True:
    incoming = ser.read(ser.in_waiting or 1)
    buffer += incoming

    while True:
        start = buffer.find(bytes([START_BYTE]))
        if start == -1:
            buffer.clear()
            break

        if start > 0:
            buffer = buffer[start:]

        if len(buffer) < 3:
            break

        packet_length = (buffer[1] << 8) | buffer[2]
        total_size = packet_length + 2

        if packet_length < 10 or packet_length > 200:
            buffer = buffer[1:]
            continue

        if len(buffer) < total_size:
            break

        pkt = buffer[:total_size]

        if (len(pkt) >= 6 and
                pkt[3] == PROTOCOL_VERSION and
                pkt[4] == PACKET_TYPE and
                pkt[5] in (DATA_TYPE_RPM_AND_MEAS, DATA_TYPE_RPM_ONLY)):

            if validate_checksum(pkt):
                points, freq, start_angle = decode_packet(pkt)

                # Detect new scan (angle wraps back to 0)
                if start_angle < last_start_angle and len(all_angles) > 50:
                    ax.clear()
                    ax.set_rmax(6000)
                    ax.set_title(f"LIDAR Scan ({freq:.1f} Hz)")
                    ax.scatter(all_angles, all_dists, s=2, c='blue')
                    plt.draw()
                    plt.pause(0.001)
                    all_angles.clear()
                    all_dists.clear()

                last_start_angle = start_angle

                for angle, dist, quality in points:
                    if dist > 0 and quality > 0:
                        all_angles.append(math.radians(angle))
                        all_dists.append(dist)

                        print(f"  angle={angle:7.2f}°  dist={dist:8.2f} mm  q={quality}")

                buffer = buffer[total_size:]
            else:
                buffer = buffer[1:]
        else:
            buffer = buffer[1:]
```

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment