moved a bunch of garbage (things that have nothing to do in root) to _trash
This commit is contained in:
parent
d094982b2c
commit
3226ed29ec
2610 changed files with 0 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,139 @@
|
|||
# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""BCM283x NeoPixel Driver Class"""
|
||||
import time
|
||||
import atexit
|
||||
import _rpi_ws281x as ws
|
||||
|
||||
try:
|
||||
# Used only for typing
|
||||
from typing import Optional
|
||||
from digitalio import DigitalInOut
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# LED configuration.
|
||||
# pylint: disable=redefined-outer-name,too-many-branches,too-many-statements
|
||||
# pylint: disable=global-statement,protected-access
|
||||
LED_FREQ_HZ = 800000 # Frequency of the LED signal. We only support 800KHz
|
||||
LED_DMA_NUM = 10 # DMA channel to use, can be 0-14.
|
||||
LED_BRIGHTNESS = 255 # We manage the brightness in the neopixel library
|
||||
LED_INVERT = 0 # We don't support inverted logic
|
||||
LED_STRIP = None # We manage the color order within the neopixel library
|
||||
|
||||
# a 'static' object that we will use to manage our PWM DMA channel
|
||||
# we only support one LED strip per raspi
|
||||
_led_strip = None
|
||||
_buf: Optional[bytearray] = None
|
||||
|
||||
|
||||
def neopixel_write(gpio: DigitalInOut, buf: bytearray) -> None:
|
||||
"""NeoPixel Writing Function"""
|
||||
global _led_strip # we'll have one strip we init if its not at first
|
||||
global _buf # we save a reference to the buf, and if it changes we will cleanup and re-init.
|
||||
|
||||
if _led_strip is None or buf is not _buf:
|
||||
# This is safe to call since it doesn't do anything if _led_strip is None
|
||||
neopixel_cleanup()
|
||||
|
||||
# Create a ws2811_t structure from the LED configuration.
|
||||
# Note that this structure will be created on the heap so you
|
||||
# need to be careful that you delete its memory by calling
|
||||
# delete_ws2811_t when it's not needed.
|
||||
_led_strip = ws.new_ws2811_t()
|
||||
_buf = buf
|
||||
|
||||
# Initialize all channels to off
|
||||
for channum in range(2):
|
||||
channel = ws.ws2811_channel_get(_led_strip, channum)
|
||||
ws.ws2811_channel_t_count_set(channel, 0)
|
||||
ws.ws2811_channel_t_gpionum_set(channel, 0)
|
||||
ws.ws2811_channel_t_invert_set(channel, 0)
|
||||
ws.ws2811_channel_t_brightness_set(channel, 0)
|
||||
|
||||
channel = ws.ws2811_channel_get(_led_strip, _neopixel_detect_channel(gpio))
|
||||
|
||||
# Initialize the channel in use
|
||||
count = 0
|
||||
if len(buf) % 3 == 0:
|
||||
# most common, divisible by 3 is likely RGB
|
||||
LED_STRIP = ws.WS2811_STRIP_RGB
|
||||
count = len(buf) // 3
|
||||
elif len(buf) % 4 == 0:
|
||||
LED_STRIP = ws.SK6812_STRIP_RGBW
|
||||
count = len(buf) // 4
|
||||
else:
|
||||
raise RuntimeError("We only support 3 or 4 bytes-per-pixel")
|
||||
|
||||
ws.ws2811_channel_t_count_set(
|
||||
channel, count
|
||||
) # we manage 4 vs 3 bytes in the library
|
||||
ws.ws2811_channel_t_gpionum_set(channel, gpio._pin.id)
|
||||
ws.ws2811_channel_t_invert_set(channel, LED_INVERT)
|
||||
ws.ws2811_channel_t_brightness_set(channel, LED_BRIGHTNESS)
|
||||
ws.ws2811_channel_t_strip_type_set(channel, LED_STRIP)
|
||||
|
||||
# Initialize the controller
|
||||
ws.ws2811_t_freq_set(_led_strip, LED_FREQ_HZ)
|
||||
ws.ws2811_t_dmanum_set(_led_strip, LED_DMA_NUM)
|
||||
|
||||
resp = ws.ws2811_init(_led_strip)
|
||||
if resp != ws.WS2811_SUCCESS:
|
||||
if resp == -5:
|
||||
raise RuntimeError(
|
||||
"NeoPixel support requires running with sudo, please try again!"
|
||||
)
|
||||
message = ws.ws2811_get_return_t_str(resp)
|
||||
raise RuntimeError(
|
||||
"ws2811_init failed with code {0} ({1})".format(resp, message)
|
||||
)
|
||||
atexit.register(neopixel_cleanup)
|
||||
|
||||
channel = ws.ws2811_channel_get(_led_strip, _neopixel_detect_channel(gpio))
|
||||
if gpio._pin.id != ws.ws2811_channel_t_gpionum_get(channel):
|
||||
raise RuntimeError("Raspberry Pi neopixel support is for one strip only!")
|
||||
|
||||
if ws.ws2811_channel_t_strip_type_get(channel) == ws.WS2811_STRIP_RGB:
|
||||
bpp = 3
|
||||
else:
|
||||
bpp = 4
|
||||
# assign all colors!
|
||||
for i in range(len(buf) // bpp):
|
||||
r = buf[bpp * i]
|
||||
g = buf[bpp * i + 1]
|
||||
b = buf[bpp * i + 2]
|
||||
if bpp == 3:
|
||||
pixel = (r << 16) | (g << 8) | b
|
||||
else:
|
||||
w = buf[bpp * i + 3]
|
||||
pixel = (w << 24) | (r << 16) | (g << 8) | b
|
||||
ws.ws2811_led_set(channel, i, pixel)
|
||||
|
||||
resp = ws.ws2811_render(_led_strip)
|
||||
if resp != ws.WS2811_SUCCESS:
|
||||
message = ws.ws2811_get_return_t_str(resp)
|
||||
raise RuntimeError(
|
||||
"ws2811_render failed with code {0} ({1})".format(resp, message)
|
||||
)
|
||||
time.sleep(0.001 * ((len(buf) // 100) + 1)) # about 1ms per 100 bytes
|
||||
|
||||
|
||||
def neopixel_cleanup():
|
||||
"""Cleanup when we're done"""
|
||||
global _led_strip
|
||||
|
||||
if _led_strip is not None:
|
||||
# Ensure ws2811_fini is called before the program quits.
|
||||
ws.ws2811_fini(_led_strip)
|
||||
# Example of calling delete function to clean up structure memory. Isn't
|
||||
# strictly necessary at the end of the program execution here, but is good practice.
|
||||
ws.delete_ws2811_t(_led_strip)
|
||||
_led_strip = None
|
||||
|
||||
|
||||
def _neopixel_detect_channel(gpio: DigitalInOut) -> int:
|
||||
"""Detect the channel for a given GPIO, added for support PWM1 pins"""
|
||||
if gpio._pin.id in (13, 19, 41, 45):
|
||||
return 1
|
||||
return 0
|
|
@ -0,0 +1,96 @@
|
|||
# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""Broadcom BCM283x pin names"""
|
||||
|
||||
# Use RPi.GPIO pins for Raspberry Pi 1-3B+
|
||||
from adafruit_blinka.microcontroller.generic_linux.rpi_gpio_pin import Pin
|
||||
|
||||
D0 = Pin(0)
|
||||
D1 = Pin(1)
|
||||
|
||||
D2 = Pin(2)
|
||||
SDA = Pin(2)
|
||||
D3 = Pin(3)
|
||||
SCL = Pin(3)
|
||||
|
||||
D4 = Pin(4)
|
||||
D5 = Pin(5)
|
||||
D6 = Pin(6)
|
||||
|
||||
D7 = Pin(7)
|
||||
CE1 = Pin(7)
|
||||
D8 = Pin(8)
|
||||
CE0 = Pin(8)
|
||||
D9 = Pin(9)
|
||||
MISO = Pin(9)
|
||||
D10 = Pin(10)
|
||||
MOSI = Pin(10)
|
||||
D11 = Pin(11)
|
||||
SCLK = Pin(11) # Raspberry Pi naming
|
||||
SCK = Pin(11) # CircuitPython naming
|
||||
|
||||
D12 = Pin(12)
|
||||
D13 = Pin(13)
|
||||
|
||||
D14 = Pin(14)
|
||||
TXD = Pin(14)
|
||||
D15 = Pin(15)
|
||||
RXD = Pin(15)
|
||||
|
||||
D16 = Pin(16)
|
||||
D17 = Pin(17)
|
||||
D18 = Pin(18)
|
||||
D19 = Pin(19)
|
||||
MISO_1 = Pin(19)
|
||||
D20 = Pin(20)
|
||||
MOSI_1 = Pin(20)
|
||||
D21 = Pin(21)
|
||||
SCLK_1 = Pin(21)
|
||||
SCK_1 = Pin(21)
|
||||
D22 = Pin(22)
|
||||
D23 = Pin(23)
|
||||
D24 = Pin(24)
|
||||
D25 = Pin(25)
|
||||
D26 = Pin(26)
|
||||
D27 = Pin(27)
|
||||
D28 = Pin(28)
|
||||
D29 = Pin(29)
|
||||
D30 = Pin(30)
|
||||
D31 = Pin(31)
|
||||
D32 = Pin(32)
|
||||
D33 = Pin(33)
|
||||
D34 = Pin(34)
|
||||
D35 = Pin(35)
|
||||
D36 = Pin(36)
|
||||
D37 = Pin(37)
|
||||
D38 = Pin(38)
|
||||
D39 = Pin(39)
|
||||
D40 = Pin(40)
|
||||
MISO_2 = Pin(40)
|
||||
D41 = Pin(41)
|
||||
MOSI_2 = Pin(41)
|
||||
D42 = Pin(42)
|
||||
SCLK_2 = Pin(42)
|
||||
SCK_2 = Pin(43)
|
||||
D43 = Pin(43)
|
||||
D44 = Pin(44)
|
||||
D45 = Pin(45)
|
||||
|
||||
# ordered as spiId, sckId, mosiId, misoId
|
||||
spiPorts = (
|
||||
(0, SCLK, MOSI, MISO),
|
||||
(1, SCLK_1, MOSI_1, MISO_1),
|
||||
(2, SCLK_2, MOSI_2, MISO_2),
|
||||
)
|
||||
|
||||
# ordered as uartId, txId, rxId
|
||||
uartPorts = ((1, TXD, RXD),)
|
||||
|
||||
# These are the known hardware I2C ports / pins.
|
||||
# For software I2C ports created with the i2c-gpio overlay, see:
|
||||
# https://github.com/adafruit/Adafruit_Python_Extended_Bus
|
||||
i2cPorts = (
|
||||
(1, SCL, SDA),
|
||||
(0, D1, D0), # both pi 1 and pi 2 i2c ports!
|
||||
)
|
|
@ -0,0 +1,187 @@
|
|||
# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""Custom PulseIn Class to read PWM signals"""
|
||||
import time
|
||||
import subprocess
|
||||
import os
|
||||
import atexit
|
||||
import random
|
||||
import struct
|
||||
import sysv_ipc
|
||||
|
||||
DEBUG = False
|
||||
queues = []
|
||||
procs = []
|
||||
|
||||
|
||||
# The message queues live outside of python space, and must be formally cleaned!
|
||||
def final():
|
||||
"""In case the program is cancelled or quit, we need to clean up the PulseIn
|
||||
helper process and also the message queue, this is called at exit to do so"""
|
||||
if DEBUG:
|
||||
print("Cleaning up message queues", queues)
|
||||
print("Cleaning up processes", procs)
|
||||
for q in queues:
|
||||
q.remove()
|
||||
for proc in procs:
|
||||
proc.terminate()
|
||||
|
||||
|
||||
atexit.register(final)
|
||||
|
||||
|
||||
# pylint: disable=c-extension-no-member
|
||||
class PulseIn:
|
||||
"""PulseIn Class to read PWM signals"""
|
||||
|
||||
def __init__(self, pin, maxlen=2, idle_state=False):
|
||||
"""Create a PulseIn object associated with the given pin.
|
||||
The object acts as a read-only sequence of pulse lengths with
|
||||
a given max length. When it is active, new pulse lengths are
|
||||
added to the end of the list. When there is no more room
|
||||
(len() == maxlen) the oldest pulse length is removed to make room."""
|
||||
self._pin = pin
|
||||
self._maxlen = maxlen
|
||||
self._idle_state = idle_state
|
||||
self._queue_key = random.randint(1, 9999)
|
||||
try:
|
||||
self._mq = sysv_ipc.MessageQueue(None, flags=sysv_ipc.IPC_CREX)
|
||||
if DEBUG:
|
||||
print("Message Queue Key: ", self._mq.key)
|
||||
queues.append(self._mq)
|
||||
except sysv_ipc.ExistentialError:
|
||||
raise RuntimeError(
|
||||
"Message queue creation failed"
|
||||
) from sysv_ipc.ExistentialError
|
||||
|
||||
# Check if OS is 64-bit
|
||||
if struct.calcsize("P") * 8 == 64: # pylint: disable=no-member
|
||||
libgpiod_filename = "libgpiod_pulsein64"
|
||||
else:
|
||||
libgpiod_filename = "libgpiod_pulsein"
|
||||
|
||||
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||
cmd = [
|
||||
dir_path + "/" + libgpiod_filename,
|
||||
"--pulses",
|
||||
str(maxlen),
|
||||
"--queue",
|
||||
str(self._mq.key),
|
||||
]
|
||||
if idle_state:
|
||||
cmd.append("-i")
|
||||
if isinstance(pin.id, tuple):
|
||||
cmd.append(f"gpiochip{pin.id[0]}")
|
||||
cmd.append(str(pin.id[1]))
|
||||
else:
|
||||
cmd.append("gpiochip0")
|
||||
cmd.append(str(pin))
|
||||
if DEBUG:
|
||||
print(cmd)
|
||||
|
||||
self._process = subprocess.Popen(cmd) # pylint: disable=consider-using-with
|
||||
procs.append(self._process)
|
||||
|
||||
# wait for it to start up
|
||||
if DEBUG:
|
||||
print("Waiting for startup success message from subprocess")
|
||||
message = self._wait_receive_msg(timeout=0.25)
|
||||
if message[0] != b"!":
|
||||
raise RuntimeError("Could not establish message queue with subprocess")
|
||||
self._paused = False
|
||||
|
||||
# pylint: disable=redefined-builtin
|
||||
def _wait_receive_msg(self, timeout=0, type=2):
|
||||
"""Internal helper that will wait for new messages of a given type,
|
||||
and throw an exception on timeout"""
|
||||
if timeout > 0:
|
||||
stamp = time.monotonic()
|
||||
while (time.monotonic() - stamp) < timeout:
|
||||
try:
|
||||
message = self._mq.receive(block=False, type=type)
|
||||
return message
|
||||
except sysv_ipc.BusyError:
|
||||
time.sleep(0.001) # wait a bit then retry!
|
||||
# uh-oh timed out
|
||||
raise RuntimeError(
|
||||
"Timed out waiting for PulseIn message. Make sure libgpiod is installed."
|
||||
)
|
||||
message = self._mq.receive(block=True, type=type)
|
||||
return message
|
||||
|
||||
# pylint: enable=redefined-builtin
|
||||
|
||||
def deinit(self):
|
||||
"""Deinitialises the PulseIn and releases any hardware and software
|
||||
resources for reuse."""
|
||||
# Clean up after ourselves
|
||||
self._process.terminate()
|
||||
procs.remove(self._process)
|
||||
self._mq.remove()
|
||||
queues.remove(self._mq)
|
||||
|
||||
def __enter__(self):
|
||||
"""No-op used by Context Managers."""
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
"""Automatically deinitializes the hardware when exiting a context."""
|
||||
self.deinit()
|
||||
|
||||
def resume(self, trigger_duration=0):
|
||||
"""Resumes pulse capture after an optional trigger pulse."""
|
||||
if trigger_duration != 0:
|
||||
self._mq.send("t%d" % trigger_duration, True, type=1)
|
||||
else:
|
||||
self._mq.send("r", True, type=1)
|
||||
self._paused = False
|
||||
|
||||
def pause(self):
|
||||
"""Pause pulse capture"""
|
||||
self._mq.send("p", True, type=1)
|
||||
self._paused = True
|
||||
|
||||
@property
|
||||
def paused(self):
|
||||
"""True when pulse capture is paused as a result of pause() or
|
||||
an error during capture such as a signal that is too fast."""
|
||||
return self._paused
|
||||
|
||||
@property
|
||||
def maxlen(self):
|
||||
"""The maximum length of the PulseIn. When len() is equal to maxlen,
|
||||
it is unclear which pulses are active and which are idle."""
|
||||
return self._maxlen
|
||||
|
||||
def clear(self):
|
||||
"""Clears all captured pulses"""
|
||||
self._mq.send("c", True, type=1)
|
||||
|
||||
def popleft(self):
|
||||
"""Removes and returns the oldest read pulse."""
|
||||
self._mq.send("^", True, type=1)
|
||||
message = self._wait_receive_msg()
|
||||
reply = int(message[0].decode("utf-8"))
|
||||
# print(reply)
|
||||
if reply == -1:
|
||||
raise IndexError("pop from empty list")
|
||||
return reply
|
||||
|
||||
def __len__(self):
|
||||
"""Returns the current pulse length"""
|
||||
self._mq.send("l", True, type=1)
|
||||
message = self._wait_receive_msg()
|
||||
return int(message[0].decode("utf-8"))
|
||||
|
||||
# pylint: disable=redefined-builtin
|
||||
def __getitem__(self, index, type=None):
|
||||
"""Returns the value at the given index or values in slice."""
|
||||
self._mq.send("i%d" % index, True, type=1)
|
||||
message = self._wait_receive_msg()
|
||||
ret = int(message[0].decode("utf-8"))
|
||||
if ret == -1:
|
||||
raise IndexError("list index out of range")
|
||||
return ret
|
||||
|
||||
# pylint: enable=redefined-builtin
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,3 @@
|
|||
# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
Binary file not shown.
|
@ -0,0 +1,3 @@
|
|||
# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
|
@ -0,0 +1,192 @@
|
|||
# SPDX-FileCopyrightText: 2025 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""
|
||||
`rotaryio` - Support for reading rotation sensors
|
||||
===========================================================
|
||||
See `CircuitPython:rotaryio` in CircuitPython for more details.
|
||||
|
||||
Raspberry Pi PIO implementation
|
||||
|
||||
* Author(s): Melissa LeBlanc-Williams
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import array
|
||||
import microcontroller
|
||||
|
||||
try:
|
||||
import adafruit_pioasm
|
||||
from adafruit_rp1pio import StateMachine
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"adafruit_pioasm and adafruit_rp1pio are required for this module"
|
||||
) from exc
|
||||
|
||||
_n_read = 17
|
||||
_program = adafruit_pioasm.Program(
|
||||
"""
|
||||
;
|
||||
; Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
|
||||
;
|
||||
; SPDX-License-Identifier: BSD-3-Clause
|
||||
;
|
||||
.pio_version 0 // only requires PIO version 0
|
||||
|
||||
.program quadrature_encoder
|
||||
|
||||
; the code must be loaded at address 0, because it uses computed jumps
|
||||
.origin 0
|
||||
|
||||
|
||||
; the code works by running a loop that continuously shifts the 2 phase pins into
|
||||
; ISR and looks at the lower 4 bits to do a computed jump to an instruction that
|
||||
; does the proper "do nothing" | "increment" | "decrement" action for that pin
|
||||
; state change (or no change)
|
||||
|
||||
; ISR holds the last state of the 2 pins during most of the code. The Y register
|
||||
; keeps the current encoder count and is incremented / decremented according to
|
||||
; the steps sampled
|
||||
|
||||
; the program keeps trying to write the current count to the RX FIFO without
|
||||
; blocking. To read the current count, the user code must drain the FIFO first
|
||||
; and wait for a fresh sample (takes ~4 SM cycles on average). The worst case
|
||||
; sampling loop takes 10 cycles, so this program is able to read step rates up
|
||||
; to sysclk / 10 (e.g., sysclk 125MHz, max step rate = 12.5 Msteps/sec)
|
||||
|
||||
; 00 state
|
||||
jmp update ; read 00
|
||||
jmp decrement ; read 01
|
||||
jmp increment ; read 10
|
||||
jmp update ; read 11
|
||||
|
||||
; 01 state
|
||||
jmp increment ; read 00
|
||||
jmp update ; read 01
|
||||
jmp update ; read 10
|
||||
jmp decrement ; read 11
|
||||
|
||||
; 10 state
|
||||
jmp decrement ; read 00
|
||||
jmp update ; read 01
|
||||
jmp update ; read 10
|
||||
jmp increment ; read 11
|
||||
|
||||
; to reduce code size, the last 2 states are implemented in place and become the
|
||||
; target for the other jumps
|
||||
|
||||
; 11 state
|
||||
jmp update ; read 00
|
||||
jmp increment ; read 01
|
||||
decrement:
|
||||
; note: the target of this instruction must be the next address, so that
|
||||
; the effect of the instruction does not depend on the value of Y. The
|
||||
; same is true for the "jmp y--" below. Basically "jmp y--, <next addr>"
|
||||
; is just a pure "decrement y" instruction, with no other side effects
|
||||
jmp y--, update ; read 10
|
||||
|
||||
; this is where the main loop starts
|
||||
.wrap_target
|
||||
update:
|
||||
mov isr, y ; read 11
|
||||
push noblock
|
||||
|
||||
sample_pins:
|
||||
; we shift into ISR the last state of the 2 input pins (now in OSR) and
|
||||
; the new state of the 2 pins, thus producing the 4 bit target for the
|
||||
; computed jump into the correct action for this state. Both the PUSH
|
||||
; above and the OUT below zero out the other bits in ISR
|
||||
out isr, 2
|
||||
in pins, 2
|
||||
|
||||
; save the state in the OSR, so that we can use ISR for other purposes
|
||||
mov osr, isr
|
||||
; jump to the correct state machine action
|
||||
mov pc, isr
|
||||
|
||||
; the PIO does not have a increment instruction, so to do that we do a
|
||||
; negate, decrement, negate sequence
|
||||
increment:
|
||||
mov y, ~y
|
||||
jmp y--, increment_cont
|
||||
increment_cont:
|
||||
mov y, ~y
|
||||
.wrap ; the .wrap here avoids one jump instruction and saves a cycle too
|
||||
"""
|
||||
)
|
||||
|
||||
_zero_y = adafruit_pioasm.assemble("set y 0")
|
||||
|
||||
|
||||
class IncrementalEncoder:
|
||||
"""
|
||||
IncrementalEncoder determines the relative rotational position based on two series of
|
||||
pulses. It assumes that the encoder’s common pin(s) are connected to ground,and enables
|
||||
pull-ups on pin_a and pin_b.
|
||||
|
||||
Create an IncrementalEncoder object associated with the given pins. It tracks the
|
||||
positional state of an incremental rotary encoder (also known as a quadrature encoder.)
|
||||
Position is relative to the position when the object is constructed.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, pin_a: microcontroller.Pin, pin_b: microcontroller.Pin, divisor: int = 4
|
||||
):
|
||||
"""Create an incremental encoder on pin_a and the next higher pin
|
||||
|
||||
Always operates in "x4" mode (one count per quadrature edge)
|
||||
|
||||
Assumes but does not check that pin_b is one above pin_a."""
|
||||
if pin_b is not None and pin_b.id != pin_a.id + 1:
|
||||
raise ValueError("pin_b must be None or one higher than pin_a")
|
||||
|
||||
try:
|
||||
self._sm = StateMachine(
|
||||
_program.assembled,
|
||||
frequency=0,
|
||||
init=_zero_y,
|
||||
first_in_pin=pin_a,
|
||||
in_pin_count=2,
|
||||
pull_in_pin_up=0x3,
|
||||
auto_push=True,
|
||||
push_threshold=32,
|
||||
in_shift_right=False,
|
||||
**_program.pio_kwargs,
|
||||
)
|
||||
except RuntimeError as e:
|
||||
if "(error -13)" in e.args[0]:
|
||||
raise RuntimeError(
|
||||
"This feature requires a rules file to allow access to PIO. See "
|
||||
"https://learn.adafruit.com/circuitpython-on-raspberrypi-linux/"
|
||||
"using-neopixels-on-the-pi-5#updating-permissions-3189429"
|
||||
) from e
|
||||
raise
|
||||
self._buffer = array.array("i", [0] * _n_read)
|
||||
self.divisor = divisor
|
||||
self._position = 0
|
||||
|
||||
def deinit(self):
|
||||
"""Deinitializes the IncrementalEncoder and releases any hardware resources for reuse."""
|
||||
self._sm.deinit()
|
||||
|
||||
def __enter__(self) -> IncrementalEncoder:
|
||||
"""No-op used by Context Managers."""
|
||||
return self
|
||||
|
||||
def __exit__(self, _type, _value, _traceback):
|
||||
"""
|
||||
Automatically deinitializes when exiting a context. See
|
||||
:ref:`lifetime-and-contextmanagers` for more info.
|
||||
"""
|
||||
self.deinit()
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
"""The current position in terms of pulses. The number of pulses per rotation is defined
|
||||
by the specific hardware and by the divisor."""
|
||||
self._sm.readinto(self._buffer) # read N stale values + 1 fresh value
|
||||
raw_position = self._buffer[-1]
|
||||
delta = int((raw_position - self._position * self.divisor) / self.divisor)
|
||||
self._position += delta
|
||||
return self._position
|
Loading…
Add table
Add a link
Reference in a new issue