Skip to the content
2 layers93 × 52 mmJT Business Card, the printed circuit board in 3D

Connection And Resuable Device

Size
93 × 52 mm
Copper layers
2
Thickness
0.6 mm
Parts
527
Nets
91
Pads
1,178
Tracks, vias
2,207, 391
Pours
5
Routed
Yes
KiCad
KiCad 9.0 (file 20241229)
Created
15 Oct 2026
Updated
15 Oct 2026

Overview

JT Business Card is an open-source printed circuit board by MontgomeryResearchLabs, from the MontgomeryResearchLabs/C.A.R.D. repository on GitHub, published under the MIT licence. It is a 2-layer board of 93 × 52 mm with 527 parts on 91 nets, routed with 2,207 tracks and 391 vias.

It is built around the RP2350A, ZD25WQ32CEIGR and DW01A, with the ST25DV04KC-IE6S3, 150060GS75000 and ABM8-272-T3 among its other parts. Its parts include 443 diodes, 38 resistors, 26 capacitors, 6 ICs and 2 transistors. It belongs with the RP2350 boards of this library.

Main components

7

README

README.md

S.I.M. (Simple Implementation Machine)

A 3D hardware-in-the-loop simulator for an RP2350 business card that was never fabricated.

S.I.M. runs the card's real Pico SDK C firmware, unmodified, against an electrical model of the circuit built straight from its KiCad files, and shows the result on a 3D card you can tilt. It answers the question a first prototype would have answered: does this firmware drive this hardware correctly?

The simulator: the LED side of the card with the fluid demo running, and the checks panel

The board: an RP2350A, a 21×21 charlieplexed matrix of 441 green 0603 LEDs on 22 GPIOs, a LIS2DH12 accelerometer, an ST25DV04K NFC tag and a coin cell. The reference firmware is a particle fluid that flows as you tilt the card.

The component side, painted from the PCB's own copper, mask openings and silkscreen

Try it

  • In a browser, nothing to install: open docs/index.html, or this repository's GitHub Pages site if it is enabled (Settings → Pages → branch main, folder /docs). Drag the card to tilt it; right-drag to orbit.
  • With your own firmware: follow the quick start below.

Quick start

Requirements: Node.js 18+, Python 3, and clang + wasm-ld (LLVM 15 or newer).

sh
npm install                       # Binaryen, three.js, esbuild

# macOS: Apple's clang has no WebAssembly target, so use Homebrew's LLVM
brew install llvm lld
export PATH="$(brew --prefix llvm)/bin:$(brew --prefix lld)/bin:$PATH"
# Ubuntu / Debian:  sudo apt install clang lld

npm run build                     # compile the reference firmware to .wasm
npm test                          # run it headless and print a report
node tools/sim.mjs firmware/fluid-demo   # compile and open the 3D view
TaskCommand
Compile a firmware folder and open it in 3Dnode tools/sim.mjs path/to/firmware
Open a .wasm you already builtnode tools/sim.mjs fw.wasm
Compile onlynode tools/build-firmware.mjs path/to/firmware -o fw.wasm
Text report, for scripts and CInode tools/run-headless.mjs fw.wasm (exit 0 = no errors, 1 = errors, 2 = firmware stopped)
Rebuild the page in docs/npm run pages

The same firmware also builds for the real chip with the Pico SDK; see firmware/fluid-demo/CMakeLists.txt.

How it works

text
 .kicad_pcb ──┬─ kicad_pcb_extract.py ─▶ board.json ───────┐
              │                         led_map.h ─▶ firmware
              └─ kicad_artwork.py ────▶ artwork.json ─┐    │
                                                      │    ▼
 firmware C ──── build-firmware.mjs ──▶ firmware.wasm ─▶ engine.js ── board.js ── devices.js
                 clang → cycle counter → asyncify     │   (2 cores,    (pins,      (LIS2DH12,
                                                      │    virtual      LED solve,   ST25DV)
                                                      ▼    time, IRQs)  I2C checks)
                                          3D page (three.js)   or   run-headless.mjs

Reading the design files. A dependency-free Python S-expression parser reads the .kicad_pcb. It finds the 22 GPIO lines through the series resistors between Net-(U1-GPIOn) and /LED_ARRAY/Dk nets, finds every LED with both pads on LED nets, uses the pads' A/K pin functions to tell anode from cathode, and clusters positions into the 21×21 grid. A second script extracts the visual layers: Edge.Cuts chained into the outline and cutouts, copper, mask openings, and silkscreen. Silkscreen text comes from the polygons KiCad caches for outline fonts, so it renders exactly.

Running the firmware. The firmware is compiled with clang to WebAssembly against a simulated Pico SDK: 22 headers with the real SDK's names and signatures, implemented on about 30 JavaScript imports. Calling an unsupported SDK function fails at link time with its name. A Binaryen pass adds a Cortex-M33 instruction-cost counter, so compute time is deterministic. Binaryen's asyncify pass lets the engine suspend and resume each core at any SDK call, which gives two cores with their own stacks and virtual clocks, kept within 5 µs of each other, plus timers, alarms and GPIO interrupts on core 0.

