Skip to the content
PCBKitNew board
4 layers195 × 58 mmEzo pmp controller, the printed circuit board in 3D

Open schematics and design files for a computer controlled nutrient dosing system

Size
195 × 58 mm
Copper layers
4
Thickness
1.6 mm
Parts
83
Nets
37
Pads
248
Tracks, vias
507, 73
Pours
12
Routed
Yes
KiCad
KiCad 10.0 (file 20260206)
Created
10 Aug 2026
Updated
13 Aug 2026

Overview

Ezo pmp controller is an open-source printed circuit board by dephekt, from the dephekt/dosing-pump-hardware repository on GitHub, published under the CERN-OHL-S-2.0 licence. It is a 4-layer board of 195 × 58 mm with 83 parts on 37 nets, routed with 507 tracks and 73 vias.

It is built around the SRV05-4, AP2112K-3.3TRG1 and AO3401A, with the BSS138LT1G, BZT52C10 and SMBJ13A among its other parts. Its parts include 24 resistors, 18 capacitors, 9 connectors, 7 ICs and 4 diodes. It belongs with the ESP32 boards of this library.

Main components

8

README

electrical/README.md

Electrical and PCB prototype v0.3

This directory contains the native KiCad schematic and routed four-layer PCB for the four-channel Atlas Scientific EZO-PMP controller. It implements the power input, logic rails, Feather carrier, per-pump I2C isolation, and four unswitched pump supply taps inside the frozen 195 x 58 mm mechanical envelope.

Open ezo-pmp-controller.kicad_pro in KiCad. The project manager shows both editors: Schematic Editor opens the hierarchical circuit and PCB Editor opens the placed and routed board in ezo-pmp-controller.kicad_pcb. In PCB Editor, press Home to fit the board, B to refill copper zones, and Alt+3 for the 3D Viewer. A board-only STEP is also checked in under exports/pcb; component models shown by KiCad depend on the optional KiCad 3D package libraries installed on the workstation.

The root schematic links these functional sheets:

SheetFunction
power_input.kicad_sch12 V input, fuse, reverse-polarity pass FET, TVS, bulk filtering, and the four pump supply taps
logic_power_feather.kicad_sch5 V buck, dedicated 3.3 V EZO rail, and ESP32-S3 TFT Feather
pump_logic.kicad_schTCA9548A, four Atlas headers, pull-ups, series damping, and ESD protection

PCB layout

  • 195 x 58 mm rounded four-layer board; 1.6 mm FR-4 is the intended stack.
  • Four 3.4 mm non-plated M3 mounting holes on the frozen 185 x 48 mm pattern.
  • Feather/TFT at the left with its antenna and USB end at the board edge.
  • Four repeated pump control headers and supply terminals along the service edge, ordered pump 1 through pump 4.
  • 12 V input, fuse, protection, and regulators grouped on the right; the mux and its reset barrier sit between the Feather and power.
  • Ground planes on In1 and the back, protected-12 V and 5 V planes on In2, and 1.0 mm input, 0.50 mm logic power, and 0.20 mm minimum signal routing.
  • 84 footprints, including 57 top-side JLCPCB SMT placements and the hand-soldered connectors, jumper, and Feather socket/module parts.

The placement drawing is exports/pcb/ezo-pmp-controller-assembly.png, and the copper/silkscreen overview is exports/pcb/ezo-pmp-controller-top.png. These files make the physical arrangement reviewable without KiCad, but the .kicad_pcb file remains the editable source of truth.

The Feather pad map was checked against Adafruit's PID 5483 PrettyPins drawing, including its two 3.3 V header pins, bottom TXD0 debug pad, and reverse-numbered right header row. The onboard TFT shares the Feather's SCK/GPIO36 and MOSI/GPIO35 header pins, so both are intentionally left unconnected on this carrier.

Pump connections

Each pump uses two separate connectors on this controller:

Controller connectionPins
J2-J5, Atlas control cable1 white SCL, 2 green SDA, 3 black GND, 4 red 3.3 V, 5 blue INT
J7-J10, pump supply1 unswitched 12 V positive, 2 ground

Every pump ships in UART mode and must be switched to I2C once before installation; see commissioning.md. The switch sets the factory address 0x67, which is what the mux design expects, so all four pumps stay identical and a replacement needs no address assignment.

The INT pin is wired only to its ESD clamp; it does not reach the MCU. Atlas asserts INT while a pump is dispensing, and dispensing state is polled over I2C instead, so the pin exists to keep the five-wire cable mating correctly. Atlas's own hydroponics kit lands the pump on a four-wire I2C terminal for the same reason.

The control footprint is a horizontal 1-by-5 2.54 mm header selected to mate with the Atlas cable's Molex 2695 controller-end housing. The prototype gate is to test the real cable on the selected C138801 header and confirm pitch, friction-lock engagement, pin-1 orientation, and service clearance before PCB release. The board silkscreen must include both pin numbers and wire colors.

