Skip to content
This repository was archived by the owner on Nov 11, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ node_modules/
.env
.env.test

# Python
__pycache__

# Misc
tmp
.DS_Store
Expand Down
80 changes: 80 additions & 0 deletions tools/iccc/bd9e302efj.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
# Copyright 2021 The Racklet Authors
# SPDX-License-Identifier: Apache-2.0

# Tool for computing component constants for the ROHM Semiconductor BD9E302EFJ.
# Component naming and computed values apply to the Application Example 2 (Fast load response)
# in https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/switching_regulator/bd9e302efj-e.pdf.

import math

from iccc import IC, Component
from quantities import Quantity
from util import VoltageDivider

# Common IEC60063 series for components
series = "E96"


class BD9E302EFJ(IC):
target_voltage_div = VoltageDivider(
lambda v, r1, r2: (r1 + r2) / r2 * v['V_fb'],
"V_tgt", "R_dmin", "R_dmax", "R_div series", minimum_magnitude=4
)

valuations = {
"L": lambda v: Component(
# Conditional alternate calculation from the datasheet
v["V_in"] / (4 * v["F_osc"] * v["dI_l"]) if v["V_out"] * 2 > v["V_in"] else
v["V_out"] * (v["V_in"] - v["V_out"]) * 1 / (v["V_in"] * v["F_osc"] * v["dI_l"]),
"H", series, True
),
"L saturation min": lambda v: v["A_max"] + 0.5 * v["dI_l"],
"Voltage ripple": lambda v: (v["dI_l"] * (v["C_out ESR"] + 1 /
(8 * v["C_out CAP"] * v["F_osc"]))).rescale("mV"),
"V_out": target_voltage_div.voltage(),
"R1": target_voltage_div.r1(),
"R2": target_voltage_div.r2(),
"R3": lambda v: Component(2 * math.pi * v["V_out"] * v["F_crs"] * v["C_out CAP"] /
(v["V_fb"] * v["G_mp"] * v["G_ma"]), "ohm", series, True),
"C1": lambda v: Component(1 / (2 * math.pi * v["R1"] * 20000), "F", series, True),
"C2": lambda v: Component(min(1 / (2 * math.pi * v["R3"] * v["F_z"]), 15E-9), "F", series, True),
}


if __name__ == "__main__":
# TODO: Replace Quantity with q.<unit>
config = {
"F_osc": Quantity(550E3, "Hz"), # This is a constant in the BD9E302EFJ
"V_in": Quantity(24, "V"), # Input voltage
"A_max": Quantity(3, "A"), # Maximum output current
"dI_l": Quantity(1, "A"), # Inductor ripple current
"V_fb": Quantity(0.8, "V"), # Feedback reference voltage
"G_mp": Quantity(20, "A/V"), # Current sense gain
"G_ma": Quantity(140E-6, "A/V"), # Error amplifier transconductance
"C_out CAP": Quantity(47E-6, "F"), # Capacitance of C_out
"C_out ESR": Quantity(2E-3, "ohm"), # Equivalent series resistance of C_out
"V_tgt": Quantity(5.1, "V"), # Lower bound of the target voltage
"R_dmin": Quantity(500E3, "ohm"), # Minimum resistance of the voltage divider
"R_dmax": Quantity(700E3, "ohm"), # Maximum resistance of the voltage divider (specified in datasheet)
"R_div series": "E96", # The resistor series to use in the voltage divider
}

# Optimize for fast load response
fast_response = True

# According to the datasheet F_crs should typically be around 20 KHz. For
# the two fast load response application examples it has been set to around
# 24 KHz, and for the two others it is around 16 Khz (reverse-engineered
# from the equation for the phase compensation resistor R3).
config["F_crs"] = Quantity(24000 if fast_response else 16000, "Hz")

# The datasheet recommends inserting a zero point under 1/6 of F_crs.
# All application examples use a 6.8 nF capacitance for C2, based on
# which the actual multipliers are roughly 1/12 with fast load response
# and 1/6 without.
config["F_z"] = (1 / 12 if fast_response else 1 / 6) * config["F_crs"]

ic = BD9E302EFJ(config)
for k in ic.valuations.keys():
print(f"{k}: {ic.evaluated[k]}")
155 changes: 155 additions & 0 deletions tools/iccc/bd9e302efj_old.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#!/usr/bin/env python3
# Copyright 2021 The Racklet Authors
# SPDX-License-Identifier: Apache-2.0

