---
title: "How to Build an Async GPS Server with Rust & W5500?"
url: "https://maker.wiznet.io/Benjamin/projects/raspberrypi-pico-eth-wiznet-gps-rs/"
markdown_url: "https://maker.wiznet.io/Benjamin/projects/raspberrypi-pico-eth-wiznet-gps-rs/md"
type: "UCC: User Created Content"
author: "Jin-san (zinntikumugai)"
author_url: "https://github.com/zinntikumugai/raspberrypi-pico-eth_wiznet_gps_rs"
editor: "WIZnet"
editor_url: "https://maker.wiznet.io/"
original_author: "Jin-san (zinntikumugai)"
original_url: "https://github.com/zinntikumugai/raspberrypi-pico-eth_wiznet_gps_rs"
license: "MIT license (MIT)"
published: "2026-01-14"
language: "en"
tags: ["Rust", "W5500", "GPS"]
hardware: ["WIZnet W5500", "Raspberry Pi Pico"]
likes: 0
views: 873
comments: 0
source: "WIZnet Makers (https://maker.wiznet.io/)"
---

# How to Build an Async GPS Server with Rust & W5500?

> Async Rust (Embassy) GPS server on RP2040 + W5500. Delivers real-time GPS data via TCP API with full NMEA parsing and memory-safe concurrent task handling.

