rushabhcodes/ESP32-E-Reader

A compact ESP32-C3 e-reader PCB integrating LiPo charging/protection, USB-C, 3.3 V regulation, e-paper display bias generation, microSD storage, and tactile controls.

Version
1.0.85
License
unset
Stars
3

index.circuit.tsx

PCBPCB preview for index.circuit.tsx
SchematicSchematic preview for index.circuit.tsx
/**
 * ESP32 E-Reader Rev. B
 *
 * Board-level composition. Electrical data and exact PCB placements live in
 * design-data.ts; functional blocks live in circuit-sections.tsx.
 */

import { Fragment } from "react";
import { assembly } from "tscircuit";
import { SparkFun_PRT_13855 } from "./imports/SparkFun_PRT_13855";
import antennaEnvelopeUrl from "./assets/enclosure/antenna-envelope-DO-NOT-PRINT.glb";
import batteryLinerUrl from "./assets/enclosure/battery-liner-DO-NOT-PRINT.glb";
import batteryAdhesiveUrl from "./assets/enclosure/battery-adhesive-DO-NOT-PRINT.glb";
import { Waveshare_3_97inch_ePaper_G } from "./imports/Waveshare_3_97inch_ePaper_G";
import displayConnection from "./assets/enclosure/display-connection.json";
import frontChassisUrl from "./assets/enclosure/front-chassis.glb";
import displayCushioningUrl from "./assets/enclosure/display-cushioning.glb";
import pcbCoverFastenersUrl from "./assets/enclosure/pcb-cover-fasteners.glb";
import rearCoverUrl from "./assets/enclosure/rear-cover.glb";
import pcbMounts from "./assets/enclosure/pcb-mounts.json";
import { CircuitSections } from "./circuit-sections";
import { nets, traceThicknessByNet } from "./design-data";
import { createPreExpansionAutorouter } from "./pre-expansion-autorouter";

// The compact tactile switches fit inside the display-width lower section.
const boardOutline = [
	{ x: -26.25, y: 48.08 },
	{ x: 26.25, y: 48.08 },
	{ x: 29.79, y: 46.62 },
	{ x: 31.25, y: 43.08 },
	{ x: 31.25, y: -44 },
	{ x: 30.37, y: -46.12 },
	{ x: 28.25, y: -47 },
	{ x: -28.25, y: -47 },
	{ x: -30.37, y: -46.12 },
	{ x: -31.25, y: -44 },
	{ x: -31.25, y: 43.08 },
	{ x: -29.79, y: 46.62 },
];

