Skip to content

Commit de76723

Browse files
Merge origin/main (BeaglePlay adafruit#1115) into future-proof
2 parents 27174ea + ddc4686 commit de76723

22 files changed

Lines changed: 829 additions & 67 deletions

File tree

docs/examples.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ See the `CircuitPython docs <https://circuitpython.readthedocs.io/>`_ for extens
3535
.. literalinclude:: ../examples/piblinka.py
3636
:caption: examples/piblinka.py
3737
:linenos:
38+
39+
.. literalinclude:: ../examples/usb_hid_keyboard.py
40+
:caption: examples/usb_hid_keyboard.py
41+
:linenos:

examples/usb_hid_keyboard.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# SPDX-FileCopyrightText: 2023 Björn Bösel for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
"""
6+
USB HID Keyboard Example for Raspberry Pi Zero (W)
7+
===================================================
8+
9+
Demonstrates how to use Blinka's ``usb_hid`` module to turn a
10+
Raspberry Pi Zero into a USB HID keyboard.
11+
12+
Prerequisites
13+
-------------
14+
1. Enable the dwc2 overlay (once, then reboot)::
15+
16+
sudo bash -c "echo 'dtoverlay=dwc2' >> /boot/config.txt"
17+
sudo reboot
18+
19+
2. Load the libcomposite kernel module::
20+
21+
sudo modprobe libcomposite
22+
23+
To make it persistent across reboots::
24+
25+
sudo bash -c "echo 'libcomposite' >> /etc/modules"
26+
27+
3. Install the CircuitPython HID library::
28+
29+
pip3 install adafruit-circuitpython-hid
30+
31+
Wiring
32+
------
33+
- Wire a button between GP20 and GND (types Shift+A).
34+
- Wire a button between GP21 and GND (types "Hello World!").
35+
- Wire an LED + 1k resistor between GP16 and GND.
36+
- Connect the Pi Zero to the host computer via USB.
37+
38+
Run with::
39+
40+
sudo -E python3 usb_hid_keyboard.py
41+
42+
The ``-E`` flag preserves the user environment so pip-installed
43+
packages are found when running as root.
44+
"""
45+
46+
import time
47+
48+
from adafruit_hid.keyboard import Keyboard
49+
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
50+
from adafruit_hid.keycode import Keycode
51+
52+
import board
53+
import digitalio
54+
import usb_hid
55+
from usb_hid import Device
56+
57+
# Button pins (activate internal pull-ups)
58+
keypress_pins = [board.D20, board.D21]
59+
60+
# What each button sends: a Keycode or a string
61+
keys_pressed = [Keycode.A, "Hello World!\n"]
62+
control_key = Keycode.SHIFT
63+
64+
# Set up button inputs with pull-ups
65+
key_pin_array = []
66+
for pin in keypress_pins:
67+
key_pin = digitalio.DigitalInOut(pin)
68+
key_pin.direction = digitalio.Direction.INPUT
69+
key_pin.pull = digitalio.Pull.UP
70+
key_pin_array.append(key_pin)
71+
72+
# Set up LED output
73+
led = digitalio.DigitalInOut(board.D16)
74+
led.direction = digitalio.Direction.OUTPUT
75+
76+
# Create the USB HID keyboard
77+
usb_hid.enable([Device.KEYBOARD], boot_device=0)
78+
keyboard = Keyboard(usb_hid.devices)
79+
keyboard_layout = KeyboardLayoutUS(keyboard)
80+
81+
print("Waiting for key press...")
82+
83+
while True:
84+
for key_pin in key_pin_array:
85+
if not key_pin.value: # Button pressed (grounded)
86+
i = key_pin_array.index(key_pin)
87+
print("Pin #%d is grounded." % i)
88+
89+
led.value = True
90+
91+
while not key_pin.value:
92+
pass # Wait for release
93+
94+
key = keys_pressed[i]
95+
if isinstance(key, str):
96+
keyboard_layout.write(key)
97+
else:
98+
keyboard.press(control_key, key)
99+
keyboard.release_all()
100+
101+
led.value = False
102+
103+
time.sleep(0.01)

setup.py

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,48 @@
2121
with io.open(os.path.join(here, "README.rst"), encoding="utf-8") as f:
2222
long_description = "\n" + f.read()
2323

24-
with io.open(os.path.join(here, "requirements.txt"), encoding="utf-8") as f:
25-
requirements = [
26-
line.strip()
27-
for line in f.readlines()
28-
if line.strip() and not line.startswith("#")
29-
]
24+
if not glob.glob("//usr//include//python3.*//Python.h"):
25+
raise RuntimeError(
26+
"This package requires a Python development environment. "
27+
"Please install the python3-dev package for your distribution."
28+
)
29+
30+
board_reqs = []
31+
if os.path.exists("/proc/device-tree/compatible"):
32+
with open("/proc/device-tree/compatible", "rb") as f:
33+
compat = f.read()
34+
# Jetson Nano, TX2, Xavier, etc
35+
if b"nvidia,tegra" in compat:
36+
board_reqs = ["Jetson.GPIO"]
37+
# Pi 5 and Earlier
38+
elif (
39+
b"brcm,bcm2835" in compat
40+
or b"brcm,bcm2836" in compat
41+
or b"brcm,bcm2837" in compat
42+
or b"brcm,bcm2838" in compat
43+
or b"brcm,bcm2711" in compat
44+
or b"brcm,bcm2712" in compat
45+
):
46+
lgpio_req = "lgpio>=0.2.2.0"
47+
try:
48+
import lgpio
49+
except ImportError:
50+
print(
51+
"\n*** lgpio is not installed. On Raspberry Pi OS, install it with:\n"
52+
" sudo apt-get install -y python3-lgpio\n"
53+
" Then recreate your virtual environment with --system-site-packages\n"
54+
" or install the wheel from:\n"
55+
" https://github.com/adafruit/lgpio-python-wheels\n"
56+
)
57+
board_reqs = [
58+
"rpi_ws281x>=4.0.0",
59+
lgpio_req,
60+
"RPi.GPIO",
61+
"Adafruit-Blinka-Raspberry-Pi5-Neopixel",
62+
]
63+
# BeagleBone Black, Green, PocketBeagle, BeagleBone AI, etc.
64+
elif b"ti,am335x" in compat:
65+
board_reqs = ["Adafruit_BBIO"]
3066

3167
setup(
3268
name="Adafruit-Blinka",
@@ -76,7 +112,16 @@
76112
"micropython-stubs": ["*.pyi"],
77113
},
78114
include_package_data=True,
79-
install_requires=requirements,
115+
install_requires=[
116+
"Adafruit-PlatformDetect>=3.89.1",
117+
"Adafruit-PureIO>=1.1.7",
118+
"binho-host-adapter>=0.1.6",
119+
"pyftdi>=0.40.0",
120+
"adafruit-circuitpython-typing",
121+
"sysv_ipc>=1.1.0;sys_platform=='linux' and platform_machine!='mips'",
122+
"toml>=0.10.2;python_version<'3.11'",
123+
]
124+
+ board_reqs,
80125
license="MIT",
81126
classifiers=[
82127
# Trove classifiers
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# SPDX-FileCopyrightText: 2026 Melissa LeBlanc-Williams for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
"""Pin definitions for the BeaglePlay."""
5+
from adafruit_blinka.microcontroller.am625x import pin
6+
7+
# MIKROBUS connector I2C (i2c-3, /dev/play/mikrobus/i2c)
8+
SCL = pin.MIKROBUS_SCL
9+
SDA = pin.MIKROBUS_SDA
10+
11+
# QWIIC / STEMMA QT connector (i2c-5, /dev/play/qwiic/i2c)
12+
QWIIC_SCL = pin.QWIIC_SCL
13+
QWIIC_SDA = pin.QWIIC_SDA
14+
15+
# Grove connector I2C (i2c-1, /dev/play/grove/i2c)
16+
GROVE_SCL = pin.GROVE_SCL
17+
GROVE_SDA = pin.GROVE_SDA
18+
19+
# MIKROBUS general-purpose GPIO
20+
MIKROBUS_GPIO1_7 = pin.MIKROBUS_GPIO1_7
21+
MIKROBUS_GPIO1_8 = pin.MIKROBUS_GPIO1_8
22+
MIKROBUS_GPIO1_9 = pin.MIKROBUS_GPIO1_9
23+
MIKROBUS_GPIO1_10 = pin.MIKROBUS_GPIO1_10
24+
MIKROBUS_GPIO1_11 = pin.MIKROBUS_GPIO1_11
25+
MIKROBUS_GPIO1_12 = pin.MIKROBUS_GPIO1_12
26+
MIKROBUS_W1 = pin.MIKROBUS_W1
27+
MIKROBUS_GPIO1_14 = pin.MIKROBUS_GPIO1_14
28+
MIKROBUS_GPIO1_15 = pin.MIKROBUS_GPIO1_15
29+
MIKROBUS_GPIO1_16 = pin.MIKROBUS_GPIO1_16
30+
MIKROBUS_GPIO1_17 = pin.MIKROBUS_GPIO1_17
31+
MIKROBUS_GPIO1_18 = pin.MIKROBUS_GPIO1_18
32+
MIKROBUS_GPIO1_20 = pin.MIKROBUS_GPIO1_20
33+
MIKROBUS_GPIO1_21 = pin.MIKROBUS_GPIO1_21
34+
MIKROBUS_GPIO1_22 = pin.MIKROBUS_GPIO1_22
35+
MIKROBUS_GPIO1_23 = pin.MIKROBUS_GPIO1_23
36+
MIKROBUS_GPIO1_24 = pin.MIKROBUS_GPIO1_24
37+
MIKROBUS_GPIO1_25 = pin.MIKROBUS_GPIO1_25
38+
39+
# MIKROBUS UART (/dev/ttyS0)
40+
TX = pin.UART_TX
41+
RX = pin.UART_RX
42+
43+
# User LEDs (gpiochip2)
44+
LED_USR0 = pin.USR0
45+
LED_USR1 = pin.USR1
46+
LED_USR2 = pin.USR2
47+
LED_USR3 = pin.USR3
48+
LED_USR4 = pin.USR4
49+
50+
# User button
51+
USR_BUTTON = pin.USR_BUTTON

src/adafruit_blinka/board/particle/tachyon.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
# SPDX-License-Identifier: MIT
44
"""Pin definitions for the Tachyon."""
55

6+
import re as _re
7+
68
from adafruit_blinka.microcontroller.quectel.qcm6490 import pin
79

810
for it in pin.i2cPorts:
@@ -51,3 +53,16 @@
5153
UART_RX = D15
5254

5355
PWM1 = D13
56+
57+
# Expose raw GPIO line-number names (GPIO_6, GPIO_24, GPIO_44, …) so that
58+
# scripts and demo files can use them directly instead of the D-number aliases.
59+
60+
61+
def _export_gpio_names():
62+
for name in dir(pin):
63+
if _re.match(r"^GPIO_\d+$", name):
64+
globals()[name] = getattr(pin, name)
65+
66+
67+
_export_gpio_names()
68+
del _export_gpio_names, _re

src/adafruit_blinka/microcontroller/am625x/__init__.py

Whitespace-only changes.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# SPDX-FileCopyrightText: 2026 Melissa LeBlanc-Williams for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
"""TI AM625X pin names (BeaglePlay)"""
5+
from adafruit_blinka.microcontroller.generic_linux.libgpiod_pin import Pin
6+
7+
# gpiochip1 (4201000.gpio) – MCU domain GPIO
8+
QWIIC_SCL = Pin((1, 17)) # MCU_I2C0_SCL
9+
QWIIC_SDA = Pin((1, 18)) # MCU_I2C0_SDA
10+
11+
# gpiochip2 (600000.gpio) – main_gpio0
12+
USR0 = Pin((2, 3)) # USR0 LED
13+
USR1 = Pin((2, 4)) # USR1 LED
14+
USR2 = Pin((2, 5)) # USR2 LED
15+
USR3 = Pin((2, 6)) # USR3 LED
16+
USR4 = Pin((2, 9)) # USR4 LED
17+
USR_BUTTON = Pin((2, 18))
18+
19+
# gpiochip3 (601000.gpio) – main_gpio1
20+
# MIKROBUS I2C (i2c-3)
21+
MIKROBUS_SCL = Pin((3, 22)) # MIKROBUS_GPIO1_22 / main_i2c3_scl
22+
MIKROBUS_SDA = Pin((3, 23)) # MIKROBUS_GPIO1_23 / main_i2c3_sda
23+
24+
# Grove I2C (i2c-1)
25+
GROVE_SCL = Pin((3, 28)) # main_i2c1_scl
26+
GROVE_SDA = Pin((3, 29)) # main_i2c1_sda
27+
28+
# MIKROBUS general-purpose GPIO lines (gpiochip3)
29+
MIKROBUS_GPIO1_7 = Pin((3, 7))
30+
MIKROBUS_GPIO1_8 = Pin((3, 8))
31+
MIKROBUS_GPIO1_9 = Pin((3, 9))
32+
MIKROBUS_GPIO1_10 = Pin((3, 10))
33+
MIKROBUS_GPIO1_11 = Pin((3, 11))
34+
MIKROBUS_GPIO1_12 = Pin((3, 12))
35+
MIKROBUS_W1 = Pin((3, 13)) # 1-Wire GPIO
36+
MIKROBUS_GPIO1_14 = Pin((3, 14))
37+
MIKROBUS_GPIO1_15 = Pin((3, 15))
38+
MIKROBUS_GPIO1_16 = Pin((3, 16))
39+
MIKROBUS_GPIO1_17 = Pin((3, 17))
40+
MIKROBUS_GPIO1_18 = Pin((3, 18))
41+
# line 19 is VDD_3V3_SD (regulator control – not a user GPIO)
42+
MIKROBUS_GPIO1_20 = Pin((3, 20))
43+
MIKROBUS_GPIO1_21 = Pin((3, 21))
44+
MIKROBUS_GPIO1_22 = MIKROBUS_SCL # dual-use: I2C SCL or GPIO
45+
MIKROBUS_GPIO1_23 = MIKROBUS_SDA # dual-use: I2C SDA or GPIO
46+
# lines 24/25 are dual-use: MIKROBUS UART TX/RX or GPIO
47+
MIKROBUS_GPIO1_24 = Pin((3, 24))
48+
MIKROBUS_GPIO1_25 = Pin((3, 25))
49+
50+
# MIKROBUS UART TX/RX aliases (/dev/ttyS0)
51+
UART_TX = MIKROBUS_GPIO1_24
52+
UART_RX = MIKROBUS_GPIO1_25
53+
54+
# ordered as (i2cId, SCL, SDA)
55+
i2cPorts = (
56+
(3, MIKROBUS_SCL, MIKROBUS_SDA), # MIKROBUS I2C → /dev/play/mikrobus/i2c
57+
(1, GROVE_SCL, GROVE_SDA), # Grove I2C → /dev/play/grove/i2c
58+
(5, QWIIC_SCL, QWIIC_SDA), # QWIIC → /dev/play/qwiic/i2c
59+
)
60+
61+
# ordered as (spiId, sckId, mosiId, misoId)
62+
# MIKROBUS SPI is managed by the Greybus/CC1352P7 coprocessor;
63+
# no spidev is exposed by default on stock images.
64+
spiPorts = ()
65+
66+
# ordered as (uartId, txId, rxId)
67+
# MIKROBUS UART → /dev/ttyS0
68+
uartPorts = ((0, UART_TX, UART_RX),)

src/adafruit_blinka/microcontroller/amlogic/a311d/pulseio/PulseIn.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
import atexit
99
import random
10+
import signal
1011
import struct
1112
import sysv_ipc
1213

@@ -31,6 +32,18 @@ def final():
3132
atexit.register(final)
3233

3334

35+
def _signal_handler(signum, frame): # pylint: disable=unused-argument
36+
"""Handle SIGTERM/SIGINT to ensure cleanup runs"""
37+
final()
38+
raise SystemExit(1)
39+
40+
41+
try:
42+
signal.signal(signal.SIGTERM, _signal_handler)
43+
except (OSError, ValueError):
44+
pass # Not all environments allow signal handling
45+
46+
3447
# pylint: disable=c-extension-no-member
3548
class PulseIn:
3649
"""PulseIn Class to read PWM signals"""
@@ -108,14 +121,26 @@ def _wait_receive_msg(self, timeout=0, type=2):
108121

109122
# pylint: enable=redefined-builtin
110123

124+
def __del__(self):
125+
self.deinit()
126+
111127
def deinit(self):
112128
"""Deinitialises the PulseIn and releases any hardware and software
113129
resources for reuse."""
114130
# Clean up after ourselves
115-
self._process.terminate()
116-
procs.remove(self._process)
117-
self._mq.remove()
118-
queues.remove(self._mq)
131+
if self._process is not None:
132+
self._process.terminate()
133+
if self._process in procs:
134+
procs.remove(self._process)
135+
self._process = None
136+
if self._mq is not None:
137+
try:
138+
self._mq.remove()
139+
except sysv_ipc.ExistentialError:
140+
pass # Already removed
141+
if self._mq in queues:
142+
queues.remove(self._mq)
143+
self._mq = None
119144

120145
def __enter__(self):
121146
"""No-op used by Context Managers."""

0 commit comments

Comments
 (0)