LuxLibre

LuxLibre › LuxLibre tools

LuxLibre tools

lux_calc.py is a small command-line calculator for choosing flashlight parts. Each subcommand checks one number that a design rule asks for: standby drain, cell current, beam distance, heat, trace width, battery divider and NTC thermistor. It needs Python 3.8 or newer and nothing outside the standard library.

python3 tools/lux_calc.py -h                 # list of subcommands
python3 tools/lux_calc.py thermal -h         # options of one subcommand
python3 -m unittest tools/test_lux_calc.py   # run the tests (from the repository root)

The tool gives estimates for comparing parts. It does not replace the datasheet or a measurement of the finished light (see docs/testing.md).

Common behaviour

Defaults

Some defaults are standards or come from a source. Others are LuxLibre defaults: project choices for a first estimate. Replace them with datasheet values as soon as you have them.

Option Default Kind
standby --percent-per-month 3 BAT-6 limit
runtime --cell-v 3.6 V Li-ion nominal voltage
runtime --usable 0.9 LuxLibre default
--driver-eff 0.9 LuxLibre default
--vf0, --rs 2.75 V, 0.15 Ω LuxLibre default, a straight-line fit; use the LED datasheet curve
cellcurrent --r-int 0.05 Ω LuxLibre default; check the cell datasheet
throw --optic-eff 0.7 LuxLibre default
thermal --finish anodised, h = 11 W/m²K (bare 6.5) derived values, see thermal
thermal --ambient 25 °C test condition of THM-6
thermal --surface-max 48 °C THM-1, ISO 13732-1 metal, 10 min contact (CENELEC Guide 29)
thermal --material aluminium typical specific heat values; check your alloy
trace --oz, --rise-c 1 oz, 10 °C the DRV-5 check case
divider --tolerance-pct 1 % common resistor tolerance
ntc --adc-bits 12 common ADC resolution

Subcommands

Subcommand What it gives Design rule
standby largest standby current, or drain per month BAT-6
runtime runtime at a constant LED power or current REQ-2
cellcurrent current from the cell and its voltage under load BAT-1
throw peak intensity, beam distance, lux REQ-2
thermal sustainable heat, turbo time THM-2, THM-3
trace PCB trace width (IPC-2221) DRV-5
divider battery-voltage divider check or suggestion BAT-9
ntc NTC resistance, temperature, ADC reading THM-4

standby

Supports BAT-6: standby drain at most 3 % of the capacity per month. Standby current is what the light draws while it is off (MCU asleep, protection IC, dividers).

$ python3 tools/lux_calc.py standby --capacity-mah 800
Standby drain (BAT-6)
  capacity             800 mAh
  allowed drain        3 % per month
  max standby current  32.9 µA
  formula: I_max = C * pct/100 / 730 h * 1000  [µA]
  note: 730 h = one average month. Measure the real standby current.

With --json (here for a measured 12 µA):

$ python3 tools/lux_calc.py standby --capacity-mah 800 --current-ua 12 --json
{
  "command": "standby",
  "capacity_mah": 800.0,
  "current_ua": 12.0,
  "bat6_ok": true,
  "percent_per_month": 1.095,
  "months_to_empty": 91.32420091324201,
  "formula": "pct = I/1000 * 730 h / C * 100;  months = 100 / pct",
  "notes": [
    "730 h = one average month. Measure the real standby current."
  ]
}

runtime

Helps with REQ-2 before a prototype exists. It assumes the driver holds a constant power until the usable energy is gone.

Example: a 3000 mAh cell such as the Samsung 30Q (datasheet) driving an LED at 1.5 A:

$ python3 tools/lux_calc.py runtime --capacity-mah 3000 --led-a 1.5
Runtime estimate at constant power (REQ-2)
  LED current          1.5 A
  LED Vf (vf0 + rs*I)  2.98 V
  LED power            4.46 W
  driver efficiency    90 %
  input power          4.96 W
  energy used          9.72 Wh (3000 mAh x 3.6 V x 0.9)
  runtime              1.96 h (118 min)
  formula: P_in = P_led / eff;  t = C/1000 * cell_v * usable / P_in  [h]
  note: Constant-power estimate. FL 1 runtime (to 10 % of the 30 s output) must be measured.

