Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions PCB/diagrams/power_control_layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ <h2>What's on this sheet</h2>
<tr><td class="ref">D_OR1 / D_OR2</td><td class="val">1N4148W</td><td class="val">SOD-123</td><td class="role">Diode-OR: PWR_BTN_TRIGGER (actual button press, via the inverter) or MCU hold, either one latches power on</td></tr>
<tr><td class="ref">R_GATE1</td><td class="val">100kΩ</td><td class="val">0402</td><td class="role">Q_PWR1 gate pull-up — off by default</td></tr>
<tr><td class="ref">R_LATCH_G1</td><td class="val">1MΩ</td><td class="val">0402</td><td class="role">Q_LATCH1 gate pull-down — off by default; sized to not starve the diode-OR's drive (callout D)</td></tr>
<tr><td class="ref">C_LATCH_G1</td><td class="val">100nF</td><td class="val">0402</td><td class="role">Latch-gate filter — keeps the ~2ms battery-insertion transient from lifting PWR_LATCH_G (callout I)</td></tr>
<tr><td class="ref">R_BTN_PU1</td><td class="val">100kΩ</td><td class="val">0402</td><td class="role">Button sense pull-up to 3V3 (not VSYS) — MCU reads pressed (0V) vs idle (3.3V); callout H</td></tr>
<tr><td class="ref">R_INV_BASE1</td><td class="val">100kΩ</td><td class="val">0402</td><td class="role">Dedicated VSYS pull-up for Q_INV1's base — decoupled from PWR_BTN_SENSE (callout F)</td></tr>
<tr><td class="ref">D_INV_ISO1</td><td class="val">BAT54W</td><td class="val">SOD-123</td><td class="role">Schottky isolator: lets a press pull Q_INV1's base low without loading PWR_BTN_SENSE</td></tr>
Expand Down Expand Up @@ -406,6 +407,14 @@ <h2>Suggested floorplan</h2>
<rect x="333" y="343" width="14" height="26" rx="2" fill="#20241f" stroke="#4a5044" stroke-width="1.5"/>
<text x="352" y="360" text-anchor="start" font-family="JetBrains Mono, monospace" font-size="9.5" fill="#8fd3ff">R_LATCH_G1 1M</text>

<!-- C_LATCH_G1: 100nF on PWR_LATCH_G so the brief Q_INV1-not-yet-on
window at battery insertion can't lift the latch gate (see callout I) -->
<path d="M340,332 L312,332 L312,343" fill="none" stroke="#8fd3ff" stroke-width="3" stroke-linecap="round"/>
<path d="M312,369 L312,395 L340,395" fill="none" stroke="#8fd3ff" stroke-width="3" stroke-linecap="round"/>
<circle cx="340" cy="332" r="3.5" fill="#8fd3ff"/>
<rect x="305" y="343" width="14" height="26" rx="2" fill="#20241f" stroke="#4a5044" stroke-width="1.5"/>
<text x="300" y="360" text-anchor="end" font-family="JetBrains Mono, monospace" font-size="9.5" fill="#8fd3ff">C_LATCH_G1 100n</text>

<!-- D_OR1 -->
<rect x="368" y="376" width="14" height="28" rx="2" fill="#20241f" stroke="#4a5044" stroke-width="1.5"/>
<text x="386" y="395" text-anchor="start" font-family="JetBrains Mono, monospace" font-size="9.5" fill="#8fd3ff">D_OR1 (from inverter)</text>
Expand Down Expand Up @@ -509,6 +518,10 @@ <h2>Suggested floorplan</h2>
<line x1="420" y1="200" x2="360" y2="188" stroke="#c9d6cd" stroke-width="1" stroke-dasharray="3,3" opacity="0.65"/>
<circle cx="432" cy="203" r="12" fill="#ff8a3d"/>
<text x="432" y="207.5" text-anchor="middle" font-size="12.5" font-weight="700" fill="#231205">G</text>

