Skip to content

Commit 2f3e99b

Browse files
authored
Leave the hub's PSELF and RESET#/CDP pins open (#13)
Both pins are strap inputs sampled at power-up, and both were being driven to the wrong state. Adafruit's CH334F breakout (product 5997) leaves each one open; matching it fixes a bug that killed every restore through the dongle. PSELF (18) was tied to GND, so the hub advertised bus-powered and its downstream ports could only promise one unit load, 100 mA. Every Apple restore-stage device asks for 500 mA: DFU 0x1227 unconfigured, but works -- DFU is endpoint-0 control transfers only, which are legal in Address state Recovery 0x1281 configures MacBook Pro 0x12ac kUSBFailedRequestedPower = 500, restore dies here The last stage is the first that needs interfaces, and interfaces only exist inside a configuration. Left open the pin floats high on its internal pull-up, which is self-powered -- 500 mA per port, exactly the ask. The board still draws from the host's VBUS; this is a declaration, not a supply, and nothing draws the current either way (the target runs on its own battery and there is no per-port VBUS switching). RESET#/CDP (16) was held high by R5. That pin is also sampled at power-up: high enables CDP, the downstream charging-port advertisement, and disables the hub's low-power sleep. The datasheet wants it "completely suspended when not reset". R5 is removed; the pin keeps its ~25k internal pull-up. Fab package regenerated: 91 vias (was 93), 56 placements (was 57), 10k quantity down to 2. DRC 0 violations, 0 unconnected.
1 parent 05bc061 commit 2f3e99b

48 files changed

Lines changed: 584751 additions & 586148 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/dongle/PRD.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ below.
3030
- Power the target from an external USB-C PD charger, up to 100 W (20 V / 5 A).
3131
- Source to the target whatever PD profile it asks for (5 V, 9 V, 15 V, 20 V,
3232
and ideally PPS ranges), subject to what the attached charger can supply.
33-
- Keep the dongle's own logic bus-powered from the host by default. The external
33+
- Keep the dongle's own logic bus-powered from the host by default. This is
34+
about where power comes from, not what the USB hub declares — the hub's
35+
PSELF pin is left open so it advertises self-powered, which is what allows
36+
500 mA per downstream port (an Apple restore needs it). The external
3437
charger powers the target, not the dongle logic.
3538
- Keep the power subsystem optional to populate: an unpopulated board is
3639
electrically a Dongle Lite and must pass all Lite requirements.

hardware/dongle-lite/PRD.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ target's CC line. This mechanism is public and proven by the AsahiLinux
3030
on macOS, Windows, and Linux.
3131
- Present the target to the host as a normal DFU USB device so restorekit /
3232
Apple Configurator can restore it over USB 2.0.
33-
- Be bus-powered from the host port. No wall power required.
33+
- Be bus-powered from the host port. No wall power required. Note this is
34+
about where the board draws power, not what the hub *declares*: U3's PSELF
35+
pin is left open so the hub advertises self-powered, because that is what
36+
lets its downstream ports promise the 500 mA an Apple restore asks for.
37+
Strapping PSELF to GND to "match" being bus-powered breaks restores.
3438
- Expose the target's low-level serial console (AP/SEP UART over SBU) to the
3539
host as a second serial device, for debugging failed restores.
3640
- Be the smallest, cheapest board that does the above, and serve as the shared
@@ -206,7 +210,7 @@ assembly footprint at M1. Prefer JLCPCB Basic parts to cut loading fees.
206210
| Crystal + load caps | 12 MHz 3225 4-pin + 2x 22 pF | verify | RP2040 XOSC |
207211
| RP2040 USB series R | 22R x2 (0402) | Basic | on D+/D- up to the hub |
208212
| Boot button | SMD tactile 4P | Basic | RP2040 BOOTSEL |
209-
| USB 2.0 hub | CH334F (or FE1.1s) | C425949 (verify) | 1 upstream, 2 down |
213+
| USB 2.0 hub | CH334F | C5187527 | 1 upstream, 2 down (C425949 in an earlier draft was wrong — see BOM-sourcing.md) |
210214
| USB-C receptacle (Host + Target) | HRO TYPE-C-31-M-12 | C165948 | 16-pin, breaks out SBU + CC + D+/D- |
211215
| SBU level translators | 74AVC1T45GW | C282330 | 2x (1 per SBU line), per CS v3 |
212216
| 1.2 V rail | TLV70212 (1.2 V LDO) | C81462 | per CS v3.1 (divider is the older alt) |
@@ -253,6 +257,10 @@ assemble at JLCPCB. This is the highest-confidence source for the SBU block.
253257
| R1,R2,R5,R6 | 4.7k 0402 | C25900 | pullups |
254258
| R3, R4 | 10k 0402 | C25744 | |
255259

