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.
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
| 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:
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.
| Aspect | W5500 (Hardware) | LwIP (Software) |
|---|---|---|
| CPU Usage | <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
- 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 파싱과 네트워크 통신을 저전력으로 동시에 처리합니다.

시스템 아키텍처
기술적 분석: 왜 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.


