gokul/usb3-sd-hub
Engineering draft: four-layer, computer-powered USB 3 hub with one USB-C host socket, two USB-C and two USB-A accessory sockets, and a full-size SD slot. Board and three schematic previews are available. Routing and production checks are incomplete; not fabrication ready.
- Version
- 0.1.5
- License
- MIT
- Stars
- 0
design/guided-usb-routes.ts
import type { FanoutTracePath } from "@tscircuit/props";
type Point = { x: number; y: number };
type NumericPath = Omit<FanoutTracePath, "route"> & {
route: Array<FanoutTracePath["route"][number] & Point>;
};
const traceWidthMm = 0.136;
const halfCenterGapMm = (traceWidthMm + 0.15) / 2;
const cornerExtensionMm = halfCenterGapMm * (Math.SQRT2 - 1);
const spineLengthDifferenceMm = 16 * halfCenterGapMm * (Math.SQRT2 - 1);
const meanderCornerMm = 0.1;
const meanderDepthMm =
(spineLengthDifferenceMm + 4 * meanderCornerMm * (2 - Math.SQRT2)) / 2;
function wire(
point: Point,
layer: "top" | "bottom" = "bottom",
): NumericPath["route"][number] {
return { route_type: "wire", ...point, width: traceWidthMm, layer };
}
function via(
point: Point,
fromLayer: "top" | "bottom",
): NumericPath["route"][number] {
return {
route_type: "via",
...point,
from_layer: fromLayer,
to_layer: fromLayer === "top" ? "bottom" : "top",
via_diameter: 0.45,
via_hole_diameter: 0.2,
};
}
function upstreamTransmitPath(positive: boolean): NumericPath {
const polarity = positive ? 1 : -1;
const launch = { x: positive ? -2.9 : -3.9, y: 13.51 };
const landing = { x: positive ? -24.95 : -24.25, y: 8.9 };
const muxPad = { x: positive ? -24.800102 : -24.400052, y: 9.800104 };
const traceY = 15.2 + polarity * halfCenterGapMm;
const meander: Point[] = positive
? []
: [
{ x: -9, y: traceY },
{ x: -9.1, y: traceY - meanderCornerMm },
{ x: -9.1, y: traceY - meanderDepthMm + meanderCornerMm },
{ x: -9.2, y: traceY - meanderDepthMm },
{ x: -9.5, y: traceY - meanderDepthMm },
{ x: -9.6, y: traceY - meanderDepthMm + meanderCornerMm },
{ x: -9.6, y: traceY - meanderCornerMm },
{ x: -9.7, y: traceY },
];
const spine: Point[] = [
{ x: launch.x, y: 13.643 },
{ x: -3.4 + polarity * halfCenterGapMm, y: 14 },
{
x: -3.4 + polarity * halfCenterGapMm,
y: 14.2 + polarity * cornerExtensionMm,
},
{ x: -4.4 + polarity * cornerExtensionMm, y: traceY },
...meander,
{ x: -22.6 - polarity * cornerExtensionMm, y: traceY },
{
x: -23.6 - polarity * halfCenterGapMm,
y: 14.2 + polarity * cornerExtensionMm,
},
{
x: -23.6 - polarity * halfCenterGapMm,
y: 10.8 + polarity * cornerExtensionMm,
},
{
x: -24.6 - polarity * halfCenterGapMm,
y: 9.8 + polarity * cornerExtensionMm,
},
{ x: -24.6 - polarity * halfCenterGapMm, y: 9.3 },
{ x: -24.6 - polarity * halfCenterGapMm, y: 9.107 },
];
const topCornerY = 9.35 - Math.abs(muxPad.x - landing.x);
return {
connection: positive ? "C_UP_TXP.pin2" : "C_UP_TXN.pin2",
route: [
wire(launch, "top"),
via(launch, "top"),
wire(launch),
...spine.map((point) => wire(point)),
wire(landing),
via(landing, "bottom"),
wire(landing, "top"),
wire({ x: landing.x, y: topCornerY }, "top"),
wire({ x: muxPad.x, y: 9.35 }, "top"),
wire(muxPad, "top"),
],
};
}
export const upstreamTransmitPaths: FanoutTracePath[] = [
upstreamTransmitPath(true),
upstreamTransmitPath(false),
];
function upstreamReceivePath(positive: boolean): NumericPath {
const polarity = positive ? 1 : -1;
const launch = { x: positive ? -4.2 : -4.9, y: 11.8 };
const landing = { x: positive ? -23.45 : -22.75, y: 8.5 };
const hubPad = { x: positive ? -4.4 : -4.8, y: 10.85 };
const muxPad = {
x: positive ? -23.599952 : -23.1999528,
y: positive ? 9.800104 : 9.7999008,
};
const stemX = -4.6 + polarity * halfCenterGapMm;
const endX = -22.6 - polarity * halfCenterGapMm;
const horizontalY = 13.3 + polarity * halfCenterGapMm;
return {
connection: positive ? "U_HUB.UP_RXP" : "U_HUB.UP_RXN",
route: [
wire(hubPad, "top"),
wire({ x: hubPad.x, y: launch.y - Math.abs(launch.x - hubPad.x) }, "top"),
wire(launch, "top"),
via(launch, "top"),
wire(launch),
wire({ x: stemX, y: launch.y + Math.abs(stemX - launch.x) }),
wire({ x: stemX, y: 12.3 + polarity * cornerExtensionMm }),
wire({ x: -5.6 + polarity * cornerExtensionMm, y: horizontalY }),
wire({ x: -21.6 - polarity * cornerExtensionMm, y: horizontalY }),
wire({ x: endX, y: 12.3 + polarity * cornerExtensionMm }),
wire({ x: endX, y: landing.y + Math.abs(landing.x - endX) }),
wire(landing),
via(landing, "bottom"),
wire(landing, "top"),
wire({ x: landing.x, y: 9.35 - Math.abs(muxPad.x - landing.x) }, "top"),
wire({ x: muxPad.x, y: 9.35 }, "top"),
wire(muxPad, "top"),
],
};
}
// Add length only to the shorter member, on a clear horizontal region.
function matchReceiveLengths(paths: NumericPath[]): NumericPath[] {
const lengths = paths.map((path) =>
path.route.slice(1).reduce((sum, end, index) => {
const start = path.route[index];
return (
sum +
(start.route_type === "wire" &&
end.route_type === "wire" &&
start.layer === end.layer
? Math.hypot(end.x - start.x, end.y - start.y)
: 0)
);
}, 0),
);
const shorterIndex = lengths[0] < lengths[1] ? 0 : 1;
const differenceMm = Math.abs(lengths[0] - lengths[1]);
const cornerMm = 0.1;
const depthMm = (differenceMm + 4 * cornerMm * (2 - Math.SQRT2)) / 2;
return paths.map((path, index) => {
if (index !== shorterIndex) return path;
const direction = index === 0 ? 1 : -1;
const routeIndex = path.route.findIndex(
(point) =>
point.route_type === "wire" &&
point.layer === "bottom" &&
point.x < -20 &&
point.y > 13,
);
const y = path.route[routeIndex].y;
return {
...path,
route: [
...path.route.slice(0, routeIndex),
wire({ x: -12, y }),
wire({ x: -12.1, y: y + direction * cornerMm }),
wire({ x: -12.1, y: y + direction * (depthMm - cornerMm) }),
wire({ x: -12.2, y: y + direction * depthMm }),
wire({ x: -12.5, y: y + direction * depthMm }),
wire({ x: -12.6, y: y + direction * (depthMm - cornerMm) }),
wire({ x: -12.6, y: y + direction * cornerMm }),
wire({ x: -12.7, y }),
...path.route.slice(routeIndex),
],
};
});
}
export const upstreamReceivePaths = matchReceiveLengths([
upstreamReceivePath(true),
upstreamReceivePath(false),
]);