Skip to content

Commit 6e3a290

Browse files
authored
Merge pull request #16 from upb-lea/develop
add area/volume LUT for balancing resistors
2 parents ba51188 + ecda370 commit 6e3a290

7 files changed

Lines changed: 125 additions & 6 deletions

File tree

docs/wordlist

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,6 @@ pllsi
8484
CKC
8585
CAS
8686
SMD
87+
ni
88+
pdf
89+
vishay

pecst/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@
1414
from pecst.film import *
1515
from pecst.ceramic import *
1616
from pecst.electrolytic import *
17+
18+
# resistor data (for balancing electrolytic capacitors)
19+
from pecst.resistor import *

pecst/constants.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
ELECTROLYTIC_CAPACITOR_DATA_DIRECTORY = "electrolytic_capacitor_data"
3737
ELECTROLYTIC_CAPACITOR_DOWNLOAD_DIRECTORY = "electrolytic_downloads"
3838
CERAMIC_CAPACITOR_DOWNLOAD_DIRECTORY = "ceramic_downloads"
39+
RESISTORS_DATA_DIRECTORY = "resistor_data"
3940

4041
# film capacitor settings
4142
FILM_CAPACITOR_SERIES_NAME_LIST = ["B3271*P", "B3272*AGT", "B3277*P"]
@@ -47,6 +48,11 @@
4748
ELECTROLYTIC_DEFAULT_TOLERANCE_PERCENT = CapacitanceTolerance.TwentyPercent
4849
ELECTROLYTIC_CAPACITOR_SERIES_VALUES = "series_values"
4950

