
KSGER Controller REV 2.0
omega9380/KSGER-Coverplates
T12 Soldering Iron control board. A design based on schematics created by Mindstorm88 on the EEVBlog forums.

KiCad 10 board: 19V->12V buck for two 2-pin 12V fans, 12V->5V->3.3V for a 3.3V Pro Mini, follows the NUC ITE's PWM duty, and simulates the tach signal. ERC/DRC clean.
Mosfet fan switch is an open-source printed circuit board by kavehs87, from the kavehs87/nuc-fan-controller repository on GitHub. It is a 2-layer board of 63.2 × 54.4 mm with 28 parts on 39 nets, routed with 189 tracks and 2 vias.
It is built around the BC547, IRF7416T and IRFZ44N, with the MBR10100CT among its other parts. Its parts include 8 resistors, 4 capacitors, 4 transistors, 4 connectors and 3 diodes. It belongs with the AVR (ATmega, ATtiny) boards of this library.
A KiCad schematic, a real ngspice simulation, and the same circuit actually built and running on perfboard — of the thing every 3D printer needs: an Arduino pin turning 12 V fans on and off with a MOSFET. And since the design grew a NUC fan-header socket, the same Arduino can read the NUC's own fan request and answer on its tachometer line.
What is finished. The perfboard build is the circuit: it runs, it is quiet across the duty
range, and it needs no replacement board. The KiCad board is routed by hand and is being etched at
home from fab/ — the same circuit made permanent, not a repair for anything the perfboard got
wrong. The part of this project that actually changed something is the measurement work under
Firmware, and why 0 % was humming: why 0 % hummed, and why the carrier is 100 Hz.
Every real component on this sheet is a part that actually sits in my parts cabinets
(L1 and R1). Nothing here is a catalogue placeholder. The fan model is sized to the
fans I actually use: 100 mA each at 12 V.
Arduino D9 (3.3 V PWM)
|
R1 1k ---- Q2 2N7002 level shifter: 3.3 V logic pulls /BOOT down to ~0 V
| |
R2 10k /BOOT ---- Q3 IRF7416 (P-ch) gate booster: switches +12 V onto the gate
| | |
GND R3 10k R4 1k
|
/GATE ---- Q1 IRFZ44N the switch that carries the fans
| |
R5 10k /DRAIN --- 12 V fan(s) --- +12 V
GND |
D1 1N5819 (flyback, cathode to +12 V)
NUC fan header J4
PWM request 5 V --R7 10k--. R8 10k to GND -> /DEMAND 2.50 V at full request -> A0
tach answer <--Q4 BC547-- base via R9 1k from /TACH_PWM (D10, 100 Hz carrier)
+12 V pin not connected the fans are powered by this board's own brickRun everything with one command:
./sim/run.shIt exports the netlist from the schematic, runs ngspice, writes five plots and two
comparison tables, and prints the numbers that matter. Output lands in sim/out/
(git-ignored).
| net | what it is | who drives it |
|---|---|---|
/PWM | the Arduino pin (D9 = OC1A, timer 1 channel A) | Arduino Pro Mini 3V3 |
/LGATE | 2N7002 gate, held at 3.0 V by the R1/R2 divider | /PWM through R1 1k |
/BOOT | booster node: +12 V when the pin is low, ~0 V when it is high | R3 10k up, Q2 down |
/HIGATE | +12 V handed to the power FET by Q3 | Q3 IRF7416 |
/GATE | IRFZ44N gate, settles at 10.91 V | R4 1k from /HIGATE, pulled down by R5 10k |
/DRAIN | the switching node under the fan(s) | Q1 IRFZ44N, clamped by D1 |
/FAN_W | between a fan's winding and its drag resistance | fan model L1 + R6 |
/NUC_PWM | the NUC's own fan request, 5 V at J4 pin 1 | the NUC (V3 in simulation) |
/DEMAND | that request halved and smoothed for a 3.3 V ADC input | R7/R8 divider, C5 + C6 |
/TACH_PWM | the Pro Mini's answer, D10 = OC1B, a 100 Hz carrier | Arduino Pro Mini 3V3 |
/TBASE | /TACH_PWM after R9 1k, into Q4's base | R9 1k |
/NUC_TACH | the tach line the NUC reads, pulled up inside the NUC | Q4 as an open collector |
Why /GATE is 10.9 V and not 12 V. A MOSFET gate draws no steady current, so R4 and
R5 are simply a divider: 12 V × 10 k / (1 k + 10 k) = 10.91 V. That is still deep inside the
region where the IRFZ44N's 17.5 mΩ is specified. R5 exists to keep the gate firmly at 0 V
when the pin is low, so the fan cannot drift on.
Why the logic is not inverted. Two inversions (Q2 inverts, Q3's P-channel orientation inverts again) cancel. Pin high → fan on. At power-up the pin is floating, R2 keeps Q2 off, so Q3 is off, so Q1 is off. The fans stay off.
Why Q3's source must be the +12 V terminal. A P-channel device's body diode conducts drain → source. Wire Q3 the other way round and that diode feeds +12 V straight into Q1's gate through R4 — the fans would be permanently on and no amount of code would fix it. This is the single easiest way to get this circuit wrong, so the sheet draws Q3 rotated 180° with its source on the rail.
The same board also talks to a PC. A NUC's 4-pin fan header wants to drive a fan itself: it puts out a PWM request, expects a tachometer answer on the second pin, and supplies the fan on the third. This board refuses both jobs and does them anyway:
NUC fan header J4 Pro Mini (on the board, U1)
1 PWM request, 5 V ──R7 10k──┐
2 tach answer <──Q4 BC547──┼── D10 (OC1B) /TACH_PWM 100 Hz carrier out
3 fan supply not connected│ (5 V on a 6th-gen NUC, 12 V on newer ones)
4 GND ───────────────────────┴── GND (shared with the 12 V brick)
A0 /DEMAND 2.50 V at full request, read as 775 counts
D9 /PWM the fan drive, unchanged
R8 10k from /DEMAND to GND, with C5 1µ + C6 100n across itRPM = 30 × f_tach, and the legal window (700–10 500 RPM) is 23–350 Hz on that
wire. Copying the 1 kHz fan drive onto it would read 30 000 RPM and be rejected, so D10
generates its own 100 Hz carrier (which reads 3 000 RPM). "Same PWM being sent to the
fans" is the right idea — the frequency is what has to differ.The ITE side of this design was assumed here and measured one directory up: 4 577 paired samples at
1 Hz on a 6th‑generation i3 NUC, logged in ../bench/data/, analysed by ../bench/analyze-*.py, and
tabulated with their evidence in ../README.md, The facts both halves rest on. Read them there — they
are deliberately not repeated here. Those numbers govern, not the assumptions below.
Three of them decide how this board is used:
../bench/data/ are on an Uno's 5 V ADC reference, where 512 counts is full duty. On
this board's 3.3 V / 8 MHz Pro Mini the same 2.50 V tap is 775 counts. Carry the calibration as
volts, or multiply counts by 5 / 3.3.The enclosure board those measurements were designed for — nuc-fan-controller, with its 19 V → 12 V
module, its BC547 gate stage and its 31.25 kHz drive on D11 — is retired: the perfboard here is the
reference. That design is intact in git at the tag nuc-fan-controller-full-2026-10-23, and the sketch
that carries the measured ITE constants, ../firmware/nuc-tach-modeB/nuc-tach-modeB.ino, is still here.
sim/nuc_link.py (run by ./sim/run.sh) is the check: duty-by-duty tap voltage, ripple,
ADC counts, the divider's load, the tach levels and the carrier window. Numbers and
caveats: docs/pcb-notes.md, The NUC link.
| ref | part | package | cabinet · drawer | qty in bin |
|---|---|---|---|---|
| U1 | Arduino Pro Mini (3.3 V / 8 MHz, ATmega328P) | 35-hole module, on female sockets | L1 · C5 | 1 |
| Q1 | IRFZ44N N-channel power MOSFET | TO-220 | R1 · B4 | 12 |
| Q2 | 2N7002 N-channel MOSFET | SOT-23 | R1 · A4 | 11 |
| Q3 | IRF7416 P-channel MOSFET | SO-8 | R1 · A4 | 6 (+1 already on a breakout board) |
| Q4 | BC547 NPN small-signal transistor | TO-92 | R1 · D3 | 20 |
| D1 | 1N5819 Schottky, 40 V / 1 A | DO-41 | L1 · E1 | 30 |
| D2 | 1N5822 Schottky, 40 V / 3 A — spare footprint, not soldered | DO-27 | L1 · E1 | 9 |
| D3 | MBR10100CT Schottky, 100 V / 10 A dual — spare footprint, not soldered | TO-220 | L1 · E1 | 3 |
| R1, R4, R9 | 1 kΩ through-hole | axial | R1 · A2 | 20 |
| R2, R3, R5, R7, R8 | 10 kΩ through-hole | axial | R1 · B2 | 20 |
| C3, C6 | 100 n (104) ceramic disc | radial | L1 · C9 | 30 |
| C4 | 10 µ 16 V electrolytic | radial, 6.3 mm | L1 · C1 | 20 |
| C5 | 1 µ 50 V electrolytic | radial, 8 mm | L1 · E7 | 8 |
| V1 | Switching Power Supply Module, 12 V 450 mA | module | L1 · B7 | 2 |
These items on the sheet are not bin parts. They carry dnp yes (do not populate)
and have no footprint on the board:
mosfet-fan-switch:Arduino_Pro_Mini_328, generated by
tools/pro_mini.py from the same hole map as the board's socket footprint: 35 passive
pins, one per module hole, so a symbol pin number, a footprint pad number and the
silkscreen name beside a hole are one number. U1 is excluded from the simulation
(exclude_from_sim yes) because ngspice has no model for a microcontroller, and V2
and V5 (VPULSE sources) stand in for the two pins it drives.sim/fan_count.py says when to fit one (see How many fans one output can carry).This is the actual lesson. sim/variants.py runs the same fan, same diode, same resistors
against every drive option the bin allows:
| variant | pin | gate | fan | V(drain) | state | Rds(on) | in the FET |
|---|---|---|---|---|---|---|---|
| booster-3v3 (this sheet) | 3.3 V | 10.91 V | 102.3 mA | 1.6 mV | ohmic | 15.7 mΩ | 0.16 mW |
| booster-3v3-wc (Vgs(th) = 4.0 V unit) | 3.3 V | 10.91 V | 102.3 mA | 1.6 mV | ohmic | 15.5 mΩ | 0.16 mW |
| direct-3v3 (one transistor) | 3.3 V | 3.00 V | 99.6 mA | 51.9 mV | ohmic | 521 mΩ | 5.17 mW |
| direct-3v3-wc (worst-case unit) | 3.3 V | 3.00 V | 0 mA | 12.0 V | off | — | 0 |
| direct-5v (if you use the 5 V Pro Mini) | 5.0 V | 4.54 V | 100.0 mA | 5.7 mV | ohmic | 56.6 mΩ | 0.57 mW |
| 2n7002-only (2N7002 as the pass element) | 3.3 V | 3.00 V | 52.0 mA | 5.76 V | saturated | — | 300 mW |
Read the two bold rows:
pin voltage where the fan starts : 2.27 V
/BOOT when the pin is high (2N7002 sinks 1.2 mA): 11.6 mV
/GATE steady state (12 V x R5/(R4+R5)) : 10.91 V
fan current with the power FET on : 102.3 mA
V(drain) with the power FET on : 1.6 mV
Rds(on) at that point : 15.7 mΩ
power FET dissipation while on : 0.16 mW
V(drain) spike at turn-off (clamped by D1) : 12.36 V
Arduino pin current (max) : 0.30 mA
/GATE rise 5 tau (R4=1k, C1=1.5n) : 7.5 µs
/BOOT rise 5 tau (R3=10k, C2=1.7n) : 85.0 µs
/BOOT back at 12 V after the pin goes low : 120.6 µs
fan L/R time constant : 83.3 µs
peak fan current during a PWM on-time : 99.8 mA
average fan current at 1 kHz / 50 % duty : 54.2 mAA few of those deserve a comment:
/BOOT has to charge back up
to 12 V through R3 10 kΩ. With the netlist's C2 = 1.7n — only the IRF7416's own Ciss — that
is τ = 17 µs and the fan stays on ~55 µs after the pin goes low. The perfboard measured
~170 µs, because that node also carries its wiring: about 8 nF rather than 1.7 nF, see
"Why the carrier is 100 Hz". At 100 Hz the difference is 3 % of the off half-period, which is
why the board runs there. If you want several kHz, drop R3 to 1 kΩ and fit the Q5 gate
helper described under that heading; R3 = 1k then sinks 12 mA, well inside the 2N7002's
115 mA rating, but has to dissipate about 145 mW.sim/fan_count.py clones the sheet's fan branch as many times as you ask and re-runs both
analyses. The gate drive does not change at all — the fans are on the drain side — so the
only things to check are the parts that carry load current: Q1, D1 and the 12 V supply.
python3 sim/fan_count.py # 1..4 fans, the 450 mA bin module
python3 sim/fan_count.py --fans 1 2 4 8 16 32 64 --supply-ma 8000| fans | each | total | V(drain) | Q1 loss | spike | D1 peak | D1 avg | D1 loss | supply avg | supply peak |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 102.3 mA | 102.3 mA | 1.6 mV | 0.16 mW | 12.36 V | 99 mA | 8 mA | 2.5 mW | 48 mA | 102 mA |
| 2 | 101.1 mA | 202.2 mA | 3.2 mV | 0.65 mW | 12.38 V | 197 mA | 15 mA | 5.3 mW | 94 mA | 202 mA |
| 3 | 100.7 mA | 302.2 mA | 4.8 mV | 1.46 mW | 12.40 V | 295 mA | 22 mA | 8.2 mW | 141 mA | 301 mA |
| 4 | 100.5 mA | 402.1 mA | 6.4 mV | 2.59 mW | 12.41 V | 392 mA | 30 mA | 11.2 mW | 187 mA | 401 mA |
Three fans fit, with room to spare. What the numbers say:
Wiring three fans on one output also means one flyback diode serves all three, and one blown fan stalls the others only mechanically, not electrically.
The 450 mA module in L1 · B7 is what makes "three fans" the answer above. Fed from a real 12 V brick (8 A, which is what this build actually uses), the same sheet and the same three transistors scale a long way:
| fans | total | V(drain) | Q1 loss | Q1 rise | spike | D1 peak | D1 avg | D1 loss | supply peak |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 102 mA | 1.6 mV | 0.16 mW | +0.0 °C | 12.36 V | 99 mA | 8 mA | 2.5 mW | 102 mA |
| 4 | 402 mA | 6.4 mV | 2.6 mW | +0.2 °C | 12.41 V | 392 mA | 30 mA | 11 mW | 402 mA |
| 8 | 801 mA | 12.9 mV | 10.3 mW | +0.6 °C | 12.45 V | 779 mA | 58 mA | 24 mW | 800 mA |
| 16 | 1.60 A | 25.7 mV | 41 mW | +2.5 °C | 12.51 V | 1.53 A | 104 mA | 47 mW | 1.60 A |
| 32 | 3.19 A | 51.4 mV | 164 mW | +10 °C | 12.60 V | 2.99 A | 201 mA | 102 mW | 3.18 A |
| 64 | 6.35 A | 102 mV | 650 mW | +40 °C | 12.77 V | 5.93 A | 399 mA | 242 mW | 6.34 A |
Q1 rise is the junction temperature rise in free air, using the TO-220's
62 °C/W junction-to-ambient rating with no heatsink.
Read the two right-hand columns carefully, because a diode has three different ratings and only two of them apply here:
So the practical ladder, using only parts already in L1 · E1:
| fans | what to change |
|---|---|
| up to ~8 | nothing — the sheet is exactly right as drawn |
| ~9 to ~30 | fit D2: the 1N5822 (3 A, DO-27 — a fatter body than D1's DO-41, and it already has a footprint on the board) |
| ~30 to ~60 | fit D3: the MBR10100CT (100 V, 10 A dual, TO-220 — two anodes on /DRAIN, common cathode on +12 V), or two 1N5822 |
| beyond | Q1 starts wanting a heatsink around 8 A, and the wiring matters more than the semiconductors |
Both spares are already drawn and already have copper on the board, in parallel with D1;
dnp yes means nobody solders them until this table says to. See Spare flyback diodes in
docs/pcb-notes.md for their pad → net table.
The turn-off spike creeps up with load — 12.36 V at one fan, 12.77 V at sixty-four — simply because the Schottky's forward drop grows with current. Still nowhere near the IRFZ44N's 55 V or the IRF7416's 30 V.
And the honest caveat, again: this fan model always limits itself to 100 mA. Real fans draw a surge when they start, and a stalled fan draws amps. Sixteen fans all starting on the same PWM edge can momentarily ask for 3–5 A, which an 8 A supply absorbs and a 450 mA module does not.
sim/run.sh writes these to sim/out/:
| file | what it shows |
|---|---|
dc_transfer.png | sweeping the pin 0 → 3.3 V: /BOOT falls from 12 V to 12 mV while /GATE jumps from 0 to 10.9 V, and the fan starts at 2.27 V |
transient.png | 1 kHz / 50 % PWM: all four node voltages, the fan current, and the 0.3 mA the pin actually supplies |
switching_edge.png | one turn-on edge (gate charging through R4) and one turn-off edge (the 12.36 V clamp and the freewheeling decay) |
drive_compare.png | the variant table above as a chart: fan current and dissipation for each drive option |
fan_count.png | fan counts side by side, and the current the flyback diode carries at the largest count |
nuc_link.csv | not a plot: the NUC-link duty table (tap voltage, ripple, ADC counts) that sim/nuc_link.py prints |
./sim/run.sh # export -> ngspice -> plots -> variant and fan-count tablesRequirements: KiCad 9 or 10 (kicad-cli), ngspice, Python 3 with numpy and matplotlib.
Files:
mosfet-fan-switch.kicad_sch the sheet (A3, ERC clean: 0 errors / 0 warnings)
mosfet-fan-switch.kicad_sym five local symbols: four package-accurate transistors, and
the Pro Mini itself - 35 pins, one per module hole
sym-lib-table registers that library for this project
footprints/mosfet-fan-switch.pretty/ the Pro Mini socket the board uses
fp-lib-table registers it for this project
mosfet-fan-switch.kicad_pcb the board - 63.2 x 54.4 mm, routed by hand, being etched
fab/export.sh regenerate everything in fab/ from the board file
fab/gerber/ F.Cu + B.Cu + Edge.Cuts gerbers, Excellon drill + 1:1 map
fab/preview/ 1:1 copper SVGs, and KiCad's board statistics
fab/README.md the fabrication record (the printer's LCD as the exposure lamp)
fab/test-card.gbr exposure / trace-width / mirroring calibration card
tools/pro_mini.py the module's hole map -> socket footprint AND schematic symbol
tools/embed_pro_mini_schematic.py the one-shot migration that replaced the Nano with U1
tools/skeleton_board.py strip a board down to placement + pad nets (historical)
tools/make_package_symbols.py put the four package symbols on the sheet
tools/tidy_q3_labels.py tie Q3's SO-8 legs together, label each node once
tools/shift_q3_clearance.py move Q3 clear of the /BOOT branch it was crossing
tools/fix_schematic_junctions.py put KiCad's junction dots back after any scripted edit
docs/mosfet-fan-switch.pdf the sheet as a PDF
docs/pcb-notes.md board rules, pad-to-net table, the NUC link, assembly notes
docs/pcb-etch-bottom.svg/.pdf the earlier 56 x 92 plan: toner-transfer print (1:1, mirrored)
docs/pcb-assembly-bottom.pdf that plan's copper-side assembly drawing, pads numbered
docs/pcb-assembly-top.pdf its component-side viewYou can also run the simulation inside KiCad: Simulator → Run Simulator. The sheet carries
.op, .dc V2 0 3.3 0.01 and .tran 1u 5m cards as text, and Q1/Q2/Q3/D1 point at the
.lib files through their Sim.Library / Sim.Name fields, so KiCad's built-in ngspice picks
the real models up.
/BOOT fall while /GATE rises. That step is the booster./GATE, re-run: the fan still runs
(100 mA) but Q1 now burns 5.2 mW. Then change Q1's Sim.Name to mos_irfz44n_wc: dead fan.
Same drawer, same part number, different transistor.L1 / R6 fan branch twice, tie all three drains to /DRAIN, and re-run the
transient: that is the three-fan case above, drawn./DRAIN spike.tw from 500u to 250u (25 % duty): the average fan current falls, but the
pulses the fans see are still a full 12 V.The five .lib files are level-1 fits to the spec records in my own parts cabinet, not
vendor models. Each one is calibrated on the two numbers I actually recorded for the part:
| part | cabinet record | fit |
|---|---|---|
| IRFZ44N | Vgs(th) 2.0–4.0 V, Rds(on) ≤ 0.0175 Ω at Vgs = 10 V, 55 V / 49 A / 94 W | vto=2.8 kp=11.11 rd=5m → 17 mΩ at 10 V |
| IRFZ44N (worst case) | same bin, threshold at the datasheet maximum | vto=4.0 kp=13.33 rd=5m → 17.5 mΩ at 10 V |
| 2N7002 | Vgs(th) ≤ 2.5 V, Rds(on) ≤ 1.2 Ω at Vgs = 10 V, Id max 115 mA, 0.2 W | vto=2.0 kp=0.104 → 1.2 Ω at 10 V |
| IRF7416 | Vgs(th) −2 V, Rds(on) ≤ 0.025 Ω at Vgs = −10 V, 30 V / 10 A, 2.5 W | vto=-2.0 kp=5.0 → 25 mΩ at −10 V |
| 1N5819 | 40 V / 1 A Schottky | is=1e-7 rs=0.05 → 0.36 V at 100 mA |
What a level-1 model gets right, and what this circuit depends on: the threshold voltage, the on-resistance at a properly driven gate, and the fact that at Vgs = 3 V the 2N7002 is comfortably on while the IRFZ44N is barely on at all.
What it gets wrong, honestly:
direct-3v3-wc reports exactly 0 mA. A real worst-case unit
would trickle some milliamps. The point of the row is "not usable", not "literally zero".10 mH + 120 Ω reproduces the electrical lesson (the winding current that makes
the flyback diode necessary) and the right steady current, but not the aerodynamics. It also
cannot show a stalled fan: a real stalled 12 V fan draws amps through its few-ohm winding,
whereas this model always limits itself to 100 mA. The IRFZ44N and the 1N5819 are both rated
far above any stall current a small fan can pull, so the design is safe even where the model
is blind.Kept here so the next schematic is faster:
Sim.Library file that does not exist makes KiCad export a broken device card
(Q1 Q1.irfz44n with no nodes at all) instead of MQ1 /DRAIN /GATE GND GND irfz44n.
Write the .lib files before exporting.Sim.Pins for .model cards and writes every device line in
symbol pin order. So the symbol's pin numbers decide the SPICE terminal order, and
the only reliable way to get ngspice's order is a symbol whose pins are the real package
legs plus a .subckt wrapper that re-orders them (sim/models/*.lib). The old
Simulation_SPICE:NMOS three-pin symbol left KiCad handing ngspice a dangling bulk node
(NC-Q2-0) — with the package symbols and wrappers there is nothing left to repair, and
sim/prepare_deck.py no longer tries.M d g s model is refused with
not enough nodes; M d g s s model (bulk spelled out, tied to the source) runs.Pin not connected and the netlist names the terminal unconnected-_Q3-D-Pad7_. When a
wire ties several package legs together, segment it so every pin lands on a segment end.kicad-cli resolves a symbol from the sheet's own lib_symbols cache, not from the
library. Insert an instance whose lib_id has no cached definition and the file still
loads, but ERC sees a symbol with no pins: Pin not connected at the instance's
anchor, Label not connected on every net label attached to it, and the pin printed as
Pin 1 [???, ?]. Carry the cached copy in yourself — the library's (symbol "Name" …)
block, renamed to (symbol "Library:Name" …) — which is what
tools/embed_pro_mini_schematic.py does..kicad_sch wire is exactly two points. Three points in one (wire (pts …)) and
kicad-cli refuses the whole file with Failed to load schematic and no other clue.
Draw an L as two wires.endpoint_off_grid fires on any pin or wire end
that is not a multiple of it; symbol pin offsets are multiples of 1.27, so putting the
anchors on the grid clears the whole batch at once.kicad-cli needs the project context next to the schematic. Copy a .kicad_sch to a
scratch directory and every command answers Failed to load schematic, because
sym-lib-table, fp-lib-table and the .kicad_pro are not there. It looks exactly like a
corrupt file. Copy those three along with it, or test in the project directory.Sim.Device=V, Sim.Type=PULSE, Sim.Pins=1=+ 2=- and
a Value of VPULSE. Otherwise the export writes V2 /PWM GND dc="3.3V PWM" y1=0 ... which
ngspice cannot parse..control … .endcontrol card on the sheet breaks ngspice. The sheet carries classic
.op / .dc / .tran / .print cards; prepare_deck.py replaces them with a .control
block at the end so wrdata can dump plottable vectors.(text "...") blocks in a .kicad_sch must contain escaped \n. Raw newlines
parse for ERC but make kicad-cli sch export pdf fail with "Failed to load schematic".(junction …) at every T-junction or ERC reports
"Unconnected wire endpoint". Crossings without a junction stay unconnected — the BOOT rail
crossing Q3's source wire is one of those on purpose.wrdata repeats the x column before every vector, so the value columns are 1, 3, 5, …Routed, and being etched at home. 63.2 × 54.4 mm, GND pour on F.Cu, 166 F.Cu
tracks, 23 B.Cu jumpers, 2 vias. fab/ is the export set and the process record for the
route actually being used — the Mono 6K's LCD as the exposure lamp — and fab/export.sh
regenerates it. The perfboard build is still where every measured number in this README came
from; the board is the same circuit made permanent.
Routing this board is human work. Its copper was drawn by hand. pcb/build_board.py and
pcb/router.py — the generator and the autorouter that produced the earlier layout — are out
of the working tree, complete at the tag fan-switch-booster-pre-cleanup-2026-10-24, and
nothing here regenerates the board. Do not re-route it, and do not run Update PCB from
Schematic on it: one attempt split the net names and produced 84 DRC violations.
| outline | 63.20 × 54.40 mm, 1.6 mm FR4, four M3 non-plated holes at the corners |
| clearance between nets | 0.50 mm — KiCad's reported minimum; DRC is clean at 0.50 and reports four violations at 0.55 |
| tracks | 0.80 mm throughout |
| copper | F.Cu 2807 mm² = 82 % of the board (pour + 166 tracks); B.Cu 369 mm² = 11 % (23 jumpers, no pour) |
| drills | 0.3 mm ×2 (the vias), 0.8 ×27, 1.0 ×53, 1.1 ×8, 1.2 ×2, 3.2 NPTH ×4 — 96 holes |
| pads | 90 through-hole, 11 SMD, 4 non-plated; 28 footprints |
Those rules are the ones a home-etched board actually needs rather than the fab defaults KiCad
ships with, and they live in the Default netclass of mosfet-fan-switch.kicad_pro.
B.Cu is not being etched. The bottom copper of the blank is stripped entirely — otherwise
every through-hole pad's bottom annulus ties to a solid sheet and the links short through it —
and the four nets those 23 jumpers carried are bridged by four pieces of insulated wire:
/P3V3 12.0 mm, /RST 15.2 mm, /GATE 17.9 mm, /BOOT 52.3 mm. Those are the only splits;
every other net is a single conductor on F.Cu alone. Pads, wire lengths and the reason are in
fab/README.md.
Q2 (SOT-23) and Q3 (SO-8) are the only surface-mount parts, and they sit on the copper side. Everything else is through-hole.
The outline went 56 × 68 → 56 × 92 → 63.2 × 54.4: the current routing is wider and
shorter than the generated plan, and it fits an M3 hole in each corner. The fan driver is the
same arrangement as the first version (tag checkpoint-fan-driver-v1): the level shifter
R1/R2/Q2 down the left edge, the booster Q3 with R3/R4/R5 on the right, the power FET Q1 above
it, and the three fans out through the 2×03 header J3 at the right edge. The NUC interface
band hugs the top edge — J4, the socket the NUC's own fan cable plugs into, the 5 V → 3.3 V
request divider R7/R8 with C5/C6, and the open-collector tach answer R9 + Q4 at the left. The
module deck is U1 on three female sockets through the middle-left, with J5 (the FTDI
programming header, wired pin for pin to the module's own programming row) below it at the left
corner. +12 V comes in at J1 near the middle, with D1 and the spares D2 and D3 along the
bottom. There is no J6 on this board: the module is flashed through its own bootloader, so
the ICSP header was removed.
The symbols on the sheet are the package pinout now. The MOSFET and transistor symbols
used to be KiCad's SPICE symbols, which number their pins D-G-S because that is the
order ngspice wants — while SOT-23 is 1 gate / 2 source / 3 drain, the IRF7416 SO-8 is
1-3 source / 4 gate / 5-8 drain, and the IRFZ44N TO-220 is 1 gate / 2 drain / 3 source.
The sheet now carries four local symbols (mosfet-fan-switch.kicad_sym) whose pin numbers
are the leg numbers, so the numbers printed beside each transistor leg match the
footprint pad, and KiCad's Update PCB from Schematic agrees with the layout instead of
fighting it. The ngspice order moved into .subckt wrappers in sim/models/.
docs/pcb-notes.md has the pad-to-net table and the wrapper table.
| file | what it is for |
|---|---|
fab/gerber/mosfet-fan-switch-F_Cu.gtl | the artwork to expose — the copper that survives |
fab/gerber/mosfet-fan-switch.drl, -drl_map.pdf | the Excellon drill set (96 holes) and its 1:1 map |
fab/preview/copper-top.svg | 1:1, page trimmed to the outline: the assembly sheet, and the pad-numbered view of the side that keeps its copper |
fab/preview/copper-bottom.svg | the side that gets stripped — the corridor the four wire links follow |
fab/README.md | the process: panel and exposure calibration, the UVtools settings, stripping the bottom copper, the four links, drilling |
fab/test-card.gbr | the calibration card — trace ladder, gap ladder, 10.000 mm square, drill crosshairs, mirroring check |
docs/pcb-notes.md | design rules, courtyard boxes, the pad→net table, programming the module. Its Drilling and Etching sections describe the earlier 56 × 92 plan and are out of date |
docs/pcb-etch-bottom.svg/.pdf, docs/pcb-assembly-*.pdf, docs/pcb-render-*.png | plots of the earlier unrouted 56 × 92 placement, kept as the corridor plan and the copper-side assembly view. They carry no copper. fab/preview/ is the current artwork |
fab/export.sh regenerates the current artwork from the board file, so it is the one to re-run
after any change to the board. The docs/pcb-* plots were generated from the placement that
predated the routing and are kept for the reasoning they carry, not as something to print.
The two surface-mount parts (Q2 SOT-23, Q3 IRF7416 SO-8) sit on the copper side,
which is what keeps this a one-layer board. Their outlines and labels are therefore
stored mirrored, and a top-side view of the board shows them reversed — Q3 reads as
ƧQ and pads 1–4 run right-to-left, so that view cannot tell you which pad is pin 1.
Read the copper-side drawing, not the top render: on the copper side the notch sits
beside pad 1, and pads 1–3 are the three pads that run straight into the wide +12 V bus.
Rotate the datasheet's top-view picture 90° counter-clockwise and it is the board. In
KiCad, View → Flip Board View shows the same thing, and the footprints carry their
3D models so the 3D viewer shows both chips lying on the copper. docs/pcb-notes.md has
the picture and the proof.
The end of docs/pcb-notes.md records the KiCad 10 behaviour that had to be pinned down
before the DRC would go clean: a footprint's placement rotation
rotates pad positions only — a pad's copper rotates only by that pad's own angle, so a
hand-written rotated SO-8 has overlapping pads until the angle is baked into each pad;
track ends are round, so a 2 mm bus bulges 1 mm past its endpoint and can short a pad
1.27 mm away; and text angles in the file are board-absolute, not relative to the
footprint.
Q1 does not need a heatsink: at 100 mA it dissipates 0.16 mW, at 300 mA (three fans) 1.5 mW. What the TO-220 and the 17.5 mΩ are for is a stalled fan or a shorted output, where several amps flow.
The 12 V supply decides the fan count. The module in the bin (L1 · B7) is rated 450 mA: comfortable for three 100 mA fans, tight for four, not enough for three 150 mA fans. This build runs from an 8 A 12 V brick, which moves the limit to the flyback diode — see the ladder in With a bigger 12 V supply above.
Q3's SO-8 package is the awkward one. There is one IRF7416 already mounted on a breakout board in R1 · A4 — use that for the first build.
Keep R4 (1 k) in series with Q1's gate. It slows the edge just enough to keep switching noise down and limits the current Q3 has to source when charging 1.5 nF.
Keep R5 (10 k) from gate to ground. Without it a floating gate can sit at any voltage and the fans can half-turn-on while the Arduino is resetting.
Wire multiple fans in parallel on the drain side, all meeting at /DRAIN, with the one
1N5819 from /DRAIN to +12 V. Keep each fan's wiring short and equal so no single cable
carries the sum of the switching edges.
If you drive this from Klipper, the pin needs to be a hardware PWM pin and Klipper wants an inverted PWM output for a low-side switch that turns on with a high pin:
[heater_fan fan]
pin: !PH6
pwm_cycle_time: 0.010
max_power: 1.0
heater_temp: 45.0
fan_speed: 0.6!PH6 is a hardware PWM pin on the Mega-class board that runs Klipper in my printer. If the
Pro Mini on this board is the Klipper MCU, the pin wired to /PWM is D9 = OC1A, which
Klipper spells !PB1.
pwm_cycle_time: 0.010 is 100 Hz — well above the ~60 µs turn-off delay of this driver, so
the delays are invisible. Do not use a plain [fan] section: a [heater_fan] only runs the
fan while the heater is active, which is what you want on a printer. Klipper drives one fan
per pin, so three fans on one output means wiring them together in parallel — which is exactly
the case sim/fan_count.py just measured.
J4,J5and U1's pad numbers are designators on this board — the pad→net table is indocs/pcb-notes.mdand the pad-numbered 1:1 sheet isfab/preview/copper-top.svg.J6(a 2×03 ICSP header) andJP1(a 0 Ω link) belonged to the earlier 56 × 92 plan and are not on the routed board: the module is flashed through its own bootloader, so this board has no ISP route at all. The perfboard build uses the same Pro Mini in sockets and the same four signals; it just has no silk to point at.
/PWM (D9 = OC1A),
pad 24 /TACH_PWM (D10 = OC1B), pad 20 /DEMAND (read it on A0), and /GND on
pads 4, 29 and 31. Pad 13 RAW takes /P12V, which is how the module powers itself;
pads 16 and 28 are the module's 3.3 V output, and that rail is what the /DEMAND
divider is referenced to. The board carries no regulator of its own, and the module's
ground must be the same ground as J1's 12 V brick and J4's pin 4./DEMAND on A0 and map it straight onto the fan duty: 2.50 V is a full request
(775 counts), so duty = min(1.0, counts / 775) is the whole control law. The 5.5 ms
time constant at the tap means the ADC reading is already the average, so a plain
analogRead per 100 ms is enough — no oversampling needed./PWM and /TACH_PWM are Timer 1's two channels (OC1A and OC1B), so one TCCR1A /
TCCR1B / ICR1 setup gives them the same frequency and two different duties — a 16-bit timer
has one counter, not two. That works here, and only because of the numbers: the fan drive is
100 Hz and the tach carrier wants 100 Hz too, so a single period serves both. If the fan carrier
ever moves — fitting Q5 and going to 25 kHz, say — the tach moves with it, and 25 kHz reads
750 000 RPM on the NUC's tach line and is rejected outright. Then the tach has to be generated
somewhere else: Timer 2 (D11 = OC2B, fast PWM with prescaler 512 and OCR2A = 155 gives
100.16 Hz), or a software toggle from the Timer 1 overflow interrupt, which is what
fan-switch-debug actually does. The Uno stays usable as the programmer, and it would also run
the same four signals at 5 V logic, in which case R7/R8 should become a 10 k / 20 k divider (or
simply go, if you feed /DEMAND from a 3.3 V reference instead of the NUC's 5 V)./P12V
while it is being programmed.tools/pro_mini.py
is the clone layout — 12 pads per row, 15.24 mm between the rows, six programming
holes in their own row. A genuine SparkFun board is 13 pads per row at 12.70 mm with the
programming holes in the end column. ROW_SPACING, PADS_PER_ROW and SPARE are at the
top of that file for exactly this argument.firmware/fan-switch-debug/ drive + tach blink, the first one that ran on the bench
firmware/fan-switch-ladder/ 100 % -> 50 %, one step per minute, no serial needed
firmware/fan-switch-serial/ duty commanded over the UART: 0..100, ?, h
firmware/fan-switch-freq/ duty AND carrier over the UART: 0..100, f <Hz>, ?, h
firmware/bench/ the scripts that drive the bench: flash, set, sequence, verifyAll four drive /PWM on D9 = OC1A, fast PWM with TOP from ICR1. Timer 1 is the only
16-bit timer on the module and D9 is the only pin carrying OC1A, which is why the sketches touch
TCCR1A/B, ICR1 and OCR1A directly instead of calling analogWrite() — that would have moved
the whole thing to Timer 2 at 980 Hz.
The two sketches the bench uses now — ladder and freq — default to ICR1 = 9999 with a
prescaler of 8: 100 Hz, 5 000 µs high at 50 %. That is not the value they started on: both ran at
1 kHz until the carrier was measured, and serial and debug are left at 1 kHz as the record of
what the bench ran before that measurement. The reason for the change is the next section.
OCR1A = 0, and it is not 0 %In fast PWM, OCR1A = 0 does not switch the output off. The datasheet calls it "a narrow
spike for each TOP+1 timer clock cycle": one timer clock, whatever the carrier is. At 8 MHz that
is a 125 ns pulse on D9, once every carrier period, forever — every 1 ms at the 1 kHz these
sketches originally ran at, every 10 ms at the 100 Hz they run at now.
Through the Q2/Q3 booster that spike is not small. Measured in sim/out/mosfet-fan-switch.spice
with the fan branch attached:
| node | what the spike does |
|---|---|
/LGATE | 3.00 V pulled low |
/BOOT | 12 V → 8.055 V |
/HIGATE | held at 12 V |
/GATE | 10.905 V for 36.66 µs |
i(L1) | 31.1 mA peak, 2.57 mA mean, every cycle |
So the fan was being kicked with 10.9 V on its MOSFET gate a thousand times a second, at the 1 kHz carrier this board originally ran, while the sketch believed the output was off. 36.66 µs of a 1 ms period is 3.7 % — enough to make the winding and its own capacitance ring audibly. That is the hum.
The fix is not a smaller number, it is a different mode: clear COM1A1:0 to 00 so OC1A
leaves the pin, then hold D9 low as ordinary GPIO. Timer 1 keeps running, but it is no longer
connected to anything.
if (pct == 0) {
TCCR1A &= (uint8_t)~(_BV(COM1A1) | _BV(COM1A0)); // OC1A off the pin
digitalWrite(PWM_PIN, LOW); // held low as GPIO
return;
}
TCCR1A = _BV(COM1A1) | _BV(WGM11); // back to fast PWM
OCR1A = (uint16_t)((uint32_t)(PWM_TOP + 1) * pct / 100);Confirmed on the bench. fan-switch-serial written through the module's own bootloader
(-c arduino -b 57600, ATmegaBOOT_168_atmega328_pro_8MHz.hex) over J5 with DTR connected:
at duty 0 % OCR1A 0/8000 the fan is silent, and 50 spins it up on command. Every sketch
since carries the same fix, so 0 % is silent whatever the carrier is set to.
The sketches started at 1 kHz on the theory that the hum was the chopping, and that putting the
carrier above hearing — the 4-pin fan PWM standard uses 25 kHz for exactly that reason — would fix
it. On the bench the opposite happened: 25 kHz is not quieter, it is the fan running at full
speed no matter what duty is commanded. A Hantek DSO2D10 with both channels on the board, CH1 on
Q1 pin 1 (/GATE) and CH2 on Q1's tab (/DRAIN), ground clip on the board's input ground, says
why.
At 1 kHz, 50 % commanded, OCR1A 4000/8000, so 500 µs high:
| measurement | value |
|---|---|
/GATE high width | 652 µs of a 1000 µs period |
/DRAIN low width | 680 µs |
/GATE swing | −0.2 V → 11 V, rise 12 µs |
/DRAIN swing | −1.2 V → 12 V |
/DRAIN average | 2.99 V, so the fan's own average supply is 12 − 2.99 = 9.01 V |
The two channels agree with each other, which is what makes the reading trustworthy: the gate is high for exactly as long as the drain is low. A commanded 500 µs of conduction became 650–680 µs. Turn-on is fast, 12 µs. Turn-off takes roughly 170 µs.
That single number explains everything the bench had already shown, including measurements taken before the scope existed. A fixed time error adds a fixed voltage offset: 12 V × 170 µs ÷ 1000 µs ≈ 2 V, and a multimeter sweep across the fan at 1 kHz had produced a straight line — 9.6 V at 60 %, 8.6 V at 50 %, 0.1 V per percent — sitting about 2 V above the ideal line at every duty. The scope derived the same 2 V from a time measurement, independently. It also explains why the hum appeared at every chopped duty from 60 % down to 10 % and at neither extreme: at 100 % there is no chopping, and at 0 % the pin is held low as GPIO.
And it explains 25 kHz. The excess is a constant ~170 µs, but at 25 kHz the off half-period is 20 µs. The gate is told to fall and never gets there, so the output is simply stuck on — which is exactly what the fan was doing.
The netlist models /BOOT with C2 = 1.7n, which is only the IRF7416's own Ciss. Re-running the
transient at 1 kHz / 50 % against the measured 652 µs:
C2 in the model | /GATE high, 500 µs commanded |
|---|---|
| 1.7 nF (what the netlist says) | 538 µs |
| 4.7 nF | 580 µs |
| 6.8 nF | 616 µs |
| 10 nF | 689 µs |
| the board | 652 µs |
So the perfboard's /BOOT node carries roughly 8 nF, not 1.7 nF — the extra ~6 nF is the
wiring. That is the difference between a model that reports 53 % duty and a board that delivers
68 %. It is worth remembering that a hand-built board is three times its own netlist at this node.
Two ways out, and they were both measured:
Slow the carrier. The error is a fixed time, so at a long period it stops mattering. At 100 Hz, 50 % the scope reads:
| measurement | value |
|---|---|
| period | 10.02 ms |
/GATE high / low width | 4.82 ms / 5.20 ms |
| delivered duty | 48.10 % for 50 % commanded |
/GATE average | 4.76 V |
170 µs out of a 5 ms off-half is 3 %, and the delivered duty is honest. The fan is quiet — the noise the 1 kHz carrier produced is gone. This is the fix the board is running now, and it cost nothing but a constant in the sketch.
Or make the turn-off fast, which buys the high carriers back. A BC547 (Q5) with its emitter
to ground, base to /BOOT through 4.7 kΩ, and collector to /GATE clamps the gate directly
instead of waiting for /BOOT to charge back up. On the calibrated board:
| variant | 1 kHz | 10 kHz | 25 kHz |
|---|---|---|---|
as built, R3 = 10k | 580 µs (want 500) | stuck on | stuck on |
Q5, R3 = 10k, Rb = 4.7k | 500 µs | 53 µs | 23 µs, fan 58 mA |
Q5, R3 = 1k, Rb = 4.7k | 496 µs | 49 µs | 19 µs, fan 47.5 mA |
Wanted values are 500, 50 and 20 µs. The helper with R3 dropped to 1 kΩ is essentially exact at
every carrier from 1 kHz to 25 kHz. With R3 = 1k the off state also gets cheaper rather than
more expensive: /BOOT settles near 10 V, which is high enough that Q3 genuinely turns off, so
/HIGATE and R4 stop carrying anything and the off-state loss collapses to about 25 mW. The
cost moves to the on state — R3 = 1k then carries about 12 mA while the fan runs, so R3 has
to dissipate around 145 mW, fine for a quarter-watt through-hole part but a part to choose
knowingly. That variant is modelled and not fitted: 100 Hz already solved the noise, so Q5 is on
the shelf until a carrier above hearing is wanted.
One more thing visible in the 1 kHz trace: /DRAIN undershoots to −1.2 V, which means Q1's
body diode conducts briefly at turn-on. The freewheel current circulating through D1 and the fan's
own 10 mH has to collapse, and it does that by pulling the drain below ground. It is not the noise
source, but it is why the drain edges look ragged.
With the carrier fixed at 100 Hz the board was run up the entire range, 10 seconds per rung, with
OCR1A read back from the sketch at every step:
| duty | OCR1A | /GATE high per 10 ms | fan supply | what the bench did |
|---|---|---|---|---|
| 0 % | 0 | 0 — pin held low as GPIO | 0 V | silent |
| 10 % | 1000 | ~1.0 ms | ~1.2 V | would not spin the fan |
| 20 % | 2000 | ~2.0 ms | ~2.4 V | would not spin the fan |
| 30 % … 90 % | 3000 … 9000 | ~3 … 9 ms | ~3.6 … 10.7 V | ran |
| 100 % | 10000 | always high | ~11.9 V | ran, smooth |
No humming at any rung. That is the result the 1 kHz carrier could not produce at any chopped duty, and it is the reason 100 Hz is the default.
The dead bottom of the ladder is the fan, not the switch. A 12 V brushless fan needs something like 4–7 V at its terminals before its own controller will start the rotor, so 10 % and 20 % are being delivered correctly and the fan is simply declining to start — expected behaviour on a 2-wire fan, and worth knowing before anyone reads "nothing happens at 10 %" as a board fault. The exact rung where it starts was not pinned down. The other half of the same fact: once spinning, a fan keeps turning at a terminal voltage well below the one it needed to start, so the speed at a given duty depends on whether the fan was already running.
A thermostat wants low duty means low speed, but a fan that will not start at 1.2 V makes the
bottom of the range dead. The usual answer is a start kick — 100 % for a second or two to get the
rotor going, then drop to the commanded duty. That belongs in fan-switch-freq as a k command
and in the ladder as a kick before each rung settles. Not implemented; the bench is happy without
it.
That was the other theory, and the simulation killed it. A capacitor there does not absorb the spike, it becomes the other half of an LC tank with the fan's own 10 mH winding:
Cfan | mean fan current at 0 % | ring with L1 = 10 mH |
|---|---|---|
| none | 2.57 mA | — |
| 100 nF | 14.9 mA | harmless |
| 10 µF | 69.6 mA | 503 Hz, Q 3.8, ~2.4 ms |
| 100 µF | 96.1 mA | 159 Hz, Q 12, ~24 ms |
At 10 % duty it is worse still: 74.9 mA with 10 µF, 97.0 mA with 100 µF. A bulk capacitor across the terminals makes the fan hotter and louder, not quieter. The only capacitor on that side of the board worth having is 100 nF film at J3, and that is for EMI, not for the hum.
Both routes in docs work; the FTDI route is the one that keeps the bench quiet:
arduino-cli compile --fqbn arduino:avr:pro:cpu=8MHzatmega328 <sketch>
avrdude -C <avrdude.conf> -c arduino -P /dev/ttyUSB0 -b 57600 -p m328p \
-U flash:w:<sketch>.ino.hex:ifirmware/bench/ wraps exactly this. flash-bootloader.sh compiles and writes, set.sh leaves the
sketch in a commanded state so the board can be probed with the port released, seq.sh runs a timed
sequence so a duty range can be listened to, and verify-flash.sh reads all 32 kB back over ISP and
compares it against the compiled hex.
avrdude just times out. The FTDI breakout's DTR to J5-1 is the whole auto-reset circuit.TX0, the module's output, so it goes to the breakout's
RX; J5-3 is RX1, the module's input, so it goes to the breakout's TX. Matching the
silkscreen names label-to-label is the wrong way round. If a breakout's labels are ambiguous,
jumper its own TX to its RX with nothing else attached and send bytes at it — 20 out, 20 back
identifies the pair by function.ATmegaBOOT then sits
listening for a sync byte for about a second — any command sent in that window is eaten by the
bootloader instead of reaching the sketch. That is how f 100 once arrived as unknown: j. The
bench uses a set.sh that opens the port, waits 3 s for the bootloader to give up, sends the
command list, waits again, sends the list a second time, and then releases the port; the sketch
holds whatever it was last commanded, so the bench stays in that state while it is probed with a
scope or a meter.firmware/fan-switch-debug blinks the tach LED with PORTB ^= _BV(PB6), but Arduino D10 is
PB2. The disassembly confirms it (in r24,0x05; ldi r25,0x40; out 0x05): it toggles a bit that
is not the tach pin. It is left as written because that sketch is the record of the first bench
run. Fix it to _BV(PB2) before reusing it on the embedded-Pro-Mini board.
firmware/fan-switch-freq, flashed through its own bootloader, and boots on
its own at duty 0 % OCR1A 0/10000 carrier 100 Hz. Nothing has to be sent for it to be in the
quiet configuration.Q5 and the 1 kΩ R3 are not fitted. The board is the perfboard build with R3 = 10k, and
100 Hz is what makes that work.Q5 is fitted: the ~170 µs
turn-off is longer than the off half-period there, so the output is stuck on.archive/ keeps the first version of this project: a single logic-level IRLZ44N driven
directly at 5 V, plus its placeholder models and its plots. It is kept because the reason it
worked (a logic-level part, a 5 V pin) is exactly the reason the 3.3 V version does not.
| Qty | Value | Footprint | References |
|---|---|---|---|
| 5 | 10k | Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal | R2, R3, R5, R7, R8 |
| 4 | M3 mounting hole | MountingHole:MountingHole_3.2mm_M3 | MK1, MK2, MK3, MK4 |
| 3 | 1k | Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal | R1, R4, R9 |
| 1 | 100n | Capacitor_THT:C_Disc_D5.0mm_W2.5mm_P5.00mm | C3 |
| 1 | 100n | Capacitor_THT:C_Disc_D3.8mm_W2.6mm_P2.50mm | C6 |
| 1 | 10u | Capacitor_THT:CP_Radial_D6.3mm_P2.50mm | C4 |
| 1 | 12V IN | Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical | J1 |
| 1 | 1n5819 | Diode_THT:D_DO-41_SOD81_P10.16mm_Horizontal | D1 |
| 1 | 1n5822 | Diode_THT:D_DO-27_P15.24mm_Horizontal | D2 |
| 1 | 1u | Capacitor_THT:CP_Radial_D8.0mm_P3.50mm | C5 |
| 1 | 2n7002 | Package_TO_SOT_SMD:SOT-23 | Q2 |
| 1 | Arduino_Pro_Mini_3V3 | mosfet-fan-switch:Arduino_Pro_Mini_328_Socket | U1 |
| 1 | BC547 | Package_TO_SOT_THT:TO-92_Inline_Wide | Q4 |
| 1 | FAN OUT | Connector_PinHeader_2.54mm:PinHeader_2x03_P2.54mm_Vertical | J3 |
| 1 | FTDI HEADER | Connector_PinHeader_2.54mm:PinHeader_1x06_P2.54mm_Vertical | J5 |
| 1 | NUC FAN HEADER | Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Vertical | J4 |
| 1 | irf7416 | Package_SO:SO-8_3.9x4.9mm_P1.27mm | Q3 |
| 1 | irfz44n | Package_TO_SOT_THT:TO-220-3_Vertical | Q1 |
| 1 | mbr10100ct | Package_TO_SOT_THT:TO-220-3_Vertical | D3 |
It measures 63.2 × 54.4 mm and has 2 copper layers, 1.6 mm thick.
28 parts, built around the BC547, IRF7416T, IRFZ44N and MBR10100CT. 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.
From the kavehs87/nuc-fan-controller repository on GitHub, at the commit this page was made from: the board is fan-switch-booster/mosfet-fan-switch.kicad_pcb and its schematic fan-switch-booster/archive/v1-irlz44n-5v-low-side.kicad_sch. PCBKit opens the board in the browser, with nothing to install.
Its repository states no licence, so its author keeps all rights: ask kavehs87 before reusing the design.

omega9380/KSGER-Coverplates
T12 Soldering Iron control board. A design based on schematics created by Mindstorm88 on the EEVBlog forums.

Javitax47/NLTL_Lite
Implementación pasiva de cinemática relativista general Agujeros negros · Agujeros blancos · Arrastre de marco · Proceso de Penrose · Lensing gravitacional · Solitones PCB 150 × 150 mm · 857 componentes · 37…

mlab-modules/BATPOWER04
High-efficiency step-down converter based on the LM3485 PFET buck controller. Input voltage range is 4.5 V to 16 V.