# Tool for computing component constants for the ROHM Semiconductor BD9E302EFJ.
# Component naming and computed values apply to the Application Example 2 (Fast load response)
# in https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/switching_regulator/bd9e302efj-e.pdf.

import itertools
import iec60063
import math
from tabulate import tabulate


def avg(*args):
return sum(args) / len(args)


# BD9E constants
F_osc = 550000 # Hertz (this can't be changed)

# Variables
# TODO: Take these as command line arguments?
V_in = 24 # Volts, input voltage
A_max = 3 # Amperes, maximum output current
dI_L = 1.0 # Amperes, inductor ripple current
V_fb = 0.8 # Volts, feedback reference voltage
G_mp = 20 # A/V, current sense gain
G_ma = 140E-6 # A/V, error amplifier transconductance

# Output capacitor C_out specifications
C_C_out = 47E-6 # Farads, capacitance of C_out
R_C_out = 2E-3 # Ohms, equivalent series resistance of C_out

V_out_min = 5.1 # Volts, minimum voltage for V_out

R_div_max = 700E3 # 700 KOhm maximum from the specification
R_div_min = 500E3 # 500 KOhm minimum


def compute_V_out(dR1, dR2):
return (dR1 + dR2) / dR2 * V_fb


def generate_resistors(multipliers):
result = []
for m in multipliers:
result += [m * float(v) for v in iec60063.E96]
return result


def resolve_V_out():
resolved = False
(b_voltage, b_R1, b_R2) = (math.inf, 0, 0)
for d_R1, d_R2 in itertools.permutations(generate_resistors([10000, 100000]), 2):
d_sum = d_R1 + d_R2
if d_sum > R_div_max or d_sum < R_div_min:
continue
value = compute_V_out(d_R1, d_R2)
if value < V_out_min:
continue
if value < b_voltage:
resolved = True
(b_voltage, b_R1, b_R2) = (value, d_R1, d_R2)
assert resolved
return b_voltage, b_R1, b_R2


V_out, R1, R2 = resolve_V_out()

# Enable fast load response
fast_response = True

# According to the datasheet F_crs should typically be around 20 KHz. For
# the two fast load response application examples it has been set to around
# 24 KHz, and for the two others it is around 16 Khz (reverse-engineered
# from the equation for the phase compensation resistor R3).
F_crs = 24000 if fast_response else 16000 # Hertz

# The datasheet recommends inserting a zero point under 1/6 of F_crs.
# All application examples use a 6.8 nF capacitance for C2, based on
# which the actual multipliers are roughly 1/12 with fast load response
# and 1/6 without.
F_z = (1/12 if fast_response else 1 / 6) * F_crs


# Inductance of the external inductor L
def L_inductance():
if V_out * 2 > V_in:
return V_in / (4 * F_osc * dI_L) # Alternate calculation from the datasheet
return V_out * (V_in - V_out) * 1 / (V_in * F_osc * dI_L)


def L_saturation_A_min():
return A_max + 0.5 * dI_L


def voltage_ripple():
return dI_L * (R_C_out + 1 / (8 * C_C_out * F_osc))


def R3_resistance():
return 2 * math.pi * V_out * F_crs * C_C_out / (V_fb * G_mp * G_ma)


def C1_capacitance():
return 1 / (2 * math.pi * R1 * 20000)


def C2_capacitance():
return min(1 / (2 * math.pi * R3_resistance() * F_z), 15E-9) # Upper limit from the datasheet


def sub(s):
return f"<sub>{s}</sub>"


def table_format(d):
return tabulate(d, headers=["Parameter", "Value"], tablefmt="github")


configuration = [
["Frequency", F_osc],
["Input voltage", V_in],
["Maximum current", A_max],
["Ripple current", dI_L],
["Feedback reference V", V_fb],
["Current sense gain", G_mp],
["Error amp transconductance", G_ma],
[f"C{sub('out')} capacitance", C_C_out],
[f"C{sub('out')} ESR", R_C_out],
[f"Minimum V{sub('out')}", V_out_min],
["Fast load response", fast_response],
[f"F{sub('CRS')} frequency", F_crs],
[f"F{sub('Z')} frequency", F_z],
]

