Chạy web server trên vi điều khiển 8-bit giá 1 đô

7 phút đọc English
Featured image for Chạy web server trên vi điều khiển 8-bit giá 1 đô

Tôi đã từng đọc một blog post được serve từ cái Raspberry Pi nhét sau router nhà ai đó ở Auckland. Trang load mất bốn giây. Cái cảm giác như đang gọi điện thoại cho một người thật thay vì fetch document từ CDN edge node cách đó 30 km.

Cái này thách thức hơn một chút.

Maurycy làm cho con chip $1 serve một website. Không phải Pi, không phải ESP32, không phải “vi điều khiển có WiFi.” Mà là AVR64DD32 - một lõi 8-bit, 8 kB RAM, 64 kB flash, 24 MHz. Con chip không có ngoại vi nào đủ tốc độ để nói chuyện với Ethernet. Không sao. Anh ấy ship nó luôn.

Phần cứng

AVR64DD32 có giá khoảng một đô. Với một con chip ở phân khúc này, spec này là khá hào phóng:

Tài nguyênThông số
CPU1× lõi AVR 8-bit @ 24 MHz
RAM8 kB
Flash64 kB
EEPROM256 byte
Điện áp1,8 – 5,5 V
Giá~$1

Vấn đề là: tất cả các chân IO của nó chỉ chạy tối đa ở 12 MHz. Chuẩn Ethernet chậm nhất, 10BASE-T, chạy ở 10 Mbps - nhưng dùng mã hoá Manchester, nhân đôi tốc độ tín hiệu thực tế lên 20 Mbps. AVR không thể tạo ra tín hiệu đó.

Giải pháp bình thường là mua một IC Ethernet chuyên dụng từ DigiKey và chờ vài tuần để ship về. Maurycy không muốn chờ.

Hack mạng: dùng SLIP

SLIP - Serial Line Internet Protocol, RFC 1055 (1988) - là cách cũ nhất và đơn giản nhất để truyền IP packet qua cổng serial:

Trước khi gửi packet:
  - Bọc nó trong các byte 0xC0.
  - Nếu packet chứa 0xC0, thay bằng 0xDB 0xDC.
  - Nếu packet chứa 0xDB, thay bằng 0xDB 0xDD.

Đó là toàn bộ giao thức. Ba quy tắc. Không header, không length field, không handshake.

SLIP có từ thời tiền sử, nhưng Linux vẫn hỗ trợ nó. Phía host chỉ cần ba lệnh:

stty -F /dev/ttyUSB0 115200 raw cs8
slattach -m -F -L -p slip /dev/ttyUSB0
# /dev/ttyUSB0 giờ là một network interface

Phần cứng phía AVR không cần thêm gì - không component ngoài. Maurycy gắn thêm một diode chống nối ngược (vì kiểu gì cũng cắm sai), thêm vài LED cho đẹp. Con chip tiêu thụ vài milliwatt và lấy điện từ chân 5V của adapter serial USB. Một dây duy nhất.

Stack mạng

Giờ con chip có kết nối mạng. Để build server, cần implement ba giao thức trên chip 8 kB RAM.

IP thì dễ. AVR chỉ cần trả lời, không cần tự khởi tạo kết nối. Implement IP response chủ yếu là: đổi chỗ địa chỉ nguồn và đích, reset TTL là xong. IPv6 đã bỏ hoàn toàn packet fragmentation, và các OS hiện đại cũng tắt nó trên IPv4 rồi.

TCP thì không dễ. TCP yêu cầu vi điều khiển phải theo dõi connection state, retransmit packet bị mất, xử lý window size, xử lý RST và FIN đúng cách, và đối phó với vô số edge case. Maurycy mất vài ngày để implement đủ dùng. Vẫn còn bug. Trang vẫn load được.

HTTP anh ấy không implement. Server gửi một response hardcoded cho bất kỳ request nào, bất kể URL. Điều này ổn khi chỉ có đúng một trang để serve.

