krishnax12/tensa-zangetsu-keychain-nfc
Summary: Calculates target inductance and estimated resonance for a 14.5 MHz square antenna using chip, interturn, bridge, and connection capacitances.
- Version
- 0.0.13
- License
- unset
- Stars
- 1
scripts/check-antenna-clearance.mjs
import assert from "node:assert/strict"
function pointSegmentDistance({ point, start, end }) {
const dx = end.x - start.x
const dy = end.y - start.y
const lengthSquared = dx * dx + dy * dy
const fraction = lengthSquared === 0 ? 0 : Math.max(0, Math.min(1,
((point.x - start.x) * dx + (point.y - start.y) * dy) / lengthSquared,
))
return Math.hypot(point.x - start.x - fraction * dx, point.y - start.y - fraction * dy)
}
function cross({ start, end, point }) {
return (end.x - start.x) * (point.y - start.y) - (end.y - start.y) * (point.x - start.x)
}
function segmentDistance({ start, end, coilStart, coilEnd }) {
if (
cross({ start, end, point: coilStart }) * cross({ start, end, point: coilEnd }) < 0 &&
cross({ start: coilStart, end: coilEnd, point: start }) * cross({ start: coilStart, end: coilEnd, point: end }) < 0
) return 0
return Math.min(
pointSegmentDistance({ point: start, start: coilStart, end: coilEnd }),
pointSegmentDistance({ point: end, start: coilStart, end: coilEnd }),
pointSegmentDistance({ point: coilStart, start, end }),
pointSegmentDistance({ point: coilEnd, start, end }),
)
}
export function checkAntennaClearance(circuitJson) {
const traces = circuitJson.filter((element) => element.type === "pcb_trace")
const coil = traces.find((trace) => !trace.source_trace_id)
assert(coil, "Missing printed antenna")
let minimumGap = Infinity
for (const trace of traces.filter((element) => element.source_trace_id)) {
const first = trace.route[0]
const last = trace.route.at(-1)
const coilStart = coil.route[0]
const coilEnd = coil.route.at(-1)
const connectsOuter = [first, last].some((point) => Math.hypot(point.x - coilStart.x, point.y - coilStart.y) < 1e-5)
const connectsInner = [first, last].some((point) => Math.hypot(point.x - coilEnd.x, point.y - coilEnd.y) < 1e-5)
assert(connectsOuter || connectsInner, "RF trace does not terminate at the coil")
for (let coilIndex = 0; coilIndex < coil.route.length - 1; coilIndex++) {
// Only the terminal escape is allowed to join its corresponding RF trace.
// The winding segments remain obstacles even though they share a net.
if (connectsOuter && coilIndex === 0) continue
if (connectsInner && coilIndex === coil.route.length - 2) continue
const coilStart = coil.route[coilIndex]
const coilEnd = coil.route[coilIndex + 1]
for (let index = 0; index < trace.route.length; index++) {
const start = trace.route[index]
const end = trace.route[index + 1]
let gap
if (start.route_type === "via" && [start.from_layer, start.to_layer].includes(coilStart.layer)) {
gap = pointSegmentDistance({ point: start, start: coilStart, end: coilEnd }) - (start.via_diameter + coilStart.width) / 2
} else if (start.route_type === "wire" && end?.route_type === "wire" && start.layer === coilStart.layer && end.layer === coilStart.layer) {
gap = segmentDistance({ start, end, coilStart, coilEnd }) - (start.width + coilStart.width) / 2
} else continue
minimumGap = Math.min(minimumGap, gap)
assert(gap >= 0.15 - 1e-5, `${trace.source_trace_id} touches or approaches winding ${coilIndex}: gap ${gap.toFixed(6)} mm`)
}
}
}
assert(Number.isFinite(minimumGap))
return { minimum_rf_trace_to_winding_gap_mm: minimumGap }
}