Cameron Prince 2025-04-28 22:48:22 +00:00 zatwierdzone przez GitHub
commit 8dadb92881
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
4 zmienionych plików z 188 dodań i 3 usunięć

Wyświetl plik

@ -177,6 +177,64 @@ class InputEnc:
return self._adj
# Special mode where an encoder with a "press" pushbutton is the only control.
# nxt and prev are Pin instances corresponding to encoder X and Y.
# sel is a Pin for the encoder's pushbutton.
# encoder is the division ratio.
# Note that using a single click for adjust mode failed because the mode changed when
# activating pushbuttons, checkboxes etc.
class InputI2CEnc:
def __init__(self, encoder):
from gui.primitives import I2CEncoder
self._encoder = encoder # Encoder in use
self._enc = I2CEncoder(encoder=encoder, callback=self.enc_cb)
self._precision = False # Precision mode
self._adj = False # Adjustment mode
self._sel = Pushbutton(sel, suppress=True)
self._sel.release_func(self.release) # Widgets are selected on release.
self._sel.long_func(self.precision, (True,)) # Long press -> precision mode
self._sel.double_func(self.adj_mode, (True,)) # Double press -> adjust mode
# Screen.adjust: adjust the value of a widget. In this case 1st button arg
# is an int (discarded), val is the delta. (With button interface 1st arg
# is the button, delta is +1 or -1).
def enc_cb(self, position, delta): # Eencoder callback
if self._adj:
Screen.adjust(0, delta)
else:
Screen.ctrl_move(_NEXT if delta > 0 else _PREV)
def release(self):
self.adj_mode(False) # Cancel adjust and precision
Screen.sel_ctrl()
def precision(self, val): # Also called by Screen.ctrl_move to cancel mode
if val:
if not self._adj:
self.adj_mode()
self._precision = True
else:
self._precision = False
Screen.redraw_co()
# If v is None, toggle adjustment mode. Bool sets or clears
def adj_mode(self, v=None): # Set, clear or toggle adjustment mode
self._adj = not self._adj if v is None else v
if not self._adj:
self._precision = False
Screen.redraw_co() # Redraw curret object
def encoder(self):
return self._encoder
def is_precision(self):
return self._precision
def is_adjust(self):
return self._adj
# Wrapper for global ssd object providing framebuf compatible methods.
# Must be subclassed: subclass provides input device and populates globals
# display and ssd.
@ -286,9 +344,13 @@ class Display(DisplayIP):
global display, ssd
ssd = objssd
if incr is False: # Special encoder-only mode
ev = isinstance(encoder, int)
assert ev and touch is False and decr is None and prev is not None, "Invalid args"
ipdev = InputEnc(nxt, sel, prev, encoder)
if not isinstance(encoder, (int, bool)):
assert touch is False and nxt is None and sel is None and prev is None and decr is None, "Invalid args"
ipdev = InputI2CEnc(encoder)
else:
ev = isinstance(encoder, int)
assert ev and touch is False and decr is None and prev is not None, "Invalid args"
ipdev = InputEnc(nxt, sel, prev, encoder)
else:
if touch:
from gui.primitives import ESP32Touch

Wyświetl plik

@ -28,6 +28,7 @@ _attrs = {
"Pushbutton": "pushbutton",
"ESP32Touch": "pushbutton",
"Switch": "switch",
"I2CEncoder": "i2cencoder",
}
# Copied from uasyncio.__init__.py

Wyświetl plik

