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 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. # Wrapper for global ssd object providing framebuf compatible methods.
# Must be subclassed: subclass provides input device and populates globals # Must be subclassed: subclass provides input device and populates globals
# display and ssd. # display and ssd.
@ -286,6 +344,10 @@ class Display(DisplayIP):
global display, ssd global display, ssd
ssd = objssd ssd = objssd
if incr is False: # Special encoder-only mode if incr is False: # Special encoder-only mode
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) ev = isinstance(encoder, int)
assert ev and touch is False and decr is None and prev is not None, "Invalid args" assert ev and touch is False and decr is None and prev is not None, "Invalid args"
ipdev = InputEnc(nxt, sel, prev, encoder) ipdev = InputEnc(nxt, sel, prev, encoder)

Wyświetl plik

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