<line x1="262" y1="338" x2="303" y2="352" stroke="#c9d6cd" stroke-width="1" stroke-dasharray="3,3" opacity="0.65"/>
<circle cx="252" cy="334" r="12" fill="#ff8a3d"/>
<text x="252" y="338.5" text-anchor="middle" font-size="12.5" font-weight="700" fill="#231205">I</text>
</g>
</svg>
</div>
Expand All @@ -534,6 +547,7 @@ <h2>Why it's arranged this way</h2>
<li><span class="badge">F</span><p><b>D_OR1 can't read PWR_BTN_SENSE directly — found the hard way, on real hardware.</b> The original design had R_BTN_PU1 (to VSYS) and a since-removed R_PWR_SENSE1 (to GND) both on that node, dividing it to roughly half VSYS at idle — comfortably above a diode's ~0.6V forward threshold, so D_OR1 was forward-biased (and the board powered on) the instant a battery was plugged in, button or no button. The inverter fixes this properly instead of just retuning the divider: idle, Q_INV1 is on and holds PWR_BTN_TRIGGER at GND; pressed, it turns off and R_INV_PU1 pulls PWR_BTN_TRIGGER to VSYS. Only a real press can forward-bias D_OR1 now. Keep this block close to D_OR1 — it's parts serving one signal, not worth spreading out.<br><br><b>Second-order bug, found the same way:</b> the inverter's own base resistor originally tapped PWR_BTN_SENSE directly, in series with R_BTN_PU1 — stealing enough idle base current to sag the MCU's own button-read node to ~1V instead of near-VSYS. R_INV_BASE1 is now its own dedicated 100kΩ pull-up straight to VSYS (not sharing current with R_BTN_PU1), and D_INV_ISO1 (a Schottky, chosen specifically for its lower forward drop than Q_INV1's own base-emitter junction) lets a press still pull the base low without ever loading PWR_BTN_SENSE.</p></li>
<li><span class="badge">G</span><p><b>PWR_EN had no defined rest state — TI's own datasheet says not to do this.</b> Q_PWR1's drain feeds the buck's EN pin directly, with nothing else on that node. TLV62569's EN pin is a bare comparator input with no internal pull ("Do not leave floating" per its datasheet) — with Q_PWR1 off, EN was held by MOSFET/input leakage alone, no guaranteed logic level. R_PWR_EN_PD1 (100kΩ to GND) fixes it: negligible loading when Q_PWR1's tens-of-mΩ RDS(on) is driving EN high, solid GND the instant it's off.</p></li>
<li><span class="badge">H</span><p><b>PWR_BTN_SENSE must never sit above the MCU's rail.</b> It lands directly on GPIO2 (pin 38), whose ceiling is VDD + 0.3V (~3.6V), but R_BTN_PU1 used to pull it to VSYS — up to ~4.5V on USB, and it back-fed the unpowered 3V3 rail through the pin's clamp diode whenever the MCU was off. Since R_INV_BASE1 gave the inverter its own VSYS pull-up (callout F), R_BTN_PU1 no longer has to supply the inverter, so it now pulls to 3V3: idle reads 3.3V, and with the MCU off the node is simply unpowered instead of leaking into the rail.</p></li>
<li><span class="badge">I</span><p><b>Battery insertion used to blip PWR_EN for ~2ms — found in simulation, since no board existed to bench-test.</b> When VSYS first appears, Q_INV1 isn't conducting yet: its base current is diverted through D_INV_ISO1 into the discharged C_DBNC1 until that charges to ~0.4V. For that ~1–2ms PWR_BTN_TRIGGER follows VSYS, PWR_LATCH_G rises to ~VSYS−0.6V, and Q_PWR1 briefly enables the buck. C_LATCH_G1 (100nF against the 100kΩ pull-up) makes the gate rise slowly enough that the transient peaks at ~0.3V — well under the AO3400A's minimum threshold — at the cost of 4–8ms of press latency, and it gives release a little extra hold time. Keep it right at PWR_LATCH_G.</p></li>
</ol>
</section>

