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

verification/pair-measurements.ts

PCBPCB preview for verification/pair-measurements.ts
SchematicSchematic preview for verification/pair-measurements.ts
type Point = { x: number; y: number }
type Segment = { start: Point; end: Point; layer: string; length: number }
export type TraceForAudit = { connection_name?: string; route: Array<{ route_type: string; x: number; y: number; layer?: string }> }

export function getWireSegments(trace: TraceForAudit): Segment[] {
  return trace.route.slice(1).flatMap((end, index) => {
    const start = trace.route[index]
    return start.route_type === "wire" && end.route_type === "wire" && start.layer && start.layer === end.layer
      ? [{ start, end, layer: start.layer, length: Math.hypot(end.x - start.x, end.y - start.y) }]
      : []
  })
}

function distanceToSegment(point: Point, segment: Segment) {
  const dx = segment.end.x - segment.start.x
  const dy = segment.end.y - segment.start.y
  const fraction = segment.length === 0 ? 0 : Math.max(0, Math.min(1, ((point.x - segment.start.x) * dx + (point.y - segment.start.y) * dy) / segment.length ** 2))
  return Math.hypot(point.x - segment.start.x - fraction * dx, point.y - segment.start.y - fraction * dy)
}

export function inspectPairSeparation({ trace, pairedTrace }: { trace: TraceForAudit; pairedTrace: TraceForAudit }) {
  const segments = getWireSegments(trace)
  const pairedSegments = getWireSegments(pairedTrace)
  let continuousUncoupledLengthMm = 0
  let longestProvenUncoupledLengthMm = 0
  let continuousPotentialUncoupledLengthMm = 0
  let longestPotentialUncoupledLengthMm = 0
  for (const segment of segments) {
    const sampleCount = Math.max(1, Math.ceil(segment.length / 0.02))
    const sampleLengthMm = segment.length / sampleCount
    for (let index = 0; index < sampleCount; index++) {
      const fraction = (index + 0.5) / sampleCount
      const midpoint = { x: segment.start.x + fraction * (segment.end.x - segment.start.x), y: segment.start.y + fraction * (segment.end.y - segment.start.y) }
      const nearestDistanceMm = Math.min(...pairedSegments.filter((pairedSegment) => pairedSegment.layer === segment.layer).map((pairedSegment) => distanceToSegment(midpoint, pairedSegment)))
      if (nearestDistanceMm + sampleLengthMm / 2 > 0.336) {
        continuousPotentialUncoupledLengthMm += sampleLengthMm
        longestPotentialUncoupledLengthMm = Math.max(longestPotentialUncoupledLengthMm, continuousPotentialUncoupledLengthMm)
      } else {
        continuousPotentialUncoupledLengthMm = 0
      }
      if (nearestDistanceMm - sampleLengthMm / 2 > 0.336) {
        continuousUncoupledLengthMm += sampleLengthMm
        longestProvenUncoupledLengthMm = Math.max(longestProvenUncoupledLengthMm, continuousUncoupledLengthMm)
      } else {
        continuousUncoupledLengthMm = 0
      }
    }
  }
  return { connection_name: trace.connection_name, copper_length_mm: segments.reduce((sum, segment) => sum + segment.length, 0), vias: trace.route.filter((point) => point.route_type === "via").length, longest_proven_uncoupled_length_mm: longestProvenUncoupledLengthMm, longest_potential_uncoupled_length_mm: longestPotentialUncoupledLengthMm }
}