260+
> **Superseded.** The table above is the early estimate, and its reference
261+
> designators do not match the built design. The authoritative BOM and
262+
> placement list are `mfg-jlcpcb-1s4l/bom.csv` and `cpl.csv`.
263+
256264
Open BOM decisions:
257265

258266
- The SBU block is now resolved to CS v3.1 parts (all JLCPCB-verified above);

hardware/dongle-lite/README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ kicad-cli sch erc dongle-lite.kicad_sch
6767
|-----|------|------|----------|
6868
| U1 | RP2354A | C41378174 | MCU (USB-native composite; 2 MB internal flash) |
6969
| U2 | FUSB302BMPX | C132291 | USB-PD PHY, drives target CC (Apple DFU VDM) |
70-
| U3 | CH334F | C5187527 | USB 2.0 hub — host sees MCU + target |
70+
| U3 | CH334F | C5187527 | USB 2.0 hub — host sees MCU + target. `PSELF` (18) and `RESET#/CDP` (16) are left **open** — see below |
7171
| U5 | RT9013-33GB | C47773 | 3.3 V LDO from host 5 V |
7272
| U6 | TLV70212 | C81462 | 1.2 V LDO for the SBU translator low side |
7373
| U8, U9 | 74AVC1T45GW | C282330 | SBU1/SBU2 level translators (3.3 ↔ 1.2 V) |
@@ -78,6 +78,24 @@ kicad-cli sch erc dongle-lite.kicad_sch
7878

7979
Full BOM/CPL for ordering are in the `mfg-jlcpcb*/` packages.
8080

81+
### Hub strap pins
82+
83+
`PSELF` (18) and `RESET#/CDP` (16) on U3 are both left open, matching Adafruit's
84+
CH334F breakout (product 5997).
85+
86+
`PSELF` has a built-in pull-up and defaults high = **self-powered**, which is
87+
what makes the hub advertise 500 mA per downstream port. Tied to GND it
88+
advertises bus-powered and caps every port at one unit load, 100 mA. A target
89+
Mac in DFU fits under that, but every later restore stage asks for 500 mA, so
90+
the host refuses to configure the target and the restore dies partway through
91+
with a power error. The board still draws from the host's VBUS — this is a
92+
declaration, not a supply.
93+
94+
`RESET#/CDP` is sampled at power-up too: held high it enables CDP (downstream
95+
charging-port advertisement) and turns off the hub's low-power sleep. The
96+
datasheet wants it "completely suspended when not reset", so there is no
97+
pull-up on it.
98+
8199
## ERC configuration
82100

83101
`pin_not_driven`, `pin_to_pin`, `lib_symbol_issues`, and `lib_symbol_mismatch`

hardware/dongle-lite/case/README.md

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ uv run case.py
1111

1212
writes `output/`:
1313

14-
- `dongle-lite-case.step` — assembly (bottom + lid in place)
14+
- `dongle-lite-case.step` — assembly (bottom + lid + inlay in place)
1515
- `bottom.stl` — tray, prints as-is
1616
- `lid.stl` — already flipped flat-side-down for printing
17+
- `lid-inlay.stl` — the white text inlay, same orientation as `lid.stl`
1718

1819
## Design
1920

