Devesh36/orbit-turntable
ORBIT is a USB-C-powered display turntable for photographing jewellery, models and small products. Four-layer ESP32-C3 / ULN2003 PCB, geared stepper, OLED, push encoder, home switch and buzzer, with an enclosed and exploded assembly. Hardware prototype; firmware and physical qualification remain separate.
- Version
- 0.1.1
- License
- unset
- Stars
- 0
index.circuit.tsx
PCB
Schematic
import 'tscircuit'
import {Fragment} from 'react'
import {ESP32_C3_WROOM_02_N4} from './imports/ESP32_C3_WROOM_02_N4'
import {ULN2003ADR} from './imports/ULN2003ADR'
import {AP2112K_3_3TRG1} from './imports/AP2112K_3_3TRG1'
import {USB4105_GF_A} from './imports/USB4105_GF_A'
import {PEC11R_4020F_S0024} from './imports/PEC11R_4020F_S0024'
import {YS_SBZ9650DYB05} from './imports/YS_SBZ9650DYB05'
import {ESD5Z5_0T1G} from './imports/ESD5Z5_0T1G'
import {XL_1608UBC_04} from './imports/XL_1608UBC_04'
import {A_1TS026A_1600_0553_CT} from './imports/A_1TS026A_1600_0553_CT'
const loc=(pcbX:number,pcbY:number,schX:number,schY:number,section:string)=>({pcbX,pcbY,schX,schY,schSectionName:section,schSheetName:section})
export const nets:Record<string,string[]>={
VBUS:['J1.pin7','J1.pin8','J1.pin17','J1.pin18','F1.pin1','D1.pin1'],
V5:['F1.pin2','U3.pin1','U3.pin3','C1.pin1','C4.pin1','U2.pin9','J2.pin5','BZ1.pin1','TP1.pin1'],
V3V3:['U3.pin5','U1.pin1','C2.pin1','C3.pin1','C5.pin1','R3.pin1','R4.pin1','R5.pin1','R6.pin1','R7.pin1','R8.pin1','R9.pin1','R10.pin1','R11.pin1','R12.pin1','J3.pin1','TP2.pin1'],
GND:['J1.pin1','J1.pin2','J1.pin3','J1.pin4','J1.pin5','J1.pin6','J1.pin19','J1.pin20','D1.pin2','U3.pin2','U1.pin9','U1.pin19','U2.pin8','U2.pin6','U2.pin7',...Array.from({length:10},(_,i)=>`C${i+1}.pin2`),'R1.pin2','R2.pin2','R13.pin2','R14.pin2','R15.pin2','R16.pin2','R17.pin2','SW1.pin3','SW1.pin4','SW2.pin3','SW2.pin4','ENC1.pin2','ENC1.pin5','ENC1.pin7','ENC1.pin8','J3.pin2','J4.pin2','TP3.pin1'],
CC1:['J1.pin15','R1.pin1','R18.pin1'], CC2:['J1.pin9','R2.pin1','R19.pin1'],
CC1_ADC:['R18.pin2','C9.pin1','U1.pin18'], CC2_ADC:['R19.pin2','C10.pin1','U1.pin17'],
EN:['U1.pin2','R3.pin2','C6.pin1','SW1.pin1','SW1.pin2','TP4.pin1'],
BOOT_START:['U1.pin8','R4.pin2','SW2.pin1','SW2.pin2','ENC1.pin4','TP5.pin1'],
STRAP2:['U1.pin16','R5.pin2'], STATUS_N:['U1.pin7','R6.pin2','LED1.pin1'], LED_A:['R7.pin2','LED1.pin2'],
SDA:['U1.pin13','R8.pin2','J3.pin3'], SCL:['U1.pin14','R9.pin2','J3.pin4'],
ENCODER_A:['U1.pin10','R10.pin2','C7.pin1','ENC1.pin1'], ENCODER_B:['R20.pin2','R11.pin2','C8.pin1','ENC1.pin3'], UART_RX:['U1.pin11','R20.pin1','TP6.pin1'],
HOME_N:['U1.pin15','R12.pin2','J4.pin1'],
COIL_IN1:['U1.pin3','U2.pin1','R13.pin1'], COIL_IN2:['U1.pin4','U2.pin2','R14.pin1'],
COIL_IN3:['U1.pin5','U2.pin3','R15.pin1'], COIL_IN4:['U1.pin6','U2.pin4','R16.pin1'],
COIL_A:['U2.pin16','J2.pin1'], COIL_B:['U2.pin15','J2.pin2'], COIL_C:['U2.pin14','J2.pin3'], COIL_D:['U2.pin13','J2.pin4'],
BEEP:['U1.pin12','U2.pin5','R17.pin1','TP7.pin1'], BUZZER_LOW:['U2.pin12','BZ1.pin2'],
}
const resistors:[string,string,number,number,number,number,string][]=[
['R1','5.1k',-14,-14.5,-26,8,'power'],['R2','5.1k',-3,-14.5,-22,8,'power'],
['R3','10k',-29,-12,-5,12,'mcu'],['R4','10k',-18,-12,0,12,'mcu'],['R5','10k',-18,-8,5,12,'mcu'],
['R6','10k',-18,-4,23,-15,'controls'],['R7','1k',8,-15,28,-15,'controls'],
['R8','4.7k',-11,13,-8,-12,'display'],['R9','4.7k',-6,13,-4,-12,'display'],
['R10','10k',18,-16,5,-12,'controls'],['R11','10k',18,-21,10,-12,'controls'],
['R12','10k',24,22,18,-12,'display'],
['R13','47k',-1,7,13,6,'motor'],['R14','47k',-1,3,16,6,'motor'],['R15','47k',4,3,19,6,'motor'],['R16','47k',9,3,22,6,'motor'],['R17','47k',14,3,25,6,'motor'],
['R20','4.7k',14,-21,14,-20,'controls'],
['R18','100k',-15,-11,-26,3,'power'],['R19','100k',-9,-11,-22,3,'power'],
]
const mpn:Record<string,string>={'5.1k':'RC0603FR-075K1L','10k':'RC0603FR-0710KL','1k':'RC0603FR-071KL','4.7k':'RC0603FR-074K7L','47k':'RC0603FR-0747KL','100k':'RC0603FR-07100KL'}
const caps:[string,string,string,number,number,number,number,string,string][]=[
['C1','1uF','0603',-4,-9,-25,14,'power','CL10A105KB8NNNC'],
['C2','1uF','0603',-4,1,-18,14,'power','CL10A105KB8NNNC'],
['C3','100nF','0603',-33,-12,-7,3,'mcu','CL10B104KB8NNNC'],
['C4','4.7uF','0805',15,16,28,15,'motor','CL21A475KPFNNNE'],
['C5','10uF','0805',-33,-16,-7,0,'mcu','CL21A106KPFNNNE'],
['C6','1uF','0603',-29,-16,-5,8,'mcu','CL10A105KB8NNNC'],
['C7','10nF','0603',23,-21,5,-20,'controls','CL10B103KB8NNNC'],
['C8','10nF','0603',28,-21,10,-20,'controls','CL10B103KB8NNNC'],
['C9','1nF','0603',-15,-7,-26,0,'power','CL10B102KB8NNNC'],
['C10','1nF','0603',-9,-7,-22,0,'power','CL10B102KB8NNNC'],
]
export const outline=([[40,23,0],[-40,23,90],[-40,-23,180],[40,-23,270]] as const).flatMap(([x,y,a])=>Array.from({length:13},(_,i)=>({x:+(x+3*Math.cos((a+i*7.5)*Math.PI/180)).toFixed(5),y:+(y+3*Math.sin((a+i*7.5)*Math.PI/180)).toFixed(5)})))
export default function OrbitBoard(){return <board title="ORBIT / STEPPER TURNTABLE" width={86} height={52} outline={outline} thickness={1.6} layers={4}
solderMaskColor="black" schTraceAutoLabelEnabled autorouter={{local:true,traceClearance:0.18}}
minTraceWidth={0.18} minBoardEdgeClearance={0.3} minViaHoleDiameter={0.2} minViaPadDiameter={0.45} minViaEdgeToPadEdgeClearance={0.15}>
{([
['power','USB-C POWER + CURRENT DETECT',-24,20],
['mcu','MCU / RESET / PROGRAM',1,14],
['motor','STEPPER + BUZZER DRIVER',23,20],
['display','I2C DISPLAY / HOME SWITCH',6,-9],
['controls','ENCODER / START / STATUS',17,-9],
] as const).map(([name,title,x,y])=><Fragment key={name}>
<schematicsheet name={name} displayName={title}>
<schematicsection name={name} displayName=" "/>
<schematictext text={title} schX={x} schY={y} fontSize={0.4}/>
</schematicsheet>
</Fragment>)}
<USB4105_GF_A name="J1" {...loc(-8,-20.5,-30,13,'power')} noConnect={['pin10','pin11','pin12','pin13','pin14','pin16']} schWidth={4} schHeight={9}/>
<fuse name="F1" manufacturerPartNumber="1206L050/15YR" currentRating="0.5A" voltageRating="15V" footprint="1206" {...loc(2,-20,-23,18,'power')}/>
<ESD5Z5_0T1G name="D1" {...loc(0,-24,-18,8,'power')}/>
<AP2112K_3_3TRG1 name="U3" {...loc(-4,-4,-20,14,'power')} noConnect={['pin4']} schWidth={3} schHeight={3}/>
<ESP32_C3_WROOM_02_N4 name="U1" {...loc(-28,0,0,2,'mcu')} pcbRotation={90} schWidth={5} schHeight={10}
/>
<ULN2003ADR name="U2" {...loc(8,10,21,13,'motor')} schWidth={5} schHeight={7} noConnect={['pin10','pin11']}>
<silkscreentext text="ORBIT" pcbX={-3.0} pcbY={-11.0} fontSize={1.8}/>
<silkscreentext text="USB-C 5V | 1.5A SOURCE" pcbX={0.0} pcbY={-33.5} fontSize={0.65}/>
<silkscreentext text="MOTOR A B C D +5V" pcbX={0.0} pcbY={15.0} fontSize={0.65}/>
<silkscreentext text="OLED 3V3 G D C" pcbX={-16.0} pcbY={13.0} fontSize={0.6}/>
<silkscreentext text="HOME G" pcbX={21.0} pcbY={15.0} fontSize={0.6}/>
<silkscreentext text="DIAL / PUSH TO START" pcbX={20.0} pcbY={-7.0} fontSize={0.65}/>
<silkscreentext text="UART RX TX" pcbX={-35.0} pcbY={-35.0} fontSize={0.6}/>
</ULN2003ADR>
<YS_SBZ9650DYB05 name="BZ1" {...loc(30,12,33,8,'motor')}/>
<PEC11R_4020F_S0024 name="ENC1" {...loc(28,-8,7,-16,'controls')} schWidth={3.5} schHeight={4}/>
<XL_1608UBC_04 name="LED1" {...loc(14,-10,29,-18,'controls')}/>
<A_1TS026A_1600_0553_CT name="SW1" {...loc(-29,21,-4,-4,'mcu')} internallyConnectedPins={[["pin1","pin2"],["pin3","pin4"]]}/>
<A_1TS026A_1600_0553_CT name="SW2" {...loc(-20,-15,1,-4,'mcu')} internallyConnectedPins={[["pin1","pin2"],["pin3","pin4"]]}/>
<pinheader name="J2" pinCount={5} pitch="2.54mm" manufacturerPartNumber="61300511121" {...loc(8,22,31,15,'motor')}/>
<pinheader name="J3" pinCount={4} pitch="2.54mm" manufacturerPartNumber="61300411121" {...loc(-8,20,-5,-17,'display')}/>
<pinheader name="J4" pinCount={2} pitch="2.54mm" manufacturerPartNumber="61300211121" {...loc(29,23,19,-17,'display')}/>
{resistors.map(([name,resistance,x,y,sx,sy,section])=><resistor key={name} name={name} resistance={resistance} footprint="0603" manufacturerPartNumber={mpn[resistance]} {...loc(x,y,sx,sy,section)}/>)}
{caps.map(([name,capacitance,footprint,x,y,sx,sy,section,manufacturerPartNumber])=><capacitor key={name} name={name} capacitance={capacitance} footprint={footprint} manufacturerPartNumber={manufacturerPartNumber} {...loc(x,y,sx,sy,section)}/>)}
{['5V','3V3','GND','EN','BOOT'].map((text,i)=><testpoint key={text} name={`TP${i+1}`} doNotPlace footprintVariant="pad" padDiameter={1.3} {...loc(-29+i*3,-19,-6+i*3,-5.5,'mcu')}/>)}
{/* USB data pins are deliberately unused. UART0 normally occupies the encoder/BEEP pins;
external serial flashing uses exposed pads; R20 isolates the encoder contact from RX. */}
<testpoint name="TP6" doNotPlace footprintVariant="pad" padDiameter={1.3} {...loc(-29,-23,9,-3,'mcu')}/>
<testpoint name="TP7" doNotPlace footprintVariant="pad" padDiameter={1.3} {...loc(-25,-23,9,-5.5,'mcu')}/>
{Object.entries(nets).map(([name,pins])=><Fragment key={name}>
<net name={name} isGroundNet={name==='GND'} isPowerNet={['V5','V3V3','VBUS'].includes(name)} nominalTraceWidth={['V5','VBUS', 'COIL_A','COIL_B','COIL_C','COIL_D'].includes(name)?0.4:0.18}/>
{pins.map(pin=><Fragment key={pin}><trace from={`.${pin.split('.')[0]} > .${pin.split('.')[1]}`} to={`net.${name}`}/></Fragment>)}
</Fragment>)}
<keepout shape="rect" pcbX={-39.25} pcbY={0} width={7.5} height={50} layers={['top','inner1','inner2','bottom']} excludeRefs={['.U1']}/>
{[-1,1].flatMap(s=>[-17,38].map(x=><Fragment key={`${x}/${s}`}><hole name={`H_${x}_${s}`} diameter={2.7} pcbX={x} pcbY={s*22}/></Fragment>))}
</board>}
