Ordering a 4-layer keyboard PCBA and plate at JLCPCB
This guide orders rev 1 of the auspex main board at JLCPCB: a 230.60 x 97.25 mm, 4-layer, 1.6 mm PCB with every JLC-placed part on the bottom side, assembled by JLC, plus an FR4 switch plate as a second, copper-free PCB, plus a resin fit print of the case at JLC3DP. The project, its gates and the generators that produce the board are described in auspex: an open 5x12 Hall-effect keyboard; why the gates below exist and what each one has caught is in Verifying a PCB as code.
Prerequisites: a routed KiCad 10 project whose footprints carry an LCSC field, kicad-cli on the path, and a JLCPCB account. The order described here was placed on 2026-11-04 through JLCONE, JLC’s desktop app. Board field names are from that day’s form, except Layer sequence and Inner Copper Weight, whose exact labels were not recorded; of the assembly fields, only Confirm Parts Placement and Confirm Production file are exact labels, the rest are reconstructed from memory. The project’s files carry its working name, gridlock, which is why that word appears in file names below.
Constants
Section titled “Constants”| Fact | Value | Where it comes from |
|---|---|---|
| Board outline | 230.60 x 97.25 mm | the board file; the pack step prints it |
| Copper layers | 4: F.Cu signal + GND fill, In1.Cu +3V3A plane, In2.Cu GND plane, B.Cu parts + routing + GND fill | board stackup |
| Stackup in the board file | 0.035 Cu / 0.2 mm FR4 prepreg / 0.035 Cu / 1.1 mm core / 0.035 Cu / 0.2 mm prepreg / 0.035 Cu, er 4.5 | (stackup ...) block of the .kicad_pcb |
| Vias | 1017, all 0.3 mm drill / 0.6 mm pad | the committed board file |
| Assembly side | bottom only (every JLC-placed part on B.Cu) | Economic PCBA places one side |
| JLC BOM | 20 lines | pack output, rev 1 |
| JLC CPL | 225 placements | pack output, rev 1 |
| Not placed by JLC | the 60 switches, mounting holes | switches are pressed in by hand; both are excluded from BOM and CPL |
| Plate | 2-layer FR4, no copper, 14.00 mm switch cutouts, 2.2 mm mount holes drawn on Edge.Cuts | plate project |
Overview
Section titled “Overview”- Three gates run against the board: strict routed DRC, a live check of every LCSC part, and a check of every CPL rotation against JLC’s footprint for that part.
- Only when all three pass is the order pack written: board gerbers zip, BOM, CPL and plate gerbers zip.
- The pack goes into one PCB quote with assembly and a second PCB quote for the plate.
- JLC reviews the files, sends the placement and production file for approval, and bills any supplement after review.
| Component | Version at time of writing |
|---|---|
KiCad / kicad-cli | 10.0.8 |
| Rotation correction table | Fabrication Toolkit plugins/transformations.csv, read 2026-11-03, with project overrides |
| JLC order surface | JLCONE desktop app (the same quote as https://cart.jlcpcb.com/quote) |
Step 1: export gerbers and drill for every copper layer
Section titled “Step 1: export gerbers and drill for every copper layer”Plot only the layers a fab uses, and on a 4-layer board that list must include the inner layers:
kicad-cli pcb export gerbers -o gerbers/ \ --layers F.Cu,In1.Cu,In2.Cu,B.Cu,F.Mask,B.Mask,F.Silkscreen,B.Silkscreen,F.Paste,B.Paste,Edge.Cuts \ --subtract-soldermask board.kicad_pcbkicad-cli pcb export drill -o gerbers/ board.kicad_pcbWithout --layers, kicad-cli plots every enabled layer (fab, courtyard, user layers), which bloats the zip and gives the fab a drawing layer to misread as copper or outline. With a fixed list written for a 2-layer board, the inner planes disappear without any error, which is what happened on this order (see Gotchas). The project’s exporter now builds the list from the board’s copper stack, and the pack step counts copper gerbers in the zip and fails unless there is one per copper layer in the board file.
Step 2: write the JLC BOM and CPL
Section titled “Step 2: write the JLC BOM and CPL”JLC wants two CSV files. The column names and conventions follow the Fabrication Toolkit KiCad plugin (https://github.com/bennymeg/Fabrication-Toolkit), which JLC’s own KiCad instructions point at:
| File | Columns | Notes |
|---|---|---|
| BOM | Comment, Designator, Footprint, LCSC | grouped by (value, footprint, LCSC); parts flagged exclude-from-BOM and switches are left out |
| CPL | Designator, Mid X, Mid Y, Layer, Rotation | parts flagged exclude-from-position-files, switches, mounting holes and test points are left out |
CPL positions are relative to the board’s auxiliary origin, in mm, with Y negated (KiCad’s Y axis points down, JLC’s up). Layer is top or bottom. The rotation has two terms:
- Bottom-side mirror. JLC’s rotation is as seen from above the component, and a bottom part is seen from below, so a bottom part at KiCad angle
abecomes180 - a. Top-side parts keepa. - Footprint correction. KiCad’s footprint and JLC’s library footprint for the same LCSC part are not always drawn at the same zero angle. A per-footprint delta is added, then the sum is wrapped into [0, 360).
The correction table started as the upstream transformations.csv (regex on footprint name -> delta, for example ^LQFP- 270, ^TSSOP- 270, ^SOT-23 180). It is a starting point only: JLC’s footprint for the exact LCSC part is the authority, and Step 3 checks every row against it.
Step 3: run the pre-flight gates
Section titled “Step 3: run the pre-flight gates”The project’s make order refuses to write the pack unless all three of these pass.
Strict routed DRC
Section titled “Strict routed DRC”kicad-cli pcb drc --format json --severity-all -o drc-routed.json board.kicad_pcbThe project gates the JSON report rather than the exit code: every violation class fails except missing global libraries (the dev box has no global KiCad libraries installed), the USB-C receptacle’s silkscreen overhanging the board edge, and rev 1’s known violations, which are accepted only when they match a counted baseline file exactly (the uncoupled USB pair, a via near an unplated hole, small test-pad labels). A sentinel rule at the end of the board’s custom rule file must also fire exactly once, which proves kicad-cli loaded that file.
That is the gate as it stands now; it was added on 2026-11-05, the day after this order. The order itself was gated by the earlier version: the text .rpt report with the same allow-list (missing libraries and the edge silkscreen), no sentinel and no baseline. Why the gate changed and what the baseline holds are in Verifying a PCB as code.
Live BOM check
Section titled “Live BOM check”For each unique (value, LCSC number, footprint) in the schematic netlist (kicad-cli sch export netlist --format kicadsexpr), the checker queries JLC’s parts search and requires:
| Check | Rule |
|---|---|
| Exists | the LCSC number resolves to a part |
| Value | the capacitance, resistance or MPN prefix appears in JLC’s description or model, with a digit boundary so 30pF cannot match inside 330pF |
| Package | an imperial chip size in the footprint name (0402, 0603, 0805, 1206) appears in JLC’s description |
| Stock | at least 5 x the number of placements of that part |
It also prints whether each part is Basic or Extended. On 2026-11-03 it caught a capacitor entered as 30 pF whose LCSC number is a 330 pF part. The value field said 30 pF and the footprint was right, so only a lookup of the number could find it.
CPL rotation against JLC’s footprint
Section titled “CPL rotation against JLC’s footprint”For every placed footprint with an LCSC number, the checker fetches JLC’s footprint (EasyEDA’s component data; on the parts compared, its result agreed with JLC’s placement preview, which is why the gate trusts it), finds the quarter turn that maps KiCad’s pads onto JLC’s matched by pad number, and checks that the exporter’s correction undoes it. Parts whose pads fit at several rotations (2-pad passives) pass if any fitting rotation matches.
It then checks the final CPL angle per part a second way, from geometry alone: the part’s pads as they sit on the board, mirrored (x -> -x) for a bottom part, matched against JLC’s footprint. This catches a change to the mirror term. A refactor from 180 - a to a + 180 agrees at 0 and 180 degrees, so the passives still pass, but rotates every part at 90 or 270 degrees by 180, which on this board is all 60 sensors.
What it caught on 2026-11-04. The two SOT-23 rows came from JLC’s placement preview of the 2-layer test variant of the same design. The JST SH row was found by the rotation check itself, on the main board, when the 3-pin SWD connector went onto it later the same day:
| Part | Package | Table said | JLC’s footprint needs |
|---|---|---|---|
| USBLC6-2SC6 (ESD) | SOT-23-6 | 180 (^SOT-23) | 270 |
| TLV75733 (LDO) | SOT-23-5 | 180 (^SOT-23) | 270 |
| BM03B-SRSS-TB (JST SH, SWD) | JST SH top entry | 0 | 180: JLC’s drawing of this part has its pads mirrored in x against KiCad’s; JLC’s SM03B and SM04B drawings agree with KiCad |
JLC’s placement preview of the test variant showed the two SOT-23 parts 90 degrees off. The single ^SOT-23 -> 180 rule is right for SOT-23-3 and wrong for these SOT-23-5/6 parts. The HE60 v2.1 reference design’s released position file carries the same 180 for these parts. Whether HE60 corrected them in JLC’s review screen is not recorded, so a published CPL still needs checking part by part.
Step 4: build the order pack
Section titled “Step 4: build the order pack”| File | Upload where |
|---|---|
<project>-gerbers.zip | “Add gerber file” on the PCB quote page |
<project>-bom-jlc.csv | PCB Assembly step, “Add BOM File” |
<project>-cpl-jlc.csv | PCB Assembly step, “Add CPL File” |
<project>-plate-gerbers.zip | a second PCB quote: the switch plate |
The pack step also prints the board size, copper layer count, board thickness, BOM line count, placement count and assembly side, so the form is filled from the board file rather than from memory.
Step 5: the 4-layer board form
Section titled “Step 5: the 4-layer board form”Upload the gerbers zip first and read what JLC detects before touching anything else. The form on 2026-11-04:
| Field | Set | Note |
|---|---|---|
| Base Material | FR-4 | default |
| Layers | 4 | must match both the pack output and the layer count JLC detects from the gerbers |
| Layer sequence | F_Cu / In1_Cu / In2_Cu / B_Cu | 4-layer only; confirm the inner gerbers landed in the right order |
| Dimensions | 230.60 x 97.25 mm | read from the gerber |
| PCB Qty | 5 | minimum |
| Different Design | 1 | |
| Delivery Format | Single PCB | |
| PCB Thickness | 1.6 mm | the board file’s stackup is 1.6 mm |
| PCB Color | Green | |
| Surface Finish | LeadFree HASL | OSP is the form default and disables Economic PCBA (“The PCB specs you chose are not supported for Economic PCBA”) |
| Outer Copper Weight | 1 oz | the design rules assume it |
| Inner Copper Weight | 0.5 oz | form default; JLC’s capability page states 0.5 oz as the inner default1 |
| Via Covering | Plugged | form default on 4-layer; Tented is greyed out |
| Min via hole size | 0.3 mm (0.4/0.45 mm) | default; matches the board’s 0.3 mm drills |
| Mark on PCB | Remove Mark | |
| Electrical Test | Flying Probe Fully Test | default |
| Everything else | default | a 4-layer quote adds an Engineering fee: USD 25.00 on this quote |
Stackup and impedance
Section titled “Stackup and impedance”The board file’s stackup has 1 oz inner copper; the order took the 0.5 oz default. The review of rev 1 judged the impedance effect of that difference negligible.
No impedance control was ordered (the record does not name the form field). For 4-layer 1.6 mm, JLC’s “No requirement” stackup is the same build as its JLC04161H-7628 option: 0.2104 mm of 7628 prepreg under each outer layer and a 1.065 mm core with 0.0152 mm inner copper, prepreg Er 4.4 and core Er 4.62. That is close to the 0.2 mm prepreg / 1.1 mm core, Er 4.5 throughout, in the board file: JLC’s prepreg is about 5 % thicker. Outer-layer impedance computed from the board file’s stackup should land within a few percent of the default build; this was not recomputed. Economic PCBA accepts the standard stack-up only3. Where impedance control is ordered, JLC’s standard tolerance is +/-10 %1.
For the USB 2.0 High-Speed pair, which 8 kHz polling needs, the stackup does not fix an uncoupled pair. On rev 1 the MCU-side leg of USB_D+/USB_D- came out of the autorouter as two separate traces rather than a coupled pair: sampled every 0.1 mm, 1.2 mm of the D+ run lies within 0.5 mm of D-, and 9.8 mm lies 5 mm or more away. Uncoupled 0.25 mm traces over 0.2 mm prepreg are roughly 50-58 ohm single-ended, which puts the differential impedance at about 100-116 ohm against the 90 ohm +/-15 % USB asks for. The run is about 30 mm, so it may still enumerate at High-Speed; bring-up has to prove it, and rev 2 routes the pair coupled. JLC’s impedance tolerance applies to the trace geometry supplied, so ordering impedance control would not have coupled the pair.
Step 6: assembly options
Section titled “Step 6: assembly options”Turn on PCB Assembly on the same quote page:
| Field | Set | Note |
|---|---|---|
| PCBA Type | Economic | single-sided placement, 2/4/6 layers, 0.8-1.6 mm, 2-50 pieces, 10 x 10 to 470 x 500 mm per board3. Standard PCBA places both sides, takes any stack-up, and needs 70 x 70 mm minimum. If Economic is greyed out, a PCB spec is outside its table; check the surface finish first |
| Assembly side | Bottom | the form defaults to top; click “Assemble bottom side” |
| PCBA Qty | 2 | assembly minimum; the other 3 boards ship bare |
| Tooling holes | Added by JLC | |
| Confirm Parts Placement | Yes | JLC sends the placement for approval before building |
| Confirm Production file | Yes | same, for the production file |
| Product Description | filled in | customs; on the Quote and Order step |
Economic PCBA’s single-sided placement is the reason every part on this board sits on B.Cu.
Parts list and placement preview
Section titled “Parts list and placement preview”Upload the BOM and CPL. In the parts list, every line should match the LCSC number in the BOM with no shortfall; rev 1 had 20 lines and 225 placements. Each Extended part adds a per-part fee. The main board’s count of Extended lines was not recorded; the 2-layer test variant had 9 of its 19 lines Extended (MCU, sensor, mux, USB-C, ESD, LDO, fuse, JST SH and one 2.2 uF capacitor).
In the placement preview, bottom-side parts show mirrored. Check rotation and pin 1 on:
| Ref | Part | What to look for |
|---|---|---|
| U4 | AT32F405RCT7 MCU, LQFP-64 | pin 1 marker against the footprint’s pin 1 |
| AM1-AM8 | SN74LV4051A muxes, TSSOP-16 | all eight, pin 1 on each |
| U1, U2, U3 | USBLC6-2SC6 (SOT-23-6), TLV75733 (SOT-23-5), XC6206 (SOT-23-3) | the SOT-23-5/6 parts are the ones a package-name table gets wrong |
| Y1 | 3225 4-pin crystal | orientation of the 4 pads |
| J1 | USB-C receptacle | the opening faces the board edge |
| J2 | JST SH 3-pin SWD | JLC’s drawing for this part is mirrored against KiCad’s |
| HE1 (one is enough) | MT9102ET Hall sensor, SOT-23-3 | all 60 share one rotation, so one checks them all |
On rev 1 the preview showed every part on the bottom with correct rotation and pin 1. If one is wrong, fix the correction table, regenerate the CPL, and re-upload. With the rotation gate in Step 3 in place, this step now confirms rotations the gate has already checked.
After payment, JLC’s file review runs (quoted as 10-60 minutes on this order) and the placement and production-file confirmations arrive as “Action Required” items in JLCONE. Approve them only after checking the screenshots they attach.
Step 7: the plate as a zero-copper PCB
Section titled “Step 7: the plate as a zero-copper PCB”The switch plate is a PCB project with no copper: an Edge.Cuts outline with 60 cutouts at the key centres and the mount holes. Quote it as a second PCB in the same batch:
| Field | Set |
|---|---|
| Gerber | <project>-plate-gerbers.zip |
| Layers | 2 (JLC treats it as a normal FR-4 board) |
| PCB Thickness | 1.6 mm |
| PCB Color | Black (cosmetic) |
| Surface Finish | cheapest; there is no exposed copper |
| PCB Assembly | off |
| PCB Remark | a copper-free keyboard switch plate: keep the 14.00 mm cutouts; the 2.2 mm mount-hole circles are on Edge_Cuts and must be milled or drilled non-plated; the drill file is empty |
Two things happen after the order that the instant quote does not show:
- JLC emailed asking whether a design with no traces is intentional. Reply that it is a switch plate and list what they must keep (cutout size, the non-plated holes on Edge.Cuts, the empty drill file). Putting the same text in PCB Remark at order time may avoid the email.
- JLC billed a keyboard-plate supplement after file review. The structure on 2026-11-04: “keyboard PCB: densely packed milled slots”, USD 16.41 + 49.25 per m2, plus a charge for too many slots. Taking the area as the whole order’s area fits the bill: 16.41 + 49.25 x (5 x 0.2306 x 0.09725 m2) = 21.93, with a USD 0.18 remainder consistent with the slot charge. One order is not enough to pin the formula down. The plate holds until the supplement is paid.
Budget a supplement on plate orders of this kind; it is not in the instant quote. Moving the plate to another fab has its own costs. PCBWay’s help page lists extra cost when milling and drilling counts exceed 10 under its tab-routing method (https://www.pcbway.com/helpcenter/paymentproblems/What_PCBs_will_be_charged_of_extra_cost_.html). A PCBWay-sponsored review of a roughly 320 x 140 mm 2-layer keyboard found PCBWay’s online quotes 4-5x those of an unnamed China supplier before shipping; the supplier is not identified, so this is not necessarily a JLC comparison (https://github.com/Giraffasax/SpUnLy58/blob/main/Reference/PCBWay_Review.md). A separate order also loses the consolidated shipping.
Step 8: the case fit print at JLC3DP
Section titled “Step 8: the case fit print at JLC3DP”The resin fit print is a separate JLC3DP order. The one placed on 2026-11-04 was SLA in 9600 resin, white, general sanding, USD 8.88 on that date. It checks the planar fit (board, plate, USB cable entry, SWD plug, feet, boss positions). It is not the daily case: the resin’s heat deflection temperature is 59 C at 0.45 MPa (53 C at 1.82 MPa)4, and the printed bosses are thin, so use self-tapping screws for plastic and stop at snug.
JLC3DP ran an engineering review on this upload. Its published rules for SLA resin56:
| Rule | SLA value |
|---|---|
| Recommended wall thickness (SLA service page) | 0.8 mm |
| Minimum wall by part size | 0.5 mm at 5 x 5, 0.8 at 10 x 10, 1.0 at 50 x 50, 1.5 at 100 x 100, 2.0 mm at 200 x 200 mm |
| Protrusions, positioning features, snaps, fasteners | more than 1.5 mm |
| Hole depth for diameter | 1.0 mm -> 1.0-3.0 mm deep, 1.5 -> 1.5-4.5, 2.0 -> 2.0-6.0 |
| Hole tolerance | +/-0.3 mm; holes generally shrink |
JLC does not say how a part maps onto its size columns. The case is 237.6 x 104.25 mm; read conservatively it falls in the 200 x 200 mm row, 2.0 mm. The replacement file’s floor is 2.0 mm; the file first reviewed was an older geometry with a 2.5 mm floor.
The review of this order flagged 0.51 mm walls at all five boss tops. The pilot chamfer cone in the CAD had its radii swapped, which cut an undercut under each boss face: the bore was 1.6 mm at the face and 2.8 mm at 0.6 mm below it, so a self-tapping screw would have bitten only a knife-edge lip. The review’s second note, warping of a long flat tray, was accepted for a fit check. The fix, and the mesh check that now fails a print file with walls under 0.8 mm or a bore that widens below its entry, are in Designing a keyboard case in build123d.
The order went on hold. To fix a held JLC3DP order without re-ordering, ask JLC support to enable Replace File on it, then upload the corrected STL through that option; the replacement here was uploaded on 2026-11-05.
Verification
Section titled “Verification”| Check | How |
|---|---|
| Inner layers are in the zip | the pack step lists copper gerbers; there must be 4 (F_Cu, In1_Cu, In2_Cu, B_Cu) |
| JLC sees 4 layers | the quote page’s detected layer count after the gerber upload |
| Size | JLC’s detected dimensions equal the pack output (230.60 x 97.25 mm) |
| BOM | parts list: 20 lines matched, no shortfall |
| CPL | placement preview: 225 placements, all on the bottom; U4, AM1-AM8, U1-U3, Y1, J1, J2 and one sensor rotated correctly with pin 1 right |
| Economic PCBA available | PCBA Type not greyed out (surface finish LeadFree HASL, not OSP) |
| Batch | board with PCBA and the plate checked out together |
Gotchas and lessons learned
Section titled “Gotchas and lessons learned”- “Detected 2 layer board.” JLC’s upload said this for the 4-layer board. The gerber export used a fixed 2-layer layer list and dropped In1.Cu and In2.Cu, the +3V3A and GND planes: the board would have been built with no sensor supply. The exporter now adds every inner layer and the pack step fails on a copper-gerber count mismatch. Read JLC’s detected layer count against your own before anything else on the form.
- OSP disables Economic PCBA. It is the form’s default finish. LeadFree HASL was accepted for Economic on this 4-layer order.
- A rotation table keyed on package name misrotates some parts. SOT-23-5/6 needed 270 where SOT-23-3 needs 180, and one JST SH part needed 180 because JLC’s drawing of it is mirrored. Check rotations against JLC’s footprint for each LCSC number, not against the package name.
- The bottom-side mirror is
180 - a.a + 180passes every part at 0 or 180 degrees and fails every part at 90 or 270. - Look up every LCSC number. A 330 pF LCSC number sat under a 30 pF value (Step 3).
- 4-layer form defaults differ. Via Covering is Plugged with Tented greyed out, inner copper is 0.5 oz, and an Engineering fee appears (USD 25.00 on this quote).
- The plate costs more than its quote. Budget the post-review keyboard-plate supplement; expect the “no traces” email.
- Splitting a batch to use more coupons would cost a second shipment. This was reasoned rather than tried: the board and the plate shipped in one batch.
File reference
Section titled “File reference”| Path (in the project repo) | Role |
|---|---|
tools/jlc_export.py | BOM + CPL writer, rotation correction table, layer list for gerbers and drill |
tools/cpl_rotation.py | CPL rotation maths without pcbnew: the exporter’s formula and the geometry-only expected rotation |
tools/jlc_rotation_check.py | per-part rotation gate against JLC’s footprint data |
tools/bom_check.py | live LCSC value / package / stock gate |
tools/order_summary.py | prints the form numbers and gates the copper-gerber count |
docs/ordering.md | the order checklist, corrected after every order |
mechanical/case/README.md | case print settings and the JLC3DP review record |
References
Section titled “References”-
JLCPCB, “Rigid PCB Manufacturing Capabilities,” JLCPCB. https://jlcpcb.com/capabilities/pcb-capabilities ↩ ↩2
-
JLCPCB, “Controlled Impedance PCB Layer Stackup,” JLCPCB. https://jlcpcb.com/impedance ↩
-
JLCPCB, “PCB Assembly Capabilities,” JLCPCB. https://jlcpcb.com/capabilities/pcb-assembly-capabilities ↩ ↩2
-
JLC3DP, “9600 Resin - Photosensitive Resin,” JLC3DP Help Center, read 2026-11-05. https://jlc3dp.com/help/article/photosensitive-9600-resin ↩
-
JLC3DP, “3D Printing Design Guidelines: Size, Tolerance & Design Rules,” JLC3DP Help Center. https://jlc3dp.com/help/article/3d-printing-design-guideline ↩
-
JLC3DP, “SLA (Resin) 3D Printing Service Online,” JLC3DP. https://jlc3dp.com/3d-printing/stereolithography ↩