summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rwxr-xr-xtools/build_zephyr.py5
-rwxr-xr-xtools/configure_zephyr.py2
-rwxr-xr-xtools/make_factory_image.py95
-rw-r--r--tools/meson.build1
-rwxr-xr-xtools/resistor_selector.py327
5 files changed, 425 insertions, 5 deletions
diff --git a/tools/build_zephyr.py b/tools/build_zephyr.py
index 1d9e783..5dd9e47 100755
--- a/tools/build_zephyr.py
+++ b/tools/build_zephyr.py
@@ -38,10 +38,7 @@ def main() -> None:
check=True,
)
- shutil.copy(
- build_tree / "zephyr" / args.binary_name,
- output_dir / args.target_name
- )
+ shutil.copy(build_tree / "zephyr" / args.binary_name, output_dir / args.target_name)
if __name__ == "__main__":
diff --git a/tools/configure_zephyr.py b/tools/configure_zephyr.py
index e709063..f4707c6 100755
--- a/tools/configure_zephyr.py
+++ b/tools/configure_zephyr.py
@@ -43,7 +43,7 @@ def main() -> None:
command.append(f"-DEXTRA_CONF_FILE={args.extra_config}")
if args.signing_key is not None:
- command.append(f"-DCONFIG_BOOT_SIGNATURE_KEY_FILE=\"{args.signing_key}\"")
+ command.append(f'-DCONFIG_BOOT_SIGNATURE_KEY_FILE="{args.signing_key}"')
subprocess.run(command, shell=False, check=True)
diff --git a/tools/make_factory_image.py b/tools/make_factory_image.py
new file mode 100755
index 0000000..735f657
--- /dev/null
+++ b/tools/make_factory_image.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python3
+
+
+import argparse
+import dataclasses
+import pathlib
+
+
+PADDING_BYTE: bytes = b"\xff"
+
+
+def main() -> None:
+ args = Arguments.from_cli()
+ print(args)
+
+ factory_image: bytes = join(
+ bootloader=args.bootloader.read_bytes(),
+ application=args.application.read_bytes(),
+ offset=args.offset,
+ )
+
+ args.factory_image.write_bytes(factory_image)
+
+
+def join(bootloader: bytes, application: bytes, offset: int) -> bytes:
+ padding = PADDING_BYTE * (offset - len(bootloader))
+
+ return bootloader + padding + application
+
+
+@dataclasses.dataclass
+class Arguments:
+ bootloader: pathlib.Path
+ offset: int
+ application: pathlib.Path
+ factory_image: pathlib.Path
+
+ def __post_init__(self) -> None:
+ assert isinstance(self.bootloader, pathlib.Path)
+
+ assert isinstance(self.offset, int)
+ assert self.offset >= 0
+
+ assert isinstance(self.application, pathlib.Path)
+
+ assert isinstance(self.factory_image, pathlib.Path)
+
+ def __str__(self) -> str:
+ return f"""{__file__} \\
+ --bootloader {self.bootloader} \\
+ --offset 0x{self.offset:X} \\
+ --application {self.application} \\
+ --factory_image {self.factory_image}"""
+
+ @staticmethod
+ def from_cli() -> "Arguments":
+ parser = argparse.ArgumentParser(
+ description="Join bootloader and application firmware to a factory image"
+ )
+
+ parser.add_argument(
+ "-b", "--bootloader", required=True, help="path to bootloader firmware"
+ )
+
+ default_offset = 0x40000
+ parser.add_argument(
+ "-o",
+ "--offset",
+ default=default_offset,
+ help=f"offset in bytes between bootloader and application (default: 0x{default_offset:X})",
+ )
+
+ parser.add_argument(
+ "-a", "--application", required=True, help="path to application firmware"
+ )
+
+ parser.add_argument(
+ "-f",
+ "--factory-image",
+ default=pathlib.Path("factory-image.bin"),
+ help="path to output factory image file",
+ )
+
+ args = parser.parse_args()
+
+ return Arguments(
+ bootloader=pathlib.Path(args.bootloader),
+ offset=int(args.offset),
+ application=pathlib.Path(args.application),
+ factory_image=pathlib.Path(args.factory_image),
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/meson.build b/tools/meson.build
index 85ddbb3..f58c54b 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -1,2 +1,3 @@
configure_zephyr = meson.current_source_dir() / 'configure_zephyr.py'
build_zephyr = meson.current_source_dir() / 'build_zephyr.py'
+make_factory_image = meson.current_source_dir() / 'make_factory_image.py'
diff --git a/tools/resistor_selector.py b/tools/resistor_selector.py
new file mode 100755
index 0000000..1ee51b7
--- /dev/null
+++ b/tools/resistor_selector.py
@@ -0,0 +1,327 @@
+#!/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
+import pathlib
+
+
+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,
+)
+
+
+def main() -> None:
+ getcontext().prec = 6
+
+ 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=args.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(
+ source=sorted_voltage_dividers,
+ margin=args.margin,
+ n_bits_adc=args.n_bits_adc,
+ power=args.power,
+ )
+
+ results = VoltageDividers(
+ combinations=tuple[VoltageDivider, ...](filtered_voltage_dividers)
+ )
+
+ output: str = results.to_tsv(
+ voltage=args.voltage,
+ n_bits_adc=args.n_bits_adc,
+ )
+
+ if args.output is None:
+ print(output)
+ else:
+ args.output.write_text(output)
+
+
+@dataclasses.dataclass(frozen=True, kw_only=True)
+class Arguments:
+ series: int
+ voltage: Decimal # volts
+ n_bits_adc: int # number of bits
+ output: pathlib.Path | None
+ margin: int
+ power: Decimal # watt
+ tolerance: Decimal # percent
+
+ 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
+
+ if self.output is not None:
+ assert isinstance(self.output, pathlib.Path)
+
+ assert isinstance(self.margin, int)
+ assert self.margin >= 0
+
+ assert isinstance(self.power, Decimal)
+ assert self.power >= Decimal(0.0)
+
+ assert isinstance(self.tolerance, Decimal)
+ assert self.tolerance >= Decimal(0.0)
+
+ @staticmethod
+ def from_cli() -> "Arguments":
+ parser = argparse.ArgumentParser(
+ description=DESCRIPTION,
+ )
+
+ default_series = 24
+ parser.add_argument(
+ "-s",
+ "--series",
+ default=default_series,
+ help=f"resistor E series (supported: {[k for k in IMPLEMENTED_SERIES]}, default: {default_series})",
+ )
+
+ default_voltage = 3.3
+ parser.add_argument(
+ "-v",
+ "--voltage",
+ default=default_voltage,
+ help=f"voltage [V] powering the voltage divider (default: {default_voltage})",
+ )
+
+ default_bits = 12
+ parser.add_argument(
+ "-b",
+ "--bits",
+ default=default_bits,
+ help=f"number of ADC bits (default: {default_bits})",
+ )
+
+ parser.add_argument(
+ "-o",
+ "--output",
+ default=None,
+ type=pathlib.Path,
+ help="output file to write to (default: stdout)",
+ )
+
+ default_margin: int = 10
+ parser.add_argument(
+ "-m",
+ "--margin",
+ default=default_margin,
+ help=f"min. ADC value difference between adjacent voltage dividers (default: {default_margin})",
+ )
+
+ default_power = 0.001
+ parser.add_argument(
+ "-p",
+ "--power",
+ default=default_power,
+ help=f"max. power [W] consumed by voltage divider (default: {default_power})",
+ )
+
+ default_tolerance = 1.0
+ parser.add_argument(
+ "-t",
+ "--tolerance",
+ default=default_tolerance,
+ help=f"resistor tolerance [percent] (default: {default_tolerance})",
+ )
+
+ args = parser.parse_args()
+
+ return Arguments(
+ series=int(args.series),
+ voltage=Decimal(args.voltage),
+ n_bits_adc=int(args.bits),
+ output=args.output,
+ margin=int(args.margin),
+ power=Decimal(args.power),
+ tolerance=Decimal(args.tolerance),
+ )
+
+
+@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],
+ margin: int,
+ n_bits_adc: int,
+ power: Decimal,
+) -> list[VoltageDivider]:
+ sink: list[VoltageDivider] = []
+ v_adc_max = Decimal(0.0)
+ n_adc_max = 0
+
+ for voltage_divider in source:
+ if voltage_divider.v_adc_min <= v_adc_max:
+ continue # overlapping voltage ranges
+
+ if voltage_divider.n_adc_min(n_bits_adc) < n_adc_max + margin:
+ continue # not enough ADC value margin
+
+ if voltage_divider.power > power:
+ continue # draws too much power
+
+ sink.append(voltage_divider)
+ v_adc_max = voltage_divider.v_adc_max
+ n_adc_max = voltage_divider.n_adc_max(n_bits_adc)
+
+ return sink
+
+
+if __name__ == "__main__":
+ main()