51+
# parallel resistor settings (for electrolytic capacitors)
52+
# from source https://www.vishay.com/docs/28730/ac_ac-at_ac-ni.pdf
53+
# currently only a single series is allowed!
54+
RESISTOR_SERIES_NAME = "ac"
55+
5056
# SMD ceramic capacitor section
5157
SMD_SIZE_DICT = {"size": ["C0402", "C0603", "C0805", "C1206", "C1210", "C1808", "C1812", "C1825", "C2220", "C2225", "C3040", "CAN06", "CAN08", "CAN12",
5258
"CAN13", "CAN17", "CAN18", "CAN19", "CAN21", "CAN22", "CKC18", "CKC21", "CKC33", "CAS21"],

pecst/electrolytic/selection.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
from pecst.electrolytic.current_capability import parallel_electrolytic_capacitors_lifetime_current_capability
2121
from pecst.electrolytic.power_loss import power_loss_per_electrolytic_capacitor, calc_leakage_currents
2222
from pecst.electrolytic.capacitance_change import calc_capacitance_factor_frequency, calc_capacitance_factor_temperature
23-
from pecst.electrolytic.resistors import generate_resistor_list, calculate_r_parallel_max, look_for_closest_smaller_resistance, loss_per_resistor
23+
from pecst.resistor.resistors import (generate_resistor_list, calculate_r_parallel_max, look_for_closest_smaller_resistance, loss_per_resistor,
24+
select_resistor_area_volume)
25+
from pecst.resistor.read_resistor_database import load_resistors
2426

2527
logger = logging.getLogger(__name__)
2628

@@ -205,10 +207,6 @@ def select_electrolytic_capacitors(c_requirements: CapacitorRequirements) -> tup
205207

206208
logger.info(f"After in parallel sort-out: {c_db.head()=}")
207209

208-
# volume calculation
209-
logger.debug("Volume calculation.")
210-
c_db["volume_total"] = c_db["in_parallel_needed"] * c_db["in_series_needed"] * c_db["volume"]
211-
212210
# loss calculation per capacitor
213211
logger.debug("Power loss estimation by ESR.")
214212

@@ -239,8 +237,21 @@ def select_electrolytic_capacitors(c_requirements: CapacitorRequirements) -> tup
239237
c_db.loc[:, 'power_loss_total'] = c_db.loc[:, 'power_loss_per_capacitor'] * c_db["in_parallel_needed"] * c_db["in_series_needed"] + \
240238
c_db["in_series_needed"] * c_db["loss_per_resistor"]
241239

240+
# load resistor database
241+
r_df = load_resistors("ac")
242+
243+
# resistor volume calculation
244+
c_db[["area_per_resistor", "volume_per_resistor"]] = c_db.apply(lambda x, r=r_df: select_resistor_area_volume(
245+
x["loss_per_resistor"], c_requirements.temperature_ambient, r), axis=1)
246+
242247
# calculate minimum required PCB area
243-
c_db["area_total"] = c_db["area"] * c_db["in_parallel_needed"] * c_db["in_series_needed"]
248+
c_db["area_total"] = c_db["area"] * c_db["in_parallel_needed"] * c_db["in_series_needed"] + \
249+
c_db["in_series_needed"] * c_db["area_per_resistor"]
250+
251+
# volume calculation
252+
logger.debug("Volume calculation.")
253+
c_db["volume_total"] = c_db["in_parallel_needed"] * c_db["in_series_needed"] * c_db["volume"] + \
254+
c_db["in_series_needed"] * c_db["volume_per_resistor"]
244255

245256
if not os.path.exists(c_requirements.results_directory):
246257
os.makedirs(c_requirements.results_directory)

pecst/resistor/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Initialize the balancing resistor calculation."""
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Read the resistor database."""
2+
3+
# python libraries
4+
import pathlib
5+
import logging
6+
7+
# 3rd party libraries
8+
import pandas as pd
9+
import numpy as np
10+
11+
# own libraries
12+
from pecst import constants as const
13+
14+
logger = logging.getLogger(__name__)
15+
16+
def load_resistors(resistor_series_name: str) -> pd.DataFrame:
17+
"""
18+
Load resistors from the database.
19+
20+
:param resistor_series_name: name of the resistor series to download
21+
:type resistor_series_name: str
22+
:return: unified list of resistors
23+
:rtype: tuple[pandas.DataFrame]
24+
"""
25+
# resistor data
26+
path = pathlib.Path(__file__)
27+
28+
resistor_series_path = pathlib.PurePath(path.parents[1], const.RESISTORS_DATA_DIRECTORY, resistor_series_name)
29+
30+
database_path = pathlib.PurePath(resistor_series_path, f"{resistor_series_name}.csv")
31+
r_df = pd.read_csv(database_path, sep=',', decimal='.')
32+
33+
# transfer the datasheet given units to SI units
34+
r_df['area'] = r_df["diameter"].astype(float) * r_df["length"].astype(float)
35+
r_df['volume'] = (r_df["diameter"].astype(float) / 2) ** 2 * np.pi * r_df["length"].astype(float)
36+
37+
return r_df
38+
39+
40+
if __name__ == "__main__":
41+
resistor_df = load_resistors("ac")
42+
print(f"{resistor_df}")
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
# 3rd party libraries
44
import numpy as np
5+
import pandas as pd
56

67
# own libraries
8+
from pecst.resistor.read_resistor_database import load_resistors
79

810
def generate_resistor_list(e_series_basic_list, potency_list):
911
"""
@@ -63,6 +65,24 @@ def look_for_closest_smaller_resistance(r_max: float, resistor_list: list) -> fl
6365
r_closest = resistor_list[index_r_closest]
6466
return float(r_closest)
6567

68+
69+
def look_for_closest_higher_power(power: float, power_list: list) -> float:
70+
"""
71+
Look for the closest higher power in power_list than the given power.
72+
73+
:param power: power in W
74+
:type power: float
75+
:param power_list: list of power
76+
:type power_list: list
77+
:return: closest and higher power in the list
78+
"""
79+
if np.isnan(power):
80+
return np.nan
81+
else:
82+
index_r_closest = np.searchsorted(power_list, [power, ], side='right')[0]
83+
p_closest = power_list[index_r_closest]
84+
return float(p_closest)
85+
6686
def loss_per_resistor(voltage_per_capacitor: float, resistance: float) -> float:
6787
"""
6888
Calculate the power loss per resistor.
@@ -79,7 +99,40 @@ def loss_per_resistor(voltage_per_capacitor: float, resistance: float) -> float:
7999
else:
80100
return voltage_per_capacitor ** 2 / resistance
81101

102+
def select_resistor_area_volume(power_loss: float, ambient_temperature: float,
103+
r_df: pd.DataFrame) -> pd.Series[float]:
104+
"""
105+
Select the resistor area and volume for a given resistor power dissipation.
106+
107+
:param power_loss: resistor power loss in W
108+
:type power_loss: float
109+
:param ambient_temperature: ambient temperature in °C
110+
:type ambient_temperature: float
111+
:param r_df: resistor database
112+
:type r_df: pd.Dataframe
113+
:return: area, volume
114+
"""
115+
if power_loss == 0:
116+
area = 0.0
117+
volume = 0.0
118+
else:
119+
# interpolate power due to ambient temperature
120+
r_df["power_rating_at_ambient_temperature"] = r_df.apply(lambda x: np.interp(
121+
ambient_temperature, [-40, 40, 70], [x["power_40_degree"], x["power_40_degree"], x["power_70_degree"]]), axis=1)
122+
123+
# look for the closest higher temperature
124+
higher_rated_power = look_for_closest_higher_power(power_loss, r_df["power_rating_at_ambient_temperature"].to_list())
125+
126+
# select resistor area and volume
127+
area = r_df.loc[r_df["power_rating_at_ambient_temperature"] == higher_rated_power]["area"].values[0]
128+
volume = r_df.loc[r_df["power_rating_at_ambient_temperature"] == higher_rated_power]["volume"].values[0]
129+
return pd.Series([area, volume])
130+
82131

83132
if __name__ == "__main__":
84133
r_closest = look_for_closest_smaller_resistance(r_max=105, resistor_list=[80, 90, 100, 110, 120])
85134
print(f"{r_closest=}")
135+
resistor_df = load_resistors("ac")
136+
[area, volume] = select_resistor_area_volume(2.5, 70, resistor_df)
137+
print(f"{area=}")
138+
print(f"{volume=}")

0 commit comments

Comments
 (0)