computed = [
["Output voltage", V_out],
["L inductance", L_inductance()],
["L saturation A min", L_saturation_A_min()],
["Voltage ripple", voltage_ripple()],
[f"C{sub(1)} capacitance", C1_capacitance()],
[f"C{sub(2)} capacitance", C2_capacitance()],
[f"R{sub(1)} resistance", R1],
[f"R{sub(2)} resistance", R2],
[f"R{sub(1)} + R{sub(2)}", R1 + R2],
[f"R{sub(3)} resistance", R3_resistance()],
]

print("Configured input values:")
print(table_format(configuration))
print()
print("Computed results:")
print(table_format(computed))
32 changes: 32 additions & 0 deletions tools/iccc/iccc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copyright 2021 The Racklet Authors
# SPDX-License-Identifier: Apache-2.0

import lazydict
from quantities import Quantity


# Integrated Circuit Component Configurator (iccc) is a tool to help with computing
# correct values for external components of various integrated circuits. An integrated
# circuit should be represented as a class that inherits IC and fills in the valuations.
# See the implementation for ROHM's BD9E302EFJ (bd9e302efj.py) as an example.

class IC:
# Named valuations (components properties, voltages, currents) for the IC
valuations = {}

def __init__(self, external_variables):
self.evaluated = lazydict.LazyDictionary(self.valuations)
self.evaluated.update(external_variables)


class Component(Quantity):
def __new__(cls, data, unit, series, round_up, dtype=None, copy=True):
if isinstance(data, Quantity):
# We need to drop units here since datasheet formulas might not
# necessarily be "mathematically valid" wrt. the quantities.
data = data.simplified.magnitude
cls.series = series
cls.round_up = round_up
return super().__new__(cls, data, unit, dtype, copy)

# TODO: __str__ that automatically resolves micro, nano, pico...
75 changes: 75 additions & 0 deletions tools/iccc/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Copyright 2021 The Racklet Authors
# SPDX-License-Identifier: Apache-2.0

import iec60063
import itertools
import math

import quantities as q


class VoltageDivider:
"""
Generate a resistor voltage divider.

:param compute_v: function to use for computing the target voltage
:param target_voltage: target voltage for the divider as determined by compute_v
:param sum_min: minimum total resistance of the voltage divider
:param sum_max: maximum total resistance of the voltage divider
:param series: resistor series to use, one of iec60063.all_series
:param minimum: should the given target voltage be treated as a minimum instead of maximum
:param minimum_magnitude: the minimum magnitude (power of 10) to consider for candidate resistors
:return: triple containing the final resistor values and resulting voltage
"""

def __init__(self, compute_v, target_voltage, sum_min, sum_max, series, minimum=True, minimum_magnitude=0):
self.compute_v = compute_v
self.target_voltage = target_voltage
self.sum_min = sum_min
self.sum_max = sum_max
self.series = series
self.minimum = minimum
self.minimum_magnitude = minimum_magnitude

def compute(self, v):
series = v[self.series]
sum_max = v[self.sum_max].simplified.magnitude
sum_min = v[self.sum_min].simplified.magnitude
target_voltage = v[self.target_voltage].simplified.magnitude
assert sum_max > sum_min

resistances = []
maximum_magnitude = math.floor(math.log10(sum_max)) + 1
assert maximum_magnitude > self.minimum_magnitude
for i in range(self.minimum_magnitude, maximum_magnitude):
ohms = [float(n) * (10 ** i) * q.ohm for n in iec60063.get_series(series)]
resistances += [n for n in ohms if n < sum_max]

solution_found = False
(best_voltage, best_r1, best_r2) = ((math.inf if self.minimum else -math.inf) * q.V, 0 * q.ohm, 0 * q.ohm)
for r1, r2 in itertools.permutations(resistances, 2):
r_sum = r1 + r2
if r_sum > sum_max or r_sum < sum_min:
continue
voltage = self.compute_v(v, r1, r2)
if (self.minimum and voltage < target_voltage) or (not self.minimum and voltage > target_voltage):
continue
if (self.minimum and voltage < best_voltage) or (not self.minimum and voltage > best_voltage):
solution_found = True
(best_voltage, best_r1, best_r2) = (voltage, r1, r2)
assert solution_found
v[id(self)] = (best_voltage, best_r1, best_r2)

def retrieve(self, v, i):
if not id(self) in v:
self.compute(v)
return v[id(self)][i]

def voltage(self):
return lambda v: self.retrieve(v, 0)

def r1(self):
return lambda v: self.retrieve(v, 1)

def r2(self):
return lambda v: self.retrieve(v, 2)