MicroPython firmware for the W5500-EVB-Pico2 board driving a UM980 RTK GNSS receiver.
Streams RTCM3 corrections to a Centipede NTRIP caster, monitors antenna health, and reports telemetry.
| Component | Part |
|---|---|
| MCU + Ethernet | WIZnet W5500-EVB-Pico2 (RP2350 + W5500) |
| GNSS receiver | Unicore UM980 (tri-band RTK) |
| GNSS antenna | Tri-band RTK antenna |
| IMU | ST LSM6DSV16X (vibration + tilt) |
| Environment | Sensirion SHT40 (internal temperature + humidity) |
| GPIO | Signal | Description |
|---|---|---|
| 0 | RTK_TX0 | UART0 TX → UM980 COM1 RX (control) |
| 1 | RTK_RX0 | UART0 RX ← UM980 COM1 TX (control) |
| 8 | RTK_RX1 | UART1 TX → UM980 COM2 RX (RTCM data) |
| 9 | RTK_TX1 | UART1 RX ← UM980 COM2 TX (RTCM data) |
| 12 | I2C_SDA | I2C bus — LSM6DSV16X + SHT40 |
| 13 | I2C_SCL | I2C bus — LSM6DSV16X + SHT40 |
| 16 | W5500_MISO | SPI — Ethernet |
| 17 | W5500_CS | SPI — Ethernet |
| 18 | W5500_SCK | SPI — Ethernet |
| 19 | W5500_MOSI | SPI — Ethernet |
| 20 | W5500_RST | Ethernet reset |
| 21 | W5500_INT | Ethernet interrupt |
| 23 | LED2_B | RGB LED2 blue |
| 24 | LED2_G | RGB LED2 green |
| 25 | LED2_R | RGB LED2 red |
| 27 | LED1_G | RGB LED1 green |
| 28 | LED1_R | RGB LED1 red |
| 29 | LED1_B | RGB LED1 blue |
Both LEDs are common-anode to 3V3 (active LOW: 0 = ON, 1 = OFF).
├── main.py # Main
├── base.py # All the logic and loop
├── config_manager.py # Local config, remote config download, .env loader
├── um980_config.py # UM980 driver and base station configuration
├── network_init.py # W5500 Ethernet initialization
├── ntrip_caster.py # NTRIP caster client (runs on core 1)
├── rgb_led_drv.py # RGB LED driver (PWM, common-anode)
├── lsm6dsv.py # LSM6DSV16X IMU driver
├── sht4x.py # SHT40 temperature/humidity reader
├── rtcm_decoder.py # RTCM3 frame decoder
├── rtcm_params.py # RTCM message list and intervals
├── wdt.py # Watchdog timer (shared feed_wdt())
├── config.json # Persisted local configuration (auto-generated)
└── .env # Cloud URLs (see below)
Secrets and environment-specific URLs. Loaded at boot before the remote config fetch.
Create this file on the device flash:
TELEMETRY_URL=http(s)://your_webserver/telemetry
CONFIG_URL=http(s)://your_webserver/config.jsonServed by the config server at CONFIG_URL. The device fetches this at every boot and merges it into its local config. Flash is only written if the values have changed.
Full schema:
{
"dhcp": true,
"ip": null,
"subnet": null,
"gateway": null,
"dns": null,
"base_mode": "time",
"base_duration": 60,
"base_pdop": 1,
"base_lat": 0,
"base_lon": 0,
"base_alt": 0,
"signal_group": 2,
"sbas_enabled": true,
"rtcm_interval": 1,
"ntrip_server": "crtk.net",
"ntrip_port": 2101,
"ntrip_mountpoint": null,
"ntrip_user": null,
"ntrip_password": null
}| Key | Type | Description |
|---|---|---|
dhcp |
bool | Use DHCP if true, static IP if false |
ip / subnet / gateway / dns |
string | Static IP settings (required if dhcp is false) |
base_mode |
"time" | "fixed" |
Self-survey or fixed known position |
base_duration |
int | Self-survey duration in seconds |
base_pdop |
float | Maximum PDOP for self-survey |
base_lat / base_lon |
float | Fixed position — lat/lon in decimal degrees (−90…90 / −180…180) |
base_alt |
float | Fixed position — altitude in metres (−30000…30000) |
signal_group |
int | UM980 signal group (2 = GPS+GLONASS+Galileo+BDS) |
sbas_enabled |
bool | Enable SBAS augmentation |
rtcm_interval |
int | RTCM message output interval in seconds |
ntrip_server |
string | NTRIP caster hostname |
ntrip_port |
int | NTRIP caster port |
ntrip_mountpoint |
string | NTRIP mountpoint name |
ntrip_user |
string | NTRIP username |
ntrip_password |
string | NTRIP password |
1. Load local config → determines DHCP vs static
2. Initialize Ethernet → W5500 via SPI
3. Download remote config → merges into local config, saves only if changed
4. Initialize UM980 → detect, check config, reconfigure if needed
5. Initialize sensors → LSM6DSV16X (I2C 0x6B) + SHT40 (I2C 0x44)
6. Start NTRIP thread → core 1, streams COM2 RTCM to caster
7. Main loop → see below
A non-blocking HTTP server runs on port 80 in the main loop, serving a live status page.
- Auto-refreshes every 45 seconds
- Displays LED status indicators with color and meaning
- Shows all sensor readings, NTRIP connection state, and antenna health
| Interval | Action |
|---|---|
| Every iteration (~1–2 s) | IMU check: vibration + tilt → update LED2 |
| Every 60 s | Sample SHT40 + AGC → rolling buffer (5 samples), update LED2 |
| Every 5 min | Average buffers, send telemetry, check NTRIP → update LED1 |
| Action | Effect |
|---|---|
| Hold BTN_USER (GPIO26) >3s | Reboot — re-downloads config on next boot |
Both LEDs flash WHITE while the button is held as confirmation feedback.
| Color | Meaning |
|---|---|
| 🟠 Orange | Booting / NTRIP reconnecting |
| 🔵 Cyan | Ethernet up, downloading config |
| 🟢 Green | Config downloaded, NTRIP connected |
| 🟡 Yellow | Ethernet up, remote config failed (using local) |
| 🔴 Red | Ethernet init failed / NTRIP reconnect failed |
| Color | Meaning |
|---|---|
| 🟠 Orange | Booting |
| 🟡 Yellow | UM980 initializing |
| 🔵 Cyan | UM980 up, checks pending |
| 🟢 Green | AGC good, level, no vibration |
| 🔵 Blue | AGC degraded on one or more bands |
| 🟣 Pink | Vibrating or tilted (IMU alarm) |
| 🔴 Red | UM980 init failed / AGC check error |
Every 5 minutes the device POSTs a JSON payload to TELEMETRY_URL.
The call is fire-and-forget — no retry, errors silently ignored.
{
"pitch_delta": 0.0098,
"temperature": 36.46,
"roll_delta": 0.0098,
"rms_max_delta": 0.2259,
"agc_l1": 0,
"agc_l2": 1.2,
"agc_l5": 5.6,
"humidity": 34.26,
"hw": "<hardware_unique_id>"
}| Field | Source | Description |
|---|---|---|
hw |
RP2350 unique_id() |
Device hardware ID |
rms_max_delta |
LSM6DSV16X | Max RMS angular rate delta (max−min) over 5min window (dps) |
pitch_delta / roll_delta |
LSM6DSV16X | Antenna tilt delta over 5min window, degrees (null when vibrating) |
temperature |
SHT40 | Ambient temperature in °C |
humidity |
SHT40 | Relative humidity in % (0–100) |
agc_l1 / agc_l2 / agc_l5 |
UM980 | AGC values per band (lower = better; −1 = unknown) |
(* ) rms_max_delta is the difference between the maximum and minimum RMS angular rate observed over the 5-minute window. A large delta indicates intermittent vibration events (e.g. wind gusts). env and AGC values are averaged over 5 one-minute samples before sending.
The WDT is initialized at boot with an 8-second timeout.
feed_wdt() is called inside all UART polling loops and at every main loop iteration.
The 10-second post-reboot wait after CONFIG SIGNALGROUP is fed in 500 ms increments.
To disable the WDT for debugging, set at the top of wdt.py:
DISABLE_WDT = TrueThe receiver averages its position over base_duration seconds before starting RTCM output.
Configured automatically via full_configuration() if the device is not already in base mode.
Call set_base_coordinates() after start_sensor() when the antenna position is already known:
um980.set_base_coordinates(
latitude = 42.50881200,
longitude = 1.53037157,
altitude = 1097.0,
station_id = 1 # optional, 0–4095, embedded in RTCM 1005/1006
)
um980.save_config()Coordinate limits: lat −90…90, lon −180…180, alt −30000…30000 m (signed decimal degrees).
Values outside these ranges are ignored.
All pure MicroPython — no external packages required.
| Module | Purpose |
|---|---|
machine.UART |
UM980 serial communication |
machine.I2C |
LSM6DSV16X + SHT40 |
machine.PWM |
LED dimming |
machine.WDT |
Watchdog timer |
ujson |
JSON encode/decode |
socket |
Raw TCP for HTTP (urequests not compatible with W5500 driver) |
_thread |
NTRIP client on core 1 |