imrishabh18/rp2040-motor-controller
An RP2040-based NEMA17 stepper-motor controller with USB-C programming and PD motor-power negotiation, DRV8825 dual H-bridge drive, current/temperature telemetry, protected power filtering, status RGB LED, buzzer alarm, and optional magnetic encoder.
- Version
- 1.0.41
- License
- unset
- Stars
- 3
tests/current-telemetry.test.tsx
import { expect, test } from "bun:test";
import { Circuit } from "tscircuit";
import Board from "../index.circuit";
test("two inline current sensors, safe ADC nets, RGB and independent voltage probes", async () => {
const circuit = new Circuit({platform:{pcbDisabled:true}});
circuit.add(<Board routingDisabled/>);
await circuit.renderUntilSettled();
const json:any[]=circuit.getCircuitJson();
const parent=new Map<string,string>();
const root=(id:string):string=>{if(!parent.has(id))parent.set(id,id);const p=parent.get(id)!;if(p!==id)parent.set(id,root(p));return parent.get(id)!};
for(const e of json){
const ids=e.type==='source_trace'?[...e.connected_source_port_ids,...e.connected_source_net_ids]:e.type==='source_component_internal_connection'?e.source_port_ids:[];
for(const id of ids.slice(1))parent.set(root(id),root(ids[0]));
}
const comp=(name:string)=>{const c=json.find(e=>e.type==='source_component'&&e.name===name);if(!c)throw new Error(name);return c};
const net=(name:string,pin:string)=>{const c=comp(name);const p=json.find(e=>e.type==='source_port'&&e.source_component_id===c.source_component_id&&(e.name===pin||e.port_hints?.includes(pin)));if(!p)throw new Error(name+'.'+pin);return root(p.source_port_id)};
const common=net('U1','GND'),v33=net('L_AVDD','pin1');
const currentNets:string[]=[];
for(const [phase,i] of [['A',0],['B',1]] as const){
const amp=`U_CURRENT_${phase}`,shunt=`R_PHASE_${phase}`;
// Check physical pin numbers, independent of imported aliases.
expect(net(amp,'pin8')).toBe(net(shunt,'pin1'));
expect(net(amp,'pin1')).toBe(net(shunt,'pin2'));
expect(net(shunt,'pin1')).toBe(net('DRIVER',`${phase}OUT1`));
expect(net(shunt,'pin2')).toBe(net('J1',`pin${i*2+1}`));
expect(comp(shunt).resistance).toBe(0.05);
expect(comp(shunt).supplier_part_numbers.jlcpcb).toEqual(["C7467249"]);
expect(comp(amp).manufacturer_part_number).toBe("INA240A1DR");
expect(comp(amp).supplier_part_numbers.jlcpcb).toEqual(["C2060769"]);
expect(comp(`R_ISEN_${phase}`).resistance).toBe(0.3);
expect(net(amp,'pin5')).toBe(net(`R_ADC_${phase}`,'pin1'));
const adc=net('U1',`GPIO${28+i}_ADC${2+i}`);
expect(adc).toBe(net(`R_ADC_${phase}`,'pin2'));
expect(adc).toBe(net(`C_ADC_${phase}`,'pin1'));
for(const pin of ['pin2','pin3','pin4'])expect(net(amp,pin)).toBe(common);
for(const pin of ['pin6','pin7'])expect(net(amp,pin)).toBe(v33);
expect(net(`C_CURRENT_${phase}`,'pin1')).toBe(v33);
expect(net(`C_CURRENT_${phase}`,'pin2')).toBe(common);
expect(net(`C_ADC_${phase}`,'pin2')).toBe(common);
currentNets.push(adc,net(amp,'pin5'),net(shunt,'pin1'),net(shunt,'pin2'));
}
expect(new Set([...currentNets,common,v33]).size).toBe(10);
// CH224K power feed remains sized for a 20 V request. GPIO selection has its own regression test.
expect(comp('R_PD_VDD').resistance).toBe(1000);
expect(comp('R_PD_VDD').manufacturer_part_number).toBe('CRCW12061K00FKEAHP');
expect(comp('R_PD_VDD').supplier_part_numbers.jlcpcb).toEqual(['C844867']);
expect(comp('D_MOTOR_TVS').manufacturer_part_number).toBe('SMBJ22CAQ-13-F');
expect(comp('D_MOTOR_TVS').supplier_part_numbers.jlcpcb).toEqual(['C1975329']);
expect(comp('DRIVER').manufacturer_part_number).toBe('DRV8825PWPR');
expect(json.some(e => e.type === 'source_component' && /TMC2209/.test(e.manufacturer_part_number ?? ''))).toBe(false);
expect(net('C_MOTOR_BULK','pin1')).toBe(net('DRIVER','VM'));
expect(net('C_MOTOR_BULK','pin2')).toBe(common);
const raw=net('F_MOTOR_VBUS','pin1'),protectedRail=net('D_MOTOR_BLOCK','cathode');
// Supplier pin 1 is the cathode on both polarized parts; the rail and
// ground connections must follow those physical pin numbers.
expect(net('D_MOTOR_TVS','pin1')).toBe(protectedRail);
expect(net('D_MOTOR_TVS','pin2')).toBe(common);
expect(net('D_PWR','pin1')).toBe(common);
expect(net('D_PWR','pin2')).toBe(net('R_PWR_LED','pin2'));
expect(net('D_MOTOR_BLOCK','anode')).toBe(net('F_MOTOR_VBUS','pin2'));
expect(net('D_MOTOR_BLOCK','anode')).not.toBe(protectedRail);
expect(net('TP_PD','pin1')).toBe(raw);
expect(net('TP_VMOTOR','pin1')).toBe(protectedRail);
expect(net('TP_PD_GND','pin1')).toBe(common);
expect(new Set([raw,protectedRail,common,v33,...currentNets]).size).toBe(12);
expect(net('D_STATUS','pin4')).toBe(net('J_USB','A4B9'));
expect(net('D_STATUS','pin4')).not.toBe(raw);
for(const [color,i] of [['R',0],['G',1],['B',2]] as const){
expect(net(`Q_STATUS_${color}`,'pin1')).toBe(common);
expect(net(`Q_STATUS_${color}`,'pin2')).toBe(net('U1',`GPIO${[25,2,3][i]}`));
expect(net(`Q_STATUS_${color}`,'pin3')).toBe(net(`R_STATUS_${color}`,'pin2'));
expect(net(`R_STATUS_${color}`,'pin1')).toBe(net('D_STATUS',`pin${i+1}`));
expect(comp(`R_STATUS_${color}`).resistance).toBe(1000);
}
expect(net("U_ENCODER", "SDA")).toBe(net("U1", "GPIO0"));
expect(net("U_ENCODER", "SCL")).toBe(net("U1", "GPIO1"));
},60000);
test("each sense lead terminates directly at its shunt pad without a via", async () => {
const circuit = new Circuit();
circuit.add(<Board routingDisabled/>);
await circuit.renderUntilSettled();
const json:any[]=circuit.getCircuitJson();
for(const phase of ['A','B']) for(const polarity of ['POS','NEG']) {
const source = json.find(e=>e.type==='source_trace'&&e.name===`CURRENT_${phase}_SENSE_${polarity}`);
const copper = json.filter(e=>e.type==='pcb_trace'&&e.source_trace_id===source.source_trace_id);
expect(copper).toHaveLength(1);
const route = copper[0].route;
expect(route.every((p:any)=>p.route_type==='wire'&&p.layer==='top')).toBe(true);
expect(route.length).toBeGreaterThanOrEqual(2);
const first=route[0],last=route.at(-1);
const r = json.find(e=>e.type==='source_component'&&e.name===`R_PHASE_${phase}`);
const sp = json.find(e=>e.type==='source_port'&&e.source_component_id===r.source_component_id&&e.pin_number===(polarity==='POS'?1:2));
const pp = json.find(e=>e.type==='pcb_port'&&e.source_port_id===sp.source_port_id);
expect(first.start_pcb_port_id).toBe(pp.pcb_port_id);
expect(first.x).toBeCloseTo(pp.x,6);
expect(first.y).toBeCloseTo(pp.y,6);
const length=route.slice(1).reduce((sum:number,p:any,i:number)=>sum+Math.hypot(p.x-route[i].x,p.y-route[i].y),0);
expect(length).toBeLessThan(6);
}
},60000);
