Wiznet makers

Benjamin

Published January 14, 2026 © MIT license (MIT)

83 UCC

11 WCC

8 VAR

0 Contests

0 Followers

1 Following

Original Link

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.

COMPONENTS Hardware components

WIZnet - W5500

x 1


Raspberry Pi - Raspberry Pi Pico

x 1


PROJECT DESCRIPTION

Project Overview

Modern 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.

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

System Architecture


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)

#[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 Highlights

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

// 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

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

// 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

EndpointMethodDescription
/statusGETServer status
/pingGETHealth check
/api/gpsGETCurrent GPS data (JSON)
/api/gps/mode/logGETOutput to logs only
/api/gps/mode/tcpGETOutput to TCP only
/api/gps/mode/bothGETOutput 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:

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

Rust with Embassy:

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.

AspectW5500 (Hardware)LwIP (Software)
CPU Usage<1%20-40%
RAM~2KB~30KB
LatencyDeterministicVariable (jitter)
GPS ImpactNoneMay 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


(Korean Translation)

프로젝트 개요

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

  • Rust의 메모리 안전성: 널 포인터, 버퍼 오버플로우, 데이터 경쟁 없음
  • WIZnet의 하드웨어 TCP/IP: 안정적이고 결정론적인 네트워크 스택
  • Embassy 비동기 프레임워크: RTOS 오버헤드 없이 효율적인 멀티태스킹

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

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

시스템 아키텍처


기술적 분석: 왜 Rust + WIZnet인가?

1. 소프트웨어 안전성 (Rust) 기존 C/C++ 임베디드 코드는 다음에 취약합니다:

  • 버퍼 오버플로우 (보안 취약점)
  • 널 포인터 역참조 (크래시)
  • 데이터 경쟁 (예측 불가능한 동작)

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

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

  • W5500은 TCP/IP를 하드웨어에서 처리
  • MCU가 GPS 파싱에 바쁠 때도 Ping/ACK 응답 지속
  • 실시간 애플리케이션을 위한 결정론적 지연 시간

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

#[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 파서가 포함되어 있습니다:

// 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 태스크

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:

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

Embassy를 사용한 Rust:

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

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

Q3: W5500이 이 사용 사례에서 LwIP보다 나은 이유는?

A: CPU 오프로드.

측면W5500 (하드웨어)LwIP (소프트웨어)
CPU 사용량<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.

Documents
Comments Write