The electrical model. Every pin change re-solves the charlieplex matrix. Each driven line is a source behind the pad resistance plus the 22 Ω resistor, and each conducting LED is a 2.55 V knee plus 32 Ω. Kirchhoff's current law gives a small linear system, solved directly. Current is added over time per LED, which becomes brightness and refresh rate. The model also flags pins over their drive rating, leakage glow through pull resistors, shorts, and outputs fighting other chips.

Peripherals. I2C transactions succeed only if the bus would really work: the pins are routed, and the pull-ups (powered from GPIO29 on this board) are on. A register-level LIS2DH12 model samples gravity from the 3D card's orientation, rotated into the sensor's axes using its footprint pose in the PCB.

A longer write-up of each part is in docs/HOW-IT-WORKS.md.

What it found on rev E1

FindingConsequence
LED current varies 7.8× (0.74–5.77 mA) with the number of LEDs lit in a scan slotA full pool looks patchy. Green InGaN at 3.3 V leaves ~0.7 V for the driver and 22 Ω resistor
An anode line peaks at 14.1 mA against a 12 mA drive ratingVoltage sag, adding to the dimming
GPIO26/27/28 have no-connect flags but are wired to GNDDriving one high is a short
The I2C pull-ups are powered from GPIO29, not 3V3Nothing answers on I2C until firmware drives GPIO29 high
The LIS2DH12 is rotated −45° and on the opposite side from the LEDsFirmware must rotate and mirror its axes

Build a simulator for your own board

Most of the code knows nothing about this card. The parts to adapt:

To changeWhere
How the board looks in 3DNothing: run tools/kicad_artwork.py on any KiCad 7–9 board
How LEDs and GPIOs are foundNet-name patterns and the accelerometer reference in tools/kicad_pcb_extract.py
LED drive topologysolve() in sim/core/board.js (any charlieplex works as is)
Component valuesELEC at the top of sim/core/board.js
Board-specific hazardscheckHazards() and I2CBus.checkBus() in sim/core/board.js
I2C chipsA class with start / write / read / stop in sim/core/devices.js, registered in engine.js
More SDK functionsHeader in sim/hal/include, C in sim/hal/src/hal.c, an import in sim_imports.h and makeImports()
Another MCU or SDKNew headers in sim/hal/; the compiler pipeline, scheduler and board model are unchanged

Keep a deliberately broken firmware next to the real one, like firmware/examples/common-mistakes. If the simulator does not catch the bugs you planted, it will not catch the ones you did not plan.

After a PCB change:

sh
python3 tools/kicad_pcb_extract.py board/JT-Business-Card.kicad_pcb \
    -o board/board.json --header firmware/fluid-demo/led_map.h
python3 tools/kicad_artwork.py board/JT-Business-Card.kicad_pcb -o board/artwork.json
npm run build && npm run pages

Limitations

  • Not an ARM emulator: firmware is recompiled, so direct register access (sio_hw->…), PIO, DMA, PWM, ADC, USB and flash writes are not simulated.
  • Interrupts and core switches happen at SDK calls, so races shorter than ~5 µs are not reproduced. A busy-wait on the other core must call an SDK function such as tight_loop_contents().
  • LED I-V fit, pad resistances and instruction costs are estimates; absolute currents and CPU load should be confirmed on hardware.
  • The LIS2DH12 axis directions follow the datasheet's top-view figure; confirm them by holding the first board upright.
  • Solder-mask colour and finish are not stored in KiCad files, so they are a viewer setting.

Project layout

PathContents
board/The card's KiCad PCB and schematic, plus the extracted board.json and artwork.json
firmware/fluid-demo/Reference firmware: charlieplex scan driver, LIS2DH12 driver, particle fluid
firmware/examples/common-mistakes/Firmware that is wrong on purpose, to show each check firing
sim/hal/The simulated Pico SDK
sim/core/Engine (engine.js), electrical model (board.js), I2C devices (devices.js)
sim/web/The 3D page: scene, artwork painter, UI
tools/Extractors, firmware build, cycle counter, page build, headless runner, launcher
docs/The built simulator page (for GitHub Pages) and screenshots

License

MIT.

Parts

