8bit encoder detection + debounced button detection, numpy removal (#710)

* debounce interrupt events for rotary events and button presses with 400 microseconds by default.

* Massive improvements for the encoder detection. Removed numpy as a dependency.

* Debounce button presses which started to jump around.

* formatting cleanup.
This commit is contained in:
Benjamin Zeiss 2022-03-02 03:27:14 +01:00 committed by GitHub
parent 1df7cdb1f3
commit 0297fe856a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 47 additions and 55 deletions

View File

@ -992,8 +992,8 @@ function installRaScsiCtrlBoard() {
updateRaScsiGit
sudo apt-get update && sudo apt-get install libjpeg-dev libpng-dev libopenjp2-7-dev i2c-tools raspi-config -y </dev/null
# install numpy via apt to avoid compilation
sudo apt-get install python3-numpy python3-cbor2 -y </dev/null
# install python packages through apt that need compilation
sudo apt-get install python3-cbor2 -y </dev/null
# enable i2c
if [[ $(grep -c "^dtparam=i2c_arm=on" /boot/config.txt) -ge 1 ]]; then

View File

@ -47,6 +47,7 @@ the raspberry pi model and enable transitions only for the following pi models:
The model detection can be overriden by adding a --transitions parameter to start.sh.
## Credits
### DejaVuSansMono-Bold.ttf
* Source: https://dejavu-fonts.github.io
* Distributed under DejaVu Fonts Lience (see DejaVu Fonts License.txt for full text)

View File

@ -7,5 +7,4 @@ RPi.GPIO==0.7.0
protobuf==3.19.3
unidecode==1.3.2
smbus==1.1.post2
# installed via apt to avoid lengthy compilation
#numpy==1.21.5

View File

@ -1,10 +1,10 @@
"""Module providing the interface to the RaSCSI Control Board hardware"""
# noinspection PyUnresolvedReferences
import logging
import RPi.GPIO as GPIO
import numpy
import smbus
import time
import RPi.GPIO as GPIO
import smbus
from ctrlboard_hw import pca9554multiplexer
from ctrlboard_hw.hardware_button import HardwareButton
from ctrlboard_hw.ctrlboard_hw_constants import CtrlBoardHardwareConstants
@ -16,9 +16,10 @@ from observable import Observable
# pylint: disable=too-many-instance-attributes
class CtrlBoardHardware(Observable):
"""Class implements the RaSCSI Control Board hardware and provides an interface to it."""
def __init__(self, display_i2c_address, pca9554_i2c_address):
def __init__(self, display_i2c_address, pca9554_i2c_address, debounce_ms=200):
self.display_i2c_address = display_i2c_address
self.pca9554_i2c_address = pca9554_i2c_address
self.debounce_ms = debounce_ms
self.rascsi_controlboard_detected = self.detect_rascsi_controlboard()
log = logging.getLogger(__name__)
log.info("RaSCSI Control Board detected: %s", str(self.rascsi_controlboard_detected))
@ -53,7 +54,7 @@ class CtrlBoardHardware(Observable):
self.pca_driver.write_configuration_register_port(CtrlBoardHardwareConstants.
PCA9554_PIN_LED_2,
PCA9554Multiplexer.PIN_ENABLED_AS_OUTPUT)
self.input_register_buffer = numpy.uint32(0)
self.input_register_buffer = 0
# pylint: disable=no-member
GPIO.setmode(GPIO.BCM)
@ -97,6 +98,7 @@ class CtrlBoardHardware(Observable):
self.rotary = Encoder(self.rotary_a, self.rotary_b)
self.rotary.pos_prev = 0
self.rotary.name = CtrlBoardHardwareConstants.ROTARY
self.rot_prev_state = 0b11
# noinspection PyUnusedLocal
# pylint: disable=unused-argument
@ -113,11 +115,18 @@ class CtrlBoardHardware(Observable):
value = button.state_interrupt
# ignore button press if debounce time is not reached
if button.last_press is not None:
elapsed = (time.time_ns() - button.last_press)/1000000
if elapsed < self.debounce_ms:
return
if value != button.state and value is False:
button.state = False
self.notify(button)
button.state = True
button.state_interrupt = True
button.last_press = time.time_ns()
def check_rotary_encoder(self, rotary):
"""Checks whether the rotary state has changed."""
@ -150,19 +159,20 @@ class CtrlBoardHardware(Observable):
def process_events(self):
"""Non-blocking event processor for hardware events (button presses etc.)"""
input_register_buffer_length = int(len(format(self.input_register_buffer, 'b'))/8)
if input_register_buffer_length <= 1:
return
input_register_buffer = self.input_register_buffer
self.input_register_buffer = 0
input_register_buffer_length = int(len(format(input_register_buffer, 'b'))/8)
if input_register_buffer_length < 1:
return
for i in range(0, input_register_buffer_length):
shiftval = (input_register_buffer_length-1-i)*8
input_register = (input_register_buffer >> shiftval) & 0b11111111
rot_a = self.button_value(input_register, 0)
rot_b = self.button_value(input_register, 1)
button_rotary = self.button_value(input_register, 5)
button_1 = self.button_value(input_register, 2)
button_2 = self.button_value(input_register, 3)
@ -176,19 +186,21 @@ class CtrlBoardHardware(Observable):
if button_rotary == 0:
self.rotary_button.state_interrupt = bool(button_rotary)
rot_tmp_state = 0b11
if rot_a == 0:
self.rotary.enc_a.state_interrupt = bool(rot_a)
rot_tmp_state &= 0b10
if rot_b == 0:
rot_tmp_state &= 0b01
if rot_tmp_state != self.rot_prev_state:
self.rot_prev_state = rot_tmp_state
self.rotary.enc_a.state_interrupt = bool(rot_a)
self.rotary.enc_b.state_interrupt = bool(rot_b)
self.check_rotary_encoder(self.rotary)
self.check_button_press(self.rotary_button)
self.check_button_press(self.button1)
self.check_button_press(self.button2)
self.check_rotary_encoder(self.rotary)
self.rotary.state = 0b11
self.input_register_buffer = 0
@staticmethod
def detect_i2c_devices(_bus):

View File

@ -10,57 +10,36 @@ class Encoder:
self.enc_a = enc_a
self.enc_b = enc_b
self.pos = 0
self.state = 0b0011
self.state = 0b00000000
self.direction = 0
def update(self):
"""Updates the internal attributes wrt. to the encoder position and direction."""
self.update2()
def update2(self):
"""Primary method for detecting the direction"""
value_enc_a = self.enc_a.state_interrupt
value_enc_b = self.enc_b.state_interrupt
self.direction = 0
state = self.state & 0b0011
state = self.state & 0b00111111
if value_enc_a:
state |= 0b0100
state |= 0b01000000
if value_enc_b:
state |= 0b1000
state |= 0b10000000
if state == 0b1011:
# clockwise pattern detection
if (state == 0b11010010 or state == 0b11001000 or state == 0b11011000 or
state == 0b11010001 or state == 0b11011011 or state == 0b11100000 or
state == 0b11001011):
self.pos += 1
self.direction = 1
if state == 0b0111:
self.state = 0b00000000
return
# counter-clockwise pattern detection
elif (state == 0b11000100 or state == 0b11100100 or state == 0b11100001 or
state == 0b11000111 or state == 0b11100111):
self.pos -= 1
self.direction = -1
self.state = 0b00000000
return
self.state = state >> 2
self.enc_a.state_interrupt = True
self.enc_b.state_interrupt = True
def update1(self):
"""Secondary, less well working method to detect the direction"""
if self.enc_a.state_interrupt is True and self.enc_b.state_interrupt is True:
return
if self.enc_a.state_interrupt is False and self.enc_b.state_interrupt is False:
self.enc_a.state_interrupt = True
self.enc_b.state_interrupt = True
return
self.direction = 0
if self.enc_a.state_interrupt is False:
self.pos += 1
self.direction = 1
elif self.enc_a.state_interrupt is True:
self.pos -= 1
self.direction = -1
self.enc_a.state_interrupt = True
self.enc_b.state_interrupt = True

View File

@ -11,6 +11,7 @@ class HardwareButton:
self.state = True
self.state_interrupt = True
self.name = "n/a"
self.last_press = None
def read(self):
"""Reads the configured port of the i2c multiplexer"""