Drivers, firmware, and tools for the LMS Line Sensor — an 8-channel reflectance board for LEGO SPIKE, Robot Inventor, Technic, and EV3 with Pybricks, MicroPython, or MicroBlocks.
Current UF2: firmware/firmware_ch32_line_i2c_ur_v5.6.uf2
- Connect the line sensor to your PC with USB.
- Press RESET twice quickly until a CH32V UF2 drive appears.
- Drag the UF2 onto that drive and wait for it to disappear.
- If the NeoPixels do not scan, unplug and replug USB.
Full hardware notes: firmware/README.md.
| Goal | Path | Start here |
|---|---|---|
| Configure / calibrate / watch values on a PC | USB → web dashboard | linesensor.antonsmindstorms.com or web/ |
| Script the sensor from a PC | USB → desktop Python | usb/ |
| Hub wired straight to the sensor | UART uRemote → Pybricks | examples/spike-direct/ |
| Sensor on Qwiic, hub talks to LMS-ESP32 | Hub → LineSensorUR → LMS-ESP32 LineSensorURServer → I2C |
examples/lms-esp32-ur-server/ |
| MicroPython on LMS-ESP32 only (no hub RPC) | I2C → LineSensorI2C |
micropython/ |
| Blocks programming | MicroBlocks library | microblocks/ |
Measured cycle times for reading eight channels (indicative; wiring and hub load vary):
| Setup | ~cycle |
|---|---|
| LMS-ESP32 I2C @ 400 kHz | 1.3 ms |
| USB → PC (uRemote) | 1.5 ms |
| LMS-ESP32 I2C @ 100 kHz | 2.3 ms |
| EV3 UART (direct) | 4 ms (sensors() ~5.2 ms; that call reads 13 bytes) |
| SPIKE Prime UART (direct) | 8.2 ms |
| EV3 I2C | 10 ms |
Prime ← LMS-ESP32 LineSensorURServer (uRemote) ← I2C |
14 ms |
PUPRemote note: Prime ← PUPRemote ← LMS-ESP32 I2C can look like ~2.5 ms per hub poll, while the ESP32 side takes ~5 ms to push a new sample. The hub therefore often re-reads the same values before they update.
Most of the I2C vs uRemote gap is framing overhead: uRemote sends about 33 bytes on the wire for 13 payload bytes; plain I2C and a PUPRemote 8B channel do not add that framing.
Open linesensor.antonsmindstorms.com in Chrome or Edge (Web Serial). Close any other app using the USB serial port first. See web/README.md.
cd usb
python -m pip install -r requirements.txt
python read_line_sensor.pyUpload micropython/line_sensor_pybricks.py into your Pybricks project.
On current LMS-ESP32 firmware the line sensor driver is often pre-installed. Otherwise install with ViperIDE:
- Open ViperIDE → package manager → custom package.
- Paste
https://github.com/antonsmindstorms/lms-line-sensor.gitand install.
For hub ↔ ESP32 ↔ I2C sensor, also copy micropython/line_sensor_ur_server.py (uremote is frozen on current LMS-ESP32 firmware).
Open the MicroBlocks editor and drag microblocks/LMS Line Sensor.ubl into the window.
from time import sleep
from line_sensor import LineSensorI2C
sensor = LineSensorI2C(scl_pin=4, sda_pin=5, device_addr=51)
sensor.ir_power(True)
sensor.load_calibration()
sensor.mode_calibrated()
while True:
print(sensor.position(), sensor.derivative(), sensor.shape())
sleep(0.1)from pybricks.parameters import Port
from line_sensor_pybricks import LineSensorUR
sensor = LineSensorUR(Port.B)
sensor.ir_power(True)
sensor.load_calibration()
sensor.mode_calibrated()
while True:
print(sensor.position(), sensor.derivative(), sensor.shape())Shape is an ASCII character:
SHAPE_NONE = ' '
SHAPE_STRAIGHT = '|'
SHAPE_T = 'T'
SHAPE_L_LEFT = '<'
SHAPE_L_RIGHT = '>'
SHAPE_Y = 'Y'
LineSensorI2C and LineSensorUR share one high-level API. Prefer these names in new code:
| Method | Meaning |
|---|---|
position() |
Line position (−128…127, 0 = center) |
derivative() |
Position derivative |
shape() |
Shape as an ASCII character |
sensors() |
Eight raw or calibrated values |
position_derivative_shape() |
(position, derivative, shape) |
data(*indices) |
Full packet or selected fields |
mode_raw() / mode_calibrated() |
Acquisition mode |
calibrate(duration=5) |
Calibrate and save to EEPROM |
ir_power(True/False) |
IR emitter |
leds(mode) |
Automatic NeoPixel mode |
save_calibration() / load_calibration() |
EEPROM calibration |
Compatibility aliases available on both classes: set_emitter (= ir_power) and get_cal_min / get_cal_max / set_cal_min / set_cal_max. LineSensorI2C also accepts rgb_mode / led_mode (= leds), set_neopixel (= neopixel) and get_min / get_max / set_min / set_max.
LineSensorI2C also has start_calibration() and fine-grained calibration helpers.
Line polarity: firmware 5.6 defaults to a black line. Use blackline(False) for a white line (then recalibrate); the setting is not stored in EEPROM. Both classes provide it, and LineSensorUR.blackline() without an argument reads the current value.
LineSensorURServer (LMS-ESP32) subclasses frozen uRemote and exposes the native uRemote command names over UART while talking to the sensor on I2C.
examples/spike-direct/— SPIKE hub, direct UARTexamples/lms-esp32-ur-server/— hub + LMS-ESP32 + I2C sensorexamples/spike-pup-test/— SPIKE + LMS-ESP32 + I2C line sensor over PUPRemoteexamples/spike-robocup/— SPIKE + LMS-ESP32 with PUPRemoteexamples/ev3-robocup/— EV3 + LMS-ESP32examples/pybricks_smoke_test.py— quick UART bring-up
- Published docs: docs.antonsmindstorms.com … lms-line-sensor
- Firmware / registers / packets:
firmware/README.md(canonical) - Sphinx sources:
docs/
sphinx-build -b html docs docs/_build/html- Main MicroPython source:
micropython/line_sensor.py - Pybricks bundle:
tools/generate_line_sensor_pybricks.py→micropython/line_sensor_pybricks.py - LMS-ESP32 uRemote server:
micropython/line_sensor_ur_server.py - Sphinx mocks the MicroPython
machinemodule for desktop builds - Tests need no hardware:
python -m pip install pytest && python -m pytest(seetests/README.md) - PyPI publish: bump version in
micropython/line_sensor.pyandpackage.json, thenrm -rf ./dist && python -m build && twine upload dist/*