@@ -24,16 +25,43 @@ writes `output/`:
2425
- Port bays: the receptacle sits flush with the board edge, 1.8 mm behind
2526
the outer face, so the end wall has to clear the cable's overmold, not
2627
just the plug shell — otherwise the plug stops ~2 mm short of seating.
27-
Each end is open 12.6 x 6.2 mm (flared to 14 mm over the outer 0.8 mm for
28-
a lead-in), which takes an overmold up to about 12 x 5.8 mm. The bay runs
29-
from the cavity floor up, so there's no thin ledge and nothing to bridge.
28+
Each end is open 15.0 x 6.8 mm all the way through, with an 18.6 mm
29+
rounded relief pocket 1.0 mm into the outer face for a chunky overmold to
30+
nose into. That takes an overmold up to about 14.6 x 6.3 mm, or 16 mm
31+
wide at the mouth. The bay is open to the top of the tray, so there's
32+
nothing to bridge.
33+
34+
The top is the binding constraint, not the sides: the board sits on the
35+
floor, which puts the connector centre 4.83 mm off the bottom of an
36+
8.8 mm case, leaving 0.8 mm of lid above the bay and 1.2 mm of floor
37+
below. `PORT_Z1` is what buys taller overmolds, at the cost of that roof.
3038
- Openings: 1.8 mm LED windows over D1 (PWR) and D2 (STAT), 2.4 mm
3139
paperclip hole over the BOOT button.
3240
- Engraving: bold, 0.6 mm deep with flat bottoms, all reading across the
3341
width. HOST / TARGET with arrows at the port ends, wordmark centered in
3442
the clear band above the LEDs, and STAT / PWR / BOOT right-aligned beside
35-
their own holes. The lid prints top-face-down, so a filament swap at
36-
0.6 mm gives two-color inlaid text.
43+
their own holes.
44+
45+
## White text inlay
46+
47+
`lid-inlay.stl` is exactly the volume of the engraved pockets, exported in
48+
the same orientation and position as `lid.stl` — union the two and you get
49+
a flat-topped lid, with no interference and no gap.
50+
51+
With a multi-material printer (MMU / AMS / toolchanger): load `lid.stl`,
52+
add `lid-inlay.stl` to it as a second *part* of the same object (not a
53+
separate object, so it isn't re-centred), assign white to the inlay, print
54+
the lid top-face-down. The text comes out flush and white.
55+
56+
Single extruder, no MMU: print `lid-inlay.stl` on its own in white, 0.6 mm
57+
of flat letters, and glue them in. Fiddly on the small labels.
58+
59+
What does *not* work is a plain filament swap. The lid prints top-face-down
60+
and the text is recessed, so the letters are voids in the first three
61+
layers: swapping at 0.6 mm colours the top face and leaves the letters in
62+
the body colour — the inverse of what you want. Getting white letters out
63+
of one extruder would mean embossing the text proud of the lid instead of
64+
engraving it, which trades the flush finish for something that scuffs.
3765
- Outer size: 26.2 x 81.2 x 8.8 mm, 1.8 mm walls.
3866

3967
The lid graphics are built in absolute coordinates and cut in a single

hardware/dongle-lite/case/case.py

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,19 @@
4545
# outer face, so a plug can only seat if the end wall is fully open in front
4646
# of it -- a plain shell-sized notch would leave the cable's overmold hitting
4747
# the case ~2 mm short. The bay is sized for the overmold instead of the
48-
# shell and runs from the cavity floor up, so there is no thin ledge to print.
49-
PORT_W = 12.6
50-
PORT_Z0 = 0.0 # flush with the cavity floor
51-
PORT_Z1 = 6.2 # leaves 1.0 mm of lid above the bay
52-
PORT_FLARE = 0.7 # sideways lead-in over the outer 0.8 mm
53-
PORT_FLARE_D = 0.8
48+
# shell, and a shallow recess around it in the outer face gives a chunky
49+
# overmold somewhere to nose into.
50+
#
51+
# The top is the binding constraint, not the sides: the board sits on the
52+
# floor, so the connector centre is 4.83 mm off the bottom of an 8.8 mm case
53+
# and there is only ~1 mm of lid left above the bay. Raising PORT_Z1 further
54+
# is what buys taller overmolds, at the cost of that roof.
55+
PORT_W = 15.0
56+
PORT_Z0 = -0.4 # 0.4 below the cavity floor, leaving 1.2 mm of tray floor
57+
PORT_Z1 = 6.4 # leaves 0.8 mm of lid above the bay
58+
PORT_RECESS_W = 18.6 # relief pocket in the outer face, around the bay
59+
PORT_RECESS_D = 1.0 # of the 1.8 mm wall
60+
PORT_RECESS_R = 1.2
5461

5562
LIP_T = 1.2
5663
LIP_H = 1.6
@@ -78,30 +85,25 @@ def outer_profile(height, z0):
7885

7986

8087
def port_cutter(sign):
81-
"""Plug bay through one end wall, flared for a lead-in on the outer face.
88+
"""Plug bay through one end wall, plus its relief pocket.
8289
8390
Built with the plug axis along +Z (z = 0 at the outer face, +Z inward)
84-
then rotated onto +/-Y, so the loft never has to be built on a tilted
85-
workplane.
91+
then rotated onto +/-Y, so the rounded pocket never has to be filleted on
92+
a tilted workplane -- that is what crashes OCC's cleanup.
8693
"""
8794
h = PORT_Z1 - PORT_Z0
88-
bay = (
89-
cq.Workplane("XY")
90-
.box(PORT_W, h, 3.0, centered=(True, True, False))
91-
.union(
92-
cq.Workplane("XY")
93-
.rect(PORT_W + 2 * PORT_FLARE, h)
94-
.workplane(offset=PORT_FLARE_D)
95-
.rect(PORT_W, h)
96-
.loft()
97-
)
98-
.union(
99-
cq.Workplane("XY", origin=(0, 0, -1.0))
100-
.box(PORT_W + 2 * PORT_FLARE, h, 1.0, centered=(True, True, False))
101-
)
95+
bay = cq.Workplane("XY", origin=(0, 0, -1.5)).box(
96+
PORT_W, h, 4.5, centered=(True, True, False)
97+
)
98+
recess = (
99+
cq.Workplane("XY", origin=(0, 0, -1.5))
100+
.box(PORT_RECESS_W, h, 1.5 + PORT_RECESS_D, centered=(True, True, False))
101+
.edges("|Z")
102+
.fillet(PORT_RECESS_R)
102103
)
103104
return (
104-
bay.val()
105+
bay.union(recess, clean=False)
106+
.val()
105107
.rotate((0, 0, 0), (1, 0, 0), sign * 90)
106108
.translate((0, sign * OUT_L / 2, (PORT_Z0 + PORT_Z1) / 2))
107109
)
@@ -208,7 +210,10 @@ def thru_hole(x, y, d):
208210
)
209211

210212

211-
marks = engrave("restorekit", 0, WORDMARK_Y, WORDMARK_SIZE)
213+
# The graphics are kept separate from the holes: they are both the pockets
214+
# cut into the lid and, exported on their own, the white inlay that drops
215+
# back into them.
216+
inlay = engrave("restorekit", 0, WORDMARK_Y, WORDMARK_SIZE)
212217
for m in (
213218
engrave("HOST", 0, PORT_LABEL_Y, PORT_LABEL_SIZE),
214219
engrave("TARGET", 0, -PORT_LABEL_Y, PORT_LABEL_SIZE),
@@ -217,11 +222,16 @@ def thru_hole(x, y, d):
217222
engrave("STAT", PIN_LABEL_RIGHT, LED2[1], PIN_LABEL_SIZE, halign="right"),
218223
engrave("PWR", PIN_LABEL_RIGHT, LED1[1], PIN_LABEL_SIZE, halign="right"),
219224
engrave("BOOT", PIN_LABEL_RIGHT, SW1[1], PIN_LABEL_SIZE, halign="right"),
225+
):
226+
inlay = inlay.union(m)
227+
228+
marks = inlay
229+
for h in (
220230
thru_hole(*LED1, 1.8), # PWR window
221231
thru_hole(*LED2, 1.8), # STAT window
222232
thru_hole(*SW1, 2.4), # BOOT, paperclip
223233
):
224-
marks = marks.union(m)
234+
marks = marks.union(h)
225235
lid = lid.cut(marks, clean=False)
226236

227237
# --- export ---
@@ -232,13 +242,17 @@ def thru_hole(x, y, d):
232242
cq.Assembly()
233243
.add(bottom, name="bottom", color=cq.Color(0.25, 0.25, 0.28))
234244
.add(lid, name="lid", color=cq.Color(0.85, 0.85, 0.87))
245+
.add(inlay, name="inlay", color=cq.Color(1.0, 1.0, 1.0))
235246
)
236247
assembly.export(os.path.join(out, "dongle-lite-case.step"))
237248

238249
cq.exporters.export(bottom, os.path.join(out, "bottom.stl"))
239-
# flip the lid so its flat top sits on the print bed
240-
lid_print = lid.rotate((0, 0, 0), (0, 1, 0), 180)
241-
cq.exporters.export(lid_print, os.path.join(out, "lid.stl"))
250+
# flip the lid so its flat top sits on the print bed. The inlay gets the same
251+
# transform, so loading it as a second part lands it in its pockets with no
252+
# repositioning.
253+
flip = lambda w: w.rotate((0, 0, 0), (0, 1, 0), 180)
254+
cq.exporters.export(flip(lid), os.path.join(out, "lid.stl"))
255+
cq.exporters.export(flip(inlay), os.path.join(out, "lid-inlay.stl"))
242256

243257
print("wrote", out)
244258
print(f"outer: {OUT_W} x {OUT_L} x {OUT_H} mm")

0 commit comments

Comments
 (0)