rushabhcodes/Attiny85-Arcade-Keychain

This code defines the hardware layout of an ATtiny85-based arcade-style gamepad keychain, featuring a custom PCB with power management, an OLED display, push buttons, an oscillator, a piezo transducer driven via a inverter, mechanical mounting holes, and silkscreen graphics, all modeled with detailed component footprints and electrical traces.

Version
1.0.4
License
unset
Stars
2

index.circuit.tsx

PCBPCB preview for index.circuit.tsx
SchematicSchematic preview for index.circuit.tsx
/**
 * ATtiny85 Arcade — "Armour Grade" gamepad keychain, rev 1.1
 *
 * A tscircuit recreation of the reference design
 * (Attiny_Arcader_game_rev_1.1 schematic + Attiny-Arcade_Keychain_2.0 Gerbers).
 *
 * How it works
 *   A CR2032 on the underside feeds the board through the SK-12D02VG3 slide switch.
 *   The ATtiny85 talks software-I2C to a 4-pin 0.96" OLED module on PB3/PB4,
 *   reads two Kailh Choc keyswitches on PB0/PB2 (10k pull-downs), and drives a
 *   Murata piezo from PB1 through an SN74LVC2G04 dual inverter wired as a
 *   bridge-tied load — 1Y and 2Y swing in antiphase, so the transducer sees
 *   ~2x the rail.
 *   PB5/RESET is deliberately left floating so the part stays ISP-programmable.
 *
 * Every PCB coordinate is lifted from the reference Gerbers and re-referenced
 * to the board centre at (176.848 mm, -59.9235 mm) in the original artwork.
 */
import type { ChipProps } from "tscircuit";
import {
  BOARD_OUTLINE,
  KEYCHAIN_CUTOUT,
  KEYCHAIN_CUTOUT_CENTER,
} from "./board-outline";
import { ATTINY85_20PU } from "./imports/ATTINY85_20PU";
import { PKLCS1212E2000_R1 } from "./imports/PKLCS1212E2000_R1";
import { SN74LVC2G04DBVR } from "./imports/SN74LVC2G04DBVR";
import { SK_12D02VG3 } from "./imports/SK_12D02VG3";
import { CPG135001D03 } from "./imports/CPG135001D03";
import { CoinCellHolder3003 } from "./imports/CoinCellHolder3003";
import { OledDisplay } from "./imports/OledDisplay";