527
QtyValueFootprintReferences
441LEDuser_footprints:LED_0603_1608MetricD101, D102, D103, D104, D105, D106, D107, D108, D109, D110, D111, D112, D113, D114, D115, D116 +425
2222Resistor_SMD:R_0402_1005MetricR13, R14, R15, R16, R17, R18, R19, R20, R21, R22, R23, R24, R25, R26, R27, R28 +6
15100nuser_footprints:C_0402_1005Metric_small_padsC2, C8, C11, C12, C13, C14, C15, C16, C17, C18, C20, C21, C23, C25, C26
5TestPointTestPoint:TestPoint_Pad_1.0x1.0mmBOOT1, GND1, RUN1, SWCLK1, SWD1
44.7uuser_footprints:C_0402_1005Metric_small_padsC6, C7, C9, C10
45K1Resistor_SMD:R_0402_1005MetricR1, R3, R11, R12
31KResistor_SMD:R_0402_1005MetricR2, R6, R31
31uuser_footprints:C_0402_1005Metric_small_padsC1, C5, C24
20Resistor_SMD:R_0402_1005MetricR32, R33
210uuser_footprints:C_0402_1005Metric_small_padsC19, C22
215puser_footprints:C_0402_1005Metric_small_padsC3, C4
227Resistor_SMD:R_0402_1005MetricR7, R8
23.3uRP2350_60QFN_minimal:L_pol_2016L1, L3
2HL-2302VPackage_TO_SOT_SMD:SOT-23Q6, Q7
1100Resistor_SMD:R_0402_1005MetricR10
110KResistor_SMD:R_0402_1005MetricR37
1110Resistor_SMD:R_0402_1005MetricR38
1150060GS75000150060GS7500:LED_0603_WRELED1
120KResistor_SMD:R_0402_1005MetricR9
133Resistor_SMD:R_0402_1005MetricR5
1ABM8-272-T3Crystal:Crystal_SMD_3225-4Pin_3.2x2.5mmY1
1Battery_Celluser_footprints:BatteryHolder_CustomBT1
1DW01APackage_TO_SOT_SMD:SOT-23-6U5
1LIS2DH12TRPackage_LGA:LGA-12_2x2mm_P0.5mmU4
1LOGOLOGOG***
1REG_3.3VPackage_TO_SOT_SMD:SOT-23U2
1RP2350ARP2350_60QFN_minimal:RP2350-QFN-60-1EP_7x7_P0.4mm_EP3.4x3.4mm_ThermalViasU1
1ST25DV04KC-IE6S3footprints:SO-8_STMU6
1Schottky DiodeDiode_SMD:D_SOD-323D1
1TP4057Package_TO_SOT_SMD:SOT-23-6J3
1USB_C_Receptacle_USB2.0_14Puser_footprints:USB_C_PADDLEJ1
1ZD25WQ32CEIGRPackage_SON:Winbond_USON-8-1EP_3x2mm_P0.5mm_EP0.2x1.6mmU3

Similar boards

  • 4 layers19 × 51 mmMorseCommunicator, the printed circuit board in 3D

    MorseCommunicator

    zeankundev/boards

    A collection and an archive of KiCad projects I made. Contains projects from 2023 - 2024.

    56 parts1
  • 4 layers24 × 45 mmRadianite uFM, the printed circuit board in 3D

    Radianite uFM

    RadianiteComputers/RadianiteBoards

    A collection of Radianite boards we have designed so far. All are licensed under CC BY-SA 4.0

    68 parts
  • 2 layers75 × 70 mmIPicoV5 Panelized, the printed circuit board in 3D

    IPicoV5 Panelized

    MiniProjectDIY/iPico

    RP2350 based UF2 hardware security key

    228 parts5CC0-1.0
  • 2 layers73.5 × 47.5 mmEinkBadge, the printed circuit board in 3D

    EinkBadge

    Las-Vejas/badge

    An E-ink badge run by RP2040, with NFC capabilities.

    67 partsGPL-3.0
  • 2 layers52.7 × 39.6 mmNFC harness V0, the printed circuit board in 3D

    NFC harness V0

    c0de111/NFC_exploration

    NFC exploration with st25 and raspberry pico

    22 parts3
  • 2 layers44.7 × 93.6 mmUltisense, the printed circuit board in 3D

    Ultisense

    darshg321/Ultisense

    an esp32-s3 devboard made to sense as many things as possible

    94 parts3
  • 2 layers35 × 62 mmTag, the printed circuit board in 3D

    Tag

    dgm91-pro/Attiny85_NFC_MULTI_TAG

    A small Project in bond with hackclub YSWS

    18 parts
  • 2 layers63.4 × 83.1 mmWO30109 FreshAir, the printed circuit board in 3D

    WO30109 FreshAir

    WOOWTECH/Woow_zigbee_fresh_air_pcb

    WO_30109 Zigbee 新風控制器 PCB(KiCad 10,git 版控;V1.3 重繪 + V2.0 安規改版)

    76 parts

Questions

How big is the JT Business Card PCB?

It measures 93 × 52 mm and has 2 copper layers, 0.6 mm thick.

What parts does the JT Business Card use?

527 parts, built around the RP2350A, ZD25WQ32CEIGR, DW01A and ST25DV04KC-IE6S3. 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 JT Business Card KiCad files?

From the MontgomeryResearchLabs/C.A.R.D. repository on GitHub, at the commit this page was made from: the board is board/JT-Business-Card.kicad_pcb and its schematic board/JT-Business-Card.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the JT Business Card design in my own project?

Its repository publishes it under the MIT licence: you may reuse it on that licence's terms (keep the author's notice, and for a copyleft licence share your changes alike).