Expand All @@ -548,6 +562,7 @@ <h2>Routing priority</h2>
<li>Keep R_INV_BASE1, D_INV_ISO1, Q_INV1, and R_INV_PU1 together as their own small block, close to D_OR1 — they're a single-purpose inverter, not independent parts.</li>
<li>Bring PWR_BTN_SENSE and PWR_HOLD in from wherever the MCU sheet actually sits — these are off-sheet nets, not local components.</li>
<li>Drop R_PWR_EN_PD1 right at Q_PWR1's drain, on the way to PWR_EN leaving the sheet — it's a bias resistor for that pin, not an independent part.</li>
<li>Put C_LATCH_G1 on the PWR_LATCH_G node next to R_LATCH_G1 and Q_LATCH1's gate, with a short GND return.</li>
</ol>
</section>

Expand Down
3 changes: 3 additions & 0 deletions PCB/diagrams/power_control_layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ A press-to-start, software-held latch: the button turns the board on, the MCU ha
| D_OR1 / D_OR2 | 1N4148W | SOD-123 | Diode-OR: `PWR_BTN_TRIGGER` (actual button press, via the inverter) or MCU hold, either one latches power on |
| R_GATE1 | 100kΩ | 0402 | Q_PWR1 gate pull-up — off by default |
| R_LATCH_G1 | 1MΩ | 0402 | Q_LATCH1 gate pull-down — off by default; sized to not starve the diode-OR's drive (callout D) |
| C_LATCH_G1 | 100nF | 0402 | Latch-gate filter — keeps the ~2ms battery-insertion transient from lifting `PWR_LATCH_G` (callout I) |
| R_BTN_PU1 | 100kΩ | 0402 | Button sense pull-up to **3V3** (not VSYS) — MCU reads pressed (0V) vs idle (3.3V); callout H |
| R_INV_BASE1 | 100kΩ | 0402 | Dedicated VSYS pull-up for Q_INV1's base — decoupled from `PWR_BTN_SENSE` (callout F) |
| D_INV_ISO1 | BAT54W | SOD-123 | Schottky isolator: lets a press pull Q_INV1's base low without loading `PWR_BTN_SENSE` |
Expand Down Expand Up @@ -50,6 +51,7 @@ Top copper layer. The VSYS→PWR_EN path runs straight across the top as one thi
| **F** | **D_OR1 can't read `PWR_BTN_SENSE` directly — found the hard way, on real hardware.** The original design had `R_BTN_PU1` (to VSYS) and a since-removed `R_PWR_SENSE1` (to GND) both on that node, dividing it to roughly half VSYS at idle — comfortably above a diode's ~0.6V forward threshold, so `D_OR1` was forward-biased (and the board powered on) the instant a battery was plugged in, button or no button. The inverter fixes this properly instead of just retuning the divider: idle, `Q_INV1` is on and holds `PWR_BTN_TRIGGER` at GND; pressed, it turns off and `R_INV_PU1` pulls `PWR_BTN_TRIGGER` to VSYS. Only a real press can forward-bias `D_OR1` now. Keep this block close to `D_OR1` — it's parts serving one signal, not worth spreading out.<br><br>**Second-order bug, found the same way:** the inverter's own base resistor originally tapped `PWR_BTN_SENSE` directly, in series with `R_BTN_PU1` — stealing enough idle base current to sag the MCU's own button-read node to ~1V instead of near-VSYS. `R_INV_BASE1` is now its own dedicated 100kΩ pull-up straight to VSYS (not sharing current with `R_BTN_PU1`), and `D_INV_ISO1` (a Schottky, chosen specifically for its lower forward drop than Q_INV1's own base-emitter junction) lets a press still pull the base low without ever loading `PWR_BTN_SENSE`. |
| **G** | **`PWR_EN` had no defined rest state — TI's own datasheet says not to do this.** `Q_PWR1`'s drain feeds the buck's EN pin directly, with nothing else on that node. TLV62569's EN pin is a bare comparator input with no internal pull ("Do not leave floating" per its datasheet) — with `Q_PWR1` off, EN was held by MOSFET/input leakage alone, no guaranteed logic level. `R_PWR_EN_PD1` (100kΩ to GND) fixes it: negligible loading when `Q_PWR1`'s tens-of-mΩ RDS(on) is driving EN high, solid GND the instant it's off. |
| **H** | **`PWR_BTN_SENSE` must never sit above the MCU's rail.** It lands directly on GPIO2 (pin 38), whose ceiling is VDD + 0.3V (~3.6V), but `R_BTN_PU1` used to pull it to VSYS — up to ~4.5V on USB, and it back-fed the unpowered 3V3 rail through the pin's clamp diode whenever the MCU was off. Since `R_INV_BASE1` gave the inverter its own VSYS pull-up (callout F), `R_BTN_PU1` no longer has to supply the inverter, so it now pulls to 3V3: idle reads 3.3V, and with the MCU off the node is simply unpowered instead of leaking into the rail. |
| **I** | **Battery insertion used to blip `PWR_EN` for ~2ms — found in simulation, since no board existed to bench-test.** When VSYS first appears, `Q_INV1` isn't conducting yet: its base current is diverted through `D_INV_ISO1` into the discharged `C_DBNC1` until that charges to ~0.4V. For that ~1–2ms `PWR_BTN_TRIGGER` follows VSYS, `PWR_LATCH_G` rises to ~VSYS−0.6V, and `Q_PWR1` briefly enables the buck. `C_LATCH_G1` (100nF against the 100kΩ pull-up) makes the gate rise slowly enough that the transient peaks at ~0.3V — well under the AO3400A's minimum threshold — at the cost of 4–8ms of press latency, and it gives release a little extra hold time. Keep it right at `PWR_LATCH_G`. |