@ -0,0 +1,61 @@
# encoder.py Asynchronous driver for incremental quadrature encoder.
# This is minimised for micro-gui. Derived from
# https://github.com/peterhinch/micropython-async/blob/master/v3/primitives/encoder.py
# Copyright (c) 2021-2024 Peter Hinch
# Released under the MIT License (MIT) - see LICENSE file
# Thanks are due to @ilium007 for identifying the issue of tracking detents,
# https://github.com/peterhinch/micropython-async/issues/82.
# Also to Mike Teachman (@miketeachman) for design discussions and testing
# against a state table design
# https://github.com/miketeachman/micropython-rotary/blob/master/rotary.py
# Now uses ThreadSafeFlag.clear()
import asyncio
from machine import Pin
class I2CEncoder:
delay = 100 # Debounce/detent delay (ms)
def __init__(self, encoder, callback):
self._v = 0 # Encoder value set by ISR
self._tsf = asyncio.ThreadSafeFlag()
trig = Pin.IRQ_RISING | Pin.IRQ_FALLING
try:
xirq = pin_x.irq(trigger=trig, handler=self._x_cb, hard=True)
yirq = pin_y.irq(trigger=trig, handler=self._y_cb, hard=True)
except TypeError: # hard arg is unsupported on some hosts
xirq = pin_x.irq(trigger=trig, handler=self._x_cb)
yirq = pin_y.irq(trigger=trig, handler=self._y_cb)
asyncio.create_task(self._run(div, callback))
def _x_cb(self, pin_x):
if (x := pin_x()) != self._x:
self._x = x
self._v += 1 if x ^ self._pin_y() else -1
self._tsf.set()
def _y_cb(self, pin_y):
if (y := pin_y()) != self._y:
self._y = y
self._v -= 1 if y ^ self._pin_x() else -1
self._tsf.set()
async def _run(self, div, cb):
pv = 0 # Prior hardware value
pcv = 0 # Prior divided value passed to callback
while True:
self._tsf.clear()
await self._tsf.wait() # Wait for an edge
await asyncio.sleep_ms(Encoder.delay) # Wait for motion/bounce to stop.
hv = self._v # Sample hardware (atomic read).
if hv == pv: # A change happened but was negated before
continue # this got scheduled. Nothing to do.
pv = hv
cv = round(hv / div) # cv is divided value.
if (cv - pcv) != 0: # dv is change in divided value.
cb(cv, cv - pcv) # Run user CB in uasyncio context
pcv = cv

Wyświetl plik

@ -0,0 +1,61 @@
# hardware_setup.py Customise for your hardware config
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch, Ihor Nehrutsa
# Supports:
# Waveshare Pico LCD 1.14" 135*240(Pixel) based on ST7789V
# https://www.waveshare.com/wiki/Pico-LCD-1.14
# https://www.waveshare.com/pico-lcd-1.14.htm
from machine import Pin, SPI, I2C
import i2cEncoderLibV2
import gc
from drivers.st7789.st7789_4bit import *
SSD = ST7789
SPI_CHANNEL = 0
SPI_SCK = 2
SPI_MOSI = 3
SPI_CS = 5
SPI_DC = 4
SPI_RST = 0
SPI_BAUD = 30_000_000
DISPLAY_BACKLIGHT = 1
I2C_CHANNEL = 1
I2C_SDA = 18
I2C_SCL = 19
I2C_INTERRUPT = 22
I2C_ENCODER_ADDRESS = 0x50
mode = LANDSCAPE
gc.collect() # Precaution before instantiating framebuf
# Conservative low baudrate. Can go to 62.5MHz.
spi = SPI(SPI_CHANNEL, SPI_BAUD, sck=Pin(SPI_SCK), mosi=Pin(SPI_MOSI), miso=None)
pcs = Pin(SPI_CS, Pin.OUT, value=1)
prst = Pin(SPI_RST, Pin.OUT, value=1)
pbl = Pin(DISPLAY_BACKLIGHT, Pin.OUT, value=1)
pdc = Pin(SPI_DC, Pin.OUT, value=0)
portrait = mode & PORTRAIT
ssd = SSD(spi, height=240, width=320, dc=pdc, cs=pcs, rst=prst, disp_mode=mode, display=PI_PICO_LCD_2)
# Setup the Interrupt Pin from the encoder.
INT_pin = Pin(I2C_INTERRUPT, Pin.IN, Pin.PULL_UP)
# Initialize the device.
i2c = I2C(I2C_CHANNEL, scl=Pin(I2C_SCL), sda=Pin(I2C_SDA))
encconfig = (i2cEncoderLibV2.INT_DATA | i2cEncoderLibV2.WRAP_ENABLE
| i2cEncoderLibV2.DIRE_RIGHT | i2cEncoderLibV2.IPUP_ENABLE
| i2cEncoderLibV2.RMOD_X1 | i2cEncoderLibV2.RGB_ENCODER)
encoder = i2cEncoderLibV2.i2cEncoderLibV2(i2c, I2C_ENCODER_ADDRESS)
encoder.reset()
# Create and export a Display instance
from gui.core.ugui import Display
# I2cEncoder Rotary w/ Button only
display = Display(ssd, None, None, None, False, None, encoder) # Encoder