export function EReaderBoard() {
	return (
		<board
			title="ESP32 E-Reader Rev. B"
			width="62.5mm"
			height="95.08mm"
			outline={boardOutline}
			doubleSidedAssembly
			layers={4}
			thickness="1.6mm"
			minViaHoleDiameter="0.3mm"
			minViaPadDiameter="0.6mm"
			minViaEdgeToPadEdgeClearance="0.25mm"
			// Standard-cost through vias, with a 0.15 mm radial annulus.
			minViaHoleEdgeToViaHoleEdgeClearance="0.2mm"
			minPlatedHoleDrillEdgeToDrillEdgeClearance="0.45mm"
			minTraceToHoleEdgeClearance="0.2mm"
			minBoardEdgeClearance="0.3mm"
			minTraceToPadEdgeClearance="0.1mm"
			// Keep nearby schematic branches wired instead of replacing them with labels.
			schMaxTraceDistance={4}
			autorouter={{
				algorithmFn: createPreExpansionAutorouter,
				allowViaInPad: false,
			}}
		>
			{/* Four M2.5 screw points match the enclosure bosses and clamp both ends. */}
			{pcbMounts.mounts.map(({ name, x, y }) => (
				<Fragment key={name}>
					<hole diameter={pcbMounts.holeDiameter} pcbX={x} pcbY={y} />
					<keepout
						shape="circle"
						radius={pcbMounts.copperKeepoutRadius}
						pcbX={x}
						pcbY={y}
						layers={["top", "inner1", "inner2", "bottom"]}
					/>
				</Fragment>
			))}

			{/* Keep GND layer transitions clear of the nearby top-layer SPI clock. */}
			<keepout
				shape="circle"
				radius="0.25mm"
				pcbX={-0.35}
				pcbY={-8.34}
				layers={["inner2"]}
			/>
			{/* The RESE escape must not drill through J2's adjacent pin-3
			    contact. Keep its via clear of the connector pad on buried layers. */}
			<keepout
				shape="circle"
				radius="0.65mm"
				pcbX={displayConnection.connector.centerX + 4.75}
				pcbY={displayConnection.connector.centerY + 1.25}
				layers={["inner1", "inner2", "bottom"]}
			/>

			{/* Fine-pitch contacts must stay free of through-drills, including
			    vias on their own net. Top copper remains available for escapes. */}
			<keepout
				shape="rect"
				width="13mm"
				height="1.4mm"
				pcbX={displayConnection.connector.centerX}
				pcbY={displayConnection.connector.centerY + 1.250061}
				layers={["inner1", "inner2", "bottom"]}
			/>
			{/* Keep layer-transition pads clear of the C28 pin-2 edge. */}
			<keepout
				shape="rect"
				width="1.6mm"
				height="1.225mm"
				pcbX={-5.9}
				pcbY={-11.57}
				layers={["inner1", "inner2", "bottom"]}
			/>
			{/* Through vias span every copper layer, even when a route only
			    changes between buried layers. Protect the USB-C contact row. */}
			<keepout
				shape="rect"
				width="1.65mm"
				height="7.4mm"
				pcbX={23.811}
				pcbY={36.26}
				layers={["inner1", "inner2", "bottom"]}
			/>
			{/* Keep drills out of both contact rows on protection IC U3. */}
			{[-22.100304, -24.398496].map((x) => (
				<Fragment key={`protection-pad-via-clearance-${x}`}>
					<keepout
						shape="rect"
						width="1.6mm"
						height="2.95mm"
						pcbX={x}
						pcbY={36.6244}
						layers={["inner1", "inner2", "bottom"]}
					/>
				</Fragment>
			))}
			{/* C19/C20 ground pads must remain clear of display-signal vias. */}
			<keepout
				shape="rect"
				width="4.05mm"
				height="1.525mm"
				pcbX={3.525}
				pcbY={-9.745}
				layers={["inner1", "inner2", "bottom"]}
			/>

			<CircuitSections />

			{/* Route the display D/C and reset signals as explicit point-to-point
			    connections. The net-level autorouter split these two paths into
			    disconnected copper segments beside J2's fine-pitch contacts. */}
			<trace from={nets.EPD_DC[0]} to={nets.EPD_DC[1]} thickness="0.2mm" schDisplayLabel="EPD_DC" />
			<trace from={nets.EPD_RST[0]} to={nets.EPD_RST[1]} thickness="0.2mm" schDisplayLabel="EPD_RST" />
			<trace from={nets.EPD_RST[1]} to={nets.EPD_RST[2]} thickness="0.2mm" schDisplayLabel="EPD_RST" />
			{Object.entries(nets).filter(([name]) => !["EPD_DC", "EPD_RST"].includes(name)).map(([name, connections]) => (
				<Fragment key={name}>
					<net
						name={name}
						isGroundNet={name === "GND"}
						isPowerNet={["GND", "V3V3", "USB_VBUS", "BATT_P"].includes(name)}
					/>
					{connections.map((connection, index) => (
						<Fragment key={`${name}-${index}`}>
							<trace
								from={connection}
								to={`net.${name}`}
								thickness={traceThicknessByNet[name] ?? "0.2mm"}
							/>
						</Fragment>
					))}
				</Fragment>
			))}

			{/* Four-layer fills provide short returns and a dedicated 3.3 V plane. */}
			<copperpour
				name="GND_FILL_TOP"
				connectsTo="net.GND"
				layer="top"
				clearance="0.2mm"
				boardEdgeMargin="0.3mm"
			/>
			<copperpour
				name="GND_FILL_BOTTOM"
				connectsTo="net.GND"
				layer="bottom"
				clearance="0.2mm"
				boardEdgeMargin="0.3mm"
			/>
			<copperpour
				name="GND_PLANE_INNER1"
				connectsTo="net.GND"
				layer="inner1"
				clearance="0.2mm"
				boardEdgeMargin="0.3mm"
			/>
			<copperpour
				name="V3V3_PLANE_INNER2"
				connectsTo="net.V3V3"
				layer="inner2"
				clearance="0.2mm"
				boardEdgeMargin="0.3mm"
			/>

			<silkscreentext text="BATT" pcbX={27} pcbY={0} fontSize="1mm" />
			<silkscreentext
				text="USB"
				pcbX={27}
				pcbY={31}
				pcbRotation={90}
				fontSize="1mm"
			/>
			<silkscreentext
				text="WAKE"
				pcbX={-28}
				pcbY={20}
				pcbRotation={90}
				fontSize="1mm"
			/>
			<silkscreentext
				text="MAIN PWR"
				pcbX={25}
				pcbY={20}
				pcbRotation={90}
				fontSize="1mm"
			/>
			<silkscreentext
				text="EN"
				pcbX={-28}
				pcbY={-31}
				pcbRotation={90}
				fontSize="1mm"
			/>
			<silkscreentext text="EPD" pcbX={1} pcbY={-23} fontSize="1mm" />
			<silkscreentext text="MICRO SD" pcbX={-22} pcbY={-28} fontSize="1mm" />
			<silkscreentext
				text="ESP32 E-Reader  Rev. B"
				pcbX={13}
				pcbY={-23}
				fontSize="1mm"
			/>
		</board>
	);
}

export default function ESP32EReader() {
	return (
		<assembly.device name="ESP32_E_READER">
			<EReaderBoard />
			<Waveshare_3_97inch_ePaper_G />
			<SparkFun_PRT_13855 />
			<assembly.cadassembly
				name="wifi_antenna_envelope"
				cadModel={{ glbUrl: antennaEnvelopeUrl, modelUnitToMmScale: 1 }}
			/>
			<assembly.cadassembly
				name="front_chassis"
				cadModel={{
					glbUrl: frontChassisUrl,
					modelUnitToMmScale: 1,
					showAsTranslucentModel: true,
				}}
			/>
			<assembly.cadassembly
				name="display_cushioning"
				cadModel={{ glbUrl: displayCushioningUrl, modelUnitToMmScale: 1 }}
			/>
			<assembly.cadassembly
				name="battery_liner"
				cadModel={{ glbUrl: batteryLinerUrl, modelUnitToMmScale: 1 }}
			/>
			<assembly.cadassembly
				name="battery_adhesive"
				cadModel={{ glbUrl: batteryAdhesiveUrl, modelUnitToMmScale: 1 }}
			/>
			<assembly.cadassembly
				name="pcb_cover_fasteners"
				cadModel={{ glbUrl: pcbCoverFastenersUrl, modelUnitToMmScale: 1 }}
			/>
			<assembly.cadassembly
				name="rear_cover"
				cadModel={{
					glbUrl: rearCoverUrl,
					modelUnitToMmScale: 1,
					showAsTranslucentModel: true,
				}}
			/>
		</assembly.device>
	);
}