This guide walks you through building a Patternflow v2.0.0 from scratch. It assumes basic familiarity with through-hole soldering and 3D printing.
This is the current detailed path for a hand-soldered official PCB plus a PLA 3D printed enclosure. For the broader assembly map, including the laser-cut path still in preparation, see docs/assembly/README.md.
No PCB? Try the breadboard build. If you don't want to order or solder a custom PCB, there's a complete PCB-free path that wires the same parts together with a snapped-off breadboard power rail and jumper wires — no soldering iron required. See the Breadboard Build Guide. You can always move up to this PCB build later.
Estimated build time: 4-6 hours of active work, plus ~11 hours of 3D printing.
Skill level: Beginner-to-intermediate. If you've assembled a mechanical keyboard or built a basic Arduino project, you're ready — this is an all through-hole build.
🚀 The v3.0 board is out — and it's the recommended build. The verified v3.0 revision brings USB-C power (with a beginner-friendly screw-terminal bypass), a fully hand-solderable board with zero SMD passives, and a snap-fit enclosure. Its guide is being finalized at BUILD_GUIDE_v3.md and will replace this page when complete. This guide covers the v2.x board — keep following it if you already have v2.x parts (v2 and v3 boards/cases are not interchangeable).
Photos in this guide were shot on an early build; where a photo looks slightly off, follow the written steps.
What changed in v2.0.0. PCB now includes a 10k pullup on GPIO0 (resolves the v1 cold-boot issue), silkscreen cleaned up to clearly mark R vs. C designators and the correct encoder solder side, firmware ships with a built-in custom-pattern template usable with any AI coding assistant, and the case files now include a 15mm encoder knob variant. Most of the case geometry is unchanged from v1; see Section 10 for the issues still open and the deliberate design notes worth knowing.
- Bill of Materials (BOM)
- 3D Printing
- Case Bonding
- PCB Assembly
- Mount the LED Matrix
- Wire Up Power and Data
- Install the PCB and Close the Case
- Firmware Upload
- First Boot
- Known Issues & Design Notes
| Ref | Item | Spec | Qty | Notes |
|---|---|---|---|---|
| - | LED Matrix Panel | HUB75, 128x64 px, P2.5, 320x160 mm | 1 | Full color SMD. Ships with HUB75 ribbon cable + power cable; both used as-is. Driver IC must be 74HC595, FM6126A, or FM6124 — see the panel compatibility warning below. |
| U1 | ESP32-S3 DevKit | ESP32-S3-WROOM-1, N16R8 (16MB Flash, 8MB PSRAM), 44-pin, 25.4mm header spacing | 1 | PSRAM is required |
| SW1-SW4 | Rotary Encoder | EC11, 5-pin, 15mm or 20mm shaft, with push-switch | 4 | Shaft length is purely preference — print the matching knob STL. The reference part (Bourns PEC11R-4220F-S0024) is 20mm. |
| - | Female Pin Socket (1x22, 2.54mm) | For ESP32-S3 module | 2 | |
| J1 | Box Header (2x8, 2.54mm) | Vertical, for HUB75 ribbon | 1 | LED matrix data |
| J2 | Screw Terminal | 2-pin, 5mm pitch | 1 | +5V input from power bank |
| J3 | Screw Terminal | 2-pin, 5mm pitch | 1 | +5V output to LED matrix |
| C11 | Electrolytic Cap 1000uF / 16V | Radial D10xL13 | 1 | Main bulk decoupling |
| - | M4 Screws | ~10mm length | 6 | LED matrix mounting |
| - | USB Cable (sacrificial) | Any USB cable, will be cut | 1 | For 5V power input |
| - | Power Bank | Any standard USB power bank that physically fits | 1 | User-supplied |
💡 Found a better part? If you discover cheaper, more reliable, or higher-quality alternative components, please let me know! I highly welcome PRs or GitHub Issues recommending better sourcing options for the community.
- LED Matrix: Full color 320×160mm P2.5 HUB75 — AliExpress (affiliate link — purchasing through this link directly supports ongoing Patternflow development at no extra cost to you. Even if you buy a different item through this link, the commission helps. Thank you!)
⚠️ Panel compatibility — check the driver IC before you buy.Patternflow drives the panel directly from the ESP32-S3 — there is no sending/receiving card. So the panel's driver IC matters more than its size or pitch. Not every "HUB75 P2.5 128×64" panel works:
- ✅ Works: driver IC is 74HC595 (plain shift register), FM6126A, or FM6124. These are the common, cheap indoor panels. Set
PANEL_PROFILEinfirmware/patternflow/config.h—PANEL_STANDARDfor 74HC595,PANEL_HIGHREFRESHfor FM6126A/FM6124.- ❌ Does NOT work: GCLK PWM "video wall" panels — driver IC FM6363C / FM6373C and similar, usually sold by advertising a very high refresh rate (1920/3840Hz) and/or that they require a Nova/Linsn/Colorlight/Huidu sending+receiving card. These need a separate GCLK signal and a proprietary addressing scheme the
ESP32-HUB75-MatrixPanel-DMAlibrary cannot produce, so the panel stays completely dark no matter what you configure (upstream issue #642, closed wontfix).The safe move: before buying, ask the seller which driver IC the panel uses. If they say FM6363C/FM6373C, 3840Hz, or "needs a receiving card", pick a different panel — ideally one explicitly advertised as "hzeller / ESP32-HUB75-MatrixPanel-DMA compatible." The linked panel above is a known-good one.
PCB: order from your preferred fab using the patternflow_v2.1_gerber.zip Gerbers in hardware/pcb/gerber/ (or the KiCad source in hardware/pcb/kicad/). I used PCBway (sponsored).
⚠️ For this (v2) guide, order v2.1 only. This guide and its case files are built around the v2.1 board. The newer v3.0 board (patternflow_v3.0_gerber.zip, verified in #114) is a different size — it does not fit the v2 cases described here, and v2.1 boards don't fit the v3 cases. The v3 build guide is being written; its case files live inhardware/case/bed_330mm/andbed_256mm/.
If you want to order the PCB without manually uploading Gerbers, the Patternflow PCB is also listed as a PCBWay open-source project:
ℹ️ ESP32-S3 sourcing. Espressif-branded modules are the reference; AliExpress modules generally work fine too. If yours exhibits the cold-boot issue (Issue #16), the on-module 10k GPIO0 pullup fix photographed in that issue solves it — one resistor.
ℹ️ Rotary encoders. Any 5-pin EC11 with a click-switch works — the cheapest packs just fail more often. PEC11R-4220F-S0024 (Bourns, Mouser/DigiKey) is the reliable reference part.
- 3D printer (I used Bambu P1S)
- White and black PLA filament
- Soldering iron, solder, flux, tweezers
- Wire cutters or strong nippers (for trimming the LED matrix back)
- Cyanoacrylate glue (super glue)
- Phillips screwdriver, small flathead for screw terminals
- Wrench or pliers for the encoder nuts
| File | Contents | Color | Print Orientation |
|---|---|---|---|
legacy_v2/plate_main.stl |
Main body (vertical, tall part) | White | Vertical (standing up) |
legacy_v2/plate_dividers.stl |
Back covers and internal divider plates | White | Flat |
knobs/knobs_15mm.stl |
All 4 knobs for 15mm shaft encoders (one file) | Black | Standard |
knobs/knobs_20mm.stl |
All 4 knobs for 20mm shaft encoders (one file) | Black | Standard |
Print the main body, dividers, and one knob file. Print the knob STL that matches your encoder shaft length — knobs_15mm.stl or knobs_20mm.stl. Each file is one print job; each knob STL contains all four knobs.
Note: an
easyfitmain-body variant (alignment tabs along the bond seam) existed briefly but was retired over a fit defect (Issue #154). Use the standardplate_main.stl; the old variant remains at the v2.1.0 tag if you're curious.
I used a Bambu P1S with default settings, with one tweak:
- Nozzle: 0.4mm
- Layer height: Default (0.2mm)
- Infill: Default
- Support: Default tree support disabled. Use standard (regular) support instead.
- Brim: Off
- Aux fan: Lower to ~20%
- Total print time: ~11 hours combined
The main body (plate_main.stl) is the long, thin part. I orient it standing up — this is the orientation the slicer will probably default to. Supports are needed and easy to remove.
Why standard supports, not tree: During earlier prototypes I found tree supports more troublesome on this geometry. Standard supports remove cleanly here.
The case prints in halves because it's too tall for most printers in one piece. Bond everything before any electronics work — glue needs time to cure, and a fully bonded case is much easier to handle later.
Apply super glue along the seam between the upper and lower halves of the main body. Press firmly and hold until set.
Bonding tip. Tape the halves of
plate_main.stltogether and keep firm, even pressure until the glue sets — done right, this gives the cleanest seam, but it takes a steady hand. If a thin seam still shows, sprinkle a little baking soda into the wet glue line and it fills in.
Same procedure — bond the upper and lower halves of the back panel together.
Inside the case, there's an internal divider that separates the LED matrix volume from the electronics + power bank volume. The divider has a hole for the USB cable to pass through.
Insert the divider from the front side (the power bank / lower side), sliding it up into position. Apply super glue along the divider edges to bond it to the case interior. Do not insert it from the back.
Allow ~5 minutes after every bond step for the glue to fully cure before handling.
This is an all through-hole build. Work small-to-tall — shortest parts first, tallest last.
No SMD required. The PCB has footprints for optional 0805 passives (encoder pull-ups, filter caps, ESP32 decoupling caps) and an R13 GPIO0 pull-up, but they are not populated — a unit runs fine without any of them. See the design note in Section 10 for the rationale and the on-module cold-boot fix if you ever need it.
Solder, in order (small/short to tall):
- Female pin sockets (1×22 ×2) for ESP32-S3 — these go on the front of the PCB. The ESP32 module will plug into these later. Do not solder the ESP32 directly.
- J1 (HUB75 box header), J2 (USB power input screw terminal), and J3 (LED matrix power output screw terminal).
- C11 (1000µF electrolytic) — watch polarity (long lead = positive).
- Rotary encoders (SW1–SW4) — see the critical warning below before soldering.
CRITICAL -- Solder rotary encoders on the BACK of the PCB.
Insert each encoder from the back of the PCB so its body sits on the back and its leads come through to the front, then solder the leads on the front. The v2.0 silkscreen marks this clearly -- follow it.
If you solder them on the wrong side, the shafts will not reach the case front panel and the build is non-functional. Desoldering through-hole rotary encoders from a populated PCB is extremely painful -- I made this exact mistake on my own first build. Stop and check the side twice before soldering each encoder.
Wrong side (front) vs. correct side (back):
The photos in this guide show the earlier 20mm-shaft encoders. The 15mm EC11 encoders install the same way; only the matching knob STL changes.
Press all parts flush against the PCB and keep them perpendicular before soldering.
Leave the female sockets empty for now. The ESP32-S3 module gets flashed separately over USB-C in Section 8, and only after flashing does it get plugged into the PCB. This avoids any chance of weird interactions during flashing and keeps the module easily removable for later updates.
If you're designing your own PCB or verifying wiring manually, the full pin assignment table for the ESP32-S3-WROOM-1 N16R8 DevKit as used in Patternflow is below. Most builders following this guide don't need it — the off-the-shelf PCB handles all of this — so it's collapsed by default.
📌 Click to expand: full ESP32-S3 pinout
Numbering is top-to-bottom with the USB connector at the top.
| # | Pin | Function |
|---|---|---|
| 1 | 3V3 | +3.3 V supply |
| 2 | 3V3 | +3.3 V supply |
| 3 | RST | Not connected (NC) |
| 4 | IO4 | ENC1_A |
| 5 | IO5 | ENC2_A |
| 6 | IO6 | ENC3_A |
| 7 | IO7 | ENC4_A |
| 8 | IO15 | ENC2_SW |
| 9 | IO16 | ENC3_B |
| 10 | IO17 | ENC3_SW |
| 11 | IO18 | ENC4_B |
| 12 | IO8 | ENC1_B |
| 13 | IO3 | Not connected (NC) |
| 14 | IO46 | HUB_A |
| 15 | IO9 | ENC1_SW |
| 16 | IO10 | ENC2_B |
| 17 | IO11 | HUB_B |
| 18 | IO12 | HUB_D |
| 19 | IO13 | HUB_B2 |
| 20 | IO14 | HUB_OE |
| 21 | 5V | +5 V input |
| 22 | GND | GND |
| # | Pin | Function |
|---|---|---|
| 23 | GND | GND |
| 24 | TX | Not connected (NC) |
| 25 | RX | Not connected (NC) |
| 26 | IO1 | ENC4_SW |
| 27 | IO2 | HUB_CLK |
| 28 | IO42 | HUB_R1 |
| 29 | IO41 | HUB_G1 |
| 30 | IO40 | HUB_B1 |
| 31 | IO39 | HUB_G2 |
| 32 | IO38 | HUB_R2 |
| 33 | IO37 | NC (PSRAM internal) |
| 34 | IO36 | NC (PSRAM internal) |
| 35 | IO35 | NC (PSRAM internal) |
| 36 | IO0 | GPIO0 boot strap (R13 pad unpopulated — see Section 10) |
| 37 | IO45 | Not connected (NC) |
| 38 | IO48 | HUB_C |
| 39 | IO47 | HUB_LAT |
| 40 | IO21 | HUB_E |
| 41 | IO20 | Not connected (NC) |
| 42 | IO19 | Not connected (NC) |
| 43 | GND | GND |
| 44 | GND | GND |
IO35–IO37 are internally connected to the PSRAM on the N16R8 variant. Do not use these pins for external connections.
The LED matrix has two small alignment bumps on its back, diagonally opposite each other. These prevent it from sitting flat against the case.
Cut them off with strong nippers or pliers. Slight residual nubs are fine — flat enough is flat enough.
A future case revision will include recesses for these bumps so trimming isn't needed.
- From the front of the case, lower the LED matrix into its slot.
- Flip the case over.
- From the back, secure the matrix with the M4 screws (×6).
The screws thread directly into the LED matrix's mounting holes. Don't over-tighten.
At this point the matrix is in the case but the PCB is not yet installed. You'll do all the wire-side work first — connecting the USB power input, the matrix power, and the HUB75 ribbon to the PCB while it's still loose and easy to handle. Then in Section 7 the whole PCB-with-cables-attached assembly drops into the case.
Cut the sacrificial USB cable short — trim it to a length that routes from the power bank compartment, through the divider hole, to the PCB position without excessive slack. Strip the +5V (red) and GND (black) wires.
Pass the cable through the divider hole. Connect to J2 (with the PCB-side facing you):
- Inner terminal → +5V (red)
- Outer terminal → GND (black)
Tighten with a small flathead.
The LED matrix ships with a power cable (red/black) that has two red (+) and two black (−) wires. Hold it up to estimate reach to J3 before cutting — give it just enough length to route cleanly without strain. Then cut, strip, and bundle each pair (the two reds together, the two blacks together) before inserting.
Polarity matches J2: inner = +5V (red pair), outer = GND (black pair).
⚠️ Watch polarity. Reversing it will damage the matrix.J2 (input) and J3 (output to LED matrix) are connected internally on the PCB. You don't need to bridge them externally — the PCB handles +5V distribution.
📏 Exact recommended cable lengths will be added to this guide in a future revision. For now, measure against your specific case + power bank position.
The HUB75 ribbon cable that ships with the matrix is used as-is — do not cut it. Just plug one end into the matrix's data input and the other end into J1 on the PCB. The keying on the box header ensures correct orientation.
The PCB sits in the dedicated PCB slot, with the rotary encoders facing through the case front. The slot is intentionally tight in v1.0.
- Hold the PCB at an angle, encoder side down.
- Slide the bottom row of encoders into their case slots first.
- While tilting the PCB toward flat, guide the upper encoders into their slots simultaneously.
- Push the PCB flat against the case interior.
From the front of the case, attach each rotary encoder's nut and tighten with a wrench or pliers. This both secures the encoder shafts to the front face and locks the PCB in place.
Slide the back cover panel into place along the rear of the case.
Slide the PCB compartment cover panel into its slot to close off the electronics section.
Press-fit the four black knobs onto the encoder shafts. Use the knob set that matches your encoder shaft length: knobs_15mm.stl for 15mm encoders, or knobs_20mm.stl for 20mm encoders.
At this point the Patternflow body is mechanically complete. The only thing left is the brain.
There are two firmware paths:
- Release firmware: flash the official Patternflow OS from the browser. This is the recommended path for this build guide.
- Custom firmware: use Arduino IDE when you are adding your own pattern or changing firmware source.
The ESP32-S3 module is flashed separately, with the module outside the PCB, and only plugged in afterwards.
No installation required. Works on any desktop with Chrome or Edge.
- Visit patternflow.work on a desktop browser.
- Connect your ESP32-S3 to your computer via a USB-C data cable — do not insert it into the PCB yet.
- Scroll to the Patterns section and click "Flash Patternflow OS".
- Select the correct serial port when prompted and follow the on-screen steps.
- Once flashing is complete, disconnect the USB-C cable.
⚠️ The Web Serial API is only supported on desktop Chrome and Edge. Firefox and Safari are not supported.
Use this method if you want to modify the firmware source, add custom patterns, or if the browser flasher doesn't work for your setup. Custom patterns are not added to the release flasher automatically; you compile and upload your own firmware build. See docs/assembly/firmware/custom-patterns.md and firmware/CUSTOM_PATTERNS.md.
- Arduino IDE (latest version)
- ESP32 board package installed (Tools → Board → Boards Manager → search "esp32")
In Arduino IDE, Tools menu:
- Board: ESP32S3 Dev Module
- PSRAM: OPI PSRAM
- Flash Size: 16MB
- Partition Scheme: 16M Flash (3MB APP/9.9MB FATFS) or similar with PSRAM-aware partition
- USB CDC On Boot: Disabled
- Upload Mode: UART0 / Hardware CDC
The firmware sketch lives in firmware/patternflow/. The folder contains:
| File | Role |
|---|---|
patternflow.ino |
Main sketch — entry point, setup() / loop() |
config.h |
Pin mappings, brightness, pattern parameter limits — edit this for custom hardware |
core_display.h |
HUB75 display driver and rendering pipeline |
core_encoders.h |
Rotary encoder handling and parameter update logic |
pattern_origin.h |
Built-in pattern: Origin |
pattern_wave_saw.h |
Built-in pattern: Wave Saw |
- Connect the ESP32-S3 module to your computer with a USB-C data cable.
- Select the correct port under Tools → Port.
- Open
firmware/patternflow/patternflow.ino. Arduino IDE will load all the.hfiles in the same folder automatically. - If you're building for custom hardware, edit
config.hto adjust pin mappings, brightness, or pattern limits. - Click Upload.
If the upload fails, hold BOOT on the ESP32-S3 while pressing RESET, then click Upload again.
OTA updates work via Arduino IDE's network port option once the device has been on the same Wi-Fi network at least once. OTA in v2.0.0 is functional but not the recommended path. Use the browser flasher or wired upload as the primary method.
With flashing complete and the USB cable disconnected, plug the ESP32-S3 module into the female pin sockets on the Patternflow PCB.
- Slide a power bank into the battery compartment.
- Slide the battery cover into place to hold it.
- Connect the power bank to the USB cable wired into J2.
- The LED matrix should illuminate with the default pattern within a second or two.
- Turn the four knobs to confirm they all respond.
If your unit does not boot reliably, press RESET on the ESP32-S3 module once. On v2.0 boards this should not be necessary -- if it consistently is, open an issue with your module source (AliExpress / Espressif / other) and a photo of the GPIO0 area on your PCB.
-
Cold-boot reliability (was Issue #1). GPIO0 is a strapping pin on the ESP32-S3 and was left floating in v1.0. After extended power-off, residual charge could leak into an indeterminate state, sometimes registering LOW on power-on and sending the module into serial bootloader mode instead of normal boot -- looking exactly like "boot failure." v2.0 adds a 10k pullup from GPIO0 to 3.3V on the PCB. Full debugging story in Issue #16. Two weeks of debugging compressed into one comment from u/Infrated on r/AskElectronics. Genuine Espressif modules tended not to exhibit the issue at all, but v2.0 covers both genuine and clone modules.
-
Silkscreen ambiguity (was Issue #3). 0805 resistor vs. capacitor designators are now clearly marked, and the correct encoder solder side is on the silkscreen.
- Issue #4 -- LED matrix back has alignment bumps. The matrix manufacturer leaves two small alignment bumps on the back of the panel. Current workaround: cut them off during assembly (see Section 5.1). They cut easily. A future case revision (planned to land with the LED diffuser variant) will add recesses to accommodate them.
-
0805 passives left unpopulated. The board carries footprints for encoder pull-ups (R1–R12), encoder filter caps (C1–C10, C12–C13), and ESP32 decoupling caps (C14–C15), but bench testing confirmed a unit runs fine with none of them populated — the ESP32-S3's internal pull-ups plus firmware debouncing cover the encoders. They're dropped from the BOM and build steps to keep this an all through-hole build. The pads are still there if you're spinning a derivative and want the belt-and-suspenders version.
-
R13 (GPIO0 pull-up) not populated — on-module fix if needed. R13 addressed the cold-boot strapping issue, which mostly affects clone/AliExpress ESP32-S3 modules; genuine Espressif modules boot fine without it. If you do hit the symptom (board fails to start after being unplugged for a few minutes), solder a 10kΩ leaded resistor directly on the ESP32 module between GPIO0 and 3.3V — no board rework, no extra part to source if you have a 10kΩ on hand. Root-cause write-up and reference photos in Issue #16.
-
Encoder direction handled in firmware (was Issue #2). The encoder PCB footprint is rotated relative to the natural CW=increment direction. Rather than re-spinning the PCB, the firmware inverts the sign. This is transparent to the user. If you fork the firmware or design a derivative PCB, mind this.
-
C11 (1000uF electrolytic) retained. Issue #16 discussion noted that 1000uF is roughly 100x over the USB inrush spec for desktop-USB-powered devices -- and that is correct. Patternflow is powered by a power bank, not a desktop USB port, so the inrush argument does not apply. Without C11 the panel can flicker visibly during the boot transient, so it stays. If you are designing a derivative that connects to a host PC, drop C11 to <=50uF.
-
Encoder shaft length variants (was Issue #5). 15mm and 20mm shafts are functionally identical here — it's purely a matter of what you can source or prefer. Both knob STLs are included in
hardware/case/knobs/; print the one that matches the encoders you bought. The reference part in the BOM (Bourns PEC11R-4220F-S0024) happens to be 20mm.
This is Patternflow v2.0.0. The cold-boot issue is fixed; the case still needs manual matrix-bump trimming. The remaining open item is listed above.
If you build one -- please open an issue on GitHub with photos and any notes. If something in this guide was unclear or wrong, send a PR. If you fix one of the Known Issues, you will be credited as a contributor.
-- SeungHun





