Safety behavior

Dosing is stopped in firmware, not in hardware. Each EZO-PMP contains its own motor controller and dispenses by volume: the carrier tells a pump to move a number of millilitres and the pump decides when to stop. Earlier revisions put a quad high-side switch, a watchdog, and an arm gate between the supply and the pumps so the carrier could cut a pump off mid-dose. That was removed in v0.3. These pumps are normally deployed straight off a wall brick with no such hardware anywhere in the path, so the failure it guarded against -- a pump that will not stop -- is one the pump's own firmware has to handle. Adding a cutoff here would have protected this one carrier and nothing else.

What remains:

  • The 12 V input is fused, reverse-polarity protected by a P-channel pass FET, and clamped by a 13 V TVS. Supply the board from 12 V only; at 24 V the TVS conducts continuously rather than protecting anything.
  • The TCA9548A reset is pulled up only by the EZO rail and asserted through an open-drain MOSFET, so a USB-powered Feather GPIO can never drive the unpowered pump-logic rail. The gate is pulled down, so the mux releases itself at power-on and needs no firmware co-operation to be reachable.
  • The I2C trunk pull-ups reference EZO_3V3, not the Feather rail, so the bus is only driven when both domains are up.
  • The Feather VBUS header and its USB-C connector are the same net inside the module. Remove the Feather from its socket to flash it. D4 makes an attached host harmless rather than merely forbidden: the host's 5.0 V exceeds the 4.6 V the carrier offers, so the diode turns off and USB powers the Feather alone.

None of this is an emergency-stop-rated safety system, and there is no hardware path that can interrupt a dose in progress. Wet energized use remains prohibited until the mechanical and powered electrical gates pass.

Regenerate and validate

The checked-in KiCad files and BOMs are generated from the explicit component and connectivity model in tools/generate_kicad.py. Dependencies are declared in the repository's pyproject.toml and managed with uv:

sh
uv venv --system-site-packages
uv sync --inexact

--system-site-packages is required because the PCB scripts import KiCad's pcbnew, which ships with KiCad rather than from PyPI; --inexact stops uv from removing packages it did not install itself.

Routing needs Freerouting on PATH. nix profile install nixpkgs#freerouting pins 2.2.4 and brings its own JRE; route_pcb.py passes --gui.enabled=false and clears DISPLAY, so it routes and exits without opening a window.

Then, using .venv/bin/python:

sh
python3 electrical/tools/generate_kicad.py
python3 electrical/tools/validate_kicad.py
python3 electrical/tools/create_pcb.py --force
python3 electrical/tools/route_pcb.py
python3 electrical/tools/finalize_pcb.py electrical/ezo-pmp-controller.kicad_pcb
python3 electrical/tools/validate_pcb.py
python3 electrical/tools/generate_fabrication.py

route_pcb.py exports the Specctra DSN, checks its net-class membership, runs Freerouting, and imports the session back onto the board. It prints whatever the router could not finish, though that list overstates the problem — the router does not credit the locked escape copper it is handed, and the planes do not exist yet. validate_pcb.py is the gate that decides whether the board is fabricable.

Everything through routing is byte-reproducible: two rebuilds give identical bootstrap boards, identical Specctra exports and an identical routing session. That relies on tools/canonical_board.py, which every script that writes the board calls. pcbnew assigns random UUIDs to the items it creates and KiCad orders and writes items by UUID, so without canonicalising, each rebuild emitted the same geometry in a different order and the autorouter followed it to a different result.

The final zone fill is the one step that is not bit-identical. KiCad's filler is multithreaded and lands a point or two differently between runs, so the copper is electrically the same while the file churns slightly. Everything that decides the design is stable; only the computed fill polygons move. The session is checked in under routing/, so when the netlist has not changed route_pcb.py --reuse-session skips the router and reproduces identical copper. Re-run without that flag whenever the netlist changes — a session routed against a stale netlist will attach copper to the wrong pads.

The generator also owns the schematic presentation, split across four modules:

