shibosoftwaredev/f1c100s-linux-nema17-stepper-controller
A four-layer NEMA 17 motor-controller PCB combining an Allwinner F1C100S Linux computer, USB‑PD/buck power system, W5500 Ethernet, STM32 real-time motion control, DRV8825 stepper driver, protection/temperature/current telemetry, USB interfaces, and local LED/buzzer diagnostics.
- Version
- 0.4.56
- License
- unset
- Stars
- 0
tests/software-current.test.ts
import { test,expect } from "bun:test"
import fs from "node:fs"
import { originalSourceContract,stablePhysicalContract } from "./software-current-contract"
const c=JSON.parse(fs.readFileSync("dist/index/circuit.json","utf8")) as any[]
const b=JSON.parse(fs.readFileSync("baseline/v055-circuit.json","utf8")) as any[]
const of=(type:string)=>c.filter(e=>e.type===type)
const part=(name:string)=>of("source_component").find(e=>e.name===name)
const port=(name:string,pin:string|number)=>of("source_port").find(e=>e.source_component_id===part(name).source_component_id && (e.name===pin||e.pin_number===pin||e.port_hints?.includes(String(pin))))
const parent=new Map<string,string>()
function root(x:string):string { if(!parent.has(x))parent.set(x,x);const p=parent.get(x)!;if(p!==x)parent.set(x,root(p));return parent.get(x)! }
for(const e of c) {
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 node=(name:string,pin:string|number)=>root(port(name,pin).source_port_id)
test("software-current addition preserves all original source records except the declared DAC feed",()=>originalSourceContract(b,c))
test("only the declared reference resistor and two bypass capacitors move; other hardware remains unchanged",()=>{
stablePhysicalContract({before:b,after:c})
const cin=of("pcb_component").find(e=>e.source_component_id===part("C_U_2V5_IN").source_component_id)
expect(cin.center.x).toBeCloseTo(-3.65,8)
expect(cin.center.y).toBeCloseTo(-12.51,8)
expect(cin.layer).toBe("bottom")
expect(cin.rotation).toBe(90)
const declared=JSON.parse(fs.readFileSync("software-current-placement.json","utf8"))
for(const name of ["R_REF_TOP","C_U_2V5_IN","C_ETH_1"]) {
const sc=b.find(e=>e.type==="source_component"&&e.name===name)
const before=b.find(e=>e.type==="pcb_component"&&e.source_component_id===sc.source_component_id)
const after=of("pcb_component").find(e=>e.source_component_id===sc.source_component_id)
expect(after.center.x).toBeCloseTo(declared[name].x,9)
expect(after.center.y).toBeCloseTo(declared[name].y,9)
expect(after.layer).toBe(declared[name].layer)
expect(after.rotation).toBe(declared[name].rotation)
const radians=(after.rotation-before.rotation)*Math.PI/180
for(const pad of b.filter(e=>e.type==="pcb_smtpad"&&e.pcb_component_id===before.pcb_component_id)) {
const p=of("pcb_smtpad").find(e=>e.pcb_smtpad_id===pad.pcb_smtpad_id)
const x=pad.x-before.center.x,y=pad.y-before.center.y
expect(p.x).toBeCloseTo(after.center.x+x*Math.cos(radians)-y*Math.sin(radians),7)
expect(p.y).toBeCloseTo(after.center.y+x*Math.sin(radians)+y*Math.cos(radians),7)
expect(p.layer).toBe(after.layer)
for(const k of ["shape","radius","pcb_port_id"])
expect(p[k]).toEqual(pad[k])
const swap=Math.abs(after.rotation-before.rotation)%180===90
expect(p.width).toBeCloseTo(swap?pad.height:pad.width,7)
expect(p.height).toBeCloseTo(swap?pad.width:pad.height,7)
}
}
})
test("MCP4725 address, supply, bypass and reference wiring match the manufacturer pinout",()=>{
expect(part("U_CURRENT_DAC").manufacturer_part_number).toBe("MCP4725A0T-E/CH")
expect(part("U_CURRENT_DAC").supplier_part_numbers.jlcpcb).toEqual(["C144198"])
expect(node("U_CURRENT_DAC",1)).toBe(node("R_REF_TOP",1))
expect(node("U_CURRENT_DAC",2)).toBe(node("R_REF_BOTTOM",2))
expect(node("U_CURRENT_DAC",3)).toBe(node("C_U_2V5_IN",1))
expect(node("U_CURRENT_DAC",3)).toBe(node("R_V3V3_MOTION",1))
expect(node("R_V3V3_MOTION",2)).toBe(node("U_MOTION",4))
expect(part("R_V3V3_MOTION").resistance).toBe(0)
expect(node("U_CURRENT_DAC",4)).toBe(node("U_MOTION",17))
expect(node("U_CURRENT_DAC",5)).toBe(node("U_MOTION",16))
expect(node("U_CURRENT_DAC",6)).toBe(node("U_CURRENT_DAC",2))
expect(node("C_CURRENT_DAC",1)).toBe(node("U_CURRENT_DAC",3))
expect(node("C_CURRENT_DAC",2)).toBe(node("U_CURRENT_DAC",2))
expect(part("C_CURRENT_DAC").capacitance).toBeCloseTo(100e-9,14)
expect(part("C_CURRENT_DAC").supplier_part_numbers.jlcpcb).toEqual(["C49062"])
expect(node("U_CURRENT_DAC",1)).not.toBe(node("U_MOTOR","V3P3OUT"))
expect(node("R_REF_TOP",2)).toBe(node("U_MOTOR","AVREF"))
expect(node("R_REF_TOP",2)).toBe(node("U_MOTOR","BVREF"))
expect(part("R_REF_TOP").resistance).toBe(9100)
expect(part("R_REF_BOTTOM").resistance).toBe(2000)
for(const name of ["R_ISEN_A","R_ISEN_B","R_INLINE_A","R_INLINE_B"])
expect(part(name).resistance).toBe(.3)
})
function body(source:string,signature:string) {
const start=source.indexOf(signature);expect(start).toBeGreaterThanOrEqual(0)
const first=source.indexOf("{",start);let depth=1,i=first+1
while(depth&&i<source.length){if(source[i]==="{")depth++;if(source[i]==="}")depth--;i++}
return source.slice(start,i)
}
test("STEP/IRQ priorities, watchdog, GPIO boot states, ADC and temperature functions are byte-preserved",()=>{
const before=fs.readFileSync("../../work/nema17-clean-schematic-v054-IAQpFd/firmware/stm32/main.c","utf8")
const after=fs.readFileSync("firmware/stm32/main.c","utf8")
for(const sig of ["void TIM3_IRQHandler", "void SysTick_Handler", "void USART2_IRQHandler", "void TIM17_IRQHandler", "static void board_init", "static bool adc_read", "static bool adc_init", "static bool temp_read"])
expect(body(after,sig)).toBe(body(before,sig))
for(const cmd of ["0x20","0x21"])
expect(body(after,`if(frame[4]==${cmd} && !status)`)).toBe(body(before,`if(frame[4]==${cmd} && !status)`))
expect(after).toContain("i2c_write(0xc0)")
expect(after).toContain("i2c_write(0xc1)")
expect(after).not.toContain("i2c_write(0x60)") // MCP4725 EEPROM command is forbidden.
})