#!/usr/bin/env python3
# Sybren's Light Intensity Script
# Copyright (C) 2020 dr. Sybren A. StÃ¼vel
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You may have received a copy of the GNU General Public License along
# with this program. If not, see .
############### Set up parameters here ##########################
initial_single_steps = 6
total_steps = 30
pwm_bits = 10
############### Nothing to modify under this line ###############
from typing import Iterable, List
from math import log
import textwrap
# "power number" = PWM value + 1
# The microcontroller still outputs light when PWM=0, and max when PWM=1023
# This code works with OFF -> pownum=0, PWM=0 -> pownum = 1, PWM=1023 -> pownum=1024
# Having value 0 for OFF makes the math more sane.
# The number of "power steps" is one more than the number of PWM steps, because
# the PWM cannot turn the light off but the system as a whole can.
PowNum = int # range [0..1024]
PWM = int # range [-1..1023]
Intensity = float # range [0.0, 1.0]
def light_pwm_values(
initial_single_steps: int,
num_pwm_steps: int,
pwm_bits: int,
) -> Iterable[PWM]:
assert initial_single_steps > 0
assert num_pwm_steps > 3
assert pwm_bits > 1
num_power_steps = 2 ** pwm_bits + 1
# The first power step is skipped, as that's "OFF" and has no valid PWM value.
# Start with a number of single steps for a linear power increase.
yield from range(initial_single_steps)
# The first visual intensity of the exponential power curve:
intensity = power_to_visual_intensity(initial_single_steps, num_power_steps)
# The slope of the linearly increasing visual intensity:
intensity_delta = (1.0 - intensity) / (num_pwm_steps - initial_single_steps)
for step in range(initial_single_steps + 1, num_pwm_steps + 1):
intensity += intensity_delta
power = visual_intensity_to_power(intensity, num_power_steps)
yield power - 1
def power_to_visual_intensity(power: PowNum, num_power_steps: int) -> Intensity:
return log(power + 1, num_power_steps)
def visual_intensity_to_power(visual_intensity: float, num_power_steps: int) -> PowNum:
return int(round(num_power_steps ** visual_intensity)) - 1
if pwm_bits <= 8:
int_type = 'uint8_t'
elif pwm_bits <= 16:
int_type = 'uint16_t'
else:
int_type = 'uint32_t'
print(f"static const {int_type} light_map_{pwm_bits}bit[] = {{")
pwm_values = light_pwm_values(initial_single_steps, total_steps, pwm_bits)
pwm_values_joined = ", ".join(str(pwm_value) for pwm_value in pwm_values)
for line in textwrap.wrap(pwm_values_joined + "};"):
print(f" {line}")