#!/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()