Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dead zone to knobs #212

Merged
merged 26 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
026463c
Add deadzone to AnalogReader class
redoxcode Dec 22, 2022
51e6935
Fixed missing self.
redoxcode Dec 22, 2022
e72a048
Moved deadzone code to Knob class
redoxcode Dec 22, 2022
2c2096f
deadzone again in AnalogueReader, improved _sample_adc
redoxcode Dec 23, 2022
fc44bb1
fix formating
redoxcode Dec 23, 2022
b1ac39c
activate deadzones for knobs
redoxcode Dec 23, 2022
c664dc4
update test to account for deadzones of 0.01
redoxcode Dec 23, 2022
133cb3d
update to account for deadzones of 0.01
redoxcode Dec 23, 2022
837ff90
Fixed formating
redoxcode Dec 26, 2022
c352735
deadzone usage closer to sample size usage
redoxcode Jan 5, 2023
1eb4e19
fix unindent bug
redoxcode Jan 5, 2023
49339a8
Fix formating
redoxcode Jan 5, 2023
77f8ece
fixed bug in knob init
redoxcode Jan 5, 2023
bc44a55
Add documentation
redoxcode Jan 5, 2023
b2e7684
fix formatting
redoxcode Jan 5, 2023
0a57fd0
Test with and without deadzone
redoxcode Jan 5, 2023
3fd12e3
fixed wrong deadzone for one test case
redoxcode Jan 5, 2023
5ceacb5
Add tests with and without deadzone
redoxcode Jan 5, 2023
8180e91
fixed bug when trying to overwrite deadzone with 0.0
redoxcode Jan 5, 2023
7790804
Merge branch 'Allen-Synthesis:main' into add-dead-zone-to-knobs
redoxcode Jan 5, 2023
b7b39c7
Add deadzone parameter to Knob read_position()
redoxcode Jan 9, 2023
666cf26
test for read_position with and without deadzones
redoxcode Jan 19, 2023
7005e2e
test read_position with and without deadzones
redoxcode Jan 19, 2023
6a275a1
Fix wrong deadzone value
redoxcode Jan 19, 2023
fd6cb70
Fix wrong expected value
redoxcode Jan 19, 2023
c94ccbd
Fix another wrong expected value
redoxcode Jan 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 35 additions & 18 deletions software/firmware/europi.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,42 +128,54 @@ class AnalogueReader:
not need to be used by user scripts.
"""

def __init__(self, pin, samples=DEFAULT_SAMPLES):
def __init__(self, pin, samples=DEFAULT_SAMPLES, deadzone=0.0):
redoxcode marked this conversation as resolved.
Show resolved Hide resolved
self.pin_id = pin
self.pin = ADC(Pin(pin))
self.set_samples(samples)
self.set_deadzone(deadzone)

def _sample_adc(self, samples=None):
# Over-samples the ADC and returns the average.
values = []
value = 0
for _ in range(samples or self._samples):
values.append(self.pin.read_u16())
return round(sum(values) / len(values))
value += self.pin.read_u16()
return round(value / (samples or self._samples))

def set_samples(self, samples):
"""Override the default number of sample reads with the given value."""
if not isinstance(samples, int):
raise ValueError(f"set_samples expects an int value, got: {samples}")
self._samples = samples

def percent(self, samples=None):
"""Return the percentage of the component's current relative range."""
return self._sample_adc(samples) / MAX_UINT16
def set_deadzone(self, deadzone):
"""Override the default deadzone with the given value."""
if not isinstance(deadzone, float):
raise ValueError(f"set_deadzone expects an float value, got: {deadzone}")
self._deadzone = deadzone

def range(self, steps=100, samples=None):
def percent(self, samples=None, deadzone=None):
"""Return the percentage of the component's current relative range."""
dz = self._deadzone
if deadzone is not None:
dz = deadzone
value = self._sample_adc(samples) / MAX_UINT16
value = value * (1.0 + 2.0 * dz) - dz
return clamp(value, 0.0, 1.0)

def range(self, steps=100, samples=None, deadzone=None):
"""Return a value (upper bound excluded) chosen by the current voltage value."""
if not isinstance(steps, int):
raise ValueError(f"range expects an int value, got: {steps}")
percent = self.percent(samples)
percent = self.percent(samples, deadzone)
if int(percent) == 1:
return steps - 1
return int(percent * steps)

def choice(self, values, samples=None):
def choice(self, values, samples=None, deadzone=None):
"""Return a value from a list chosen by the current voltage value."""
if not isinstance(values, list):
raise ValueError(f"choice expects a list, got: {values}")
percent = self.percent(samples)
percent = self.percent(samples, deadzone)
if percent == 1.0:
return values[-1]
return values[int(percent * len(values))]
Expand Down Expand Up @@ -238,6 +250,11 @@ class Knob(AnalogueReader):
method, which will then be used on all analogue read calls for that
component.

An optional ``deadzone`` parameter can be used to place deadzones at both
positions (all the way left and right) of the knob to make sure the full range
is available on all builds. The default value is 0.01 (resulting in 1% of the
travel used as deadzone on each side). There is usually no need to change this.

Additionally, the ``choice()`` method can be used to select a value from a
list of values based on the knob's position::

Expand All @@ -254,17 +271,17 @@ def clock_division(self):
would only return values which go up in steps of 2.
"""

def __init__(self, pin):
super().__init__(pin)
def __init__(self, pin, deadzone=0.01):
super().__init__(pin, deadzone=deadzone)

def percent(self, samples=None):
def percent(self, samples=None, deadzone=None):
"""Return the knob's position as relative percentage."""
# Reverse range to provide increasing range.
return 1 - (self._sample_adc(samples) / MAX_UINT16)
return 1.0 - super().percent(samples, deadzone)

def read_position(self, steps=100, samples=None):
def read_position(self, steps=100, samples=None, deadzone=None):
"""Returns the position as a value between zero and provided integer."""
return self.range(steps, samples)
return self.range(steps, samples, deadzone)


class DigitalReader:
Expand Down Expand Up @@ -298,7 +315,7 @@ def _bounce_wrapper(self, pin):
return
self.last_rising_ms = time.ticks_ms()
return self._rising_handler()
elif self.value() == LOW:
else:
if time.ticks_diff(time.ticks_ms(), self.last_falling_ms) < self.debounce_delay:
return
self.last_falling_ms = time.ticks_ms()
Expand Down
Loading