Skip to the content
PCBKitNew board
2 layers50.5 × 83 mmPixelpine, the printed circuit board in 3D

PixelPine: a 7 cm PCB Christmas tree with 240 charlieplexed LEDs and a personality (KiCad, simulator, firmware sizing, docs)

Size
50.5 × 83 mm
Copper layers
2
Thickness
1.6 mm
Parts
271
Nets
39
Pads
558
Tracks, vias
1,658, 142
Pours
1
Routed
Yes
KiCad
KiCad 10.0 (file 20260206)
Created
2 Oct 2026
Updated
4 Nov 2026

Overview

Pixelpine is an open-source printed circuit board by machmar, from the machmar/pixelpine repository on GitHub. It is a 2-layer board of 50.5 × 83 mm with 271 parts on 39 nets, routed with 1,658 tracks and 142 vias.

It is built around the PY32F003F18P6TU and XL-1608WWC-06. Its parts include 240 diodes, 19 resistors, 2 capacitors, 1 IC and 1 connector.

Main components

2

README

README.md

PixelPine — a 7 cm PCB Christmas tree with 240 charlieplexed LEDs

KTE Project 1 · design-for-manufacture study · rev A · 2026-09-23

A chubby, cute, 70 mm tall PCB tree covered in 240 warm-white 0603 LEDs on a 2.12 mm grid. The LEDs are charlieplexed on 16 pins of a $0.13 Puya PY32F003 (Cortex-M0+, 64 KB flash) with software PWM. It runs from USB-C and slots into a PCB stand fin. The tree is a character, Pine, whose job is showing light shows. He's bad at it: he daydreams, naps, gets grumpy and gets excited to show you things. You interact with him through the capacitive star, which senses tap, pet, press, a front-and-back squeeze and tickles. The product firmware runs on the first boards and matches the simulator frame for frame (firmware/pixelpine/). Landed cost ≈ €3.2 at 1 000 pcs, €2.0 at 10 000 pcs; suggested shelf price €12.90.

Try Pine in your browser: machmar.github.io/pixelpine (the animation simulator).

FrontBack
topbottom

Deliverables

You asked forWhere
Schematichardware/pixelpine_schematic.pdf · KiCad 10 project hardware/pixelpine.kicad_pro (ERC: 0 errors, 0 warnings)
PCB shape and layouthardware/pixelpine.kicad_pcb: outline, stand fin, all 267 parts (+2 logo marks) placed with nets, silkscreen, CE/WEEE marks, routed on 2 layers (Freerouting, then finished by hand in KiCad: GND pour on both layers, 142 vias). DRC: 0 unconnected, schematic parity 0 issues; 2 cosmetic starved thermal notes (see the order guide). The LEDs sit in a routing-friendly arrangement: see LED arrangement
BOM (LCSC / JLCPCB)docs/bom.md · JLC upload files hardware/production/jlc_bom.csv, jlc_cpl.csv
Order fileshardware/production/: Gerber + drill zips for the tree (PCB + assembly) and the stand fin (bare PCB), BOM/CPL, 3D previews, and an order guide with every JLC setting and what to check in the placement preview
PC app to preview the animationssimulator/index.html, live at machmar.github.io/pixelpine (published from simulator/ by .github/workflows/pages.yml): open it in any browser; works offline apart from fonts. Pine runs live: touch his star (hover / click / hold / shift / ctrl / fast clicks) and watch his feelings change. Preview mode plays any show, face or transition. Includes an electrical model of the charlieplex (current sag, PWM depth, refresh, pin current)
Pine: character, faces, transitions, gesturesdocs/animations.md: behaviour state machine, emotions, 15 light shows, 16 faces, 6 transitions, touch gestures, RAM/flash budget
Market analysisdocs/market-analysis.md
EU certification setupdocs/eu-compliance/: applicability matrix, EMC test plan, technical file + risk assessment, DoC template, labels and leaflet (EN/CZ)
Final price analysis incl. assemblydocs/cost-analysis.md, generated by docs/cost_model.py
Connectivity & power consumptiondocs/power-and-connectivity.md
Product firmware (beyond the brief)firmware/pixelpine/: the simulator ported to C and running on the board: 59 KB of 63 KB flash, 7.0 KB of 8 KB RAM, 50 fps, 122 Hz 10-bit BCM scan. Checked frame by frame against the simulator's own JavaScript: Pine's state machine and emotions identical in every whole-Pine test, LED levels within 1 step of 255 on ≥ 99.99 % of LED-frames there (per show: 93–100 %, fireflies lowest). Touch calibrated on the board; room-light sensing isn't possible on rev A (and how rev B fixes it); a whole-tree touch field was tried and dropped (why). Flashing: firmware/flash/
Firmware fit checkfirmware/sizetest/: real compile of all shows, faces, brain and drivers for both MCUs. The original CH32V003 overflowed (20.9 KB of 16 KB), so the MCU was switched to the PY32F003F18P6: 17.2 KB of 64 KB flash (27 %), 1.9 KB of 8 KB RAM (23 %), built with the Arm GNU Toolchain. See docs/animations.md
Design specdocs/superpowers/specs/2026-09-23-pixelpine-design.md
All docs as PDFdocs/pdf/PixelPine-documentation.pdf: everything above in one linked PDF (cover, contents), plus one PDF per doc in docs/pdf/. Regenerate with python docs/export_pdfs.py after editing a doc