const tscircuitLogoSvg = `data:image/svg+xml,${encodeURIComponent(`
<svg
   viewBox="0 0 92 101"
   version="1.1"
   id="svg5"
   sodipodi:docname="tscircuit-logo.svg"
   inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg">
  <defs
     id="defs5" />
  <sodipodi:namedview
     id="namedview5"
     pagecolor="#ffffff"
     bordercolor="#999999"
     borderopacity="1"
     inkscape:showpageshadow="2"
     inkscape:pageopacity="0"
     inkscape:pagecheckerboard="0"
     inkscape:deskcolor="#d1d1d1"
     inkscape:zoom="5.9018913"
     inkscape:cx="56.422592"
     inkscape:cy="51.932505"
     inkscape:window-width="1920"
     inkscape:window-height="1052"
     inkscape:window-x="0"
     inkscape:window-y="0"
     inkscape:window-maximized="1"
     inkscape:current-layer="svg5" />
  <path
     d="m 19.872307,100.852 -2.349,-0.154 -2.31,-0.459 -2.229,-0.757 -2.112,-1.042 -1.9580002,-1.308 -1.77,-1.552 -1.552,-1.77 -1.308,-1.958 -1.042,-2.112 -0.757,-2.229 -0.459,-2.31 -0.154,-2.349 v -38 l 0.154,-2.349 0.459,-2.31 0.757,-2.229 1.042,-2.112 1.308,-1.958 1.552,-1.77 1.77,-1.552 1.9580002,-1.308 2.112,-1.042 2.229,-0.757 2.31,-0.459 2.349,-0.154 h 42 l 2.349,0.154 2.31,0.459 2.229,0.757 2.112,1.042 1.958,1.308 1.77,1.552 1.552,1.77 1.308,1.958 1.042,2.112 0.757,2.229 0.459,2.31 0.154,2.349 v 38 l -0.154,2.349 -0.459,2.31 -0.757,2.229 -1.042,2.112 -1.308,1.958 -1.552,1.77 -1.77,1.552 -1.958,1.308 -2.112,1.042 -2.229,0.757 -2.31,0.459 -2.349,0.154 z"
     fill="#000000"
     id="path1" />
  <path
     d="m 12.372307,37.852 c 2.6943,0 4.3782,2.9167 3.0311,5.25 -0.6252,1.0829 -1.7807,1.75 -3.0311,1.75 -2.6943002,0 -4.3782002,-2.9167 -3.0311002,-5.25 0.6252,-1.0829 1.7807002,-1.75 3.0311002,-1.75 z"
     fill="#ffffff"
     id="path2" />
  <path
     d="m 29.297007,59.975 v 11.524 c 0,0.8026 0.1864,1.3903 0.559,1.763 0.4014,0.344 1.0607,0.516 1.978,0.516 h 2.795 v 5.074 h -3.784 c -5.074,0 -7.611,-2.4654 -7.611,-7.396 V 59.975 h -2.838 V 55.03 h 2.838 v -5.891 h 6.063 v 5.891 h 5.332 v 4.945 z m 18.7779,19.264 c -1.9493,0 -3.698,-0.344 -5.246,-1.032 -1.548,-0.7167 -2.7806,-1.677 -3.698,-2.881 -0.8886,-1.204 -1.376,-2.537 -1.462,-3.999 h 6.063 c 0.1147,0.9173 0.559,1.677 1.333,2.279 0.8027,0.602 1.7917,0.903 2.967,0.903 1.1467,0 2.0354,-0.2294 2.666,-0.688 0.6594,-0.4587 0.989,-1.0464 0.989,-1.763 0,-0.774 -0.4013,-1.3474 -1.204,-1.72 -0.774,-0.4014 -2.021,-0.8314 -3.741,-1.29 -1.7773,-0.43 -3.2393,-0.8744 -4.386,-1.333 -1.118,-0.4587 -2.0926,-1.161 -2.924,-2.107 -0.8026,-0.946 -1.204,-2.2217 -1.204,-3.827 0,-1.3187 0.3727,-2.5227 1.118,-3.612 0.774,-1.0894 1.8634,-1.9494 3.268,-2.58 1.4334,-0.6307 3.1104,-0.946 5.031,-0.946 2.838,0 5.1027,0.7166 6.794,2.15 1.6914,1.4046 2.623,3.311 2.795,5.719 h -5.762 c -0.086,-0.946 -0.4873,-1.6914 -1.204,-2.236 -0.688,-0.5734 -1.6196,-0.86 -2.795,-0.86 -1.0893,0 -1.935,0.2006 -2.537,0.602 -0.5733,0.4013 -0.86,0.9603 -0.86,1.677 0,0.8026 0.4014,1.419 1.204,1.849 0.8027,0.4013 2.0497,0.817 3.741,1.247 1.72,0.43 3.139,0.8743 4.257,1.333 1.118,0.4586 2.0784,1.1753 2.881,2.15 0.8314,0.946 1.2614,2.2073 1.29,3.784 0,1.376 -0.387,2.6086 -1.161,3.698 -0.7453,1.0893 -1.8346,1.9493 -3.268,2.58 -1.4046,0.602 -3.053,0.903 -4.945,0.903 z"
     fill="#ffffff"
     id="path3" />
  <path
     d="m 69.872307,22.352 4.5,2 7,-18.5 -7,-5 z"
     fill="#000000"
     stroke="#000000"
     id="path4" />
  <path
     d="m 78.658007,25.4493 0.5267,2.0892 9.3097,-5.3919 -0.0773,-4.0052 z"
     fill="#000000"
     stroke="#000000"
     id="path5" />
</svg>
  `)}`;

/**
 * The registry part ships generic pin1..pin8 labels. Re-label them so the
 * traces below can talk in PBn terms, and re-type the component so those
 * names are what the compiler checks the selectors against.
 */
