---
title: "warp-core"
url: "https://maker.wiznet.io/scott/projects/warp-core/"
markdown_url: "https://maker.wiznet.io/scott/projects/warp-core/md"
type: "UCC: User Created Content"
author: "qhandley (Oregon State University hybrid rocket team)"
author_url: "https://github.com/qhandley/warp-core"
editor: "WIZnet"
editor_url: "https://maker.wiznet.io/"
original_author: "qhandley (Oregon State University hybrid rocket team)"
original_url: "https://github.com/qhandley/warp-core"
published: "2026-10-20"
language: "en"
hardware: ["WIZnet W5500"]
likes: 0
views: 7
comments: 0
source: "WIZnet Makers (https://maker.wiznet.io/)"
---

# warp-core

> warp-core: A Hybrid Rocket Launch Controller Built on an 8-bit AVR and the W5500

Original author: qhandley (Oregon State University hybrid rocket team) (source: https://github.com/qhandley/warp-core)

## Components

- **WIZnet W5500** x 1 ([docs](https://docs.wiznet.io/Product/Chip/Ethernet/W5500))

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

## Summary

**warp-core** is the **launch control firmware** that an ECE capstone team built for the Oregon State University (OSU) hybrid rocket team. It receives JSON commands from a mission control PC over the **hardware TCP socket of the WIZnet W5500** and hands them to **FreeRTOS** tasks running on an **ATmega1284p**. It is a useful reference because it shows that an 8-bit MCU with 16 KB of SRAM can run a remote control link in a small amount of code, with no TCP/IP stack of its own.

## Overview

A hybrid rocket uses a **solid fuel** together with a **liquid or gaseous oxidizer**. The oxidizer is usually loaded at the pad shortly before launch. Once filling starts, people must stay far from the rocket and operate the valves and ignition remotely.

warp-core is the **pad-side brain** of that remote operation. Its README names two challenges unique to hybrid rockets: **pre-flight remote oxidizer filling** and **ignition timing matched to oxidizer injection**.

- **Developers**: Quinn Handley, Thomas Prihoda

- **Timeline**: first commit 2019-10-22, last commit 2020-03-08, 48 commits

- **Environment**: custom AVR controller, FreeRTOS AVR port, avr-gcc 5.4.0

→ GitHub: <https://github.com/qhandley/warp-core>

## Why Remote Control Is Required

### How the Repository Was Actually Used

- **Confirmed**: According to the capstone page, the subscale system (T3st Core) supported **many fuel grain tests**

- **Full-scale launch not completed**: The phase 3 full-scale launch was **postponed due to COVID-19**. **Spaceport America Cup 2020**, planned for the same year, was also canceled in March 2020

- **Activity**: 1 star, 1 fork. The repository was created for a university capstone and has not been updated since

→ Capstone project page: <https://sites.google.com/oregonstate.edu/ece44x201932/home>
→ Competition cancellation notice (ESRA, 2020-03-24, archived copy): <http://web.archive.org/web/20250914092355/https://www.soundingrocket.org/news1/2020-spaceport-america-cup-cancelled>

### What the Competition Rules Require

The Design, Test & Evaluation Guide (**DTEG**) from **ESRA**, which runs the student rocketry competition, sets these requirements for hybrid propulsion and launch equipment.

| Section | Requirement |
| --- | --- |
| **5.2.2.3** | **Nitrous oxide (N₂O) is the only** allowed hybrid oxidizer |
| **5.10.1** | Hybrid and liquid systems must be able to **remotely vent** all propellants |
| **11.3.4** | Hybrid launch controllers must include **oxidizer fill level instrumentation** |
| **11.5.1** | Launch control systems must be **single fault tolerant** (key or jumper interlock) |
| **11.5.3** | Hybrid launch controllers must **automatically vent oxidizer if power or communication is lost** during fill |

In other words, **remote operation itself is mandatory**, and **what happens when the link drops** is an explicit requirement. We come back to this in "Limitations and Future Improvements."

The edition cited here is the **2024 revision (Rev 2024 V1.1.3)**. We could not confirm the section numbers or wording of the edition in force in 2020, when warp-core was written.

→ ESRA IREC DTEG (2024 V1.1.3): <https://www.soundingrocket.org/uploads/9/0/6/4/9064598/2025_irec_dteg_v1.1.3_10-22-24.pdf>

## Architecture

The full system designed by the capstone team consists of an **MCU board, FPGA board, I/O board, power board, and a C# GUI**. The warp-core repository contains **the firmware for the MCU board**.

- **Covered by the code**: mission control → W5500 → TCP task → queue → control task

- **Described only on the capstone page**: the FPGA gateway between the MCU and the I/O board with its failsafe, the RS232 link to the rocket avionics, and the C# GUI

## Technology Background

### Filling and Igniting a Hybrid Rocket

A **hybrid rocket** stores its fuel (a solid grain) and its oxidizer (a fluid such as N₂O) separately. Combustion starts only when the oxidizer flows into the combustion chamber. Unlike a solid motor, **the oxidizer valve can start and stop combustion**.

- **Fill**: N₂O flows from a supply tank at the pad into the rocket's tank. From this point the rocket carries a pressure vessel

- **Vent**: If the launch is scrubbed, the oxidizer must be dumped remotely (DTEG 5.10.1)

- **Ignite**: The igniter heats the fuel grain surface, then **the main oxidizer valve opens**. The order and spacing of these two steps decide whether ignition is clean

This is why warp-core lists "ignition timing matched to oxidizer injection" as a core challenge. Every command from mission control **acts on high-pressure oxidizer**, so the reliability of the command link is a safety issue.

### FreeRTOS Tasks and Queues

**FreeRTOS** is a real-time kernel for small MCUs. A program is split into **tasks**, and the kernel shares the CPU among them by priority. Tasks exchange data through **queues**, which decouple the sender from the receiver in time.

warp-core uses this structure directly.

- **vTCPServerTask** (priority +2): handles only the network. It wakes every **10 ticks (100 ms at a 100 Hz tick)** through `vTaskDelayUntil()` and checks the socket

- **vControlTask** (priority +1): only takes commands from the queue and drives the output

- **xControlCmdQueue**: a 10-entry queue that connects the two tasks

The benefit of this split is that **the control task's code path stays the same** even when the network side slows down or reconnects.

→ FreeRTOS queue documentation: <https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/02-Queues-mutexes-and-semaphores/01-Queues>

### Knowing When a TCP Connection Is Gone

TCP is **connection-oriented**, but it does not always tell you that the peer has disappeared.

| Situation | Does the server know? |
| --- | --- |
| The mission control program exits normally | **Yes** — the peer sends a **FIN** to close the connection |
| The mission control PC loses power, or the cable is cut | **No** — no packet arrives, so the connection still looks alive |

There are two ways to catch the second case.

- **TCP keep-alive**: after a period with no data, send an empty probe to ask whether the peer is still there (RFC 1122)

- **Application-level heartbeat**: mission control sends an "alive" message at a fixed interval, and the pad side enters a safe state if it misses them for too long

To meet the "automatic venting on communication loss" rule in DTEG 11.5.3 in software, you need at least one of these.

→ RFC 1122 (4.2.3.6 TCP Keep-Alives): <https://www.rfc-editor.org/rfc/rfc1122>

## Technical Highlights

- **Communication and control are separated**: the TCP task only receives and parses, and the control task on the other side of the queue drives the output. Communication (+2) runs at a higher priority than control (+1)

- **A command is a single integer**: from `{"cmd": 5}`, only the value of the `cmd` key is extracted and converted with `atoi()`. The parser is **jsmn** (MIT license), a single header file that uses no dynamic memory

- **Bare-metal port without Arduino**: the firmware brings in WIZnet **ioLibrary** and fills in the hardware-dependent functions in `wizchip_conf.c` — **SPI chip select and byte read/write** — with AVR registers. Burst read/write calls these byte functions in a loop

- **Debug output**: the `_TCP_DEBUG_` macro prints received data, the chip version, and the IP settings to USART1

- **Signs of iterative development**: the commit log includes "Fixed memory bug causing reset loop" and "Protected wizchip recv with critical section," and the README FAQ explains how to check RTOS reset causes (stack overflow, heap settings, watchdog)

## Where WIZnet Fits

### TOE in Use: Yes

warp-core uses the W5500 as a **hardware TCP/IP offload engine (TOE)**. The evidence is in the code.

- `socket(sn, Sn_MR_TCP, tcpPORT, 0x00)` — opens an ioLibrary hardware socket in **TCP mode**

- `getSn_SR()` reads **the socket status register inside the chip** to drive the state machine

- The firmware build (the source list in the makefile) **does not include** a software TCP/IP stack such as lwIP. The repository does contain the demo lwIP and uIP sources that ship with FreeRTOS, but they are not built

### Configuration

| Item | Value | Location |
| --- | --- | --- |
| Chip | **W5500** (`_WIZCHIP_ W5500`) | `lib/Ethernet/wizchip_conf.h` |
| Sockets used | **Socket 0 only** | `tcp_server.c` |
| Socket buffers | TX **16 KB**, RX **16 KB**, all assigned to socket 0 | `tcp_server.c` |
| Address | Static IP **10.0.0.10**, gateway 10.0.0.1 | `tcp_server.h` |
| Port | **8080** | `tcp_server.h` |
| SPI chip select | **PB3** | `wizchip_conf.c` |

The W5500's 8 sockets share an internal buffer of **16 KB TX and 16 KB RX**. Because warp-core accepts only one connection, it **gives the entire buffer to socket 0**.

→ W5500 datasheet: <https://docs.wiznet.io/Product/Chip/Ethernet/W5500/datasheet>

### How One Command Is Processed

1. **Open the socket** — if socket 0 is closed (`SOCK_CLOSED`), the MCU opens it in TCP mode with `socket()`. When the chip reaches `SOCK_INIT`, the MCU calls `listen()`

2. **Connect** — when the mission control PC connects to `10.0.0.10:8080`, **the W5500 handles the SYN/SYN-ACK/ACK handshake** and the state changes to `SOCK_ESTABLISHED`. The MCU sees this when it reads the status register every 100 ms

3. **Receive** — when the PC sends `{"cmd": 3}`, **the W5500 receives the TCP segment, returns the ACK**, and stores the payload in its internal RX buffer

4. **Fetch** — the MCU reads the number of waiting bytes with `getSn_RX_RSR(0)` and pulls up to **64 bytes** over SPI with `recv()`

5. **Parse and forward** — the MCU extracts the `cmd` value `3` with jsmn and passes it to the control task with `xQueueSend()`

6. **Close** — when mission control closes the connection, the W5500 receives the FIN and moves to `SOCK_CLOSE_WAIT`. The MCU finishes with `disconnect()` and returns to step 1

**What the MCU does not do**: ARP replies, the TCP handshake, ACKs, retransmission, buffering received data
**What the MCU does**: check the status register, read 64 bytes, parse JSON, forward to the queue

### Analogy: The Radio Operator at the Pad

The W5500 is the **radio operator** in the pad bunker. Tuning the radio, answering calls, and asking for repeats when a message is garbled are all the operator's job. The commander (ATmega1284p) only asks the operator every 100 ms, "Any orders?", reads **the one line the operator wrote down**, and hands it to a subordinate (the control task). The commander never needs to learn radio procedure.

### What If the Chip Were Not There [Inferred]

The ATmega1284p has **16 KB** of SRAM. warp-core reserves **10,000 bytes** of it as the FreeRTOS heap and allocates **2,048** (bytes, in the AVR port) from that heap for the TCP task stack.

- With a MAC/PHY-only Ethernet chip and a software TCP/IP stack (uIP, lwIP, etc.), **packet buffers, connection state, and retransmission timers** compete with the RTOS for the same SRAM

- The MCU would have to keep retransmission timing, so TCP handling would be **tied to task scheduling**

- In the current design, received data stays in the chip's 16 KB buffer, and the MCU uses **a single 64-byte array** for incoming data

## Similar Projects on WIZnet Makers

Several university rocket teams already use the W5500 in their ground equipment. They solve the same problem in different ways, which makes them worth reading side by side.

- **Hybrid PCC** — the **hybrid rocket pad control box** from the **CU InSpace** team at Carleton University in Canada. It is also hybrid rocket ground control, but it is built on an **RP2040 + W5500-EVB-Pico** board, in contrast to warp-core's custom PCB
  → Project link: <https://maker.wiznet.io/Sunny_/projects/hybrid-pcc/>

- **phoenix-gs (Phoenix Ground Station)** — the liquid rocket ground station from **RPL** at UC San Diego. Like warp-core, it sends valve commands and receives sensor data. But it opens the W5500 in **MACRAW mode** and talks in **custom Layer-2 frames with no TCP/IP**, the opposite choice to warp-core's **TOE approach**
  → Project link: <https://maker.wiznet.io/matthew/projects/phoenix-gs-phoenix-ground-station/>

- **PSPL CMS** — the liquid rocket avionics from the **Purdue Space Program Liquids** team. Built on **RP2040 + W5500**, it splits commands into **TCP request-response (CommandNet)** and sensor data into **UDP multicast (SensorNet)**. Commands get a reply, which is the difference from warp-core (Hybrid PCC also reports command results by design)
  → Project link: <https://maker.wiznet.io/Aimee0/projects/cms--rocket-developed-by-the-purdue-space-program-liquids-team/>

All four projects build their wired Ethernet segment with **a W5500 attached over SPI**. Hybrid PCC, however, relies on a separate wireless bridge for the long link between mission control and the pad, and PSPL CMS uses Ethernet as a backbone between the rocket's flight computers and ground equipment. What differs is how much of the chip each one uses.

| Item | **warp-core** (OSU) | Hybrid PCC (Carleton) | phoenix-gs (UCSD) | PSPL CMS (Purdue) |
| --- | --- | --- | --- | --- |
| Propulsion | Hybrid | Hybrid | Liquid | Liquid |
| MCU | **ATmega1284p** (8-bit) | RP2040 | Arduino-compatible (model unknown) | RP2040 |
| W5500 mode | **TOE** | Unknown | **MACRAW** | Unknown |
| Command transport | **TCP**, JSON | Unknown | Layer-2 frame, 1 byte | **TCP**, MessagePack |
| Command reply | No | Yes (reports results by design) | No | Yes (request-response) |
| RTOS | **FreeRTOS** | Apache NuttX | Unknown | FreeRTOS |

Of these, warp-core is **the only one confirmed to run on an 8-bit MCU**, and it **leans on the chip's TCP engine** to build its remote control link. Placed next to phoenix-gs, which builds raw frames over MACRAW, it shows clearly that the same chip can serve as either "a finished TCP socket" or "a bare Ethernet port."

## Use Cases

- **Remote valve and ignition links for student rocket teams and engine test stands** — a minimal skeleton that connects mission control and the pad with a single wired TCP link. The communication-loss handling described under "Limitations" must be added first

- **Adding wired remote control to 8-bit equipment** — an example of attaching one W5500 over SPI to existing AVR-based equipment to create a TCP command port

- **Porting ioLibrary to bare-metal AVR** — a minimal port that changes only the chip select and SPI read/write functions in `wizchip_conf.c`, without the Arduino library

- **Practicing the network/control split on an RTOS** — the structure that links a communication task and a control task through a queue fits in about 260 lines of non-comment code (`tcp_server`, `control`), which makes it good teaching material

## Limitations and Future Improvements

In roughly one capstone cycle, warp-core got a **hardware socket state machine, RTOS task separation, and JSON command parsing** working. When the full-scale launch stopped because of COVID, some parts were left unpolished. Teams that want to build on it should address the following first.

### 1. Communication Loss Goes Undetected — The Most Important Improvement

- **Today**: keep-alive is not configured, and there is no application-level heartbeat. If the mission control PC shuts off or a cable is pulled, no FIN arrives, so the socket stays in `SOCK_ESTABLISHED`

- **Why it matters**: DTEG 11.5.3 requires **automatic oxidizer venting on communication loss during fill**. If the firmware cannot detect the loss, it cannot meet that rule in software (we could not confirm whether the FPGA failsafe in the capstone design covered this)

- **Improvements**:
  - **Use the W5500 keep-alive** — the W5500 has a per-socket keep-alive timer register, **`Sn_KPALVTR`**. Writing a value above 0 (in **5-second** units) makes the chip send keep-alive packets on its own. If the peer does not respond, the chip drops the connection and raises a timeout interrupt. The MCU only needs to watch `getSn_SR()` change to `SOCK_CLOSED`. Setup is one line: `setSn_KPALVTR()`. Note that keep-alive starts only **after data has been sent or received at least once** following `SOCK_ESTABLISHED`, so this alone cannot catch a loss right after connecting, before any command arrives
  - **Application-level heartbeat** — mission control sends, for example, `{"cmd": 0}` every second. The control task uses a queue timeout and, if no command arrives within the limit, enters a **safe state that opens the vent valve**. This can detect a loss faster than keep-alive

→ W5500 datasheet PDF v1.1.0 (Sn_KPALVTR, p. 56): <https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf>

### 2. Commands Get No Reply

- **Today**: the server code never calls `send()`. Mission control cannot tell whether a command arrived or ran

- **Improvement**: send back the result and the current valve state. The request-response structure in PSPL CMS is a good reference

### 3. The Control Task Is Still a Skeleton

- **Today**: it **toggles an LED** regardless of the command value. The real valve and ignition outputs are not in the repository

- **Improvement**: add a per-command output map, an interlock check before ignition, and sequence validation (fill → ignite) to the control task

### 4. Small Code Defects

- **Received string boundary**: if exactly 64 bytes arrive, there is no NUL terminator, so `strlen()` can read past the buffer. Sizing the buffer at 65 bytes and keeping the last byte 0 fixes it

- **Queue type mismatch**: an `int8_t` command goes into a queue sized for `BaseType_t`, and the receiver reads it into a `BaseType_t *` variable. Making the three types match is safer

- **Busy waiting**: the control task polls the queue with a timeout of 0, leaving no time for lower-priority tasks. Giving the queue a wait time lets the CPU rest, and the same timeout can serve as the heartbeat monitor

### 5. Operational Notes

- **One** client, **static IP**, **no authentication** — the design assumes a closed launch site network

- The repository has **no LICENSE file**. Check with the authors before reuse (the bundled jsmn and the FreeRTOS kernel are each MIT-licensed)

## FAQ

**Q1. Can it be moved to an MCU other than the ATmega1284p?**
The README says to first check whether a **FreeRTOS port** exists for the target MCU. On the W5500 side, only four ioLibrary functions (chip select and SPI read/write) need to be rewritten for the new MCU, so most of the porting effort is on the RTOS side.

**Q2. Why TCP instead of UDP?**
The repository does not say. [Inferred] Valve and ignition commands must not be lost but are small in volume, so **TCP, with retransmission and ordering handled by the chip**, was probably the simpler choice. For high-volume data where only the latest value matters, such as sensor readings, some designs add a separate UDP channel, as PSPL CMS does.

**Q3. Can several mission control PCs connect at once?**
The current code uses only socket 0. The W5500 has **8** hardware sockets, so calling `listen()` on the same port for each socket would accept multiple clients. The 16 KB buffers would need to be split across those sockets, though, and you first need logic that decides **who holds command authority**.

**Q4. What does it add to the parts list?**
On the MCU board, roughly a **W5500**, a 25 MHz crystal, and an RJ45 jack with integrated magnetics (MagJack). [Inferred] The README links to the OSU team's schematic on Google Drive for the actual parts, but the link would not open as of October 2026, so we could not confirm them.

---

---

## 요약

**warp-core**는 오리건주립대(OSU) 하이브리드 로켓 팀을 위해 ECE 캡스톤 팀이 만든 **발사 제어 펌웨어**입니다. 관제 PC가 보낸 JSON 명령을 **WIZnet W5500의 하드웨어 TCP 소켓**으로 받아, **ATmega1284p** 위의 **FreeRTOS** 태스크로 넘깁니다. SRAM 16KB짜리 8비트 MCU에서도 TCP/IP 스택 없이 원격 제어 링크를 짧은 코드로 구성할 수 있다는 점에서 참고할 만한 사례입니다.

## 개요

하이브리드 로켓은 **고체 연료**와 **액체·기체 산화제**를 함께 씁니다. 산화제는 보통 발사 직전 발사대에서 충전하므로, 충전이 시작되면 사람은 로켓에서 멀리 떨어진 채로 밸브와 점화를 조작해야 합니다.

warp-core는 이 원격 조작의 **발사대 쪽 두뇌**입니다. README는 이 프로그램이 다루는 하이브리드 로켓 고유의 과제로 **발사 전 원격 산화제 충전**과 **산화제 주입에 맞춘 점화 타이밍**을 꼽습니다.

- **개발**: Quinn Handley, Thomas Prihoda

- **기간**: 2019-10-22 첫 커밋 ~ 2020-03-08 마지막 커밋, 커밋 48개

- **환경**: 맞춤 제작한 AVR 컨트롤러, FreeRTOS AVR 포트, avr-gcc 5.4.0

→ GitHub: <https://github.com/qhandley/warp-core>

## 왜 원격 제어가 필요한가

### 저장소의 실제 사용

- **확인됨**: 캡스톤 페이지에 따르면 서브스케일 시스템(T3st Core)이 **연료 그레인 시험을 여러 차례** 지원했습니다

- **실물 크기 발사는 미완료**: 3단계 실물 크기 발사는 **COVID-19로 연기**됐습니다. 같은 해 열릴 예정이던 **Spaceport America Cup 2020**도 2020년 3월 취소됐습니다

- **활동 지표**: Star 1, Fork 1. 대학 캡스톤 과제로 만들어진 저장소이며, 이후 업데이트는 없습니다

→ 캡스톤 프로젝트 페이지: <https://sites.google.com/oregonstate.edu/ece44x201932/home>
→ 대회 취소 공지(ESRA, 2020-03-24, 보관본): <http://web.archive.org/web/20250914092355/https://www.soundingrocket.org/news1/2020-spaceport-america-cup-cancelled>

### 대회 규정이 요구하는 것

학생 로켓 대회를 주관하는 **ESRA**의 설계·시험·평가 지침(**DTEG**)은 하이브리드 추진계와 발사 장비에 다음을 요구합니다.

| 조항 | 요구 내용 |
| --- | --- |
| **5.2.2.3** | 하이브리드 산화제는 **아산화질소(N₂O)만** 허용 |
| **5.10.1** | 하이브리드·액체 추진은 모든 추진제를 **원격으로 배출(venting)** 할 수단을 갖출 것 |
| **11.3.4** | 하이브리드 발사 제어기는 **산화제 충전량 계측**을 포함할 것 |
| **11.5.1** | 발사 제어 시스템은 **단일 고장 허용**(키·점퍼 인터록)일 것 |
| **11.5.3** | 충전 중 **전원 또는 통신이 끊기면 산화제를 자동 배출**하는 페일세이프를 갖출 것 |

즉 **원격 조작 자체가 규정상 필수**이고, 특히 **통신 링크가 끊겼을 때 어떻게 되는가**가 명시적 요구사항입니다. 이 점은 아래 「한계와 개선 방향」에서 다시 다룹니다.

인용한 판본은 **2024년판(Rev 2024 V1.1.3)** 이며, warp-core가 개발된 2020년 당시 판본의 조항 번호와 문구는 확인하지 못했습니다.

→ ESRA IREC DTEG (2024 V1.1.3): <https://www.soundingrocket.org/uploads/9/0/6/4/9064598/2025_irec_dteg_v1.1.3_10-22-24.pdf>

## 시스템 구성

캡스톤 팀이 설계한 전체 시스템은 **MCU 보드, FPGA 보드, I/O 보드, 전원 보드, C# GUI**로 이뤄집니다. 그중 warp-core 저장소가 담고 있는 것은 **MCU 보드의 펌웨어**입니다.

- **코드로 확인되는 범위**: 관제 → W5500 → TCP 태스크 → 큐 → 제어 태스크까지

- **캡스톤 페이지에만 있는 범위**: MCU와 I/O 보드 사이의 FPGA 게이트웨이와 페일세이프, 로켓 아비오닉스와의 RS232 연결, C# GUI

## 기술 배경

### 하이브리드 로켓의 충전과 점화

**하이브리드 로켓**은 연료(고체 그레인)와 산화제(N₂O 등 유체)를 따로 보관하다가, 산화제를 연소실로 흘려보낼 때 비로소 연소가 시작됩니다. 고체 모터와 달리 **산화제 밸브로 연소를 시작하고 멈출 수 있다**는 것이 핵심입니다.

- **충전**: 발사대의 공급 탱크에서 로켓 탱크로 N₂O를 채웁니다. 이때부터 로켓은 고압 용기를 실은 상태가 됩니다

- **배출**: 발사를 취소하면 탱크의 산화제를 원격으로 내보내야 합니다(DTEG 5.10.1)

- **점화**: 점화기로 연료 그레인 표면을 달군 뒤 **주 산화제 밸브를 연다**. 두 동작의 순서와 간격이 맞아야 정상 점화가 됩니다

warp-core가 「산화제 주입에 맞춘 점화 타이밍」을 과제로 꼽는 이유가 여기에 있습니다. 관제 쪽 명령 하나하나가 **고압 산화제를 다루는 동작**이므로, 명령 링크의 신뢰성이 곧 안전 문제가 됩니다.

### FreeRTOS 태스크와 큐

**FreeRTOS**는 소형 MCU용 실시간 커널입니다. 프로그램을 여러 **태스크**로 나누고, 커널이 우선순위에 따라 CPU를 나눠 줍니다. 태스크끼리는 **큐**로 데이터를 주고받으며, 큐는 보내는 쪽과 받는 쪽을 시간적으로 분리합니다.

warp-core는 이 구조를 그대로 씁니다.

- **vTCPServerTask** (우선순위 +2): 네트워크만 담당. `vTaskDelayUntil()`로 **10 tick(100 Hz tick 기준 100 ms)** 마다 깨어나 소켓을 확인합니다

- **vControlTask** (우선순위 +1): 큐에서 명령을 꺼내 출력만 담당합니다

- **xControlCmdQueue**: 길이 10의 큐로 두 태스크를 잇습니다

네트워크 쪽이 느려지거나 재접속을 하더라도 **제어 태스크의 코드 경로는 바뀌지 않는다**는 것이 이 분리의 장점입니다.

→ FreeRTOS 큐 문서: <https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/02-Queues-mutexes-and-semaphores/01-Queues>

### TCP 연결이 끊긴 것을 아는 방법

TCP는 **연결형** 프로토콜이지만, 상대가 사라졌다는 사실을 늘 알려주지는 않습니다.

| 상황 | 서버가 아는가 |
| --- | --- |
| 관제 프로그램이 정상 종료 | **안다** — 상대가 **FIN**을 보내 연결 종료를 알림 |
| 관제 PC가 갑자기 꺼짐, 케이블 단선 | **모른다** — 아무 패킷도 오지 않으므로 연결이 살아 있는 것처럼 보임 |

이 두 번째 경우를 잡는 방법은 두 가지입니다.

- **TCP keep-alive**: 일정 시간 데이터가 없으면 빈 확인 패킷을 보내 상대가 살아 있는지 묻는 기능입니다(RFC 1122)

- **앱 레벨 하트비트**: 관제 쪽이 주기적으로 "살아 있음" 메시지를 보내고, 발사대 쪽은 일정 시간 못 받으면 안전 동작으로 들어갑니다

DTEG 11.5.3의 「통신 두절 시 자동 배출」을 소프트웨어로 지키려면 이 중 하나 이상이 필요합니다.

→ RFC 1122 (4.2.3.6 TCP Keep-Alives): <https://www.rfc-editor.org/rfc/rfc1122>

## 기술 특징

- **통신과 제어의 분리**: TCP 태스크는 수신과 파싱까지만, 출력은 큐 너머의 제어 태스크가 맡습니다. 우선순위도 통신(+2)이 제어(+1)보다 높게 잡혀 있습니다

- **명령 형식은 정수 하나**: `{"cmd": 5}` 처럼 `cmd` 키의 값만 꺼내 `atoi()`로 변환합니다. 파서는 헤더 파일 하나로 된 **jsmn**(MIT 라이선스)으로, 동적 메모리를 쓰지 않습니다

- **Arduino 없는 bare-metal 이식**: WIZnet **ioLibrary**를 가져와 `wizchip_conf.c` 의 하드웨어 의존 함수인 **SPI 칩 선택·바이트 읽기·쓰기**를 AVR 레지스터로 채웠습니다(버스트 읽기·쓰기는 이 바이트 함수를 반복 호출합니다)

- **디버그 출력**: `_TCP_DEBUG_` 매크로로 USART1에 수신 내용, 칩 버전, IP 설정을 찍습니다

- **반복 개발의 흔적**: 커밋 기록에 「Fixed memory bug causing reset loop」, 「Protected wizchip recv with critical section」이 남아 있고, README FAQ에도 RTOS 리셋 원인(스택 오버플로, 힙 설정, 워치독)을 점검하는 법을 적어 두었습니다

## W5500이 맡은 일

### TOE 사용 판정: 예

warp-core는 W5500을 **하드웨어 TCP/IP 오프로드(TOE)** 칩으로 씁니다. 근거는 코드에 그대로 있습니다.

- `socket(sn, Sn_MR_TCP, tcpPORT, 0x00)` — ioLibrary의 하드웨어 소켓을 **TCP 모드**로 엽니다

- `getSn_SR()` 로 **칩 내부의 소켓 상태 레지스터**를 읽어 상태머신을 돌립니다

- 펌웨어 빌드(makefile의 소스 목록)에 lwIP 같은 소프트웨어 TCP/IP 스택이 **들어가지 않습니다**. 저장소에는 FreeRTOS 배포본의 데모용 lwIP·uIP 소스가 함께 들어 있지만 빌드에 쓰이지 않습니다

### 설정값

| 항목 | 값 | 위치 |
| --- | --- | --- |
| 칩 | **W5500** (`_WIZCHIP_ W5500`) | `lib/Ethernet/wizchip_conf.h` |
| 사용 소켓 | **소켓 0 하나** | `tcp_server.c` |
| 소켓 버퍼 | TX **16KB**, RX **16KB** 모두 소켓 0에 할당 | `tcp_server.c` |
| 주소 | 고정 IP **10.0.0.10**, 게이트웨이 10.0.0.1 | `tcp_server.h` |
| 포트 | **8080** | `tcp_server.h` |
| SPI 칩 선택 | **PB3** | `wizchip_conf.c` |

W5500은 소켓 8개가 **TX 16KB, RX 16KB**의 내부 버퍼를 나눠 씁니다. warp-core는 연결을 하나만 받으므로 **버퍼 전체를 소켓 0에 몰아준** 설정입니다.

→ W5500 데이터시트: <https://docs.wiznet.io/Product/Chip/Ethernet/W5500/datasheet>

### 명령 하나가 처리되는 순서

1. **소켓 열기** — 소켓 0이 닫혀 있으면(`SOCK_CLOSED`) MCU가 `socket()`으로 TCP 모드로 엽니다. 칩이 `SOCK_INIT`이 되면 `listen()`을 호출합니다

2. **접속** — 관제 PC가 `10.0.0.10:8080`으로 접속하면 **SYN/SYN-ACK/ACK 핸드셰이크를 W5500이 처리**하고, 상태가 `SOCK_ESTABLISHED`로 바뀝니다. MCU는 100 ms마다 상태 레지스터를 읽다가 이를 확인합니다

3. **수신** — 관제 PC가 `{"cmd": 3}`을 보내면 **W5500이 TCP 세그먼트를 받아 ACK를 돌려주고** 페이로드를 내부 RX 버퍼에 쌓습니다

4. **꺼내기** — MCU가 `getSn_RX_RSR(0)`으로 쌓인 바이트 수를 읽고, `recv()`로 최대 **64바이트**를 SPI로 가져옵니다

5. **파싱과 전달** — MCU가 jsmn으로 `cmd` 값 `3`을 꺼내 `xQueueSend()`로 제어 태스크에 넘깁니다

6. **종료** — 관제 쪽이 연결을 닫으면 W5500이 FIN을 받아 `SOCK_CLOSE_WAIT`로 바뀌고, MCU가 `disconnect()`로 마무리한 뒤 1번으로 돌아갑니다

**MCU가 하지 않는 일**: ARP 응답, TCP 핸드셰이크, ACK, 재전송, 수신 데이터 버퍼링
**MCU가 하는 일**: 상태 레지스터 확인, 64바이트 읽기, JSON 파싱, 큐 전달

### 비유: 발사대의 통신병

W5500은 발사대 벙커의 **통신병**입니다. 무전기 주파수를 맞추고, 호출에 응답하고, 잘 안 들린 말은 다시 불러 달라고 하는 일은 모두 통신병이 합니다. 지휘관(ATmega1284p)은 100 ms마다 통신병에게 "온 명령 있나?"라고만 묻고, 받아 적은 **명령문 한 줄**만 읽어 부하(제어 태스크)에게 넘깁니다. 지휘관은 무전 절차를 몰라도 됩니다.

### 만약 이 칩이 없었다면 [추정]

ATmega1284p의 SRAM은 **16KB**이고, warp-core는 그중 **10,000바이트**를 FreeRTOS 힙으로 잡고, 그 힙에서 TCP 태스크 스택으로 **2,048**(AVR 포트에서는 바이트 단위)을 받습니다.

- MAC/PHY만 있는 이더넷 칩에 소프트웨어 TCP/IP 스택(uIP, lwIP 등)을 올리면, **패킷 버퍼·연결 상태·재전송 타이머**가 같은 SRAM을 두고 RTOS와 경쟁합니다

- 재전송 타이밍을 MCU가 지켜야 하므로 TCP 처리가 **태스크 스케줄링에 묶입니다**

- 현재 구조에서는 수신 데이터가 칩 안의 16KB 버퍼에 머물고, MCU는 수신 데이터용으로 **64바이트 배열 하나**만 씁니다

## WIZnet Makers 유사 사례

대학 로켓 팀의 지상 장비에서 W5500을 쓴 사례가 이미 여럿 있습니다. 같은 문제를 서로 다른 방식으로 풀었기 때문에 비교하며 읽기 좋습니다.

- **Hybrid PCC** — 캐나다 칼턴대 **CU InSpace** 팀의 **하이브리드 로켓 발사대 제어 상자**입니다. 같은 하이브리드 로켓 지상 제어이지만, **RP2040 + W5500-EVB-Pico** 보드로 구성해 맞춤 PCB를 만든 warp-core와 대비됩니다
  → 프로젝트 링크: <https://maker.wiznet.io/Sunny_/projects/hybrid-pcc/>

- **phoenix-gs (Phoenix Ground Station)** — UC 샌디에이고 **RPL**의 액체 로켓 지상국입니다. 밸브 명령을 보내고 센서 값을 받는 점은 같지만, W5500을 **MACRAW 모드**로 열어 **TCP/IP 없이 커스텀 Layer-2 프레임**으로 통신합니다. warp-core의 **TOE 방식**과 정반대 선택입니다
  → 프로젝트 링크: <https://maker.wiznet.io/matthew/projects/phoenix-gs-phoenix-ground-station/>

- **PSPL CMS** — 퍼듀대 **Purdue Space Program Liquids** 팀의 액체 로켓 아비오닉스입니다. **RP2040 + W5500**으로, 명령은 **TCP 요청-응답(CommandNet)**, 센서는 **UDP 멀티캐스트(SensorNet)** 로 나눴습니다. 명령에 응답이 돌아온다는 점이 warp-core와의 차이입니다(Hybrid PCC도 설계상 명령 처리 결과를 회신합니다)
  → 프로젝트 링크: <https://maker.wiznet.io/Aimee0/projects/cms--rocket-developed-by-the-purdue-space-program-liquids-team/>

네 사례 모두 **SPI로 붙는 W5500**으로 유선 Ethernet 구간을 만들었습니다. 다만 Hybrid PCC는 관제와 발사대 사이의 장거리 구간을 별도 무선 브리지가 맡고, PSPL CMS의 Ethernet은 로켓 내부 비행 컴퓨터와 지상 장비를 잇는 백본입니다. 차이는 칩을 어디까지 쓰느냐에 있습니다.

| 항목 | **warp-core** (OSU) | Hybrid PCC (Carleton) | phoenix-gs (UCSD) | PSPL CMS (Purdue) |
| --- | --- | --- | --- | --- |
| 추진 방식 | 하이브리드 | 하이브리드 | 액체 | 액체 |
| MCU | **ATmega1284p** (8비트) | RP2040 | Arduino 호환(모델 미상) | RP2040 |
| W5500 모드 | **TOE** | 미상 | **MACRAW** | 미상 |
| 명령 전송 | **TCP**, JSON | 미상 | Layer-2 프레임, 1바이트 | **TCP**, MessagePack |
| 명령 응답 | 없음 | 있음(설계상 처리 결과 회신) | 없음 | 있음(요청-응답) |
| RTOS | **FreeRTOS** | Apache NuttX | 미상 | FreeRTOS |

warp-core는 이 중 MCU가 **8비트로 확인된 유일한 사례**이며, **칩의 TCP 기능에 기대어** 원격 제어 링크를 만든 사례입니다. MACRAW로 프레임을 직접 짜는 phoenix-gs와 나란히 놓으면, 같은 칩이 「완성된 TCP 소켓」과 「맨 이더넷 포트」 양쪽으로 쓰일 수 있다는 점이 잘 드러납니다.

## 어디에 쓰나

- **학생 로켓 팀·엔진 시험대의 원격 밸브·점화 링크** — 관제와 발사대 사이를 유선 TCP 하나로 잇는 최소 골격으로 쓸 수 있습니다. 다만 아래 「한계」의 통신 두절 처리는 반드시 보강해야 합니다

- **8비트 장비에 유선 원격 제어 추가** — 기존 AVR 기반 장비에 W5500 하나를 SPI로 붙여 TCP 명령 포트를 만드는 예제로 읽을 수 있습니다

- **ioLibrary를 bare-metal AVR에 이식할 때** — Arduino 라이브러리 없이 `wizchip_conf.c` 의 칩 선택·SPI 읽기·쓰기 함수만 고치는 최소 이식 예입니다

- **RTOS에서 네트워크와 제어를 나누는 연습** — 통신 태스크와 제어 태스크를 큐로 잇는 구조가 주석을 빼면 260줄 남짓(`tcp_server`, `control`)의 코드로 들어 있어 교육용으로 적당합니다

## 한계와 개선 방향

warp-core는 한 학기 남짓한 캡스톤 기간에 **하드웨어 소켓 상태머신, RTOS 태스크 분리, JSON 명령 파싱**까지 동작시킨 저장소입니다. 실물 발사가 COVID로 멈추면서 다듬어지지 못한 부분이 남아 있고, 이어받아 쓰려는 팀이라면 아래를 먼저 손보는 것이 좋습니다.

### 1. 통신 두절을 감지하지 못한다 — 가장 중요한 개선 지점

- **현재**: keep-alive를 설정하지 않았고, 앱 레벨 하트비트도 없습니다. 관제 PC가 꺼지거나 케이블이 빠지면 FIN이 오지 않으므로 소켓은 계속 `SOCK_ESTABLISHED`에 머뭅니다

- **왜 중요한가**: DTEG 11.5.3은 **충전 중 통신 두절 시 산화제 자동 배출**을 요구합니다. 펌웨어가 두절을 모르면 이 요구를 소프트웨어로 지킬 수 없습니다(캡스톤 설계의 FPGA 페일세이프가 이를 맡았는지는 확인하지 못했습니다)

- **개선 방향**:
  - **W5500 keep-alive 사용** — W5500에는 소켓별 keep-alive 타이머 레지스터 **`Sn_KPALVTR`** 가 있습니다. 값을 0보다 크게 쓰면(단위 **5초**) 칩이 스스로 keep-alive 패킷을 보내고, 상대가 응답하지 않으면 연결을 끊고 타임아웃 인터럽트를 냅니다. MCU는 `getSn_SR()`이 `SOCK_CLOSED`로 바뀌는 것만 보면 됩니다. 설정은 `setSn_KPALVTR()` 한 줄입니다. 단, keep-alive는 `SOCK_ESTABLISHED` 이후 **데이터를 한 번 이상 주고받은 뒤부터** 나가므로, 접속 직후 아무 명령도 오지 않은 상태의 두절은 이것만으로 잡지 못합니다
  - **앱 레벨 하트비트** — 관제 쪽이 예를 들어 1초마다 `{"cmd": 0}`을 보내고, 제어 태스크가 큐 타임아웃으로 정해진 시간 동안 명령이 없으면 **배출 밸브를 여는 안전 상태**로 들어가게 합니다. keep-alive보다 감지 시간을 짧게 잡을 수 있습니다

→ W5500 데이터시트 PDF v1.1.0 (Sn_KPALVTR, 56쪽): <https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf>

### 2. 명령에 응답하지 않는다

- **현재**: 서버 코드에 `send()` 호출이 없습니다. 관제 쪽은 명령이 도착했는지, 실행됐는지 알 수 없습니다

- **개선 방향**: 처리 결과와 현재 밸브 상태를 돌려보내는 응답을 추가합니다. PSPL CMS의 요청-응답 구조가 참고가 됩니다

### 3. 제어 태스크가 아직 골격이다

- **현재**: 명령 값과 관계없이 **LED를 토글**합니다. 실제 밸브·점화 출력은 저장소에 없습니다

- **개선 방향**: 명령 번호별 출력 매핑, 점화 전 인터록 확인, 순서 검증(충전 → 점화)을 제어 태스크에 넣습니다

### 4. 작은 코드 결함

- **수신 문자열 경계**: 64바이트를 꽉 채워 받으면 NUL 종료 문자가 없어 `strlen()`이 버퍼 밖을 읽을 수 있습니다. 버퍼를 65바이트로 잡고 끝을 0으로 두면 됩니다

- **큐 타입 불일치**: `int8_t` 명령을 `sizeof(BaseType_t)` 크기 큐에 넣고, 받는 쪽은 `BaseType_t *` 변수로 받습니다. 세 곳의 타입을 맞추는 것이 안전합니다

- **바쁜 대기**: 제어 태스크가 타임아웃 0으로 큐를 계속 확인해 낮은 우선순위 태스크가 실행될 틈이 없습니다. 큐 대기 시간을 주면 CPU가 쉬고, 이 타임아웃을 하트비트 감시에 그대로 쓸 수 있습니다

### 5. 운영 측면

- 클라이언트 **1개**, **고정 IP**, **인증 없음** — 닫힌 발사장 네트워크를 전제로 한 설계입니다

- 저장소에 **LICENSE 파일이 없습니다**. 재사용 전 저자에게 확인하는 것이 좋습니다(포함된 jsmn과 FreeRTOS 커널은 각각 MIT 라이선스)

## FAQ

**Q1. ATmega1284p가 아닌 다른 MCU로 옮길 수 있나요?**
README는 먼저 대상 MCU용 **FreeRTOS 포트**가 있는지 확인하라고 안내합니다. W5500 쪽은 ioLibrary의 칩 선택·SPI 콜백 4개만 새 MCU에 맞게 다시 쓰면 되므로, 이식 부담은 주로 RTOS 쪽에 있습니다.

**Q2. 왜 UDP가 아니라 TCP를 썼나요?**
저장소에 이유는 적혀 있지 않습니다. [추정] 밸브·점화 명령은 유실되면 안 되는 반면 양이 적어서, **재전송과 순서 보장을 칩이 해 주는 TCP**가 단순했을 것으로 보입니다. 센서처럼 양이 많고 최신 값만 중요한 데이터라면 PSPL CMS처럼 UDP를 따로 두는 구성도 있습니다.

**Q3. 관제 PC를 여러 대 붙일 수 있나요?**
현재 코드는 소켓 0 하나만 씁니다. W5500은 하드웨어 소켓이 **8개**이므로 소켓마다 같은 포트로 `listen()`하면 여러 클라이언트를 받을 수 있습니다. 다만 16KB 버퍼를 소켓 수만큼 나눠 다시 할당해야 하고, **명령 권한을 누가 갖는지** 정하는 로직이 먼저 필요합니다.

**Q4. 부품 구성에는 무엇이 더해지나요?**
MCU 보드 기준으로 **W5500**, 25MHz 크리스털, 트랜스포머 내장 RJ45(MagJack) 정도가 추가됩니다. [추정] OSU 팀의 실제 부품 구성은 README에 회로도(Google Drive) 링크로 안내되어 있으나, 2026년 10월 현재 링크가 열리지 않아 확인하지 못했습니다.

---

Source: https://maker.wiznet.io/scott/projects/warp-core/
