@@ -204,6 +204,25 @@ class BuckConverterPowerPath(InternalSubcircuit, GeneratorBlock):
204204 Very detailed analysis including component sizing, operating modes, calculating losses
205205 """
206206
207+ @staticmethod
208+ def _d_inverse_d (d_range : Range ) -> Range :
209+ """Some power calculations require the maximum of D*(1-D), which has a maximum at D=0.5"""
210+ # can't use range ops since they will double-count the tolerance of D, so calculate endpoints separately
211+ range_endpoints = [d_range .lower * (1 - d_range .lower ), d_range .upper * (1 - d_range .upper )]
212+ raw_range = Range (min (range_endpoints ), max (range_endpoints ))
213+ if 0.5 in d_range : # the function has a maximum at 0.5
214+ return raw_range .hull (Range .exact (0.5 * (1 - 0.5 )))
215+ else :
216+ return raw_range
217+
218+ @staticmethod
219+ def _ripple_current_from_sw_current (sw_current : float , ripple_ratio : Range ) -> Range :
220+ """Calculates the ripple current from a total switch current and ripple ratio."""
221+ return Range ( # separate range parts to avoid double-counting tolerances
222+ sw_current / (1 + ripple_ratio .lower ) * ripple_ratio .lower ,
223+ sw_current / (1 + ripple_ratio .upper ) * ripple_ratio .upper
224+ )
225+
207226 class Values (NamedTuple ):
208227 dutycycle : Range
209228 inductance : Range
@@ -259,10 +278,9 @@ def _calculate_parameters(cls, input_voltage: Range, output_voltage: Range, freq
259278 inductance = Range .all ()
260279 min_ripple = 0.0
261280 if sw_current_limits .upper > 0 : # fallback for light-load
262- # since limits are defined in terms of the switch current which should have ripple factored in already,
263- # assume a safe-ish 0.25 ripple ratio was specified and unapply that before applying the limit ratio
264- inductance = inductance .intersect (inductance_scale / (sw_current_limits .upper / 1.25 * limit_ripple_ratio ))
265- min_ripple = sw_current_limits .upper / 1.25 * limit_ripple_ratio .lower
281+ ripple_current = cls ._ripple_current_from_sw_current (sw_current_limits .upper , limit_ripple_ratio )
282+ inductance = inductance .intersect (inductance_scale / ripple_current )
283+ min_ripple = ripple_current .lower
266284 if ripple_ratio .upper < float ('inf' ):
267285 assert ripple_ratio .lower > 0 , f"invalid non-inf ripple ratio { ripple_ratio } "
268286
@@ -283,28 +301,19 @@ def _calculate_parameters(cls, input_voltage: Range, output_voltage: Range, freq
283301
284302 return cls .Values (dutycycle = dutycycle , inductance = inductance ,
285303 input_capacitance = input_capacitance , output_capacitance = output_capacitance ,
286- inductor_avg_current = output_current , ripple_scale = inductance_scale , min_ripple = min_ripple ,
304+ inductor_avg_current = output_current / efficiency ,
305+ ripple_scale = inductance_scale , min_ripple = min_ripple ,
287306 output_capacitance_scale = output_capacitance_scale ,
288307 inductor_peak_currents = inductor_peak_currents ,
289308 effective_dutycycle = effective_dutycycle )
290309
291- @staticmethod
292- def _d_inverse_d (d_range : Range ) -> Range :
293- """Some power calculations require the maximum of D*(1-D), which has a maximum at D=0.5"""
294- # can't use range ops since they will double-count the tolerance of D, so calculate endpoints separately
295- range_endpoints = [d_range .lower * (1 - d_range .lower ), d_range .upper * (1 - d_range .upper )]
296- raw_range = Range (min (range_endpoints ), max (range_endpoints ))
297- if 0.5 in d_range : # the function has a maximum at 0.5
298- return raw_range .hull (Range .exact (0.5 * (1 - 0.5 )))
299- else :
300- return raw_range
301-
302310 @staticmethod
303311 @ExperimentalUserFnPartsTable .user_fn ([float , float , float ])
304312 def _buck_inductor_filter (max_avg_current : float , ripple_scale : float , min_ripple : float ):
305313 """Applies further filtering to inductors using the trade-off between inductance and peak-peak current.
306- max_avg_current is the maximum average current (not accounting for ripple) seem by the inductor
307- ripple_scale is the scaling factor from 1/L to ripple, Vo/(Vi-Vo)/fs/Vi"""
314+ max_avg_current is the maximum average current (not accounting for ripple) seen by the inductor
315+ ripple_scale is the scaling factor from 1/L to ripple
316+ This structure also works for boost converters, which would have its ripple_scale calculated differently."""
308317 def filter_fn (row : PartsTableRow ) -> bool :
309318 ripple_current = max (ripple_scale / row [TableInductor .INDUCTANCE ].lower , min_ripple )
310319 max_current_pp = max_avg_current + ripple_current / 2
@@ -377,7 +386,7 @@ def generate(self) -> None:
377386
378387 self .inductor = self .Block (Inductor (
379388 inductance = values .inductance * Henry ,
380- current = self . output_current , # min-bound only, the real filter happens in the filter_fn
389+ current = values . inductor_avg_current , # min-bound only, the real filter happens in the filter_fn
381390 frequency = self .frequency ,
382391 experimental_filter_fn = ExperimentalUserFnPartsTable .serialize_fn (
383392 self ._buck_inductor_filter , values .inductor_avg_current .upper , values .ripple_scale , values .min_ripple )
@@ -389,8 +398,8 @@ def generate(self) -> None:
389398 )))
390399 self .connect (self .pwr_out , self .inductor .b .adapt_to (VoltageSource (
391400 voltage_out = self .output_voltage ,
392- current_limits = BuckConverterPowerPath ._ilim_expr (self .inductor .actual_current_rating , self .sw_current_limits ,
393- self .actual_inductor_current_ripple )
401+ current_limits = self ._ilim_expr (self .inductor .actual_current_rating , self .sw_current_limits ,
402+ self .actual_inductor_current_ripple )
394403 )))
395404
396405 self .in_cap = self .Block (DecouplingCapacitor (
@@ -407,9 +416,8 @@ def generate(self) -> None:
407416@abstract_block_default (lambda : IdealBoostConverter )
408417class BoostConverter (SwitchingVoltageRegulator ):
409418 """Step-up switching converter"""
410- def __init__ (self , * args , ripple_current_factor : RangeLike = (0.2 , 0.5 ), ** kwargs ) -> None :
411- # TODO default ripple is very heuristic, intended 0.3-0.4, loosely adjusted for inductor tolerance
412- super ().__init__ (* args , ripple_current_factor = ripple_current_factor , ** kwargs )
419+ def __init__ (self , * args , ** kwargs ) -> None :
420+ super ().__init__ (* args , ** kwargs )
413421 self .require (self .pwr_out .voltage_out .lower () >= self .pwr_in .voltage_limits .lower ())
414422
415423
@@ -464,7 +472,7 @@ def _calculate_parameters(cls, input_voltage: Range, output_voltage: Range, freq
464472 """See BuckConverterPowerPath._calculate_parameters, this performs a similar function."""
465473 dutycycle = 1 - input_voltage / output_voltage * efficiency
466474 effective_dutycycle = dutycycle .bound_to (dutycycle_limit ) # account for tracking behavior
467- inductor_avg_current = output_current * ( output_voltage / input_voltage )
475+ inductor_avg_current = output_current / ( 1 - effective_dutycycle )
468476
469477 # calculate minimum inductance based on worst case values (operating range corners producing maximum inductance)
470478 # worst-case input/output voltages and frequency is used to avoid double-counting tolerances as ranges
@@ -482,10 +490,9 @@ def _calculate_parameters(cls, input_voltage: Range, output_voltage: Range, freq
482490 inductance = Range .all ()
483491 min_ripple = 0.0
484492 if sw_current_limits .upper > 0 : # fallback for light-load
485- # since limits are defined in terms of the switch current which should have ripple factored in already,
486- # assume a safe-ish 0.25 ripple ratio was specified and unapply that before applying the limit ratio
487- inductance = inductance .intersect (inductance_scale / (sw_current_limits .upper / 1.25 * limit_ripple_ratio ))
488- min_ripple = sw_current_limits .upper / 1.25 * limit_ripple_ratio .lower
493+ ripple_current = BuckConverterPowerPath ._ripple_current_from_sw_current (sw_current_limits .upper , limit_ripple_ratio )
494+ inductance = inductance .intersect (inductance_scale / ripple_current )
495+ min_ripple = ripple_current .lower
489496 if ripple_ratio .upper < float ('inf' ):
490497 assert ripple_ratio .lower > 0 , f"invalid non-inf ripple ratio { ripple_ratio } "
491498 inductance = inductance .intersect (inductance_scale / (inductor_avg_current .upper * ripple_ratio ))
@@ -570,7 +577,7 @@ def generate(self) -> None:
570577
571578 self .inductor = self .Block (Inductor (
572579 inductance = values .inductance * Henry ,
573- current = values .inductor_peak_currents , # min-bound only, the real filter happens in the filter_fn
580+ current = values .inductor_avg_current , # min-bound only, the real filter happens in the filter_fn
574581 frequency = self .frequency ,
575582 experimental_filter_fn = ExperimentalUserFnPartsTable .serialize_fn (
576583 BuckConverterPowerPath ._buck_inductor_filter ,
@@ -602,9 +609,8 @@ def generate(self) -> None:
602609@abstract_block_default (lambda : IdealVoltageRegulator )
603610class BuckBoostConverter (SwitchingVoltageRegulator ):
604611 """Step-up or switch-down switching converter"""
605- def __init__ (self , * args , ripple_current_factor : RangeLike = (0.2 , 0.5 ), ** kwargs ) -> None :
606- # TODO default ripple is very heuristic, intended 0.3-0.4, loosely adjusted for inductor tolerance
607- super ().__init__ (* args , ripple_current_factor = ripple_current_factor , ** kwargs )
612+ def __init__ (self , * args , ** kwargs ) -> None :
613+ super ().__init__ (* args , ** kwargs )
608614
609615
610616@abstract_block_default (lambda : IdealVoltageRegulator )
@@ -705,7 +711,7 @@ def generate(self) -> None:
705711
706712 self .inductor = self .Block (Inductor (
707713 inductance = buck_values .inductance .intersect (boost_values .inductance ) * Henry ,
708- current = buck_values .inductor_peak_currents .hull (boost_values .inductor_peak_currents ),
714+ current = buck_values .inductor_avg_current .hull (boost_values .inductor_avg_current ),
709715 frequency = self .frequency ,
710716 experimental_filter_fn = ExperimentalUserFnPartsTable .serialize_fn (
711717 BuckConverterPowerPath ._buck_inductor_filter ,
0 commit comments