ShiboSoftwareDev/muse-m5stack-sticks3-replacement
A 4-layer ESP32-S3 carrier board with USB-C 5V input, AMS1117 3.3V regulation, FFC interface, reset/boot/user buttons, status LED, and passives for compact display/control applications.
- Version
- 0.0.1
- License
- unset
- Stars
- 0
src/MuseGadgetBoard.tsx
import { Fragment } from "react"
import { AFC01_S24FCA_00 } from "../imports/AFC01_S24FCA_00"
import { AMS1117_3_3 } from "../imports/AMS1117_3_3"
import { ESP32_S3_WROOM_1_N16R8 } from "../imports/ESP32_S3_WROOM_1_N16R8"
import { TS_1187A_B_A_B } from "../imports/TS_1187A_B_A_B"
import { TYPE_C_6P } from "../imports/TYPE_C_6P"
import { createJlcFourLayerAutorouter } from "./jlc-autorouter"
import type { GadgetVariant } from "./variants"
const resistorParts = {
r1k: {
value: "1k",
lcsc: "C21190",
mpn: "0603WAF1001T5E",
},
r5k1: {
value: "5.1k",
lcsc: "C23186",
mpn: "0603WAF5101T5E",
},
r10k: {
value: "10k",
lcsc: "C2906982",
mpn: "FRC0603F1002TS",
},
} as const
const capacitorParts = {
c100n: {
value: "100nF",
lcsc: "C14663",
mpn: "CC0603KRX7R9BB104",
footprint: "0603",
},
c1u: {
value: "1uF",
lcsc: "C15849",
mpn: "CL10A105KB8NNNC",
footprint: "0603",
},
c10u: {
value: "10uF",
lcsc: "C15850",
mpn: "CL21A106KAYNNNE",
footprint: "0805",
},
} as const
const interfaceNames: Record<GadgetVariant["interfaceKind"], string[]> = {
status: [],
"spi-display": [
"SPI_SCLK",
"SPI_MOSI",
"DISPLAY_CS",
"DISPLAY_DC",
"DISPLAY_RST",
"BACKLIGHT_PWM",
"TOUCH_SDA",
"TOUCH_SCL",
],
epaper: [
"EPD_SCLK",
"EPD_MOSI",
"EPD_CS",
"EPD_DC",
"EPD_RST",
"EPD_BUSY",
"FRONTLIGHT_PWM",
"I2C_SDA",
],
voice: [
"I2S_BCLK",
"I2S_LRCLK",
"I2S_DOUT",
"I2S_DIN",
"AUDIO_SDA",
"AUDIO_SCL",
"AMP_ENABLE_PTT",
"ROTARY_AB",
],
watch: [
"SPI_SCLK",
"SPI_MOSI",
"DISPLAY_CS",
"DISPLAY_DC",
"DISPLAY_RST",
"BACKLIGHT_PWM",
"TOUCH_SDA",
"TOUCH_SCL",
],
}
const gpioConnections = {
IO4: "net.IFACE_1",
IO5: "net.IFACE_2",
IO6: "net.IFACE_3",
IO7: "net.IFACE_4",
IO15: "net.IFACE_5",
IO16: "net.IFACE_6",
IO17: "net.IFACE_7",
IO18: "net.IFACE_8",
} as const
const interfaceFanoutPinLabels = {
pin1: ["pin1"],
pin2: ["pin2"],
pin3: ["pin3"],
pin4: ["pin4"],
pin5: ["pin5"],
pin6: ["pin6"],
pin7: ["pin7"],
pin8: ["pin8"],
pin9: ["pin9"],
pin10: ["pin10"],
} as const
const interfaceFanoutRoutes = [
{ pin: "pin1", y: 4.75, destination: ".U1 > .3V3" },
{ pin: "pin2", y: 4.25, destination: ".U1 > .GND1" },
{ pin: "pin3", y: 3.75, destination: ".U1 > .IO4" },
{ pin: "pin4", y: 3.25, destination: ".U1 > .IO5" },
{ pin: "pin5", y: 2.75, destination: ".U1 > .IO6" },
{ pin: "pin6", y: 2.25, destination: ".U1 > .IO7" },
{ pin: "pin7", y: 1.75, destination: ".U1 > .IO15" },
{ pin: "pin8", y: 1.25, destination: ".U1 > .IO16" },
{ pin: "pin9", y: 0.75, destination: ".U1 > .IO17" },
{ pin: "pin10", y: 0.25, destination: ".U1 > .IO18" },
] as const
type Passive = {
name: string
part: keyof typeof resistorParts | keyof typeof capacitorParts
a: string
b: string
pcbX: number
pcbY: number
rotation?: number
schX: number
schY: number
}
export function MuseGadgetBoard({ variant }: { variant: GadgetVariant }) {
const { width, height } = variant
const left = -width / 2
const right = width / 2
const top = height / 2
const bottom = -height / 2
const espX = 4
const espY = top - 16.85
const usbX = left + 4.8
const ffcX = -15
const hasInterface = variant.interfaceKind !== "status"
const statusLedX = hasInterface ? -5 : left + 15
const statusResistorX = hasInterface ? -8 : left + 10.5
const statusY = hasInterface ? bottom + 8 : 1
const ifaceNames = interfaceNames[variant.interfaceKind]
const resistors: Passive[] = [
{ name: "R_CC1", part: "r5k1", a: "USB_CC1", b: "GND", pcbX: left + 10.5, pcbY: -4.0, rotation: 90, schX: -11, schY: 7.5 },
{ name: "R_CC2", part: "r5k1", a: "USB_CC2", b: "GND", pcbX: left + 12.2, pcbY: -4.0, rotation: 90, schX: -8, schY: 7.5 },
{ name: "R_EN", part: "r10k", a: "VCC3V3", b: "ESP_EN", pcbX: hasInterface ? -7.5 : -8.5, pcbY: hasInterface ? espY + 4.9 : 4.5, rotation: hasInterface ? 90 : 0, schX: 5, schY: 8 },
{ name: "R_BOOT", part: "r10k", a: "VCC3V3", b: "ESP_GPIO0", pcbX: right - 12.5, pcbY: -10.5, rotation: 90, schX: 8, schY: 5 },
{ name: "R_LED", part: "r1k", a: "VCC3V3", b: "STATUS_LED_A", pcbX: statusResistorX, pcbY: statusY, rotation: 0, schX: -3, schY: -6 },
]
if (variant.userButtons >= 1) {
resistors.push({ name: "R_USER1", part: "r10k", a: "VCC3V3", b: "USER1", pcbX: right - 8.5, pcbY: 3.0, rotation: 90, schX: 8, schY: 1 })
}
if (variant.userButtons >= 2) {
resistors.push({ name: "R_USER2", part: "r10k", a: "VCC3V3", b: "USER2", pcbX: right - 13, pcbY: 15.0, rotation: 90, schX: 8, schY: -2 })
}
const capacitors: Passive[] = [
{ name: "C_LDO_IN", part: "c10u", a: "VBUS5V", b: "GND", pcbX: left + 5.5, pcbY: top - 14.0, rotation: 90, schX: -8, schY: 3 },
{ name: "C_LDO_OUT", part: "c10u", a: "VCC3V3", b: "GND", pcbX: left + 15, pcbY: top - 6.0, rotation: 90, schX: -4, schY: 3 },
{ name: "C_ESP_BULK", part: "c10u", a: "VCC3V3", b: "GND", pcbX: hasInterface ? -7.5 : -8.5, pcbY: hasInterface ? espY + 8.3 : 8.0, rotation: hasInterface ? 90 : 0, schX: 1, schY: 3 },
{ name: "C_ESP_HF", part: "c100n", a: "VCC3V3", b: "GND", pcbX: hasInterface ? -10.7 : -8.5, pcbY: hasInterface ? espY + 8.3 : 6.3, rotation: hasInterface ? 90 : 0, schX: 4, schY: 3 },
{ name: "C_EN", part: "c1u", a: "ESP_EN", b: "GND", pcbX: hasInterface ? -10.7 : -8.5, pcbY: hasInterface ? espY + 4.9 : 2.8, rotation: hasInterface ? 90 : 0, schX: 7, schY: 8 },
]
return (
<board
name={variant.boardName}
title={variant.title}
width={`${width}mm`}
height={`${height}mm`}
layers={4}
autorouter={{
local: true,
groupMode: "subcircuit",
algorithmFn: createJlcFourLayerAutorouter,
}}
autorouterEffortLevel="2x"
schTraceAutoLabelEnabled
minTraceWidth="0.15mm"
minViaHoleDiameter="0.3mm"
minViaPadDiameter="0.45mm"
minBoardEdgeClearance="0.25mm"
>
<schematicsheet name="SYSTEM" displayName={`${variant.title} — system`} sheetIndex={0}>
<schematicsection name="POWER" displayName="USB-C POWER + 3.3 V REGULATOR" />
<schematicsection name="MCU" displayName="ESP32-S3 + CONTROLS" />
{hasInterface && <schematicsection name="INTERFACE" displayName={`${variant.role.toUpperCase()} — 24-PIN INTERFACE`} />}
</schematicsheet>
<copperpour
name="GND_PLANE"
layer="inner1"
connectsTo="net.GND"
unbroken
clearance="0.2mm"
outline={[
{ x: left + 0.3, y: bottom + 0.3 },
{ x: right - 0.3, y: bottom + 0.3 },
{ x: right - 0.3, y: top - 8 },
{ x: left + 0.3, y: top - 8 },
]}
/>
<copperpour
name="VCC3V3_PLANE"
layer="inner2"
connectsTo="net.VCC3V3"
unbroken
clearance="0.2mm"
outline={[
{ x: left + 0.3, y: bottom + 0.3 },
{ x: right - 0.3, y: bottom + 0.3 },
{ x: right - 0.3, y: top - 8 },
{ x: left + 0.3, y: top - 8 },
]}
/>
{[
[left + 2.8, top - 2.8],
[right - 2.8, top - 2.8],
[left + 2.8, bottom + 2.8],
[right - 2.8, bottom + 2.8],
].map(([x, y], index) => (
<Fragment key={`mount-${index + 1}`}>
<hole name={`H${index + 1}`} pcbX={x} pcbY={y} diameter="2.2mm" />
<keepout shape="circle" layers={["top", "inner1", "inner2", "bottom"]} radius="2mm" pcbX={x} pcbY={y} />
</Fragment>
))}
<TYPE_C_6P
name="U_USB"
displayName="USB-C 5 V POWER"
layer="top"
pcbX={usbX}
pcbY={-1}
pcbRotation={-90}
schX={-12}
schY={6}
schSheetName="SYSTEM"
schSectionName="POWER"
connections={{
EH: "net.GND",
GND1: "net.GND",
GND2: "net.GND",
VBUS1: "net.VBUS5V",
VBUS2: "net.VBUS5V",
CC1: "net.USB_CC1",
CC2: "net.USB_CC2",
}}
/>
<AMS1117_3_3
name="U_3V3"
displayName="AMS1117-3.3"
layer="top"
pcbX={left + 10}
pcbY={top - 6}
pcbRotation={90}
schX={-6}
schY={6}
schSheetName="SYSTEM"
schSectionName="POWER"
connections={{ GND: "net.GND", VIN: "net.VBUS5V", VOUT1: "net.VCC3V3", VOUT2: "net.VCC3V3" }}
/>
<ESP32_S3_WROOM_1_N16R8
name="U1"
displayName="ESP32-S3-WROOM-1-N16R8"
layer="top"
pcbX={espX}
pcbY={espY}
pcbRotation={0}
schX={3}
schY={6}
schHeight={4.2}
schSheetName="SYSTEM"
schSectionName="MCU"
connections={{
GND1: "net.GND",
GND2: "net.GND",
GND3: "net.GND",
"3V3": "net.VCC3V3",
EN: "net.ESP_EN",
IO0: "net.ESP_GPIO0",
IO21: "net.STATUS_LED_K",
...(variant.userButtons >= 1 ? { IO47: "net.USER1" } : {}),
...(variant.userButtons >= 2 ? { IO48: "net.USER2" } : {}),
RXD0: "net.UART_RX",
TXD0: "net.UART_TX",
...(hasInterface ? gpioConnections : {}),
}}
/>
<TS_1187A_B_A_B
name="SW_RESET"
displayName="RESET"
layer="top"
pcbX={right - 5.2}
pcbY={-5.5}
schX={8}
schY={8}
schSheetName="SYSTEM"
schSectionName="MCU"
connections={{ A: "net.ESP_EN", C: "net.ESP_EN", B: "net.GND", D: "net.GND" }}
/>
<TS_1187A_B_A_B
name="SW_BOOT"
displayName="BOOT"
layer="top"
pcbX={right - 5.2}
pcbY={-12.5}
schX={8}
schY={5}
schSheetName="SYSTEM"
schSectionName="MCU"
connections={{ A: "net.ESP_GPIO0", C: "net.ESP_GPIO0", B: "net.GND", D: "net.GND" }}
/>
{variant.userButtons >= 1 && (
<TS_1187A_B_A_B
name="SW_USER1"
displayName={variant.interfaceKind === "voice" || variant.interfaceKind === "watch" ? "PUSH TO TALK" : "USER 1"}
layer="top"
pcbX={right - 5.2}
pcbY={8.5}
schX={8}
schY={1}
schSheetName="SYSTEM"
schSectionName="MCU"
connections={{ A: "net.USER1", C: "net.USER1", B: "net.GND", D: "net.GND" }}
/>
)}
{variant.userButtons >= 2 && (
<TS_1187A_B_A_B
name="SW_USER2"
displayName="USER 2"
layer="bottom"
pcbX={right - 5.2}
pcbY={8.5}
schX={8}
schY={-2}
schSheetName="SYSTEM"
schSectionName="MCU"
connections={{ A: "net.USER2", C: "net.USER2", B: "net.GND", D: "net.GND" }}
/>
)}
<led
name="D_STATUS"
displayName="STATUS RED"
color="red"
footprint="0603"
supplierPartNumbers={{ jlcpcb: ["C2286"] }}
manufacturerPartNumber="KT-0603R"
layer="top"
pcbX={statusLedX}
pcbY={statusY}
schX={0}
schY={-6}
schSheetName="SYSTEM"
schSectionName="MCU"
connections={{ anode: "net.STATUS_LED_A", cathode: "net.STATUS_LED_K" }}
/>
{resistors.map(({ part, a, b, rotation = 0, ...props }) => {
const p = resistorParts[part as keyof typeof resistorParts]
return (
<resistor
key={props.name}
resistance={p.value}
footprint="0603"
supplierPartNumbers={{ jlcpcb: [p.lcsc] }}
manufacturerPartNumber={p.mpn}
layer="top"
pcbRotation={rotation}
schSheetName="SYSTEM"
schSectionName={props.name.startsWith("R_CC") ? "POWER" : "MCU"}
connections={{ pin1: `net.${a}`, pin2: `net.${b}` }}
{...props}
/>
)
})}
{capacitors.map(({ part, a, b, rotation = 0, ...props }) => {
const p = capacitorParts[part as keyof typeof capacitorParts]
return (
<capacitor
key={props.name}
capacitance={p.value}
footprint={p.footprint}
supplierPartNumbers={{ jlcpcb: [p.lcsc] }}
manufacturerPartNumber={p.mpn}
layer="top"
pcbRotation={rotation}
schSheetName="SYSTEM"
schSectionName={props.name.startsWith("C_LDO") ? "POWER" : "MCU"}
connections={{ pin1: `net.${a}`, pin2: `net.${b}` }}
{...props}
/>
)
})}
{hasInterface && (
<Fragment>
<AFC01_S24FCA_00
name="U_INTERFACE"
displayName={`${variant.role} FFC`}
layer="top"
pcbX={ffcX}
pcbY={-1}
pcbRotation={-90}
schX={-1}
schY={-5}
schHeight={3.2}
schSheetName="SYSTEM"
schSectionName="INTERFACE"
/>
<chip
name="INTERFACE_FANOUT"
displayName="FFC fanout vias (DNP)"
doNotPlace
pcbX={0}
pcbY={0}
schX={3}
schY={-5}
schSheetName="SYSTEM"
schSectionName="INTERFACE"
pinLabels={interfaceFanoutPinLabels}
pinAttributes={{
pin1: { requiresPower: true },
pin2: { requiresGround: true },
}}
footprint={
<footprint>
{interfaceFanoutRoutes.map(({ pin, y }, index) => (
<Fragment key={`interface-fanout-via-${pin}`}>
<platedhole
portHints={[pin]}
shape="circle"
holeDiameter="0.3mm"
outerDiameter="0.45mm"
pcbX={index % 2 === 0 ? -12.2 : -11.0}
pcbY={y}
/>
</Fragment>
))}
</footprint>
}
/>
{interfaceFanoutRoutes.map(({ pin, destination }) => (
<Fragment key={`interface-fanout-${pin}`}>
<trace
name={`TRACE_INTERFACE_${pin}_ESCAPE`}
from={`.U_INTERFACE > .${pin}`}
to={`.INTERFACE_FANOUT > .${pin}`}
thickness="0.15mm"
pcbPath={[`.INTERFACE_FANOUT > .${pin}`]}
/>
<trace
name={`TRACE_INTERFACE_${pin}_RUN`}
from={`.INTERFACE_FANOUT > .${pin}`}
to={destination}
thickness="0.15mm"
/>
</Fragment>
))}
</Fragment>
)}
{[
["TP_TX", "UART_TX", hasInterface ? right - 20 : left + 7, bottom + 7],
["TP_RX", "UART_RX", hasInterface ? right - 16 : left + 11, bottom + 7],
["TP_3V3", "VCC3V3", hasInterface ? right - 12 : left + 15, bottom + 7],
["TP_GND", "GND", hasInterface ? right - 8 : left + 19, bottom + 7],
].map(([name, net, x, y]) => (
<testpoint
key={String(name)}
name={String(name)}
footprintVariant="pad"
padShape="circle"
padDiameter="1.3mm"
layer="bottom"
pcbX={Number(x)}
pcbY={Number(y)}
schX={-9 + Number(x) * 0.01}
schY={-7}
schSheetName="SYSTEM"
schSectionName="MCU"
connections={{ pin1: `net.${net}` }}
/>
))}
{/* ESP exposed-pad thermal/stitch vias; all vias on every board use the
specified 0.30 mm drill and 0.45 mm land. */}
{[
[espX - 2.20015, espY + 0.62485],
[espX - 0.8001, espY + 0.62485],
[espX - 2.20015, espY + 2.0249],
[espX - 0.8001, espY + 2.0249],
].map(([x, y], index) => (
<Fragment key={`epad-via-${index}`}>
<via name={`VIA_ESP_GND_${index + 1}`} connectsTo="net.GND" fromLayer="top" toLayer="bottom" pcbX={x} pcbY={y} outerDiameter="0.45mm" holeDiameter="0.3mm" />
</Fragment>
))}
<via name="VIA_GND_USB" connectsTo="net.GND" fromLayer="top" toLayer="bottom" pcbX={left + 9} pcbY={-5} outerDiameter="0.45mm" holeDiameter="0.3mm" />
<via name="VIA_GND_LDO" connectsTo="net.GND" fromLayer="top" toLayer="bottom" pcbX={left + 10} pcbY={top - 12} outerDiameter="0.45mm" holeDiameter="0.3mm" />
{ifaceNames.map((label, index) => (
<Fragment key={label}>
<silkscreentext
text={`${index + 3}:${label}`}
layer="bottom"
pcbX={left + 7 + (index % 2) * 13}
pcbY={top - 4 - Math.floor(index / 2) * 2}
fontSize="0.65mm"
anchorAlignment="center_left"
/>
</Fragment>
))}
<silkscreentext text={variant.boardName} layer="bottom" pcbX={0} pcbY={bottom + 2.2} fontSize="0.8mm" anchorAlignment="center" />
<silkscreentext text="4L | VIA 0.30/0.45 mm" layer="bottom" pcbX={0} pcbY={bottom + 3.5} fontSize="0.6mm" anchorAlignment="center" />
</board>
)
}