const ATTINY85_PIN_LABELS = {
  pin1: ["PB5", "RESET"],
  pin2: ["PB3", "XTAL1"],
  pin3: ["PB4", "XTAL2"],
  pin4: "GND",
  pin5: ["PB0", "MOSI", "AREF"],
  pin6: ["PB1", "MISO", "OC0B"],
  pin7: ["PB2", "SCK", "INT0"],
  pin8: "VCC",
} as const;

const Attiny85 = ATTINY85_20PU as (
  props: ChipProps<typeof ATTINY85_PIN_LABELS>,
) => ReturnType<typeof ATTINY85_20PU>;

export default () => (
  <board
    outline={BOARD_OUTLINE}
    layers={2}
    thickness="1.6mm"
    title="ATtiny85 Arcade Keychain"
    minViaHoleDiameter="0.3mm"
    minViaPadDiameter="0.45mm"
  >
    <schematicsection name="Power" displayName="Power" />
    <schematicsection name="MCU" displayName="ATtiny85" />
    <schematicsection name="Display" displayName="OLED" />
    <schematicsection name="Buttons" displayName="Buttons" />
    <schematicsection name="Audio" displayName="Audio" />

    {/* ---------------- Power ---------------- */}
    <CoinCellHolder3003
      name="BT1"
      schSectionName="Power"
      schX={-20}
      schY={5}
      layer="bottom"
      pcbX={0.0122}
      pcbY={-1.2397}
      doNotPlace
    />
    <SK_12D02VG3
      name="S1"
      schSectionName="Power"
      schX={-24}
      schY={5}
      pcbX={-0.064}
      pcbY={16.4735}
      pcbRotation={180}
    />
    <capacitor
      name="C1"
      capacitance="100nF"
      footprint="0805"
      schSectionName="Power"
      schX={-24}
      schY={-1}
      schOrientation="vertical"
      pcbX={4.508}
      pcbY={-11.3235}
    />
    <capacitor
      name="C2"
      capacitance="100nF"
      footprint="0805"
      schSectionName="Power"
      schX={-20}
      schY={-1}
      schOrientation="vertical"
      pcbX={-4.508}
      pcbY={-11.3235}
      />

    {/* ---------------- MCU ---------------- */}
    <Attiny85
      name="IC1"
      pinLabels={ATTINY85_PIN_LABELS}
      schPinArrangement={{
        leftSide: {
          pins: ["PB5", "PB3", "PB4", "GND"],
          direction: "top-to-bottom",
        },
        rightSide: {
          pins: ["VCC", "PB2", "PB1", "PB0"],
          direction: "top-to-bottom",
        },
      }}
      schSectionName="MCU"
      schX={-3}
      schY={2}
      pcbX={24.828}
      pcbY={13.9495}
      pcbRotation={-90}
    />

    {/* ---------------- Display ---------------- */}
    <OledDisplay
      name="U1"
      schSectionName="Display"
      schX={-11}
      schY={2}
      pcbX={0}
      pcbY={11.5266}
    />

    {/* ---------------- Buttons ---------------- */}
    <resistor
      name="R1"
      resistance="10k"
      footprint="1206"
      schSectionName="Buttons"
      schX={6}
      schY={5}
      schRotation={90}
      pcbX={14.922}
      pcbY={-7.6405}
    />
    <CPG135001D03
      name="SW1"
      schSectionName="Buttons"
      schX={10}
      schY={7}
      pcbX={25}
      pcbY={-2.5}
    />
    <resistor
      name="R2"
      resistance="10k"
      footprint="1206"
      schSectionName="Buttons"
      schX={6}
      schY={-1}
      schRotation={90}
      pcbX={-14.2}
      pcbY={-7.5135}
    />
    <CPG135001D03
      name="SW2"
      schSectionName="Buttons"
      schX={10}
      schY={1}
      pcbX={-25}
      pcbY={-2.5}
    />

    {/* ---------------- Audio ---------------- */}
    <SN74LVC2G04DBVR
      name="IC2"
      schPinArrangement={{
        leftSide: { pins: ["VCC", "1A", "2A"], direction: "top-to-bottom" },
        rightSide: { pins: ["GND", "1Y", "2Y"], direction: "top-to-bottom" },
      }}
      schSectionName="Audio"
      schX={-6.06}
      schY={-9}
      pcbX={-0.084}
      pcbY={-10.3685}
      pcbRotation={180}
    />
    {/* Murata PKLCS1212E2000 in place of the reference CPT-1203-78-SMT-TR:
        same 12 mm body sitting over the battery holder, pin 1 to the left. */}
    <PKLCS1212E2000_R1
      name="LS1"
      schSectionName="Audio"
      schX={-0.21}
      schY={-10.27}
      pcbX={0.063}
      pcbY={-1.2905}
    />

    {/* ---------------- Mechanical ---------------- */}
    {/* Mounting holes are already treated as autorouter obstacles. */}
    <hole name="MH1" diameter="2.9mm" pcbX={-11.5} pcbY={11.5} />
    <hole name="MH2" diameter="2.9mm" pcbX={11.5} pcbY={11.5} />
    <hole name="MH3" diameter="2.9mm" pcbX={-11.7} pcbY={-12.3} />
    <hole name="MH4" diameter="2.9mm" pcbX={11.7} pcbY={-12.3} />
    <cutout
      shape="polygon"
      points={KEYCHAIN_CUTOUT}
      pcbX={KEYCHAIN_CUTOUT_CENTER.x}
      pcbY={KEYCHAIN_CUTOUT_CENTER.y}
    />

    {/* ---------------- Silkscreen ---------------- */}
    <silkscreentext text="ON" fontSize={2.4} pcbX={-8.9} pcbY={18.7} />
    <silkscreentext text="OFF" fontSize={2.4} pcbX={8.9} pcbY={18.7} />
    <silkscreengraphic
      imageUrl={tscircuitLogoSvg}
      width="10mm"
      height="10mm"
      pcbX={-19}
      pcbY={15}
    />
    <silkscreentext
      text="made by rushabhcodes"
      layer={"bottom"}
      fontSize={1.4}
      pcbX={0}
      pcbY={-15}
    />

    {/* ---------------- Power distribution ---------------- */}
    <trace from=".BT1 > .plus" to=".S1 > .COM" />
    <trace from=".BT1 > .minus" to="net.GND" />
    <trace from=".S1 > .NC" to="net.V3" />

    <trace from=".IC1 > .VCC" to="net.V3" />
    <trace from=".IC1 > .GND" to="net.GND" />
    <trace from=".IC2 > .VCC" to="net.V3" />
    <trace from=".IC2 > .GND" to="net.GND" />
    <trace from=".U1 > .VDD" to="net.V3" />
    <trace from=".U1 > .GND" to="net.GND" />
    <trace from=".C1 > .pin1" to="net.V3" />
    <trace from=".C1 > .pin2" to="net.GND" />
    <trace from=".C2 > .pin1" to="net.V3" />
    <trace from=".C2 > .pin2" to="net.GND" />

    {/* ---------------- OLED (software I2C) ---------------- */}
    <trace from=".IC1 > .PB3" to=".U1 > .SDA" />
    <trace from=".IC1 > .PB4" to=".U1 > .SCK" />

    {/* ---------------- Buttons: switch to +3V, 10k pull-down ---------------- */}
    <trace from=".SW1 > .pin1" to="net.V3" />
    <trace from=".SW1 > .pin2" to=".IC1 > .PB2" />
    <trace from=".R1 > .pin2" to=".IC1 > .PB2" />
    <trace from=".R1 > .pin1" to="net.GND" />

    <trace from=".SW2 > .pin1" to="net.V3" />
    <trace from=".SW2 > .pin2" to=".IC1 > .PB0" />
    <trace from=".R2 > .pin2" to=".IC1 > .PB0" />
    <trace from=".R2 > .pin1" to="net.GND" />

    {/* ---------------- Audio: bridge-tied piezo drive ---------------- */}
    <trace from=".IC1 > .PB1" to=".IC2 > .1A" />
    <trace from=".IC2 > .1Y" to=".IC2 > .2A" />
    <trace from=".IC2 > .1Y" to=".LS1 > .pin2" />
    <trace from=".IC2 > .2Y" to=".LS1 > .pin1" />

    {/* Ground pour on both layers, matching the reference artwork. */}
    <copperpour connectsTo="net.GND" layer="top" />
    <copperpour connectsTo="net.GND" layer="bottom" />
  </board>
);