The ANSI/PLATO FL 1 runtime is measured from the 30 s output until the output falls to 10 % of it (Lupine). Thermal step-down changes it a lot, so treat this result as a first estimate only.

cellcurrent

Supports BAT-1: continuous cell current at most 70 % of the rated maximum. The cell has an internal resistance R, so its voltage drops under load and the current rises.

Example: 3 A into the LED from a cell at 3.6 V rated 15 A continuous (the Samsung 30Q rating at 25 °C, datasheet):

$ python3 tools/lux_calc.py cellcurrent --led-a 3 --cell-v 3.6 --cell-max-a 15
Cell current (BAT-1)
  LED current              3 A
  LED Vf (vf0 + rs*I)      3.20 V
  LED power                9.60 W
  input power              10.7 W (eff 90 %)
  cell current             3.10 A
  cell voltage under load  3.45 V
  share of cell rating     20.6 % of 15 A
  BAT-1 (<= 70 %)          pass
  formula: P_in = (V - I*R) * I, smaller root I = (V - sqrt(V^2 - 4*R*P_in)) / (2*R)
  note: Add contact and spring resistance to --r-int if known.

Set the firmware parameter BAT_MAX_MA from this result (see docs/firmware/parameters.md).

throw

Supports REQ-2. Peak intensity in candela (cd) is the brightness at the centre of the beam. ANSI/PLATO FL 1 defines the beam distance as the distance where the light falls to 0.25 lx, which is 2 × √cd metres (Lupine). Give exactly one of three modes:

$ python3 tools/lux_calc.py throw --lux 250 --distance-m 2
Throw (REQ-2, ANSI/PLATO FL 1)
  reading                  250 lx at 2 m
  peak intensity           1000 cd
  beam distance (0.25 lx)  63.2 m
  formula: cd = E * d^2;  beam = 2 * sqrt(cd)  [m]

thermal

Supports THM-2 (sustainable heat from the outer surface) and THM-3 (turbo duration from thermal mass).

Surface and steady state:

Turbo timing (lumped model: the whole body at one temperature, starting at ambient):

Heat helper: --input-w W --lumens F gives heat = W − F/320. 320 lm per optical watt is an approximation of the luminous efficacy of radiation of a white LED. If --heat-w is not given, this heat is used for the turbo timing.

Example: a 25 mm × 90 mm anodised aluminium body of 60 g, turbo with 10 W into the LED giving 1000 lm:

$ python3 tools/lux_calc.py thermal --diameter-mm 25 --length-mm 90 --mass-g 60 --input-w 10 --lumens 1000
Thermal (THM-2, THM-3)
  heat from 10 W and 1000 lm  6.88 W
  surface area (cylinder)     80.5 cm² (25 x 90 mm)
  h (anodised)                11 W/m²K
  ambient / surface limit     25 / 48 °C
  sustainable heat            2.04 W
  mass / c (aluminium)        60 g / 0.9 J/gK
  heat in turbo               6.88 W
  R, C, tau                   11.3 K/W, 54.0 J/K, 610 s
  steady-state surface        103 °C
  time to surface limit       214 s (3.57 min)
  formula: heat = W - F/320;  P = h * A * (Tmax - Tamb);  R = 1/(h*A), C = m*c, t = -R*C * ln(1 - (Tmax-Tamb)/(Q*R))
  note: 320 lm per optical watt is an approximation for white LEDs.
  note: h for still air (convection + radiation), derived values; see THM-2. Verify with the THM-6 test.
  note: Lumped model: the whole body at one temperature, starting at ambient. The LED and driver get hotter than the surface.

THM-3 asks you to state turbo duration honestly: a real body is not at one temperature, a hand adds cooling, a pocket removes it. Confirm with the THM-6 test.

