diff options
-rwxr-xr-x | python/resistor_selector.py | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/python/resistor_selector.py b/python/resistor_selector.py new file mode 100755 index 0000000..44de3e4 --- /dev/null +++ b/python/resistor_selector.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 + + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + + +import argparse +import dataclasses +from decimal import getcontext, Decimal + + +DESCRIPTION = """Help resistor value calculation for hardware version detection""" +# fmt: off +IMPLEMENTED_SERIES = { + 24: (1.0, 1.1, 1.2, 1.3, 1.5, 1.6, 1.8, 2.0, 2.2, 2.4, 2.7, 3.0, 3.3, 3.6, 3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8, 7.5, 8.2, 9.1), +} +"""Series are hardcoded from Wikipedia since they do not follow the formula""" +# fmt: on +MULTIPLIERS = ( + 100.0, + 1000.0, + 10000.0, +) +TOLERANCE = Decimal(1.0) + + +def main() -> None: + getcontext().prec = 3 + + args = Arguments.from_cli() + + resistors: list[Resistor] = [] + for multiplier in MULTIPLIERS: + for value in IMPLEMENTED_SERIES[args.series]: + resistors.append( + Resistor( + resistance=Decimal(multiplier) * Decimal(value), + tolerance=TOLERANCE, + ) + ) + + voltage_dividers = [] + for r1 in resistors: + for r2 in resistors: + combination = VoltageDivider(voltage=args.voltage, r1=r1, r2=r2) + voltage_dividers.append(combination) + + sorted_voltage_dividers = sorted(voltage_dividers, key=lambda c: c.v_adc) + + filtered_voltage_dividers = filter(sorted_voltage_dividers) + + results = VoltageDividers( + combinations=tuple[VoltageDivider, ...](filtered_voltage_dividers) + ) + + print( + results.to_tsv( + voltage=args.voltage, + n_bits_adc=args.n_bits_adc, + ) + ) + + +@dataclasses.dataclass(frozen=True, kw_only=True) +class Arguments: + series: int + voltage: Decimal # volts + n_bits_adc: int # number of bits + + def __post_init__(self) -> None: + assert isinstance(self.series, int) + assert self.series in IMPLEMENTED_SERIES + + assert isinstance(self.voltage, Decimal) + assert 0.0 < self.voltage + + assert isinstance(self.n_bits_adc, int) + assert self.n_bits_adc > 0 + + @staticmethod + def from_cli() -> "Arguments": + parser = argparse.ArgumentParser( + description=DESCRIPTION, + ) + + parser.add_argument( + "-s", + "--series", + default=24, + help=f"resistor E series (supported: {[k for k in IMPLEMENTED_SERIES]})", + ) + + parser.add_argument("-v", "--voltage", default=3.3, help="in volts") + + parser.add_argument("-b", "--bits", default=12, help="number of ADC bits") + + args = parser.parse_args() + + return Arguments( + series=int(args.series), + voltage=Decimal(args.voltage), + n_bits_adc=int(args.bits), + ) + + +@dataclasses.dataclass(frozen=True, kw_only=True) +class Resistor: + resistance: Decimal # ohm + tolerance: Decimal # percent + + def __post_init__(self) -> None: + assert isinstance(self.resistance, Decimal) + assert self.resistance >= Decimal(0.0) + + assert isinstance(self.tolerance, Decimal) + assert self.tolerance >= Decimal(0.0) + + def __str__(self) -> str: + return f"{float(self.resistance)} Ohm ({float(self.tolerance)} %)" + + @property + def resistance_min(self) -> Decimal: + return self.resistance * (Decimal(1.0) - (self.tolerance / Decimal(100.0))) + + @property + def resistance_max(self) -> Decimal: + return self.resistance * (Decimal(1.0) + (self.tolerance / Decimal(100.0))) + + +@dataclasses.dataclass(frozen=True, kw_only=True) +class VoltageDivider: + voltage: Decimal # voltage over both resistors in volts + r1: Resistor # resistor closer to + + r2: Resistor # resistor closer to - + + @property + def power(self) -> Decimal: + return self.voltage**2 / (self.r1.resistance + self.r2.resistance) + + @property + def v_adc_min(self) -> Decimal: + return self.voltage / ( + Decimal(1.0) + self.r1.resistance_max / self.r2.resistance_min + ) + + @property + def v_adc(self) -> Decimal: + return self.voltage / (Decimal(1.0) + self.r1.resistance / self.r2.resistance) + + @property + def v_adc_max(self) -> Decimal: + return self.voltage / ( + Decimal(1.0) + self.r1.resistance_min / self.r2.resistance_max + ) + + @staticmethod + def volts_to_n_adc( + max_voltage: Decimal, voltage_adc: Decimal, n_bits_adc: int + ) -> int: + n_max = 2**n_bits_adc - 1 + continuous = voltage_adc / max_voltage * Decimal(n_max) + discrete = int(continuous + Decimal(0.5)) + return discrete + + def n_adc_min(self, n_bits_adc: int) -> int: + return self.volts_to_n_adc( + max_voltage=self.voltage, + voltage_adc=self.v_adc_min, + n_bits_adc=n_bits_adc, + ) + + def n_adc(self, n_bits_adc: int) -> int: + return self.volts_to_n_adc( + max_voltage=self.voltage, + voltage_adc=self.v_adc, + n_bits_adc=n_bits_adc, + ) + + def n_adc_max(self, n_bits_adc: int) -> int: + return self.volts_to_n_adc( + max_voltage=self.voltage, + voltage_adc=self.v_adc_max, + n_bits_adc=n_bits_adc, + ) + + def to_tsv(self, voltage: Decimal, n_bits_adc: int) -> str: + return ( + f"{self.r1}" + f"\t{self.r2}" + f"\t{self.v_adc_min}" + f"\t{self.v_adc}" + f"\t{self.v_adc_max}" + f"\t0x{self.n_adc_min(n_bits_adc=n_bits_adc):03X}" + f"\t0x{self.n_adc(n_bits_adc=n_bits_adc):03X}" + f"\t0x{self.n_adc_max(n_bits_adc=n_bits_adc):03X}" + f"\t{self.power}" + ) + + +@dataclasses.dataclass(frozen=True, kw_only=True) +class VoltageDividers: + combinations: tuple[VoltageDivider, ...] + + def to_tsv(self, voltage: Decimal, n_bits_adc: int) -> str: + output = ( + "R1" + "\tR2" + "\tV_ADC_min" + "\tV_ADC" + "\tV_ADC_max" + "\tn_ADC_min" + "\tn_ADC" + "\tn_ADC_max" + "\tpower" + ) + for combination in self.combinations: + output += "\n" + combination.to_tsv( + voltage=voltage, + n_bits_adc=n_bits_adc, + ) + return output + + +def filter(source: list[VoltageDivider]) -> list[VoltageDivider]: + sink: list[VoltageDivider] = [source[0]] + + for combination in source: + candidate_min = combination.v_adc_min + current_max = sink[-1].v_adc_max + if candidate_min > current_max: + sink.append(combination) + + return sink + + +if __name__ == "__main__": + main() |