|
| 1 | +from typing import Any |
| 2 | + |
1 | 3 | from typing_extensions import override |
2 | 4 |
|
3 | 5 | from ....circuits import * |
@@ -221,3 +223,125 @@ def generate(self) -> None: |
221 | 223 | self.en_res = self.Block(PullupResistor(resistance=510 * kOhm(tol=0.05))).connected( |
222 | 224 | self.pwr_in_zener_clamp.pwr_out, self.ic.en |
223 | 225 | ) |
| 226 | + |
| 227 | + |
| 228 | +class Lmr38020_Device(InternalSubcircuit, JlcPart, FootprintBlock): |
| 229 | + def __init__(self) -> None: |
| 230 | + super().__init__() |
| 231 | + self.gnd = self.Port(Ground(), [Common]) |
| 232 | + self.vin = self.Port(VoltageSink(voltage_limits=(4.2, 80) * Volt, current_draw=RangeExpr()), [Power]) |
| 233 | + self.sw = self.Port(VoltageSource(voltage_out=self.vin.link().voltage.hull(self.gnd.link().voltage))) |
| 234 | + self.fb = self.Port(AnalogSink(impedance=(10, float("inf")) * MOhm)) # assumed given RFbb maximum spec |
| 235 | + self.boot = self.Port( |
| 236 | + VoltageSink( |
| 237 | + voltage_limits=self.sw.link().voltage + (-0.3, 5.5) * Volt, |
| 238 | + reverse_voltage_out=(3.8, 5.5) * Volt, # assumed from UVLO to BOOT-SW abs max |
| 239 | + reverse_current_limits=0 * Amp(tol=0), |
| 240 | + ) |
| 241 | + ) |
| 242 | + self.en = self.Port( |
| 243 | + DigitalSink.from_supply( |
| 244 | + self.gnd, self.vin, voltage_limit_tolerance=(-0.3, 0.3) * Volt, input_threshold_abs=(0.95, 1.4) * Volt |
| 245 | + ) |
| 246 | + ) |
| 247 | + self.rt = self.Port(AnalogSource()) # for timing |
| 248 | + self.pg = self.Port(DigitalSource.low_from_supply(self.gnd), optional=True) # may be left open |
| 249 | + |
| 250 | + @override |
| 251 | + def contents(self) -> None: |
| 252 | + super().contents() |
| 253 | + self.assign( |
| 254 | + self.vin.current_draw, self.sw.link().current_drawn + (3, 40) * uAmp |
| 255 | + ) # shutdown to non-switching Iq |
| 256 | + self.footprint( |
| 257 | + "U", |
| 258 | + "Package_SO:HSOP-8-1EP_3.9x4.9mm_P1.27mm_EP2.41x3.1mm_ThermalVias", |
| 259 | + { |
| 260 | + "1": self.gnd, |
| 261 | + "2": self.en, |
| 262 | + "3": self.vin, |
| 263 | + "4": self.rt, |
| 264 | + "5": self.fb, |
| 265 | + "6": self.pg, |
| 266 | + "7": self.boot, |
| 267 | + "8": self.sw, |
| 268 | + "9": self.gnd, # EP |
| 269 | + }, |
| 270 | + mfr="Texas Instruments", |
| 271 | + part="LMR38020SDDAR", |
| 272 | + datasheet="https://www.ti.com/lit/ds/symlink/lmr38020.pdf", |
| 273 | + ) |
| 274 | + self.assign(self.lcsc_part, "C3192337") |
| 275 | + self.assign(self.actual_basic_part, False) |
| 276 | + |
| 277 | + |
| 278 | +class Lmr38020(VoltageRegulatorEnableWrapper, DiscreteBuckConverter, GeneratorBlock): |
| 279 | + """4.2-80 V, 2 A synchronous buck converter in SO-8 with thermal pad. |
| 280 | + Adjustable frequency, defaulting to 400kHz per the datasheet example.""" |
| 281 | + |
| 282 | + def __init__(self, *, frequency: RangeLike = 400 * kHertz(tol=0.1), **kwargs: Any) -> None: |
| 283 | + super().__init__(**kwargs) |
| 284 | + self.frequency = self.ArgParameter(frequency) |
| 285 | + self.generator_param(self.frequency) |
| 286 | + |
| 287 | + self.ic = self.Block(Lmr38020_Device()) |
| 288 | + self.connect(self.pwr_in, self.ic.vin) |
| 289 | + self.connect(self.gnd, self.ic.gnd) |
| 290 | + |
| 291 | + self.pg = self.Export(self.ic.pg, optional=True, doc="Open-drain active-high power-good flag") |
| 292 | + |
| 293 | + @override |
| 294 | + def _generator_inner_reset_pin(self) -> Port[DigitalLink]: |
| 295 | + return self.ic.en |
| 296 | + |
| 297 | + @override |
| 298 | + def generate(self) -> None: |
| 299 | + super().generate() |
| 300 | + |
| 301 | + with self.implicit_connect( |
| 302 | + ImplicitConnect(self.pwr_in, [Power]), |
| 303 | + ImplicitConnect(self.gnd, [Common]), |
| 304 | + ) as imp: |
| 305 | + self.fb = imp.Block( |
| 306 | + FeedbackVoltageDivider( |
| 307 | + output_voltage=(0.985, 1.015) * Volt, # across temperature range |
| 308 | + impedance=(10, 100) * kOhm, |
| 309 | + assumed_input_voltage=self.output_voltage, |
| 310 | + ) |
| 311 | + ) |
| 312 | + self.connect(self.fb.input, self.pwr_out) |
| 313 | + self.connect(self.fb.output, self.ic.fb) |
| 314 | + |
| 315 | + self.hf_cap = imp.Block(DecouplingCapacitor(capacitance=0.1 * uFarad(tol=0.2))) |
| 316 | + self.boot_cap = self.Block(BootstrapCapacitor(capacitance=0.1 * uFarad(tol=0.2))).connected( |
| 317 | + self.ic.sw, self.ic.boot |
| 318 | + ) |
| 319 | + |
| 320 | + target_frequency = self.get(self.frequency) |
| 321 | + rt_resistance = ( |
| 322 | + 30970 * ((target_frequency.upper / 1000) ** -1.027) * 1000, |
| 323 | + 30970 * ((target_frequency.lower / 1000) ** -1.027) * 1000, |
| 324 | + ) |
| 325 | + self.rt = imp.Block(AnalogSetpointResistor(rt_resistance)).connected(io=self.ic.rt) |
| 326 | + self.assign( |
| 327 | + self.actual_frequency, 30970 / (self.rt.actual_resistance / 1000) * 1000 |
| 328 | + ) # TODO add exponent term when the infrastructure supports it |
| 329 | + |
| 330 | + self.power_path = imp.Block( |
| 331 | + BuckConverterPowerPath( |
| 332 | + self.pwr_in.link().voltage, |
| 333 | + self.fb.actual_input_voltage, |
| 334 | + self.actual_frequency, |
| 335 | + self.pwr_out.link().current_drawn, |
| 336 | + (0, 1.8) * Amp, # low-side min switch current limit |
| 337 | + input_voltage_ripple=self.input_ripple_limit, |
| 338 | + output_voltage_ripple=self.output_ripple_limit, |
| 339 | + dutycycle_limit=(0, 0.97), # goes into PFM at low duty cycles |
| 340 | + ) |
| 341 | + ) |
| 342 | + # ForcedVoltage needed to provide a voltage value so current downstream can be calculated |
| 343 | + # and then the power path can generate |
| 344 | + (self.forced_out,), _ = self.chain( |
| 345 | + self.power_path.pwr_out, self.Block(ForcedVoltage(self.fb.actual_input_voltage)), self.pwr_out |
| 346 | + ) |
| 347 | + self.connect(self.power_path.switch, self.ic.sw) |
0 commit comments