JoystickXL with rotary encoders #32
-
Hello everyone, @fasteddy516 ! I continue my simple multibox for sims and I'm happy with the first prototype wth the JoystickXL library, thanks again 👍 So I wanted to know if I have the correct approach to add some rotary encoders (for replacing the three potentiometers) : """
Tested on a RaspberryPi Pico, Adafruit CircuitPython v8.2, JoystickXL v0.4.3
Encoder push button is on GP14, pin_a is on GP16 (turn right), pin_b is on GP15 (turn left)
"""
import board
import time
import digitalio
import rotaryio
from joystick_xl.inputs import Button
from joystick_xl.joystick import Joystick
# https://docs.circuitpython.org/en/latest/shared-bindings/digitalio/index.html
pb1 = digitalio.DigitalInOut(board.GP14)
pb1.direction = digitalio.Direction.INPUT
pb1.pull = digitalio.Pull.UP
# https://docs.circuitpython.org/en/latest/shared-bindings/rotaryio/index.html
encoder = rotaryio.IncrementalEncoder(board.GP16, board.GP15)
# https://circuitpython-joystickxl.readthedocs.io/en/latest/start.html
js = Joystick()
js.add_input(
Button(pb1), # Physical push button
Button(None, active_low=False), # Virtual input buttons
Button(None, active_low=False)
)
enc_last_pos = encoder.position
# TODO : Refactor this encoder part for multiple encoders (function)
while True:
# Virtual inputs release for new impulse
js.button[1].source_value = False
js.button[2].source_value = False
if js.button[0].is_pressed:
print ("GP14 Pressed")
enc_position = encoder.position
position_change = enc_position - enc_last_pos
if position_change > 0:
print ("GP15 ++")
js.button[1].source_value = True
js.button[2].source_value = False
elif position_change < 0:
print ("GP16 --")
js.button[1].source_value = False
js.button[2].source_value = True
enc_last_pos = enc_position
js.update()
time.sleep(0.1) #Important ! Cheers, have nice summer 🌞 ! |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
Hi @Mick3DIY, Your prototype looks great, nice work! Here are my thoughts on your rotary encoder implementation - take them with a grain of salt, as they're based on my minimal experience with rotary encoders and a quick read-through of your code:
import time
cycle_interval = 100
cycle_count = 0
debounce_ns = 1000000000 # 1s
debounce_start = time.monotonic_ns()
while True:
# Delay based on number of cycles through the main loop. The higher cycle_interval
# is set, the longer the delay. There is no precise timing here, it's just based on
# how quicky the main loop runs, and will vary from device to device based on
# processor speed.
cycle_count += 1
if cycle_count >= cycle_interval:
print(".", end="") # Print a dot every cycle_interval loops
cycle_count = 0
# Delay based on time in nanoseconds. This is a more precise timing mechanism, and
# will be consistent across devices regardless of processor speed. It still only
# guarantees a delay of *at least* debounce_ns nanoseconds, though; if something
# happens in the main loop that causes it to take longer than debounce_ns, then the
# overall delay will be longer.
debounce_time = time.monotonic_ns()
if debounce_time - debounce_start >= debounce_ns:
print("[DO STUFF HERE]") # Do stuff every debounce_ns nanoseconds
debounce_start = debounce_time
js.button[1].source_value = False
js.button[2].source_value = False
enc_position = encoder.position
position_change = enc_position - enc_last_pos
if position_change > 0:
print ("GP15 ++")
js.button[1].source_value = True
elif position_change < 0:
print ("GP16 --")
js.button[2].source_value = True
enc_last_pos = enc_position or enc_position = encoder.position
position_change = enc_position - enc_last_pos
if position_change > 0:
print ("GP15 ++")
js.button[1].source_value = True
js.button[2].source_value = False
elif position_change < 0:
print ("GP16 --")
js.button[1].source_value = False
js.button[2].source_value = True
else:
js.button[1].source_value = False
js.button[2].source_value = False
enc_last_pos = enc_position That makes it easier to do things like: if cycle_count >= cycle_interval:
# Do all of the encoder stuff here since it keeps all of the encoder-related logic together. |
Beta Was this translation helpful? Give feedback.
-
Hello @fasteddy516, thank you for your time and all your explanations (and your grain of salt) 😄
Here my new code (works well in Ubuntu 22 and Windows 11) : """
Tested on a RaspberryPi Pico, Adafruit CircuitPython v8.2, JoystickXL v0.4.3
https://learn.adafruit.com/rotary-encoder?view=all
Rotary encoder push button is on GP14, pin_a is on GP15, pin_b is on GP16
"""
import board
import digitalio
import rotaryio
from joystick_xl.inputs import Button
from joystick_xl.joystick import Joystick
# https://docs.circuitpython.org/en/latest/shared-bindings/digitalio/index.html
pb1 = digitalio.DigitalInOut(board.GP14)
pb1.direction = digitalio.Direction.INPUT
pb1.pull = digitalio.Pull.UP
# https://docs.circuitpython.org/en/latest/shared-bindings/rotaryio/index.html
encoder = rotaryio.IncrementalEncoder(board.GP15, board.GP16)
# https://circuitpython-joystickxl.readthedocs.io/en/latest/start.html
js = Joystick()
js.add_input(
Button(pb1), # Physical push button
Button(None, active_low=False), # Virtual input buttons
Button(None, active_low=False)
)
enc_last_pos = encoder.position
# TODO : Refactor this encoder part for multiple encoders (function)
while True:
# Virtual inputs release for new impulses
js.button[1].source_value = False
js.button[2].source_value = False
if js.button[0].is_pressed:
print ("Push button pressed")
enc_position = encoder.position
# Show current index position for debug (from Adafruit encoder example)
if enc_last_pos is None or enc_position != enc_last_pos:
print(enc_position)
position_change = enc_position - enc_last_pos
if position_change > 0:
print ("Encoder ++")
js.button[1].source_value = True
elif position_change < 0:
print ("Encoder --")
js.button[2].source_value = True
enc_last_pos = enc_position
js.update() I used this software AntiMicroX in both OS, do you know it ? https://github.com/AntiMicroX/antimicrox Time for refactor this code for multiple rotary encoders, thanks again for all. 🍻 |
Beta Was this translation helpful? Give feedback.
-
Hello @fasteddy516, here my new refactored code with three encoders. It works well on Ubuntu 22, Windows 11 (in DCS and ETS2). """
JoystickXL Example #XX - Start Simple with three rotary encoders (1 push button, 2 virtual inputs each).
Tested on a RaspberryPi Pico, Adafruit CircuitPython v8.2, JoystickXL v0.4.3
Rotary encoder1 push button is on GP10, pin_a is on GP11, pin_b is on GP12
Rotary encoder2 push button is on GP14, pin_a is on GP15, pin_b is on GP16
Rotary encoder3 push button is on GP19, pin_a is on GP20, pin_b is on GP21
Don't forget to adjust the number of buttons in the boot.py file (and reboot) :p
"""
import board
import digitalio
import rotaryio
from joystick_xl.inputs import Button
from joystick_xl.joystick import Joystick
# Rotary encoder1----------------------------------------------------------------------------------
# https://docs.circuitpython.org/en/latest/shared-bindings/digitalio/index.html
pb1 = digitalio.DigitalInOut(board.GP10)
pb1.direction = digitalio.Direction.INPUT
pb1.pull = digitalio.Pull.UP
# https://docs.circuitpython.org/en/latest/shared-bindings/rotaryio/index.html
encoder1 = rotaryio.IncrementalEncoder(board.GP11, board.GP12)
# Rotary encoder2----------------------------------------------------------------------------------
pb2 = digitalio.DigitalInOut(board.GP14)
pb2.direction = digitalio.Direction.INPUT
pb2.pull = digitalio.Pull.UP
encoder2 = rotaryio.IncrementalEncoder(board.GP15, board.GP16)
# Rotary encoder3----------------------------------------------------------------------------------
pb3 = digitalio.DigitalInOut(board.GP19)
pb3.direction = digitalio.Direction.INPUT
pb3.pull = digitalio.Pull.UP
encoder3 = rotaryio.IncrementalEncoder(board.GP20, board.GP21)
#--------------------------------------------------------------------------------------------------
# https://circuitpython-joystickxl.readthedocs.io/en/latest/start.html
js = Joystick()
js.add_input(
Button(pb1), # Physical push button n°1
Button(None, active_low=False), # Virtual input buttons n°1
Button(None, active_low=False),
Button(pb2), # Physical push button n°2
Button(None, active_low=False), # Virtual input buttons n°2
Button(None, active_low=False),
Button(pb3), # Physical push button n°3
Button(None, active_low=False), # Virtual input buttons n°3
Button(None, active_low=False)
)
enc_last_pos1 = encoder1.position
enc_last_pos2 = encoder2.position
enc_last_pos3 = encoder3.position
#--------------------------------------------------------------------------------------------------
def encoder_manager(encod: IncrementalEncoder, vi_pin_a: VirtualInput, vi_pin_b: VirtualInput, encod_last_pos: int):
"""Encoder manager with one physical push button and two virtual inputs"""
enc_position = encod.position
position_change = enc_position - encod_last_pos
if position_change > 0:
print ("Vi_pin_a ++")
vi_pin_a.source_value = True
elif position_change < 0:
print ("Vi_pin_b --")
vi_pin_b.source_value = True
return enc_position
#--------------------------------------------------------------------------------------------------
while True:
# Virtual inputs release for new impulses
js.button[1].source_value = False
js.button[2].source_value = False
js.button[4].source_value = False
js.button[5].source_value = False
js.button[7].source_value = False
js.button[8].source_value = False
if js.button[0].is_pressed:
print ("GP10 Pressed")
if js.button[3].is_pressed:
print ("GP14 Pressed")
if js.button[6].is_pressed:
print ("GP19 Pressed")
enc_last_pos1 = encoder_manager(encoder1, js.button[1], js.button[2], enc_last_pos1)
enc_last_pos2 = encoder_manager(encoder2, js.button[4], js.button[5], enc_last_pos2)
enc_last_pos3 = encoder_manager(encoder3, js.button[7], js.button[8], enc_last_pos3)
js.update() NB: I'm not a Python expert, so feel free to edit this code 😄 Also I include my encoders documentation (these models are with 'sliding noise' resistors in a PCB adapter) : Rotary Encoder_Alps_EC11.pdf Thank you. |
Beta Was this translation helpful? Give feedback.
Hi @Mick3DIY,
Your prototype looks great, nice work!
Here are my thoughts on your rotary encoder implementation - take them with a grain of salt, as they're based on my minimal experience with rotary encoders and a quick read-through of your code:
You're replacing potentiometers - which would normally correspond with
Axis
inputs - with encoders that appear as two discreteButton
inputs. I assume the intention is to use the encoders as UI navigation style controls (i.e. cycling through menu options, etc.) and that the relative "position" of the encoder isn't important.The
time.sleep(0.1)
at the end of your main loop is a no-no. That single statement means thatjs.update()
can be calle…