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),
]);