Đưa lên internet công cộng

AVR đang kết nối qua serial từ mạng nhà của Maurycy. ISP của anh ấy không cấp địa chỉ IPv4 public. Để người ngoài truy cập được, dữ liệu phải đi qua một VPS ở Helsinki.

WireGuard tạo một kết nối mạng ảo giữa máy nhà và VPS. Không cần trick NAT traversal. Rồi nginx trên VPS proxy mọi request đến /mcu về địa chỉ local của AVR. Khách kết nối tới VPS; VPS chuyển tiếp traffic qua WireGuard qua SLIP qua USB tới con chip 8-bit.

Đường đi của một request: trình duyệt của bạn → VPS Helsinki → WireGuard → máy nhà → USB serial (115200 baud) → AVR64DD32.

Đó là lý do trang load chậm. Và mọi bước đều đang hoạt động đúng.

Thread HN

Thread nhỏ (bảy bình luận lúc viết bài này) nhưng chất lượng.

dragontamer khen dòng AVR DD/EA/EB trong khi lo ngại về hướng đi 32-bit PIC của Microchip - các chip mới như PIC32CM MC có tính năng tương đương, khiến tương lai của AVR không chắc chắn.

JdeBP chỉ ra rằng có một erratum năm 2025 cho RFC 1055 chưa được áp dụng vào www.c. Erratum này cập nhật thuật toán decoding. Đồng thời gợi ý RFC 1144 (nén header TCP/IP của Van Jacobson cho SLIP link) là bước tiếp theo tự nhiên.

steve_taylor viết:

Tôi thích cái cảm giác nhìn HTML stream dần lên màn hình theo thời gian thực, giống hồi dialup lúc ảnh render từ trên xuống dưới từng dòng một.

reassess_blind: “Mất lúc nhưng nó load được. Tôi đã xem đủ rồi, push lên production đi.”

Điều thực sự đáng chú ý

Các lựa chọn kỹ thuật ở đây rất nhất quán. Ethernet bị loại vì Manchester encoding ở 10 Mbps vượt quá IO clock của AVR. Các giao thức USB native sẽ cần một USB host stack. SLIP là công cụ đúng: nó đã có hàng thập kỷ, chỉ cần vài dòng để implement, và Linux hỗ trợ nó từ những năm 1990.

Phần implement IP cũng tối giản đúng mức. Tác giả nêu rõ IPv6 đã bỏ hoàn toàn fragmentation, và Linux hiện đại cũng tắt fragmentation IPv4 theo mặc định. AVR không cần xử lý nó. Tước bỏ giao thức về đúng những gì phía kia thực sự gửi - và phần “khó” tự nhiên thu nhỏ lại.

TCP thực sự khó. Tác giả mất vài ngày và vẫn thừa nhận còn bug. Đây là sự trung thực. TCP không phải giao thức bạn implement trong một buổi tối trên bất kỳ phần cứng nào. Việc nó chạy được trên 8 kB RAM mới là phần đáng nể.

Đoạn cuối về IPv6 - “thôi tự lo đi, IPv6 tồn tại ba mươi năm rồi” - là kiểu note chỉ xuất hiện khi một vấn đề buộc bạn phải thực sự nghĩ về lý do nó tồn tại.

Trang web

Trang vẫn đang chạy. Bạn có thể vào maurycyz.com/mcu và xem HTML arrive từng TCP segment một, như một cái máy fax.

Source code tại maurycyz.com/projects/mcusite/ kèm theo binary đã build sẵn.

Thảo luận: news.ycombinator.com/item?id=48165295

Hoang Yell

Một nhà phát triển phần mềm và là người kể chuyện kỹ thuật. Tôi đọc Hacker News mỗi ngày và kể lại những câu chuyện hay nhất ở đây — bằng tiếng Việt và tiếng Anh, cho người tò mò nhưng không có thời gian.