summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxengineering <me@xengineering.eu>2025-05-08 21:50:56 +0200
committerxengineering <me@xengineering.eu>2025-05-13 21:19:52 +0200
commit1618a803741cfe9091a8f6dccfd775d216822100 (patch)
tree3b600781c785b3bab64a13ed85ca0b8833871c5f
parent542d031d3ba44e7867e214891a87d2ba9d59ec9e (diff)
downloadiot-contact-1618a803741cfe9091a8f6dccfd775d216822100.tar
iot-contact-1618a803741cfe9091a8f6dccfd775d216822100.tar.zst
iot-contact-1618a803741cfe9091a8f6dccfd775d216822100.zip
WIP: python: Add resistor_selector.pyhardware-versioning
-rwxr-xr-xpython/resistor_selector.py239
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()