trace

Supports DRV-5: size copper for current. Uses the IPC-2221 formula for the trace cross-section:

$ python3 tools/lux_calc.py trace --current-a 3
PCB trace width, IPC-2221 (DRV-5)
  current           3 A
  copper / layer    1 oz, external
  temperature rise  10 °C
  cross-section     74.2 mil²
  thickness         1.38 mil
  width             53.8 mil = 1.37 mm
  formula: A = (I / (k * dT^0.44))^(1/0.725) mil², k = 0.048 (external); w = A / (1.378 * oz)
  note: IPC-2152 is the newer method; IPC-2221 is conservative for outer layers.

The results match the DRV-5 values (1 A 0.30 mm, 3 A 1.37 mm, 5 A 2.77 mm), which agree with the Farnell calculator.

divider

Supports BAT-9: battery voltage measured to 1 % after calibration. A resistor divider (r-top from the battery to the ADC pin, r-bottom from the pin to ground) scales the battery voltage below the ADC reference.

$ python3 tools/lux_calc.py divider --v-max 4.2 --adc-ref 1.1
Battery voltage divider (BAT-9)
  target ratio (10 % headroom)  0.2357
  suggested r-top (E24)         910 kΩ
  suggested r-bottom (E24)      270 kΩ
  ratio                         0.2288
  ADC pin at 4.2 V              0.9610 V (reference 1.1 V)
  fits under reference          yes, 12.6 % headroom
  divider current               3.56 µA at v-max
  worst-case ratio error        1.55 % (1 % resistors)
  formula: ratio = Rb/(Rt+Rb);  V_adc = v_max * ratio;  I = v_max/(Rt+Rb);  error with Rt, Rb at opposite tolerance limits
  note: Advice: a high-value divider needs a capacitor at the ADC pin (e.g. 100 nF) so the ADC sample-and-hold sees a low source impedance.
  note: BAT-9 asks for <= 1 % error after calibration with BAT_CAL.

The divider current counts towards the standby budget of BAT-6. Calibrate with the BAT_CAL parameter.

ntc

Supports THM-4. An NTC thermistor is a resistor whose resistance falls as it gets warmer. The beta equation links resistance and temperature: 1/T = 1/298.15 + ln(R/R25)/B, with T in kelvin, R25 the resistance at 25 °C and B the beta constant from the datasheet.

$ python3 tools/lux_calc.py ntc --r25 100k --beta 4250 --temp-c 50 --r-fix 100k
NTC thermistor, beta equation (THM-4)
  R25 / beta                      100 kΩ / 4250 K
  r-fix (reference to pin) / ADC  100 kΩ / 12 bit
  temperature                     50 °C
  NTC resistance                  33.2 kΩ
  ADC ratio                       0.2492
  ADC code                        1021 of 4096
  sensitivity                     0.0321 °C per LSB
  formula: 1/T = 1/298.15 + ln(R/R25)/B  (T in K);  r = Rntc/(Rfix+Rntc), code = r * 2^bits
  note: Calibrate THM_OFFSET and the sensor-to-surface difference (THM-4).

The beta equation is an approximation over a limited range. Check it against the resistance table in the thermistor datasheet, then calibrate THM_OFFSET.

Tests

test_lux_calc.py checks the values given in the design rules (32.9 µA and 123 µA for BAT-6, the DRV-5 trace widths, 33.2 kΩ for a 100 kΩ B4250 NTC at 50 °C), round trips (NTC temperature to resistance and back, lux to cd to beam distance), the formulas against hand calculations, and every subcommand in text and JSON form. Run it from the repository root with either command:

python3 -m unittest tools/test_lux_calc.py
python3 tools/test_lux_calc.py

Comparison charts

comparison_charts.py draws the charts of docs/comparison.md from docs/data/flashlights.json, in a light and a dark version. It needs matplotlib.

python3 tools/comparison_charts.py           # writes docs/img/compare_*_{light,dark}.png