---
title: "Real-Time UDP Pixel Display & Stage Visualizer"
url: "https://maker.wiznet.io/TheoIm/projects/real-time-udp-pixel-display-stage-visualizer/"
markdown_url: "https://maker.wiznet.io/TheoIm/projects/real-time-udp-pixel-display-stage-visualizer/md"
type: "UCC: User Created Content"
author: "Jonathan L"
author_url: "https://www.hackster.io/happypikachoo/real-time-udp-pixel-display-stage-visualizer-part-1-f67f21"
editor: "WIZnet"
editor_url: "https://maker.wiznet.io/"
original_author: "Jonathan L"
original_url: "https://www.hackster.io/happypikachoo/real-time-udp-pixel-display-stage-visualizer-part-1-f67f21"
published: "2026-10-08"
language: "en"
hardware: ["WiznetHK W55MH32Q_EVB"]
likes: 0
views: 9
comments: 0
source: "WIZnet Makers (https://maker.wiznet.io/)"
---

# Real-Time UDP Pixel Display & Stage Visualizer

> A W55MH32L-EVB paints 772-byte RGB frames streamed over UDP onto an 8x32 WS2812B matrix, counting dropped frames on an OLED instead of hiding them.

Original author: Jonathan L (source: https://www.hackster.io/happypikachoo/real-time-udp-pixel-display-stage-visualizer-part-1-f67f21)

## Components

- **WiznetHK W55MH32Q_EVB** x 1

## Article

## Overview

**Real-Time UDP Pixel Display & Stage Visualizer** turns a W55MH32L-EVB into a network-controlled LED stage fixture. A host PC streams 772-byte RGB frames to the board over UDP through its on-board WIZnet W5500 Ethernet controller; the board paints them onto an 8×32 WS2812B matrix, reports its own IP, frame rate and dropped-frame count on an SSD1306 OLED, and sends a rotary encoder's position back to the PC on a second UDP port so the host can use it as a live control input.

The author publishes it as Part 1 of a series under GPL3+, with the complete MicroPython source walked through section by section.

이 프로젝트는 W55MH32L-EVB를 네트워크로 제어되는 LED 무대 조명 장치로 만듭니다. PC가 UDP로 772바이트 RGB 프레임을 보내면 보드가 8×32 WS2812B 매트릭스에 출력하고, 자신의 IP·프레임률·누락 프레임 수를 SSD1306 OLED에 표시하며, 로터리 엔코더 값을 별도 UDP 포트로 PC에 되돌려 보냅니다.

## Purpose

The author's earlier projects in this series used TCP. This one deliberately does not, and the reasoning is stated rather than assumed: TCP guarantees that data arrives exactly as sent, and pays for it with connection setup and retransmission. For LED animation that trade runs the wrong way — **if one frame is lost, showing the next frame is more useful than waiting for the missing one to be retransmitted.**

The interesting part is what follows from that decision. Having given up the transport's guarantees, the firmware builds its own minimum: a magic header so foreign packets cannot be painted onto the matrix, a frame number so gaps are visible, and a counter on the display so the cost of the choice is something you can read off the device.

**Core Objectives**

- Receive RGB animation frames over UDP with the lowest practical delay

- Reject malformed or unrelated packets before they reach the LEDs

- Detect and count missing or stale frames rather than hide them

- Keep the control loop responsive while frames are arriving

- Send control telemetry back to the host on the same wired link

TCP 대신 UDP를 택한 이유를 저자가 직접 밝히고 있습니다. 프레임 하나가 유실되면 재전송을 기다리기보다 다음 프레임을 보여주는 편이 낫기 때문입니다. 중요한 것은 그 다음입니다 — 전송 계층의 보장을 포기한 대신, 펌웨어가 매직 헤더·프레임 번호·드롭 카운터라는 최소한의 장치를 직접 만들어 그 대가를 **측정 가능한 형태**로 남겨 두었습니다.

## System Architecture

```
Host PC
   |  UDP 6454   772-byte RGB frames
   v
W55MH32L-EVB
   |- W5500 Ethernet   internal SPI2, CS PB12, RST PD9
   |- WS2812B 8x32     DIN PA7          (256 LEDs)
   |- SSD1306 OLED     I2C1 PB6/PB7, 0x3D
   |- Rotary encoder   PA0 / PA1, interrupt driven
   |
   |  UDP 6455   encoder telemetry
   v
Host PC
```

| Component | Description |
| --- | --- |
| Controller | W55MH32L-EVB running MicroPython, developed in Thonny. STM32-style pin naming (PA/PB/PD). |
| Network | WIZnet W5500 on the board's internal SPI2 bus, brought up with DHCP. |
| Display output | 8×32 WS2812B flexible matrix, 256 pixels, driven from a single data pin. |
| Diagnostics | SSD1306 OLED over I2C1, refreshed periodically so it does not interfere with frame processing. |
| Control input | Rotary encoder on two interrupt-capable pins with internal pull-ups; the ISR records movement and the main loop applies it. |
| Protocol | Custom 772-byte UDP frame: 2-byte magic header, frame number, and 3 bytes × 256 LEDs of RGB payload. |

## Role and Application of the W5500

**The wired link is the point, not an accessory.** A stage fixture that drops frames because of Wi-Fi contention is a fixture that visibly stutters in front of an audience. Ethernet gives this controller a deterministic medium, and the project's whole diagnostic design — counting drops and showing them — only makes sense on a link where a drop is an event rather than background noise.

**How the firmware brings it up.**

```
nic = network.WIZNET5K(spi, cs_pin, reset_pin)
nic.active(True)
nic.ifconfig("dhcp")
```

The reset pin is pulsed low then high, with a 200 ms delay afterwards to let the chip start, before DHCP is requested. Sockets are then created through the standard MicroPython socket module.

**What the article does not state.** MicroPython's `network.WIZNET5K` driver can, depending on how the port is built, either present the chip to a software stack or use the chip's own sockets. **The write-up does not say which applies here, so no claim is made about hardwired TCP/IP offload in this project.** Anyone needing that distinction confirmed should read the board's MicroPython port configuration directly.

이 프로젝트에서 유선 링크는 부가 요소가 아니라 핵심입니다. 무대 조명이 무선 간섭으로 프레임을 흘리면 관객 앞에서 눈에 띄게 끊기기 때문입니다. 드롭 수를 세어 화면에 표시하는 이 프로젝트의 진단 설계도, 드롭이 배경 잡음이 아니라 사건으로 취급되는 유선 링크에서만 의미가 있습니다. 다만 **MicroPython의 **`**network.WIZNET5K**`** 드라이버가 칩의 하드웨어 소켓을 쓰는지 여부는 원문에 기술되어 있지 않으므로**, 하드웨어 TCP/IP 오프로드 사용은 주장하지 않습니다.

## Key Features

| Feature | Description |
| --- | --- |
| Fixed-length frame validation | A valid frame is exactly 772 bytes; anything else is discarded before processing. 길이가 정확히 772바이트가 아니면 버립니다. |
| Magic header | A two-byte header keeps unrelated UDP traffic from being interpreted as LED data. 매직 헤더로 무관한 패킷을 걸러냅니다. |
| Frame-gap detection | A frame number is compared against the last accepted one; a jump is counted as a drop. 프레임 번호의 건너뜀을 드롭으로 집계합니다. |
| On-device diagnostics | IP, measured FPS, cumulative drops and encoder value on the OLED. IP·FPS·누적 드롭·노브 값을 OLED에 표시합니다. |
| Bounded receive drain | Up to four packets per loop pass, non-blocking, so a burst cannot starve the rest of the loop. 루프당 최대 4패킷까지만 처리합니다. |
| Serpentine index map | Logical (x, y) is translated to the panel's physical zigzag order once, at startup. 지그재그 배선을 시작 시 1회 해소합니다. |
| Return telemetry | Encoder position is sent to the host each loop on a separate UDP port. 엔코더 값을 매 루프 PC로 송신합니다. |

## How a frame is validated

The frame-processing function checks length and header before it touches a pixel:

```
if len(data) != EXPECTED_FRAME_SIZE:
    return False
if data[0:2] != FRAME_HEADER_MAGIC:
    return False
```

Only then is the frame number compared against `last_frame_id`, and the RGB payload unpacked three bytes at a time into the matrix. The board keeps `frame_count` for the current statistics period and `dropped_frames` as a running total.

That last counter is what makes the UDP decision honest. The controller does not pretend nothing was lost — it shows you how much.

## Keeping the loop responsive

Both sockets are non-blocking, and the receive function drains a bounded number of packets per pass:

```
rx_sock.bind(("0.0.0.0", UDP_RX_PORT))
rx_sock.setblocking(False)
tx_sock.setblocking(False)

MAX_PACKETS_PER_LOOP = 4
```

The reasoning given is that handling several packets per pass keeps the receive buffer from filling when the PC sends quickly, while the bound stops a burst from starving the encoder, the OLED and the telemetry path. When nothing is waiting, `OSError` breaks out immediately and control returns to the main loop.

The board listens on UDP port 6454 and sends telemetry on 6455. **Port 6454 is the port Art-Net uses, but this is not Art-Net** — the frame format described above is the project's own.

## The serpentine problem

The program treats the display logically as rows running left to right. The panel is not wired that way: each column holds eight LEDs, even columns run top to bottom and odd columns bottom to top. `build_index_map()` resolves the difference once, at startup:

```
if x % 2 == 0:
    physical_index = x * HEIGHT + y
else:
    physical_index = x * HEIGHT + (HEIGHT - 1 - y)
```

so the per-frame path is a flat lookup — `physical_index = index_map[logical_index]` — instead of arithmetic repeated 256 times for every frame. A small decision, but it is the difference between a mapping cost paid once and one paid at frame rate.

## Wiring

```
WS2812B 8x32 matrix   DIN  -> PA7          5 V, GND
Rotary encoder        CLK  -> PA0  (internal pull-up)
                      DT   -> PA1  (internal pull-up)
SSD1306 OLED          SCL  -> PB6 (I2C1)   SDA -> PB7, address 0x3D
W5500 (on board)      CS   -> PB12         RST -> PD9, hardware SPI2
```

![](https://maker.wiznet.io/upload/ckeditor5/7f6e33f03c994c4db003db70d54573e0.png)

## Engineering Value and Source Limits

The value here is not that LEDs light up over Ethernet. It is that the project treats the cost of choosing UDP as something to be measured rather than ignored — and that every piece of that measurement is visible on a two-inch display, which is exactly where a stage technician would need it.

What the article does *not* establish:

- **The “60 FPS” in the project's summary line is not backed by a published measurement.** The firmware computes a frame rate and shows it on the OLED, but no figure is reported in the write-up.

- **Whether the W5500's hardwired TCP/IP is used is not stated.** The code uses MicroPython's `network.WIZNET5K` driver with the standard socket module, which does not by itself settle the question.

- **No dropped-frame rate is reported** at any particular sending rate, so the detection mechanism is demonstrated but not characterised.

- The host-side sender is not included in this part.

- The project is labelled **Part 1 and “work in progress”**; the author's listed build time is two hours.

원문이 입증하지 *않은* 범위입니다. 요약문의 “60 FPS”는 **게시글에 측정 수치로 제시되지 않았고**, 하드웨어 TCP/IP 사용 여부도 기술되어 있지 않으며, 특정 송신 속도에서의 드롭률도 보고되지 않았습니다. 호스트 측 송신 프로그램은 이 편에 포함되지 않았고, 저자는 이 글을 Part 1 / work in progress로 표시했습니다.

## Market & Application Value

**Stage and architectural lighting.** Networked pixel control is standard in this field, and the parts of the problem this project addresses — latency, frame integrity, and a technician being able to see at a glance whether the fixture is receiving — are the parts that decide whether an installation is trusted.

**A pattern beyond LEDs.** Fixed-size frames, a magic header, a sequence number and a visible drop counter make up a reusable recipe for any low-latency UDP control link: motion control, telemetry displays, sensor fan-out. The payload changes; the shape does not.

**Teaching value.** Because the author explains the TCP-versus-UDP trade before writing a line of socket code, and then shows the compensating machinery, the project reads as a worked lesson rather than a snippet — useful to anyone meeting UDP on an MCU for the first time.

네트워크 기반 픽셀 제어는 무대·건축 조명에서 이미 표준이며, 이 프로젝트가 다루는 지연·프레임 무결성·현장에서 수신 상태를 한눈에 확인하는 문제는 설치물의 신뢰도를 좌우하는 지점입니다. 고정 길이 프레임 + 매직 헤더 + 시퀀스 번호 + 드롭 카운터라는 조합은 LED에 한정되지 않고, 저지연 UDP 제어 링크 전반에 재사용할 수 있는 설계입니다.

## Source-Backed Summary

| Board | W55MH32L-EVB, W5500 on internal SPI2 (CS PB12, RST PD9) |
| --- | --- |
| Language | MicroPython, developed in Thonny |
| Transport | UDP — RX port 6454, TX port 6455, both non-blocking |
| Frame | 772 bytes: 2-byte magic + frame number + 3 bytes × 256 LEDs |
| Integrity | Length check, magic check, frame-number gap counting |
| Display | 8×32 WS2812B, serpentine mapping resolved once at startup |
| Diagnostics | SSD1306 shows IP, FPS, cumulative drops, encoder value |
| Return path | Rotary encoder value sent to the host each loop |
| Licence | GPL3+ |
| Status | Part 1, work in progress |

## FAQ

**Q. Why UDP instead of TCP for LED data?**
Because retransmission is worth less than low delay here. A retransmitted frame arrives after the moment it was meant for; the next frame is already more correct. The author states this explicitly as the reason for the change from earlier projects in the series.

**Q. If UDP can drop frames, how do you know it is working?**
That is what the frame number and the OLED drop counter are for. The controller does not assume delivery — it counts the gaps and displays the total, so the cost of the choice is visible on the device.

**Q. Does it use the W5500's hardwired TCP/IP?**
The article does not say. The code uses MicroPython's `network.WIZNET5K` driver with the standard socket module, and that alone does not determine whether the chip's own sockets or a software stack handle the protocol.

**Q. Is this Art-Net, since it uses port 6454?**
No. The port number is the same one Art-Net uses, but the frame format is the project's own 772-byte layout. The two are not interchangeable.

**Q. Can I reproduce it from the article?**
The board-side firmware is published in full with a pin-by-pin wiring description. The host-side sender is not included in Part 1.

Source: [Real-Time UDP Pixel Display & Stage Visualizer Part 1](https://www.hackster.io/happypikachoo/real-time-udp-pixel-display-stage-visualizer-part-1-f67f21) by Jonathan L on Hackster.io

---

Source: https://maker.wiznet.io/TheoIm/projects/real-time-udp-pixel-display-stage-visualizer/
