laser-projector
This project is for a quadruple laser projection controller, aimed at supporting room-scale projections
0
Project description
Room-Scale Quad Laser Projector β When Four Galvo Heads Dance to the Same Beat, No Wires Attached
#W5500 #UDP #RP2040 #GalvoLaser #RealTimeStreaming #ArtInstallation #MusicSync #WIZnet-Pico-SDK #DualCore #MulticastControl
π Context: Personal art/entertainment installation β a room-scale interactive laser show system, built and iterated across three build generations (2023 β 2025). β Implementation status: Full hardware (custom KiCad PCB, BOM-verified) + full firmware (RP2040 + W5500) + full server software (Python control station with audio/BLE/DMX integration) all present in-repo and consistent with each other.
01 β What is this project?
Most DIY laser shows on GitHub stop at "one MCU drives one galvo pair." This project goes further: it's a four-head, room-scale laser installation where every head is an independent network node, and a single Python "control station" chΠΎΡΠ΅ographs all of them in real time β synced to music, to a hand-held motion wand, to DMX/sACN lighting cues, or to generative visual algorithms (spirographs, audio-reactive patterns, even a two-player Pong game rendered in laser light).
The project has visibly evolved through three hardware generations:
- 2023 β an Arduino-class MCU driving 4 lasers over a CAN bus. No networking; everything wired point-to-point.
- 2024 β a full redesign: each laser head becomes its own RP2040 + W5500 board, receiving its frame data over standard Ethernet UDP from a Raspberry Pi running the Python server. A SuperCollider synthesizer patch was added for audio-reactive shows.
- 2025 β same hardware, refined server software (bug fixes across song handling, wand tracking, and the Sierpinski/spirograph generators).
The result is a laser installation that behaves less like a single gadget and more like a small distributed real-time rendering cluster, with WIZnet's Ethernet offload doing the unglamorous but critical job of getting frame data to four independent heads without jitter.
02 β Why Ethernet over Wi-Fi or a wired protocol?
π· Room-scale = physically separated heads
Four laser projectors, mounted around a room, all need to receive synchronized frame data. Running four Wi-Fi radios in the same room with tight-timing laser data is an invitation to jitter and packet loss from RF contention. A wired star topology from a central Raspberry Pi avoids that problem entirely.
π· Deterministic, low-jitter delivery
Laser vector graphics are extremely sensitive to timing β a delayed point doesn't just look wrong, it visibly distorts the image or "wobbles" the beam. Standard Ethernet with a dedicated hardware TCP/IP stack per node keeps that jitter bounded in a way a shared Wi-Fi channel across four receivers cannot.
π· Simplicity of the star topology
One Raspberry Pi source, four independent Ethernet-connected sinks β no mesh, no pairing, no discovery protocol. The Pi simply targets each head at a fixed IP (10.0.0.10, .11, .12, .13) and streams.
03 β System architecture

