MustafaMulla29/rp2040-bldc-motor-controller-new
A USB‑C PD–powered RP2040 BLDC motor controller PCB with 3.3 V logic, MCT8329A sensorless commutation, three MOSFET half-bridges, protected power switching, USB data, controls, and motor connectors.
- Version
- 0.3.82
- License
- unset
- Stars
- 0
scripts/evaluate-u11.mjs
/** Datasheet/DC screening only. No circuit edits, SPICE, or hardware qualification. */
import { readFileSync, writeFileSync } from 'node:fs'
import { createHash } from 'node:crypto'
import assert from 'node:assert/strict'
const pcb = readFileSync('dist/index/circuit.json')
const hash = createHash('sha256').update(pcb).digest('hex')
const baseline = JSON.parse(readFileSync('dist/pd-electrical-review.json'))
assert.equal(hash, baseline.board_sha256, 'Re-review the power topology before reusing this analysis')
const parts = new Map(JSON.parse(pcb).filter(x => x.type === 'source_component').map(x => [x.name, x]))
assert.equal(parts.get('U11').manufacturer_part_number, 'TPS1H100AQPWPRQ1')
assert.equal(parts.get('R13').resistance, .020)
assert.equal(parts.get('D4').manufacturer_part_number, 'SMBJ22A')
for (const name of ['Q1', 'Q2']) assert.equal(parts.get(name).manufacturer_part_number, 'SI4427BDY-T1-E3')
// Unchanged assumptions from the prior sensitivity study, NOT a full worst case.
const otherSeriesOhms = .039 + .020 + .034 // Q1/Q2 at 25 C + shunt + nominal cold fuse
const driverMinimumV = 4.5
const provisionalStopV = 4.65
const candidates = [
{ name: 'TPS1H100-Q1 existing', minInputV: 3.5, hotOhms: .166 * 1.2,
roomOhms: .120, floorV: 0, disposition: 'Baseline; cannot guarantee the adverse 5 V scenario.' },
{ name: 'TPS259470L screening', minInputV: 2.7, hotOhms: .045, floorV: .0169,
disposition: 'Not accepted: 28 V absolute maximum is not protected by the present 35.5 V TVS clamp specification; also insufficient 5 V margin. Active current limiting and active-high enable exist, but footprint/support circuitry differ.' },
{ name: 'TPS259480L screening', minInputV: 3.5, hotOhms: .020, floorV: .0245,
disposition: 'Not accepted: same transient-voltage mismatch and insufficient 5 V margin. Active current limiting and active-high enable exist; new footprint, current-limit resistor, inrush capacitor, OVLO network and routing would be needed.' },
{ name: 'TPS26630 screening', minInputV: 4.5, hotOhms: .053, floorV: 0,
disposition: 'Not accepted as a robust 5 V fix: local input falls below its own 4.5 V operating minimum in the adverse case. 60 V range, active limiting and SHDN are useful, but do not solve cable drop.' },
{ name: 'Ideal zero-loss U11 (mathematical bound only)', minInputV: 0, hotOhms: 0, floorV: 0,
disposition: 'Not a part or bypass proposal. Establishes whether ANY passive U11-only substitution can meet the voltage requirement.' },
]
const cases = [
{ name: '5.0 V / short cable / room baseline', sourceV: 5, cableOhms: .1, motorA: .8, logicA: .1, idealDiodeV: .020, room: true },
{ name: '5.0 V / short cable / elevated switch loss', sourceV: 5, cableOhms: .1, motorA: .8, logicA: .1, idealDiodeV: .029 },
{ name: '4.75 V / higher cable resistance / elevated switch loss', sourceV: 4.75, cableOhms: .25, motorA: .8, logicA: .1, idealDiodeV: .029 },
]
const round = x => Number(x.toFixed(6))
const rows = cases.map(s => {
const cableDropV = (s.motorA + s.logicA) * s.cableOhms
const inputV = s.sourceV - cableDropV - s.motorA * otherSeriesOhms - s.idealDiodeV
return {
...s, cableDropV: round(cableDropV), U11InputV: round(inputV),
values: candidates.map(c => {
const resistance = s.room && c.roomOhms !== undefined ? c.roomOhms : c.hotOhms
// Approximate ORing regulation as a forward-drop floor. This is a DC
// screening envelope, not a characterization at our exact 0.8 A load.
const switchDropV = Math.max(s.motorA * resistance, c.floorV)
const driverV = inputV - switchDropV
return { candidate: c.name, switchDropV: round(switchDropV), driverV: round(driverV),
switchConductionW: round(s.motorA * switchDropV),
candidateInputWithinRange: inputV >= c.minInputV,
driverMarginV: round(driverV - driverMinimumV),
aboveProvisionalStop: driverV >= provisionalStopV }
}),
}
})
// Check reproduction of the earlier estimates and the independent ideal bound.
for (let i = 0; i < rows.length; i++)
assert.ok(Math.abs(rows[i].values[0].driverV - baseline.scenarios[i].estimated_VMOTOR_V) < .0001)
const adverse = rows.at(-1)
assert.equal(adverse.U11InputV, 4.4216)
assert.ok(adverse.values.every(v => v.driverV <= adverse.U11InputV))
assert.ok(adverse.values.every(v => v.driverV < driverMinimumV))
const results = {
board_sha256: hash,
decision: 'DO_NOT_REPLACE_U11_ALONE_FOR_GUARANTEED_5V_OPERATION',
hardware_modified: false,
scope: 'DC component screening, not measured hardware or a qualified current rating. Candidate maximum RON values use their datasheet test conditions (3 A for TPS25947/48); floor model is approximate. No candidate accepted for installation.',
assumptions: { otherSeriesOhms, driverMinimumV, provisionalStopV,
omitted: 'PCB/connector resistance, switch auxiliary current, hot fuse/PMOS increases, ADC errors, ripple, dynamic startup and motor regeneration. Logic 0.1 A is assumed. Each candidate temperature limit differs; these are individual screening bounds, not matched-temperature predictions.' },
candidates, cases: rows,
independent_bounds: {
idealU11DriverV: adverse.U11InputV,
idealEntireBoardAfterCableV: round(4.75 - (.8 + .1) * .25),
explanation: 'Even an entirely lossless PCB gives only 4.525 V after this cable, below the provisional 4.65 V stop threshold. A passive switch cannot restore the lost voltage.'
},
sources: {
existing: 'https://www.ti.com/lit/ds/symlink/tps1h100-q1.pdf',
tps25947: 'https://www.ti.com/lit/ds/symlink/tps25947.pdf',
tps25948: 'https://www.ti.com/lit/ds/symlink/tps25948.pdf',
tps2663: 'https://www.ti.com/lit/ds/symlink/tps2663.pdf',
motorDriver: 'https://www.ti.com/lit/ds/symlink/mct8329a.pdf',
},
}
writeFileSync('dist/u11-evaluation.json', JSON.stringify(results, null, 2) + '\n')
console.log(JSON.stringify({ decision: results.decision, scenarios: rows.length,
candidateCases: rows.length * candidates.length, idealU11DriverV: adverse.U11InputV,
adverseDriverV: adverse.values.map(x => ({ candidate: x.candidate, V: x.driverV })),
report: 'dist/u11-evaluation.json' }, null, 2))