Original author: Jin-san (zinntikumugai) (source: https://github.com/zinntikumugai/raspberrypi-pico-eth_wiznet_gps_rs)

## Components

- **WIZnet W5500** x 1 ([docs](https://docs.wiznet.io/Product/Chip/Ethernet/W5500))
- **Raspberry Pi Pico** x 1 ([docs](https://www.raspberrypi.com/documentation/microcontrollers/raspberry-pi-pico.html))

WIZnet parts: W5500 ([Datasheet](https://docs.wiznet.io/Product/Chip/Ethernet/W5500/datasheet?utm_source=maker&utm_medium=project&utm_campaign=w5500), [product hub](https://maker.wiznet.io/products/w5500/))

## Article

### **Project OverviewModern Rust + WIZnet Integration**

This project, created by Japanese developer **Jin-san (zinntikumugai)** in December 2025, demonstrates a modern approach to embedded development. It combines:

- **Rust's Memory Safety**: No null pointers, no buffer overflows, no data races

- **WIZnet's Hardware TCP/IP**: Reliable, deterministic network stack

- **Embassy Async Framework**: Efficient multitasking without RTOS overhead

The result is a **fully-functional GPS TCP Server** that runs on a $4 Raspberry Pi Pico, handling GPS parsing and network communication concurrently with minimal power consumption.

> ![](https://www.zinntikumugai.com/wp-content/uploads/2025/12/DSC01102-scaled.jpg.webp)
>
> *source :https://www.zinntikumugai.com/2025/12/31/-7157-/*

---

### **System Architecture**

![](https://maker.wiznet.io/upload/ckeditor5/637863843%5F1768812921%2Epng)

---

### **Why Rust + WIZnet?1. Software Safety (Rust)** Traditional C/C++ embedded code is prone to:

- Buffer overflows (security vulnerabilities)

- Null pointer dereferences (crashes)

- Data races (unpredictable behavior)

Rust eliminates these at **compile time**. This project's 400+ lines of GPS parsing code is guaranteed memory-safe.

**2. Network Stability (W5500)** Unlike software TCP/IP stacks (LwIP) that consume CPU cycles:

- W5500 handles TCP/IP in hardware

- Ping/ACK responses continue even when MCU is busy parsing GPS

- Deterministic latency for real-time applications

**3. Efficient Multitasking (Embassy)**

```plaintext
#[embassy_executor::main]
async fn main(spawner: Spawner) {
    // 4 concurrent tasks, no RTOS needed
    spawner.spawn(ethernet_task(w5500_runner)).unwrap();
    spawner.spawn(net_task(net_runner)).unwrap();
    spawner.spawn(tcp_server_task(stack)).unwrap();
    spawner.spawn(gps_task(uart_rx)).unwrap();
}
```

Each task runs independently. When waiting for data, the MCU sleeps, saving power.

---

### **Code Highlights1. Complete NMEA Parser (gps.rs)** This isn't a toy project - it includes a full NMEA 0183 parser:

```plaintext
// src/gps.rs - Full parsing of GPRMC and GPGGA sentences
fn parse_rmc(sentence: &str, gps_data: &mut GpsData) -> bool {
    let mut parts: heapless::Vec<&str, 16> = heapless::Vec::new();
    for part in sentence.split(',').take(16) {
        if parts.push(part).is_err() { break; }
    }
    
    // Status (A=Valid, V=Invalid)
    gps_data.fix_valid = *parts.get(2).unwrap_or(&"V") == "A";
    
    // Parse time (hhmmss.ss)
    if let Some(time_str) = parts.get(1) {
        gps_data.time.hour = parse_u8(&time_str[0..2]);
        gps_data.time.minute = parse_u8(&time_str[2..4]);
        gps_data.time.second = parse_u8(&time_str[4..6]);
    }
    // ... latitude, longitude, speed, date parsing
}
```

**2. Async GPS Task with Error Recovery**

```plaintext
pub async fn gps_task(mut uart_rx: UartRx<'static, Async>) {
    let mut error_count: u8 = 0;
    
    loop {
        match embassy_time::with_timeout(
            Duration::from_millis(100),
            uart_rx.read(&mut byte_buffer)
        ).await {
            Ok(Ok(_)) => {
                error_count = 0; // Reset on success
                // Parse NMEA sentence...
            }
            Ok(Err(_)) => {
                // UART overrun - recover gracefully
                error_count = error_count.saturating_add(1);
                if error_count > 3 {
                    Timer::after_millis(10).await;
                }
            }
            Err(_) => { /* Timeout - normal */ }
        }
    }
}
```

**3. JSON API Response**

```plaintext
// GET /api/gps returns:
{
  "gps": {
    "valid": true,
    "position": { "latitude": 35.681236, "longitude": 139.767125 },
    "time": { "utc": "12:34:56", "date": "2025-12-31" },
    "satellites": 8,
    "altitude": 45.5,
    "speed_knots": 0.0
  }
}
```

---

### **API Endpoints**

| Endpoint | Method | Description |
| --- | --- | --- |
| `/status` | GET | Server status |
| `/ping` | GET | Health check |
| `/api/gps` | GET | Current GPS data (JSON) |
| `/api/gps/mode/log` | GET | Output to logs only |
| `/api/gps/mode/tcp` | GET | Output to TCP only |
| `/api/gps/mode/both` | GET | Output to both |

---

### **FAQ**

#### **Q1: Why use wired Ethernet (W5500) instead of Wi-Fi?A**: **Reliability.**

- Wi-Fi is prone to interference, especially in industrial/outdoor environments

- W5500 provides hardware-level TCP/IP with deterministic timing

- The GPS task isn't interrupted by network maintenance (ARP, DHCP renewal, etc.)

For applications like vehicle tracking or asset monitoring, a wired or cellular backhaul is more reliable than Wi-Fi.

#### **Q2: Is Rust difficult for embedded development?A**: It has a learning curve, but **Embassy makes it accessible**.

**Traditional Embedded C:**

```c
while(1) {
    check_uart();  // Blocks everything else
    check_network();
}
```

**Rust with Embassy:**

```plaintext
async fn main(spawner: Spawner) {
    spawner.spawn(uart_task()).unwrap();
    spawner.spawn(network_task()).unwrap();
    // Both run concurrently!
}
```

The compiler catches memory issues before runtime. Once it compiles, it usually works.

#### **Q3: How complete is this project?A**: This is a **functional prototype**, not a production system.

**What's Implemented:**

- ✅ Full NMEA parser (GPRMC + GPGGA)

- ✅ Async TCP server with JSON API

- ✅ DHCP and static IP support

- ✅ Error recovery (UART overrun handling)

- ✅ Output mode switching (log/tcp/both)

**What's NOT Implemented:**

- ❌ TLS/HTTPS (would need external crate)

- ❌ Data logging to SD card

- ❌ Power-saving modes

- ❌ Web dashboard

This serves as an excellent **starting point** for Rust embedded development.

#### **Q4: What makes W5500 better than LwIP for this use case?A**: **CPU Offload.**

| Aspect | W5500 (Hardware) | LwIP (Software) |
| --- | --- | --- |
| **CPU Usage** | &lt;1% | 20-40% |
| **RAM** | ~2KB | ~30KB |
| **Latency** | Deterministic | Variable (jitter) |
| **GPS Impact** | None | May cause missed sentences |

When parsing 9600bps GPS data, you don't want the network stack stealing CPU cycles. W5500 handles all TCP/IP in hardware.

## **DOCUMENTS**

- **GitHub Repository**: <https://github.com/zinntikumugai/raspberrypi-pico-eth_wiznet_gps_rs>

- **Author's Website**: [https://www.zinntikumugai.com](https://www.zinntikumugai.com/)

- **Embassy Framework**: <https://embassy.dev/>

- **W5500 Rust Crate**: <https://crates.io/crates/embassy-net-wiznet>

- **WIZnet W5500 Datasheet**: <https://www.wiznet.io/product-item/w5500/>

---

## **(Korean Translation)**

### **프로젝트 개요**

일본 개발자 **Jin-san (zinntikumugai)** 이 2025년 12월에 제작한 이 프로젝트는 임베디드 개발의 현대적 접근 방식을 보여줍니다:

- **Rust의 메모리 안전성**: 널 포인터, 버퍼 오버플로우, 데이터 경쟁 없음

- **WIZnet의 하드웨어 TCP/IP**: 안정적이고 결정론적인 네트워크 스택

- **Embassy 비동기 프레임워크**: RTOS 오버헤드 없이 효율적인 멀티태스킹

결과물은 $4짜리 Raspberry Pi Pico에서 동작하는 **완전한 GPS TCP 서버**로, GPS 파싱과 네트워크 통신을 저전력으로 동시에 처리합니다.

![](https://www.zinntikumugai.com/wp-content/uploads/2025/12/DSC01102-scaled.jpg.webp)

*source :https://www.zinntikumugai.com/2025/12/31/-7157-/*

---

### **시스템 아키텍처**

![](https://maker.wiznet.io/upload/ckeditor5/637863843%5F1768813022%2Epng)

---

### **기술적 분석: 왜 Rust + WIZnet인가?1. 소프트웨어 안전성 (Rust)** 기존 C/C++ 임베디드 코드는 다음에 취약합니다:

- 버퍼 오버플로우 (보안 취약점)

- 널 포인터 역참조 (크래시)

- 데이터 경쟁 (예측 불가능한 동작)

Rust는 이러한 문제를 **컴파일 시점에** 제거합니다. 이 프로젝트의 400줄 이상의 GPS 파싱 코드는 메모리 안전이 보장됩니다.

**2. 네트워크 안정성 (W5500)** CPU 사이클을 소비하는 소프트웨어 TCP/IP 스택(LwIP)과 달리:

- W5500은 TCP/IP를 하드웨어에서 처리

- MCU가 GPS 파싱에 바쁠 때도 Ping/ACK 응답 지속

- 실시간 애플리케이션을 위한 결정론적 지연 시간

**3. 효율적인 멀티태스킹 (Embassy)**

```plaintext
#[embassy_executor::main]
async fn main(spawner: Spawner) {
    // RTOS 없이 4개의 동시 태스크
    spawner.spawn(ethernet_task(w5500_runner)).unwrap();
    spawner.spawn(net_task(net_runner)).unwrap();
    spawner.spawn(tcp_server_task(stack)).unwrap();
    spawner.spawn(gps_task(uart_rx)).unwrap();
}
```

각 태스크는 독립적으로 실행됩니다. 데이터를 기다릴 때 MCU는 절전 모드로 전환되어 전력을 절약합니다.

---

### **코드 하이라이트1. 완전한 NMEA 파서 (gps.rs - 403줄)** 단순한 예제가 아닙니다 - 완전한 NMEA 0183 파서가 포함되어 있습니다:

```plaintext
// src/gps.rs - GPRMC 및 GPGGA 문장 완전 파싱
fn parse_rmc(sentence: &str, gps_data: &mut GpsData) -> bool {
    // 상태 (A=유효, V=무효)
    gps_data.fix_valid = *parts.get(2).unwrap_or(&"V") == "A";
    
    // 시간 파싱 (hhmmss.ss)
    if let Some(time_str) = parts.get(1) {
        gps_data.time.hour = parse_u8(&time_str[0..2]);
        gps_data.time.minute = parse_u8(&time_str[2..4]);
        gps_data.time.second = parse_u8(&time_str[4..6]);
    }
    // ... 위도, 경도, 속도, 날짜 파싱
}
```

**2. 에러 복구가 포함된 비동기 GPS 태스크**

```plaintext
pub async fn gps_task(mut uart_rx: UartRx<'static, Async>) {
    let mut error_count: u8 = 0;
    
    loop {
        match embassy_time::with_timeout(
            Duration::from_millis(100),
            uart_rx.read(&mut byte_buffer)
        ).await {
            Ok(Ok(_)) => {
                error_count = 0; // 성공 시 리셋
                // NMEA 문장 파싱...
            }
            Ok(Err(_)) => {
                // UART 오버런 - 우아한 복구
                error_count = error_count.saturating_add(1);
                if error_count > 3 {
                    Timer::after_millis(10).await;
                }
            }
            Err(_) => { /* 타임아웃 - 정상 */ }
        }
    }
}
```

---

### **프로젝트 완성도 평가구현된 것:**

- ✅ 전체 NMEA 파서 (GPRMC + GPGGA)

- ✅ JSON API를 제공하는 비동기 TCP 서버

- ✅ DHCP 및 정적 IP 지원

- ✅ 에러 복구 (UART 오버런 처리)

- ✅ 출력 모드 전환 (log/tcp/both)

**구현되지 않은 것:**

- ❌ TLS/HTTPS (외부 crate 필요)

- ❌ SD 카드 데이터 로깅

- ❌ 절전 모드

- ❌ 웹 대시보드

이 프로젝트는 Rust 임베디드 개발을 위한 훌륭한 **시작점**입니다.

---

### **FAQ**

#### **Q1: 왜 Wi-Fi 대신 유선 이더넷(W5500)을 사용하나요?A**: **신뢰성.**

- Wi-Fi는 특히 산업/야외 환경에서 간섭에 취약합니다

- W5500은 결정론적 타이밍을 가진 하드웨어 수준 TCP/IP를 제공합니다

- GPS 태스크가 네트워크 유지 관리(ARP, DHCP 갱신 등)에 의해 중단되지 않습니다

차량 추적이나 자산 모니터링과 같은 응용 프로그램의 경우, 유선 또는 셀룰러 백홀이 Wi-Fi보다 더 안정적입니다.

#### **Q2: Rust는 임베디드 개발에 어렵나요?A**: 학습 곡선이 있지만, **Embassy가 접근성을 높여줍니다**.

**기존 임베디드 C:**

```c
while(1) {
    check_uart();  // 다른 모든 것을 블로킹
    check_network();
}
```

**Embassy를 사용한 Rust:**

```plaintext
async fn main(spawner: Spawner) {
    spawner.spawn(uart_task()).unwrap();
    spawner.spawn(network_task()).unwrap();
    // 둘 다 동시에 실행됩니다!
}
```

컴파일러가 런타임 전에 메모리 문제를 잡아냅니다. 컴파일되면 보통 작동합니다.

#### **Q3: W5500이 이 사용 사례에서 LwIP보다 나은 이유는?A**: **CPU 오프로드.**

| 측면 | W5500 (하드웨어) | LwIP (소프트웨어) |
| --- | --- | --- |
| **CPU 사용량** | &lt;1% | 20-40% |
| **RAM** | ~2KB | ~30KB |
| **지연 시간** | 결정론적 | 가변적 (지터) |
| **GPS 영향** | 없음 | 문장 누락 가능 |

9600bps GPS 데이터를 파싱할 때, 네트워크 스택이 CPU 사이클을 빼앗는 것을 원하지 않습니다. W5500은 모든 TCP/IP를 하드웨어에서 처리합니다.

---

**Note**: This project was created in December 2025. For the latest updates, check the GitHub repository.

---

Source: https://maker.wiznet.io/Benjamin/projects/raspberrypi-pico-eth-wiznet-gps-rs/