Key numbers

LEDs240 × XL-1608WWC-06 (0603, 2500–3000 K) at 45°, 2.12 mm pitch
MCUPuya PY32F003F18P6 (TSSOP-20, pinout 1, 24 MHz internal oscillator): 16 charlieplex lines (incl. SWD pins and BOOT0) + ADC touch star (PA7) + NRST; SWD programming
Character6 emotions (energy, focus, mood, annoyance, excitement, bond in flash), 16 faces, 6 transitions
Parts263 placements, 8 line items, 552 SMD joints, single-sided
PCB2-layer FR-4 1.6 mm, LF-HASL, JLC standard process (0.10 mm tracks and spaces at the narrowest, 0.3 / 0.4 mm vias); 1658 track segments (1991 mm), 142 vias, GND pour on both layers, 0 unrouted. Tree 50.5 × 70 mm, stand fin 36 × 10 mm
SupplyUSB-C 5 V, 3–12 mA typical (≈ 7 mA average), 19 mA max → ≤ 0.1 W
Light≈ 2 lm total, so outside the EU light-source ecodesign/energy-label scope
Pin current≤ 16.2 mA per GPIO by design (20 mA rated)
PWM8-bit binary-code modulation, 16 phases, 122 Hz refresh (the product firmware: 10-bit, same refresh)
CE routeEMC 2014/30/EU (EN IEC 55015 + EN 61547) + RoHS; GPSR, WEEE, PPWR, REACH obligations

LED arrangement

The 240 LED positions are a fixed 2.12 mm grid, but which anode/cathode pair sits at which position is a free choice, and it decides whether the board can be routed at all. The first version interleaved the pairs (anode = n mod 16), which leaves every one of the 480 LED pads as an island of its own, and Freerouting stalled with 76 connections still open after 87 minutes.

The 45° LEDs form a "brick" lattice with only ~0.55 mm between neighbouring pads. If two neighbouring pads are on the same line net, a tiny F.Cu bridge joins them for free, so a net can be a few big copper islands instead of 30 loose pads. hardware/scripts/optimize_leds.py (simulated annealing, C core in optimize_leds.c) arranges the 240 pairs for exactly that: 480 separate pads became 141 islands, only the links between islands need real wiring, and the board routes on 2 layers in about 2 minutes (it also stopped needing the 4 layers that the old arrangement was thought to need). The result is hardware/led_assignment.json, committed; geometry.py reads it, so the schematic, PCB, simulator map and firmware tables all follow from it. Every LED keeps the same orientation; the optimiser can also turn LEDs round by 180° (--flip) for ~15 % fewer islands, but that wasn't needed.

Side effect: neighbouring LEDs now sometimes share an anode line (17 % of the LED pairs closer than 3.2 mm, against 6 % with the old interleaving), so a lit blob loads some scan phases harder than before. The numbers are in docs/power-and-connectivity.md.

Regenerating the hardware