Each head is board-strapped with a 3-bit GPIO ID (pins 6/7/8, pulled up) that auto-derives its IP address (10.0.0.10 + board_id) and MAC address suffix β so all four boards run identical firmware, differentiated only by a jumper.
04 β Why WIZnet W5500? β
π· Dedicated core, dedicated job β no stack overhead competing with real-time rendering
The RP2040 is a dual-core MCU, and this project puts that architecture to direct use: core1 does nothing but service the W5500 (getSn_SR, getSn_RX_RSR, recvfrom), while core0 runs a tight 150 Β΅s loop driving the DAC and PWM outputs for the actual laser beam. Because the W5500 handles the TCP/IP stack entirely in hardware, core1's job reduces to a handful of register polls and a recvfrom() call β there is no software network stack competing for the same core cycles that need to stay free for beam-steering timing. On a bare RP2040 without hardware offload, that separation would be much harder to guarantee.
π· Socket mode used: UDP (Sn_MR_UDP)
The firmware opens socket 0 in UDP mode with SF_IO_NONBLOCK:
sockfd = socket(0, Sn_MR_UDP, 8090, SF_IO_NONBLOCK); ... size = getSn_RX_RSR(sockfd); size = recvfrom(sockfd, recv_buf, size, dest_ip, &dest_port);UDP is the correct choice here, not TCP: laser frame data is a continuous real-time stream where a dropped frame should be silently skipped, not retransmitted. TCP's retransmission and head-of-line blocking would actively hurt a system where "just draw the next frame" is always the right behavior. A one-byte sequence counter in the packet lets the firmware detect and discard out-of-order or dropped packets without any handshake overhead.
π· Built on WIZnet's own reference port
The SPI glue code (w5x00_spi.c/h) carries the WIZnet Co.,Ltd copyright header and matches WIZnet's official ioLibrary_Driver RP2040 port β the project correctly reused the vendor reference implementation rather than reinventing SPI/CS handling, which is exactly the kind of integration WIZnet's ports are meant to enable.
π· Verified in a real, physical multi-head deployment
This isn't a single-board demo β it's four physically distinct, independently-addressed W5500 boards deployed simultaneously in one room, each fed from the same source stream, with a custom KiCad-designed carrier board (combo_laser_driver) putting the W5500 directly next to the RP2040 and DAC on a single PCB (BOM-confirmed: W5500 LQFP-48, RP2040 QFN-56, MCP4922 DAC, W25Q128JVS flash).
05 β Key components
π WIZnet W5500 β UDP socket mode, hardware-offloaded frame receiver
Bare W5500 (LQFP-48) on a custom PCB, one per laser head, each running the identical firmware image and differentiated only by a GPIO strap. Handles all Ethernet/IP/UDP processing in hardware so the RP2040's second core is free for microsecond-precision beam control.
ποΈ RP2040 (dual-core)
- Core0: 150 Β΅s main loop β reads points from a lock-free queue, writes PWM (RGB laser intensity) and drives the DAC (galvo X/Y position), with a 3-second no-signal timeout that blanks the beam for safety.
- Core1: dedicated W5500 UDP receive loop, unpacking 6-byte point records (12-bit X, 12-bit Y, 8-bit R/G/B) from each incoming packet into the shared queue.
π― MCP4922 β dual 12-bit SPI DAC
Drives the X/Y galvanometer mirrors that steer the laser beam β the same vector-scanning principle used in commercial ILDA laser projectors.
π‘ Direct RGB laser diode driving
Red/Green/Blue diodes driven directly via PWM (documented operating points: Red 3.3 V/46 mA, Green 7.2 V/27 mA, Blue 7.2 V/44 mA) rather than through a separate laser projector engine.
π₯οΈ Python control station (Raspberry Pi)
app.py (pygame GUI) orchestrates laser_server.py, which streams UDP frames to all four heads at a fixed ~25.8 ms cadence. Multiple generative and interactive modes are implemented: audio visualization, parametric equations, spirographs, a laser Pong game, drum-triggered graphics, BLE-wand-driven freehand drawing (using quaternion IMU tracking via pyquaternion), and a calibration mode. sacn_handler.py additionally drives sACN/DMX-based ambient lighting in sync with the laser show.
06 β Application scenarios
01. Music-synced laser installations β the audio-visualization and drum-graphics generators make this directly usable for live performance or exhibit spaces without further development.
02. Interactive art exhibits β the BLE motion wand mode lets a visitor "draw" in the air with the laser responding to their hand motion in real time, a pattern reusable for any hands-on installation.
03. Multi-node real-time UDP streaming reference β the core1/core0 split and the custom lightweight framing protocol (sequence byte + fixed-size point records) is a clean reference for anyone building a low-latency, hardware-offloaded UDP sink on RP2040 + W5500.
04. Scalable venue lighting β the board-strap addressing scheme (GPIO-derived IP/MAC) means adding a 5th, 6th, or Nth head is a matter of flashing identical firmware and setting a jumper β a pattern applicable well beyond laser shows, to any array of identical Ethernet-addressed peripheral nodes.
Conclusion
Four independently-addressed W5500 nodes, one Raspberry Pi source, and a beam-steering loop that never has to think about the network β this project shows what "hardware offload" buys you when microseconds actually matter.
- β Custom KiCad PCB with W5500 + RP2040 + MCP4922 co-located, BOM-verified
- β Clean UDP socket-mode usage matching the real-time, loss-tolerant nature of laser streaming
- β Dual-core RP2040 architecture that isolates network I/O from beam-timing-critical code
- β Built on WIZnet's official ioLibrary RP2040 SPI port rather than a from-scratch driver
- β Three build generations of iteration, from wired CAN bus to a networked multi-head system
- β A full accompanying server ecosystem: audio reactivity, BLE wand tracking, sACN lighting sync, generative visual modes
- β Auto-addressing via GPIO strap β a reusable pattern for any identical multi-node deployment
07 β Similar Projects on WIZnet Makers
OpenLaserDAC β An open-source ESP32-S3 laser DAC firmware implementing the Ether Dream protocol over W5500 Ethernet (with WiFi fallback), driving an 8-channel DAC80508 for a single laser head. It targets compatibility with existing laser-show software (MadMapper, Liberation, any Ether Dream client) and adds a web UI with OTA firmware updates.
| Room-Scale Quad Laser Projector | OpenLaserDAC | |
|---|---|---|
| MCU | RP2040 (dual-core) | ESP32-S3 (dual-core) |
| WIZnet chip | W5500 (bare chip, custom carrier PCB) | W5500 (SPI, interrupt-driven) |
| Socket mode | UDP (Sn_MR_UDP), custom framing | TCP (Ether Dream, port 7765) + UDP broadcast discovery (port 7654) |
| Position DAC | MCP4922 (12-bit, 2-channel) | DAC80508 (16-bit, 8-channel) |
| Head count | 4, independently addressed | 1 |
| Client compatibility | Custom Python server only | Standard Ether Dream ecosystem (MadMapper, Liberation, etc.) |
| Extra features | BLE motion wand, sACN/DMX sync, audio-reactive & generative visual modes | Web dashboard, OTA updates, WiFi fallback |
| Deployment model | Fixed multi-node installation | Single, software-agnostic laser head |
Insight: the two projects sit at opposite ends of the same design space. The quad projector optimizes for a closed, purpose-built installation β custom protocol, custom server, multiple synchronized heads, rich interactive/generative content built in. OpenLaserDAC optimizes for interoperability β standard Ether Dream protocol means it drops into any existing laser-show software stack with zero custom server work, at the cost of being a single head with no built-in show logic of its own. Both independently chose the W5500 for the same underlying reason: a laser head is a latency-sensitive embedded target where a software network stack would compete with real-time DAC timing, and both land on UDP-based real-time delivery for the actual laser point stream (Ether Dream itself uses UDP for point-streaming, with TCP reserved for control/handshake). Together they sketch a natural roadmap: a future head design could adopt Ether Dream compatibility (from OpenLaserDAC) while keeping the multi-head synchronization and rich show content (from the quad projector).
Q&A
Q. Why UDP instead of TCP, given TCP's reliability guarantees? A. Laser frame data is inherently disposable β a lost frame should be skipped, not retransmitted, since retransmission would arrive after its display window has passed. UDP's fire-and-forget model plus a simple sequence-number check for drop detection is the right fit; TCP's retransmission and ordering guarantees would only add latency without adding value here.
Q. Could this run on the W6100 or W5500-based module boards instead of a bare chip on a custom PCB? A. Functionally yes β the socket API used (socket(), getSn_RX_RSR(), recvfrom()) is identical across WIZnet's W5x00 family. The custom PCB choice here looks driven by wanting the W5500 co-located with the RP2040 and DAC on one board rather than by any W5500-specific requirement.
Q. How does the 3-second timeout affect safety? A. If core0 stops receiving valid points from the queue for 3 seconds (LASER_TIMEOUT), it forces the laser output to zero β a reasonable fail-safe against a network hiccup leaving a beam parked (and potentially hazardous) at a fixed point.
Original Link: https://github.com/adeboni/laser-projector
λ£Έμ€μΌμΌ 4μ±λ λ μ΄μ νλ‘μ ν° β 4κ°μ κ°λ³΄ ν€λκ° μ νλ μμ΄ κ°μ λ°μμ λ§μΆ° μΆ€μΆλ€
#W5500 #UDP #RP2040 #κ°λ³΄λ μ΄μ #μ€μκ°μ€νΈλ¦¬λ° #μνΈμ€μΉλ¬Ό #μμ
λκΈ°ν #WIZnet-Pico-SDK #λμΌμ½μ΄ #λ©ν°μΊμ€νΈμ μ΄
π 컨ν μ€νΈ: κ°μΈ μνΈ/μν°ν μΈλ¨ΌνΈ μ€μΉ νλ‘μ νΈ β λ£Έμ€μΌμΌ μΈν°λν°λΈ λ μ΄μ μΌ μμ€ν μΌλ‘, 2023λ λΆν° 2025λ κΉμ§ μΈ μΈλμ κ±Έμ³ λ°λ³΅ κ°λ°λ¨. β ꡬν μν: νλμ¨μ΄(KiCad 컀μ€ν PCB, BOM κ²μ¦ μλ£) + νμ¨μ΄(RP2040 + W5500) + μλ² μννΈμ¨μ΄(μ€λμ€/BLE/DMX ν΅ν© νμ΄μ¬ μ μ΄ μ€ν μ΄μ ) μ μ²΄κ° μ μ₯μμ μ‘΄μ¬νλ©° μλ‘ μ ν©μ±μ΄ λ§μ.
01 β μ΄ νλ‘μ νΈλ 무μμΈκ°?
GitHubμ μ¬λ§ν DIY λ μ΄μ μΌλ "MCU νλκ° κ°λ³΄ ν μμ ꡬλνλ€"μμ λ©μΆλ€. μ΄ νλ‘μ νΈλ ν λ¨κ³ λ λμκ° 4κ°μ λ 립λ λ€νΈμν¬ λ Έλλ‘ κ΅¬μ±λ λ£Έμ€μΌμΌ λ μ΄μ μ€μΉλ¬Όμ λ§λ€μλ€. νλμ νμ΄μ¬ "μ μ΄ μ€ν μ΄μ "μ΄ μμ , μμ λ λͺ¨μ μλ, DMX/sACN μ‘°λͺ ν, λλ μμ±ν λΉμ£ΌμΌ μκ³ λ¦¬μ¦(μ€νΌλ‘κ·Έλν, μ€λμ€ λ°μ ν¨ν΄, μ¬μ§μ΄ λ μ΄μ λ‘ λ λλ§ν 2μΈμ© ν κ²μκΉμ§)μ λ§μΆ° λ€ λλ₯Ό λμμ μ§ννλ€.
μ΄ νλ‘μ νΈλ μΈ μΈλμ νλμ¨μ΄ μ§νλ₯Ό κ±°μΉ νμ μ΄ λλ ·νλ€:
- 2023λ β ArduinoκΈ MCU νλκ° CAN λ²μ€λ‘ 4κ°μ λ μ΄μ λ₯Ό ꡬλ. λ€νΈμν¬ μμ΄ μ λΆ μ λμ λ°°μ .
- 2024λ β μ λ©΄ μ¬μ€κ³: κ° λ μ΄μ ν€λκ° λ 립λ RP2040 + W5500 보λκ° λμ΄, λΌμ¦λ² 리νμ΄μμ λμκ°λ νμ΄μ¬ μλ²λ‘λΆν° νμ€ μ΄λλ· UDPλ‘ νλ μ λ°μ΄ν°λ₯Ό λ°μ. μ€λμ€ λ°μν μΌλ₯Ό μν SuperCollider μ λμ¬μ΄μ ν¨μΉλ μΆκ°λ¨.
- 2025λ β νλμ¨μ΄λ λμΌ, μλ² μννΈμ¨μ΄λ§ λ€λ¬μ(곑 μ²λ¦¬, μλ νΈλνΉ, μμλ₯΄νμ€ν€/μ€νΌλ‘κ·Έλν μμ±κΈ° λ²κ·Έ μμ ).
κ²°κ³Όλ¬Όμ λ¨μΌ κΈ°κΈ°λΌκΈ°λ³΄λ€ μμ κ·λͺ¨μ λΆμ° μ€μκ° λ λλ§ ν΄λ¬μ€ν°μ κ°κΉκ³ , WIZnetμ μ΄λλ· μ€νλ‘λκ° λμ λμ§ μμ§λ§ κ²°μ μ μΈ μν β 4κ°μ λ 립 ν€λμ μ§ν° μμ΄ νλ μ λ°μ΄ν°λ₯Ό μ λ¬νλ μΌ β μ λ§‘κ³ μλ€.
02 β μ Wi-Fiλ μ μ νλ‘ν μ½μ΄ μλλΌ μ΄λλ·μΈκ°?
π· λ£Έμ€μΌμΌ = 물리μ μΌλ‘ λΆμ°λ ν€λ
λ£Έ κ³³κ³³μ μ€μΉλ 4λμ λ μ΄μ νλ‘μ ν°κ° λκΈ°νλ νλ μ λ°μ΄ν°λ₯Ό λ°μμΌ νλ€. νμ΄λ°μ΄ μ΄μ΄ν λ μ΄μ λ°μ΄ν°λ₯Ό μν΄ ν λ°©μμ 4κ°μ Wi-Fi λΌλμ€λ₯Ό λμμ λ리λ 건 RF κ°μμΌλ‘ μΈν μ§ν°μ ν¨ν· μμ€μ μμ΄νλ μΌμ΄λ€. μ€μ λΌμ¦λ² 리νμ΄μμ λ»μ΄λμ€λ μ μ μ€ν ν ν΄λ‘μ§λ μ΄ λ¬Έμ λ₯Ό μ μ΄μ νΌν΄κ°λ€.
π· κ²°μ λ‘ μ , μ μ§ν° μ λ¬
λ μ΄μ λ²‘ν° κ·Έλν½μ νμ΄λ°μ κ·Ήλλ‘ λ―Όκ°νλ€ β ν¬μΈνΈ νλκ° μ§μ°λλ©΄ λ¨μν μ΄μν΄ λ³΄μ΄λ κ² μλλΌ μ΄λ―Έμ§κ° λμ λκ² μΌκ·Έλ¬μ§κ±°λ λΉμ΄ "νλ€λ¦°λ€." λ Έλλ§λ€ μ μ© νλμ¨μ΄ TCP/IP μ€νμ κ°μΆ νμ€ μ΄λλ·μ, 4κ° μμ λ¨μ΄ 곡μ νλ Wi-Fi μ±λλ‘λ λΆκ°λ₯ν λ°©μμΌλ‘ κ·Έ μ§ν°λ₯Ό μ΅μ νλ€.
π· μ€ν ν ν΄λ‘μ§μ λ¨μν¨
λΌμ¦λ² 리νμ΄ μμ€ νλ, μ΄λλ·μΌλ‘ μ°κ²°λ λ
립 μ±ν¬ 4κ° β λ©μλ, νμ΄λ§λ, λμ€μ»€λ²λ¦¬ νλ‘ν μ½λ νμ μλ€. νμ΄λ κ° ν€λλ₯Ό κ³ μ IP(10.0.0.10, .11, .12, .13)λ‘ μ§μ ν΄ κ·Έλ₯ μ€νΈλ¦¬λ°νλ€.
03 β μμ€ν μν€ν μ²