## Routing priority

Expand All @@ -62,6 +64,7 @@ The button's position is fixed by the enclosure before you start — everything
5. Keep R_INV_BASE1, D_INV_ISO1, Q_INV1, and R_INV_PU1 together as their own small block, close to D_OR1 — they're a single-purpose inverter, not independent parts.
6. Bring PWR_BTN_SENSE and PWR_HOLD in from wherever the MCU sheet actually sits — these are off-sheet nets, not local components.
7. Drop `R_PWR_EN_PD1` right at Q_PWR1's drain, on the way to `PWR_EN` leaving the sheet — it's a bias resistor for that pin, not an independent part.
8. Put `C_LATCH_G1` on the `PWR_LATCH_G` node next to `R_LATCH_G1` and `Q_LATCH1`'s gate, with a short GND return.

> **Checked, not just assumed:** DMG2305UX is rated for ~4A continuous with 35–50mΩ RDS(on) in this SOT-23 package — comfortably oversized for a handheld controller's sub-2A system budget, so this isn't a thermal risk the way the charger's WSON is. MMBT3904 is a general-purpose small-signal part switching microamps here (just enough to bias a diode) — nowhere near its ratings, no thermal consideration needed. BAT54W's job is purely to isolate two nodes at sub-1V forward drop, also nowhere near its ratings. Connectivity alone (ERC, netlist, DRC) doesn't catch bad DC bias points — the `R_LATCH_G1`/`PWR_BTN_SENSE` loading bugs in callouts D and F, and the floating `PWR_EN` in callout G, all passed every connectivity check and were only found by actually working through the bias/divider math or reading the driven IC's own datasheet, so don't treat a clean ERC/DRC as proof a gate-drive, sense, or enable network will behave.