Since 2026-10-01 the board is finished by hand in KiCad (GND pour, extra vias). build_all.py would regenerate it from the scripts and drop those edits, so it now stops unless you pass --regenerate. For order files run export_gerbers.py (KiCad's Python) and export_fab.py; see hardware/production/README.md.

Everything in hardware/ was generated from one geometry module, so it stays consistent:

bash
python hardware/scripts/build_all.py

geometry.py (outline, LED grid, charlieplex map from led_assignment.json) → gen_footprints.py → gen_sch.py (+ ERC, netlist check) → gen_pcb.py (KiCad's bundled Python) → route.py (Freerouting, ~2-5 min; if connections stay open it routes again from scratch, up to 4 runs) → DRC and parity → 3D renders → JLC BOM/CPL. It also refreshes simulator/led_map.js, so the simulator always shows the real LED positions. After editing the outline, run python hardware/scripts/geometry.py --search and paste the new pitch into GRID_PITCH, then python hardware/scripts/optimize_leds.py (needs a C compiler, 10–20 min) to re-arrange the LEDs for the new grid. The firmware's per-LED tables come from the same map: python firmware/pixelpine/gen_tables.py, then python firmware/pixelpine/build.py flash builds and programs a board, and python firmware/pixelpine/test/compare.py checks the firmware against the simulator.

Open items before ordering

Order files are ready: hardware/production/README.md. At order time, check LCSC stock for the 240 LEDs per board and look at the LED polarity and U1 pin 1 in JLC's placement preview.

  1. Firmware: done on the first board (2026-11-03): it powers up at 4.97 V with no short, flashes through a J-Link (firmware/flash/) and runs Pine (firmware/pixelpine/). Still open: touch thresholds measured on one board and one hand (measure a batch); the EMC worst-case and production-test firmware builds.
  2. Room light can't be sensed on rev A: in a full charlieplex every LED has an anti-parallel twin, so no LED can work as a photodiode, and Pine stays in "lights on" mode. Rev B fix at no cost: leave one LED unpopulated (firmware/pixelpine/README.md).
  3. Hover isn't detectable with the star electrode (a finger 1 cm away is below the noise); tap, pet, press, squeeze and tickle work.
  4. No whole-tree touch on rev A: the LED lines can pick up the mains hum a finger carries, but only on a mains-tied supply (strong on a laptop, weak on a wall charger, nothing on a powerbank), so it was dropped after five experiments (firmware/pixelpine/README.md). The star is the touch control.

Done: PCB routed on 2 layers (LED arrangement optimised for it, DRC clean), manufacturer name, address (Jehličková 240, 240 24 Stromečkov: made up for the school project, replace it with the registered address for a real product) and e-mail on the product, U1 and J1 placement orientation checked, MCU choice final (PY32F003F18P6, kept over the cheaper F002A for firmware-update headroom).

Parts

271
QtyValueFootprintReferences
240XL-1608WWC-06PixelPine:LED_0603_DenseD1, D10, D11, D100, D101, D102, D103, D104, D105, D106, D107, D108, D109, D110, D111, D112 +224
16100Resistor_SMD:R_0402_1005MetricR1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15, R16
25.1kResistor_SMD:R_0402_1005MetricR17, R18
1+5VTestPoint:TestPoint_Pad_D1.5mmTP1
1100nCapacitor_SMD:C_0402_1005MetricC2
110uCapacitor_SMD:C_0603_1608MetricC1
11kResistor_SMD:R_0402_1005MetricR19
1CE-Logo_8.5x6mm_SilkScreenSymbol:CE-Logo_8.5x6mm_SilkScreenLOGO1
1GNDTestPoint:TestPoint_Pad_D1.5mmTP2
1NRSTTestPoint:TestPoint_Pad_D1.5mmTP6
1PY32F003F18P6Package_SO:TSSOP-20_4.4x6.5mm_P0.65mmU1
1STARPixelPine:TouchStarTP4
1SWCLKTestPoint:TestPoint_Pad_D1.5mmTP5
1SWDIOTestPoint:TestPoint_Pad_D1.5mmTP3
1TYPE-C 6PPixelPine:USB_C_SHOUHAN_TYPE-C-6PJ1
1WEEE-Logo_4.2x6mm_SilkScreenSymbol:WEEE-Logo_4.2x6mm_SilkScreenLOGO2

Questions

How big is the Pixelpine PCB?

It measures 50.5 × 83 mm and has 2 copper layers, 1.6 mm thick.

What parts does the Pixelpine use?

271 parts, built around the PY32F003F18P6TU and XL-1608WWC-06. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the Pixelpine KiCad files?

From the machmar/pixelpine repository on GitHub, at the commit this page was made from: the board is hardware/pixelpine.kicad_pcb and its schematic hardware/pixelpine.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Pixelpine design in my own project?

Its repository states no licence, so its author keeps all rights: ask machmar before reusing the design.

Similar boards

  • 2 layers85.6 × 54 mmPCB Business Card, the printed circuit board in 3D

    PCB Business Card

    Sealflux/PCB_Business_Card

    A business card that lights up when you tap it with your phone. No battery. It just steals power from the phone's NFC radio.

    17 partsMIT
  • 4 layers65 × 40 mmPowermod, the printed circuit board in 3D

    Powermod

    zdeager/powermod

    Machine-generated PCB design for a Raspberry Pi Zero 2 W power scheduler / UPS / battery board. The electrical design is defined in code (netlist.py); the board was laid out and routed with the scripted KiCad +…

    77 parts
  • 2 layers73 × 50 mmExtension, the printed circuit board in 3D

    Extension

    roman-khvatov/RoboWay

    Кружок робототехники RoboWay

    61 parts1MIT
  • 2 layers152 × 88 mmRFBitBangSMT, the printed circuit board in 3D

    RFBitBangSMT

    profdc9/RFBitBanger

    A QRP digital transceiver created from readily available parts for off-the-grid maintenance

    271 parts222
  • 2 layers100 × 100 mmMist1010, the printed circuit board in 3D

    Mist1010

    UzixLS/mist1010-board

    10x10cm PCB variant of MIST FPGA project for G738/G706 enclosure

    271 parts43MIT
  • 2 layers220 × 160 mmK Aries bis, the printed circuit board in 3D

    K Aries bis

    F6ITU/K_Aries

    Aries, an OpenHPSDR ATU (kicad version)

    271 parts2
  • 2 layers83.8 × 48.6 mmRP2040 minimal, the printed circuit board in 3D

    RP2040 minimal

    leonidas213/picoDrone

    271 parts1
  • 2 layers120 × 100 mmCh bot, the printed circuit board in 3D

    Ch bot

    doabayomi/horizon-pcb

    PCB Design for the Horizon Controller Prototype (REV 1)

    271 parts