κ° ν€λλ GPIO 3λΉνΈ ID(6/7/8λ² ν, νμ
)λ‘ λ³΄λλ₯Ό μ€νΈλ©ν΄μ IP μ£Όμ(10.0.0.10 + board_id)μ MAC μ£Όμ λ·μ리λ₯Ό μλμΌλ‘ κ²°μ νλ€ β κ·Έλμ 4κ° λ³΄λ λͺ¨λ λμΌν νμ¨μ΄λ₯Ό λλ¦¬κ³ μ νΌ νλλ‘λ§ κ΅¬λΆλλ€.
04 β μ WIZnet W5500μΈκ°? β
π· μ μ© μ½μ΄, μ μ© μ무 β μ€μκ° λ λλ§κ³Ό κ²½μνμ§ μλ λ€νΈμν¬ μ€ν
RP2040μ λμΌμ½μ΄ MCUμ΄κ³ , μ΄ νλ‘μ νΈλ κ·Έ ꡬ쑰λ₯Ό μ νν νμ©νλ€. core1μ μ€μ§ W5500 μλΉμ€λ§ λ΄λΉνκ³ (getSn_SR, getSn_RX_RSR, recvfrom), core0μ 150Β΅s μ£ΌκΈ°μ νμ΄νΈν 루νλ‘ DACμ PWM μΆλ ₯μ ꡬλν΄ μ€μ λ μ΄μ λΉμ μ μ΄νλ€. W5500μ΄ TCP/IP μ€ν μ 체λ₯Ό νλμ¨μ΄λ‘ μ²λ¦¬νκΈ° λλ¬Έμ, core1μ μΌμ λ μ§μ€ν° ν΄λ§ λͺ λ²κ³Ό recvfrom() νΈμΆλ‘ μ€μ΄λ λ€ β λΉ μ‘°ν₯ νμ΄λ°μ μν΄ λΉμλ¬μΌ ν μ½μ΄ μ¬μ΄ν΄μ λκ³ κ²½μνλ μννΈμ¨μ΄ λ€νΈμν¬ μ€νμ΄ μλ€. νλμ¨μ΄ μ€νλ‘λκ° μλ μμ RP2040μ΄μλ€λ©΄ μ΄λ° λΆλ¦¬λ₯Ό 보μ₯νκΈ°κ° ν¨μ¬ μ΄λ €μ μ κ²μ΄λ€.
π· μ¬μ© λͺ¨λ: UDP (Sn_MR_UDP)
νμ¨μ΄λ μμΌ 0λ²μ UDP λͺ¨λ, SF_IO_NONBLOCKμΌλ‘ μ°λ€:
sockfd = socket(0, Sn_MR_UDP, 8090, SF_IO_NONBLOCK); ... size = getSn_RX_RSR(sockfd); size = recvfrom(sockfd, recv_buf, size, dest_ip, &dest_port);μ¬κΈ°μ UDPλ TCPκ° μλλΌ μ νν μ νμ΄λ€. λ μ΄μ νλ μ λ°μ΄ν°λ μ°μμ μΈ μ€μκ° μ€νΈλ¦Όμ΄κ³ , νλ μ νλκ° μμ€λλ©΄ μ¬μ μ‘ν κ² μλλΌ μ‘°μ©ν 건λλ°μ΄μΌ νλ€. TCPμ μ¬μ μ‘κ³Ό ν€λμ€λΈλΌμΈ λΈλ‘νΉμ "κ·Έλ₯ λ€μ νλ μμ κ·Έλ €λΌ"κ° νμ μ λ΅μΈ μμ€ν μμ μ€νλ € ν΄κ° λλ€. ν¨ν·μ λ΄κΈ΄ 1λ°μ΄νΈ μνμ€ μΉ΄μ΄ν° λλΆμ νΈλμ °μ΄ν¬ μ€λ²ν€λ μμ΄ μμκ° μ΄κΈλκ±°λ λλ½λ ν¨ν·μ κ°μ§ν΄ λ²λ¦΄ μ μλ€.
π· WIZnet 곡μ λ νΌλ°μ€ ν¬νΈ μμ ꡬμΆ
SPI κΈλ£¨ μ½λ(w5x00_spi.c/h)μλ WIZnet Co.,Ltd μ μκΆ ν€λκ° λΆμ΄μκ³ WIZnet 곡μ ioLibrary_Driverμ RP2040 ν¬νΈμ μΌμΉνλ€ β SPI/CS μ²λ¦¬λ₯Ό μ²μλΆν° μλ‘ μ§λ λμ λ²€λ λ νΌλ°μ€ ꡬνμ κ·Έλλ‘ μ¬μ¬μ©νλλ°, μ΄κ±΄ WIZnet ν¬νΈκ° λͺ©νλ‘ νλ μ νν ν΅ν© λ°©μμ΄λ€.
π· μ€μ 물리μ λ©ν°ν€λ λ°°ν¬μμ κ²μ¦λ¨
λ¨μΌ 보λ λ°λͺ¨κ° μλλΌ, ν λ°©μ λμμ λ°°μΉλ 4κ°μ 물리μ μΌλ‘ λ
립λ, κ°κΈ° λ€λ₯Έ μ£Όμλ₯Ό κ°μ§ W5500 보λλ€. λͺ¨λ κ°μ μμ€ μ€νΈλ¦Όμ λ°κ³ , 컀μ€ν
KiCad μ€κ³ μΊλ¦¬μ΄ 보λ(combo_laser_driver)λ W5500μ RP2040Β·DACμ ν PCBμ λλν λ°°μΉνλ€(BOM νμΈ: W5500 LQFP-48, RP2040 QFN-56, MCP4922 DAC, W25Q128JVS νλμ).
05 β ν΅μ¬ ꡬμ±μμ
π WIZnet W5500 β UDP μμΌ λͺ¨λ, νλμ¨μ΄ μ€νλ‘λ νλ μ μμ κΈ°
컀μ€ν PCB μμ λ² μ΄ W5500(LQFP-48), λ μ΄μ ν€λλ§λ€ ν κ°μ©, λμΌν νμ¨μ΄ μ΄λ―Έμ§λ₯Ό λ리며 GPIO μ€νΈλ©λ§μΌλ‘ ꡬλΆλλ€. μ΄λλ·/IP/UDP μ²λ¦¬ μ 체λ₯Ό νλμ¨μ΄μμ λ΄λΉν΄ RP2040μ λ λ²μ§Έ μ½μ΄λ₯Ό λ§μ΄ν¬λ‘μ΄ λ¨μ λΉ μ μ΄μ μ¨μ ν λΉμμ€λ€.
ποΈ RP2040 (λμΌμ½μ΄)
- Core0: 150Β΅s λ©μΈ 루ν β λ½ν리 νμμ ν¬μΈνΈλ₯Ό μ½μ΄ PWM(RGB λ μ΄μ μΈκΈ°)μ μ°κ³ DAC(κ°λ³΄ X/Y μμΉ)λ₯Ό ꡬλνλ©°, μ νΈ μμ 3μ΄ νμμμ μ μμ μ μν΄ λΉμ κΊΌλ²λ¦Ό.
- Core1: W5500 UDP μμ μ μ© λ£¨ν. λ€μ΄μ€λ ν¨ν·λ§λ€ 6λ°μ΄νΈ ν¬μΈνΈ λ μ½λ(12λΉνΈ X, 12λΉνΈ Y, 8λΉνΈ R/G/B)λ₯Ό νμ΄μ 곡μ νμ λ£μ.
π― MCP4922 β λμΌ 12λΉνΈ SPI DAC
X/Y κ°λ°λ Έλ―Έν° λ―Έλ¬λ₯Ό ꡬλν΄ λ μ΄μ λΉμ μ‘°ν₯νλ€ β μμ© ILDA λ μ΄μ νλ‘μ ν°μ λμΌν λ²‘ν° μ€μΊλ μ리.
π‘ RGB λ μ΄μ λ€μ΄μ€λ μ§μ ꡬλ
λ³λμ λ μ΄μ νλ‘μ ν° μμ§ μμ΄ R/G/B λ€μ΄μ€λλ₯Ό PWMμΌλ‘ μ§μ ꡬλνλ€(λμμ λ¬Έμνλ¨: λΉ¨κ° 3.3V/46mA, λ Ήμ 7.2V/27mA, νλ 7.2V/44mA).
π₯οΈ νμ΄μ¬ μ μ΄ μ€ν μ΄μ (λΌμ¦λ² 리νμ΄)
app.py(pygame GUI)κ° laser_server.pyλ₯Ό μ§ννλ©°, μ½ 25.8ms μ£ΌκΈ°λ‘ 4κ° ν€λ μ 체μ UDP νλ μμ μ€νΈλ¦¬λ°νλ€. μ€λμ€ μκ°ν, νλΌλ©νΈλ¦ λ°©μ μ, μ€νΌλ‘κ·Έλν, λ μ΄μ ν κ²μ, λλΌ νΈλ¦¬κ±° κ·Έλν½, BLE μλ κΈ°λ° μμ λλ‘μ(pyquaternionμΌλ‘ μΏΌν°λμΈ IMU νΈλνΉ), μΊλ¦¬λΈλ μ΄μ
λͺ¨λκΉμ§ λ€μν μμ±νΒ·μΈν°λν°λΈ λͺ¨λκ° κ΅¬νλΌ μλ€. sacn_handler.pyλ sACN/DMX κΈ°λ° μ£Όλ³ μ‘°λͺ
μ λ μ΄μ μΌμ λκΈ°νν΄ μΆκ°λ‘ ꡬλνλ€.
06 β μμ© μλ리μ€
01. μμ λκΈ°ν λ μ΄μ μ€μΉλ¬Ό β μ€λμ€ λΉμ£ΌμΌλΌμ΄μ μ λλΌ κ·Έλν½ μμ±κΈ°κ° μμ΄ λ³λ κ°λ° μμ΄ λΌμ΄λΈ 곡μ°μ΄λ μ μ 곡κ°μ λ°λ‘ μΈ μ μλ€.
02. μΈν°λν°λΈ μνΈ μ μ β BLE λͺ¨μ μλ λͺ¨λλ λ°©λ¬Έκ°μ΄ ν곡μ "κ·Έλ¦Όμ 그리면" λ μ΄μ κ° μλμμ μ€μκ°μΌλ‘ λ°μνκ² ν΄μ€λ€. μ΄λ€ 체νν μ€μΉλ¬Όμλ μ¬μ¬μ© κ°λ₯ν ν¨ν΄μ΄λ€.
03. λ©ν°λ Έλ μ€μκ° UDP μ€νΈλ¦¬λ° λ νΌλ°μ€ β core1/core0 λΆλ¦¬ ꡬ쑰μ μ체 κ²½λ νλ μ΄λ° νλ‘ν μ½(μνμ€ λ°μ΄νΈ + κ³ μ ν¬κΈ° ν¬μΈνΈ λ μ½λ)μ RP2040 + W5500μΌλ‘ μ μ§μ° νλμ¨μ΄ μ€νλ‘λ UDP μ±ν¬λ₯Ό λ§λ€λ €λ λꡬμκ²λ κΉλν μ°Έκ³ μ¬λ‘λ€.
04. νμ₯ κ°λ₯ν 곡μ°μ₯ μ‘°λͺ β GPIO μ€νΈλ© κΈ°λ° μλ μ£Όμμ§μ λ°©μ λλΆμ 5λ²μ§Έ, 6λ²μ§Έ, Nλ²μ§Έ ν€λλ₯Ό μΆκ°νλ 건 λμΌ νμ¨μ΄λ₯Ό νλμνκ³ μ νΌ νλ μΈν νλ λ¬Έμ λ‘ μ€μ΄λ λ€ β λ μ΄μ μΌλ₯Ό λμ΄ λμΌν μ΄λλ· μ£Όμμ§μ μ£Όλ³μ₯μΉ λ°°μ΄μ΄λΌλ©΄ μ΄λμλ μ μ© κ°λ₯ν ν¨ν΄μ΄λ€.
Conclusion
λ 립μ μΌλ‘ μ£Όμκ° μ§μ λ W5500 λ Έλ 4κ°, λΌμ¦λ² 리νμ΄ μμ€ νλ, κ·Έλ¦¬κ³ λ€νΈμν¬λ₯Ό μ κ²½ μΈ νμκ° μ ν μλ λΉ μ‘°ν₯ 루ν β μ΄ νλ‘μ νΈλ λ§μ΄ν¬λ‘μ΄κ° μ§μ§λ‘ μ€μν λ "νλμ¨μ΄ μ€νλ‘λ"κ° λ¬΄μμ μ¬μ€ μ μλμ§ λ³΄μ¬μ€λ€.
- β W5500 + RP2040 + MCP4922λ₯Ό ν 보λμ λ°°μΉν 컀μ€ν KiCad PCB, BOM κ²μ¦ μλ£
- β λ μ΄μ μ€νΈλ¦¬λ°μ μ€μκ°Β·μμ€νμ© νΉμ±μ μ νν λ§λ κΉλν UDP μμΌ λͺ¨λ μ¬μ©
- β λ€νΈμν¬ I/Oμ λΉ νμ΄λ° ν¬λ¦¬ν°μ»¬ μ½λλ₯Ό λΆλ¦¬νλ λμΌμ½μ΄ RP2040 μν€ν μ²
- β μ²μλΆν° μ§ λλΌμ΄λ²κ° μλλΌ WIZnet 곡μ ioLibrary RP2040 SPI ν¬νΈ μμ ꡬμΆ
- β μ μ CAN λ²μ€μμ λ€νΈμν¬ν λ©ν°ν€λ μμ€ν κΉμ§, μΈ μΈλμ κ±ΈμΉ λ°λ³΅ κ°λ°
- β μ€λμ€ λ°μ, BLE μλ νΈλνΉ, sACN μ‘°λͺ λκΈ°ν, μμ±ν λΉμ£ΌμΌ λͺ¨λκΉμ§ κ°μΆ ν μλ² μνκ³
- β GPIO μ€νΈλ© κΈ°λ° μλ μ£Όμμ§μ β λμΌ κ΅¬μ± λ©ν°λ Έλ λ°°ν¬λΌλ©΄ μ΄λμλ μ¬μ¬μ© κ°λ₯ν ν¨ν΄
07 β WIZnet Makers λ΄ μ μ¬ νλ‘μ νΈ
OpenLaserDAC β Ether Dream νλ‘ν μ½μ W5500 μ΄λλ·(WiFi ν΄λ°± ν¬ν¨)μΌλ‘ ꡬνν μ€νμμ€ ESP32-S3 λ μ΄μ DAC νμ¨μ΄. 8μ±λ DAC80508λ‘ λ¨μΌ λ μ΄μ ν€λλ₯Ό ꡬλνλ€. κΈ°μ‘΄ λ μ΄μ μΌ μννΈμ¨μ΄(MadMapper, Liberation, λͺ¨λ Ether Dream ν΄λΌμ΄μΈνΈ)μμ νΈνμ±μ λͺ©νλ‘ νλ©°, OTA νμ¨μ΄ μ λ°μ΄νΈκ° λλ μΉ UIλ κ°μ·λ€.
| λ£Έμ€μΌμΌ μΏΌλ λ μ΄μ νλ‘μ ν° | OpenLaserDAC | |
|---|---|---|
| MCU | RP2040 (λμΌμ½μ΄) | ESP32-S3 (λμΌμ½μ΄) |
| WIZnet μΉ© | W5500 (λ² μ΄μΉ©, 컀μ€ν μΊλ¦¬μ΄ PCB) | W5500 (SPI, μΈν°λ½νΈ ꡬλ) |
| μμΌ λͺ¨λ | UDP (Sn_MR_UDP), μ체 νλ μ΄λ° | TCP (Ether Dream, ν¬νΈ 7765) + UDP λΈλ‘λμΊμ€νΈ λμ€μ»€λ²λ¦¬ (ν¬νΈ 7654) |
| μμΉμ μ΄ DAC | MCP4922 (12λΉνΈ, 2μ±λ) | DAC80508 (16λΉνΈ, 8μ±λ) |
| ν€λ κ°μ | 4κ°, λ 립 μ£Όμμ§μ | 1κ° |
| ν΄λΌμ΄μΈνΈ νΈνμ± | μ체 νμ΄μ¬ μλ² μ μ© | νμ€ Ether Dream μνκ³(MadMapper, Liberation λ±) |
| μΆκ° κΈ°λ₯ | BLE λͺ¨μ μλ, sACN/DMX λκΈ°ν, μ€λμ€λ°μΒ·μμ±ν λΉμ£ΌμΌ λͺ¨λ | μΉ λμ보λ, OTA μ λ°μ΄νΈ, WiFi ν΄λ°± |
| λ°°ν¬ λͺ¨λΈ | κ³ μ λ©ν°λ Έλ μ€μΉλ¬Ό | μννΈμ¨μ΄ λ¬΄κ΄ λ¨μΌ λ μ΄μ ν€λ |
Q&A
Q. TCPμ μ λ’°μ± λ³΄μ₯μ΄ μλλ° μ UDPλ₯Ό μΌλ? A. λ μ΄μ νλ μ λ°μ΄ν°λ λ³Έμ§μ μΌλ‘ "λ²λ €λ λλ" λ°μ΄ν°λ€ β μμ€λ νλ μμ μ¬μ μ‘ν κ² μλλΌ κ±΄λλ°μ΄μΌ νλ€. μ¬μ μ‘μ΄ λμ°©ν μ¦μμ΄λ©΄ μ΄λ―Έ κ·Έ νλ μμ νμν νμ΄λ°μ μ§λκ° μκΈ° λλ¬Έμ΄λ€. UDPμ λ°μ¬ ν λ§κ°(fire-and-forget) λ°©μμ μμ€ κ°μ§λ₯Ό μν κ°λ¨ν μνμ€ λλ² μ²΄ν¬λ₯Ό λν μ‘°ν©μ΄ μ νν λ§λ μ νμ΄κ³ , TCPμ μ¬μ μ‘Β·μμ보μ₯μ μ¬κΈ°μ μ§μ°λ§ λ릴 λΏ μ»λ κ² μλ€.
Q. 컀μ€ν
PCBμ λ² μ΄μΉ© λμ W6100μ΄λ W5500 λͺ¨λ 보λλ‘λ λμκ°κΉ? A. κΈ°λ₯μ μΌλ‘λ κ·Έλ λ€ β μ¬μ©λ μμΌ API(socket(), getSn_RX_RSR(), recvfrom())λ WIZnet W5x00 κ³μ΄ μ 체μμ λμΌνλ€. μ΄ νλ‘μ νΈκ° 컀μ€ν
PCBλ₯Ό νν μ΄μ λ W5500 μ체μ νΉμ μꡬμ¬νμ΄λΌκΈ°λ³΄λ€ RP2040Β·DACμ ν 보λμ λλν λ°°μΉνκ³ μΆμλ μ€κ³ μλλ‘ λ³΄μΈλ€.
Q. 3μ΄ νμμμμ μμ μ± μΈ‘λ©΄μμ μ΄λ€ μν μ νλ? A. core0μ΄ 3μ΄κ°(LASER_TIMEOUT) νμμ μ ν¨ν ν¬μΈνΈλ₯Ό λ°μ§ λͺ»νλ©΄ λ μ΄μ μΆλ ₯μ κ°μ λ‘ 0μΌλ‘ λ§λ λ€ β λ€νΈμν¬ λ¬Έμ λ‘ λΉμ΄ ν μ μ κ³ μ λ μ±(μνν μ μλ μνλ‘) λ°©μΉλλ κ±Έ λ§λ ν©λ¦¬μ μΈ μμ μ₯μΉλ€.