Expand Down
12 changes: 12 additions & 0 deletions PCB/diagrams/power_control_layout.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
145 changes: 145 additions & 0 deletions PCB/power_control.kicad_sch
Original file line number Diff line number Diff line change
Expand Up @@ -3204,6 +3204,151 @@
)
)
)
(global_label "GND"
(shape passive)
(at 115.57 72.39 90)
(fields_autoplaced yes)
(effects
(font
(size 1.27 1.27)
)
(justify left)
)
(uuid "55f99604-9512-46a1-a9e2-2115e48ce76a")
(property "Intersheetrefs" "${INTERSHEET_REFS}"
(at 115.57 72.39 0)
(hide yes)
(show_name no)
(do_not_autoplace no)
(effects
(font
(size 1.27 1.27)
)
)
)
)
(label "PWR_LATCH_G"
(at 115.57 80.01 270)
(effects
(font
(size 1.27 1.27)
)
(justify right)
)
(uuid "dd2dda05-27ed-472e-839c-3bb9502a566d")
)
(symbol
(lib_id "Device:C")
(at 115.57 76.2 0)
(unit 1)
(body_style 1)
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(in_pos_files yes)
(dnp no)
(fields_autoplaced yes)
(uuid "526a9667-117d-48b5-85bd-c2e58526ea56")
(property "Reference" "C_LATCH_G1"
(at 116.205 78.74 0)
(show_name no)
(do_not_autoplace no)
(effects
(font
(size 1.27 1.27)
)
(justify left)
)
)
(property "Value" "100nF"
(at 116.205 73.66 0)
(show_name no)
(do_not_autoplace no)
(effects
(font
(size 1.27 1.27)
)
(justify left)
)
)
(property "Footprint" "Capacitor_SMD:C_0402_1005Metric"
(at 116.5352 72.39 0)
(show_name no)
(do_not_autoplace no)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Datasheet" ""
(at 115.57 76.2 0)
(show_name no)
(do_not_autoplace no)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Description" "Unpolarized capacitor"
(at 115.57 76.2 0)
(show_name no)
(do_not_autoplace no)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "MF" "Samsung Electro-Mechanics"
(at 0 0 0)
(hide yes)
(show_name no)
(do_not_autoplace no)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Manufacturer_Part_Number" "CL05B104KO5NNNC"
(at 0 0 0)
(hide yes)
(show_name no)
(do_not_autoplace no)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "LCSC" "C1525"
(at 0 0 0)
(hide yes)
(show_name no)
(do_not_autoplace no)
(effects
(font
(size 1.27 1.27)
)
)
)
(pin "1"
(uuid "809a995a-f135-461e-8f70-899f7c158157")
)
(pin "2"
(uuid "c3b11b9a-8585-42ac-a4c0-449d3e07d234")
)
(instances
(project "snips_controller"
(path "/21935953-32a0-46ad-8079-8177920c6978/081a4841-1e21-4908-a8a8-37f54f8809f6"
(reference "C_LATCH_G1")
(unit 1)
)
)
)
)
(symbol
(lib_id "Device:D_Small")
(at 63.5 76.2 0)
Expand Down
2 changes: 1 addition & 1 deletion PCB/production/bom/snips_controller-bom.csv
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"Id";"Designator";"Footprint";"Quantity";"Designation";"Supplier and ref";
1;"BT1";"JST_PH_S2B-PH-K_1x02_P2.00mm_Horizontal";1;"JST-PH_2Pin_Battery_RightAngle_Horizontal";;;
2;"C_3V3_1, C_XBEE_VCC4";"C_0603_1608Metric";2;"10µF";;;
3;"C_3V3_2, C_DBNC1, C_HALL1, C_OLED_2, C_RGB1, C_STICK1, C_SYS_2, C_VIN_2, C_XBEE_RC1, C_XBEE_VCC1";"C_0402_1005Metric";10;"100nF";;;
3;"C_3V3_2, C_DBNC1, C_HALL1, C_LATCH_G1, C_OLED_2, C_RGB1, C_STICK1, C_SYS_2, C_VIN_2, C_XBEE_RC1, C_XBEE_VCC1";"C_0402_1005Metric";11;"100nF";;;
4;"C_3V3_3";"C_0402_1005Metric";1;"39pF";;;
5;"C_ACCEL_1";"C_0402_1005Metric";1;"10nF";;;
6;"C_ACCEL_2";"C_0402_1005Metric";1;"100pF";;;
Expand Down
Loading
Loading