ModuleResponsibility
tools/kicad_symbols.pyVendors real symbols from the stock KiCad libraries and generates the IC rectangles and project power ports
tools/kicad_draw.pySheet objects: symbol instances, wires, junctions, labels; grid snapping and the connectivity check
tools/kicad_blocks.pyThe circuit idioms — rails, shunts, series elements, grounds
tools/layouts/*.pyOne module per sheet deciding what that sheet looks like
tools/canonical_board.pyRewrites a written board so its bytes depend only on the design, not on the UUIDs pcbnew happened to assign

Component symbols come from KiCad's own libraries, so a capacitor looks like a capacitor and a diode shows its polarity. Rails and grounds are power ports. Series elements are drawn horizontally and shunt elements vertically, which is what tells a reader at a glance who is in the signal path and who is parallel. Visible wires show connections inside a functional block; named local/global ports are reserved for connections that would otherwise cross blocks or sheets.

See tools/layouts/README.md for the layout API. Edit those layout definitions rather than moving generated symbols by hand in KiCad; regeneration intentionally replaces the child sheets. To iterate on one sheet, python3 electrical/tools/preview_sheet.py <sheet_name> renders it on its own and reports any netlist mismatch.

Test points are PCB features and therefore do not carry KiCad's red DNP cross; crosses that remain mark intentionally unpopulated options.

Connectivity rides on drawn wires, so a placement mistake is a wiring mistake. The generator checks the drawn geometry against the model before writing each sheet — nets that split, nets shorted together, and nets left unnamed all fail the build — and validate_kicad.py re-derives the whole netlist through kicad-cli afterwards.

The schematic validator invokes KiCad's own parser/exporter, resolves every footprint and pad, compares every exported pin/net pair with the source model, and checks both BOMs. It also runs kicad-cli sch erc, which KiCad 10 provides, so ERC is part of the automated gate rather than a manual step.

The PCB validator checks the 195 x 58 mm outline, four-layer stack, mounting pattern, footprints, power copper, routes, JLCPCB placements, and KiCad DRC. It fails on any DRC error or unconnected pad. The complete board-rebuild sequence intentionally overwrites hand edits; capture deliberate layout changes in the PCB scripts before using it.

The validator discovers standard footprints from KiCad's footprint environment variables and the kicad-cli installation prefix. For a custom installation, override the location explicitly:

sh
DOSING_PUMP_KICAD_FOOTPRINT_DIR=/path/to/kicad/footprints \
  python3 electrical/tools/validate_kicad.py

The generated sourcing files are:

The generated JLCPCB package is under fabrication/jlcpcb: Gerber ZIP, separate PTH/NPTH drills, grouped BOM, and a 57-row top-side CPL. See fabrication/manufacturing-notes.md for the order settings and upload checklist. These files are prototype-review outputs, not authorization to order boards before the gates below pass.

Gates before PCB fabrication

  1. Verify the Atlas cable/header mechanical fit and pin-1 orientation using the real cable and the supplied carrier-board STEP reference.
  2. Measure pump startup and stall current at 12 V; confirm the 3 A input fuse tolerates four pumps starting together without nuisance opening.
  3. Bench-check USB-only with the Feather out of its socket, 12 V with it seated, and USB attached while seated, using a current-limited sacrificial setup. Confirm D4 isolates SYS_5V in the last case.
  4. Compile and exercise four ESPHome ezo_pmp instances through explicit TCA9548A virtual-bus IDs; test a stuck bus and hot replacement.
  5. Confirm reverse polarity on the input terminal does not power the board and does not damage Q2, and that brownout and MCU reset leave the pumps in a known state.
  6. Review the completed placement against a printed 1:1 board and the real enclosure/deck, inspect every Gerber and check the ten corrected JLC placement rotations against JLCPCB's preview, run GUI ERC, and obtain an independent schematic/layout review before ordering.

The board carries four pumps and nothing more. To run more, build a second board: it brings its own regulation, its own MCU and its own current budget. There is no expansion header and no inter-enclosure I2C -- ordinary I2C must not be routed between enclosures.

Parts

83
QtyValueFootprintReferences
104.7kResistor_SMD:R_0603_1608MetricR6, R7, R12, R13, R16, R17, R20, R21, R24, R25
833RResistor_SMD:R_0603_1608MetricR10, R11, R14, R15, R18, R19, R22, R23
7100nF 50VCapacitor_SMD:C_0603_1608MetricC5, C9, C13, C20, C21, C22, C23
4MountingHole_3.2mm_M3MountingHole:MountingHole_3.2mm_M3H1, H2, H3, H4
4PUMP 12VDosing_Pump:TerminalBlock_DB103_1x02_P5.08mmJ7, J8, J9, J10
4SRV05-4Package_TO_SOT_SMD:SOT-23-6U7, U8, U9, U10
31uF 16VCapacitor_SMD:C_0603_1608MetricC10, C11, C14
322uF 25VCapacitor_SMD:C_1210_3225MetricC6, C7, C8
3Fiducial_1mm_Mask2mmFiducial:Fiducial_1mm_Mask2mmFID1, FID2, FID3
2100kResistor_SMD:R_0603_1608MetricR2, R8
210kResistor_SMD:R_0603_1608MetricR4, R5
22.2uF 100VCapacitor_SMD:C_1210_3225MetricC3, C4
2CABLE_TIE_NPTH_3.2CT1, CT2
1100nF 100VCapacitor_SMD:C_0805_2012MetricC2
110uF 10VCapacitor_SMD:C_0603_1608MetricC12
110uH 4ADosing_Pump:L_Bourns_SRP6540_7.2x6.5mmL1
112V INPUTDosing_Pump:TerminalBlock_DB103_1x02_P5.08mmJ1
117.8k 1%Resistor_SMD:R_0603_1608MetricR3
13A FASTFuse:Fuse_Littelfuse-NANO2-451_453F1
141.2k 1%Resistor_SMD:R_0603_1608MetricR1
147uF 50VCapacitor_SMD:CP_Elec_6.3x5.8C1
1AO3401APackage_TO_SOT_SMD:SOT-23Q2
1AP2112K-3.3TRG1Package_TO_SOT_SMD:SOT-23-5U2
1ATLAS_EZO_PMP_1Connector_PinHeader_2.54mm:PinHeader_1x05_P2.54mm_HorizontalJ2
1ATLAS_EZO_PMP_2Connector_PinHeader_2.54mm:PinHeader_1x05_P2.54mm_HorizontalJ3
1ATLAS_EZO_PMP_3Connector_PinHeader_2.54mm:PinHeader_1x05_P2.54mm_HorizontalJ4
1ATLAS_EZO_PMP_4Connector_PinHeader_2.54mm:PinHeader_1x05_P2.54mm_HorizontalJ5
1Adafruit ESP32-S3 TFT Feather PID 5483Module:Adafruit_FeatherA1
1BSS138LT1GPackage_TO_SOT_SMD:SOT-23Q1
1BZT52C10Diode_SMD:D_SOD-123D1
1EZO_3V3TestPoint:TestPoint_Pad_D1.5mmTP5
1FEATHER_3V3TestPoint:TestPoint_Pad_D1.5mmTP6
1FEATHER_USBTestPoint:TestPoint_Pad_D1.5mmTP7
1GNDTestPoint:TestPoint_Pad_D1.5mmTP3
1LMR16020PDDARPackage_SO:TI_SO-PowerPAD-8_ThermalViasU1
1PROTECTED_VINTestPoint:TestPoint_Pad_D1.5mmTP2
1RAW_VINTestPoint:TestPoint_Pad_D1.5mmTP1
1SMBJ13ADiode_SMD:D_SMBD2
1SS14Diode_SMD:D_SMAD4
1SS310Diode_SMD:D_SMAD3
1SYS_5VTestPoint:TestPoint_Pad_D1.5mmTP4
1TCA9548APWRPackage_SO:TSSOP-24_4.4x7.8mm_P0.65mmU3

Questions

How big is the Ezo pmp controller PCB?

It measures 195 × 58 mm and has 4 copper layers, 1.6 mm thick.

What parts does the Ezo pmp controller use?

83 parts, built around the SRV05-4, AP2112K-3.3TRG1, AO3401A and BSS138LT1G. 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 Ezo pmp controller KiCad files?

From the dephekt/dosing-pump-hardware repository on GitHub, at the commit this page was made from: the board is electrical/ezo-pmp-controller.kicad_pcb and its schematic electrical/ezo-pmp-controller.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Ezo pmp controller design in my own project?

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

Similar boards

  • 4 layers100 × 100 mmEc30-tee, the printed circuit board in 3D

    Ec30-tee

    justinpirie/ec30

    Onan EC30 Tee Board with ESP32 Control

    147 partsApache-2.0
  • 4 layers100 × 75 mmAmos, the printed circuit board in 3D

    Amos

    des256/pcbs

    Making electronics as well

    322 parts
  • 4 layers100 × 100 mmInputSwitch, the printed circuit board in 3D

    InputSwitch

    smileym4n-collab/Kicadprojects

    109 parts
  • 4 layers195.5 × 114.3 mmOpenNAS, the printed circuit board in 3D

    OpenNAS

    mquerostudio/OpenNAS

    107 parts
  • 2 layers81 × 78 mmFan controller, the printed circuit board in 3D

    Fan controller

    rmskkn/fanny

    USB 4ch PC fan controller

    97 parts
  • 4 layers148 × 78 mmHub, the printed circuit board in 3D

    Hub

    Bruhdows/klink

    Klink is a magnetic tile system for building programmable machines. First up: a marble run with sensors, buttons, lights, gates, splitters and an elevator.

    85 parts
  • 4 layers40 × 58 mmSTM32-S Plant, the printed circuit board in 3D

    STM32-S Plant

    Fred98246/STM32-Environmental-Sensor-and-Datalogging-Board

    An open-source STM32F103C8T6 development board designed for environmental monitoring, featuring integrated Li-Ion battery management, USB-C, MicroSD data logging, and onboard I2C sensors for temperature, humidity, and…

    59 parts2MIT