CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-adafruit-blinka

CircuitPython APIs for non-CircuitPython versions of Python such as CPython on Linux and MicroPython.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

peripherals.mddocs/

Specialized Peripherals

Support for advanced peripherals including NeoPixel LED strips, rotary encoders, keypads, and USB HID devices. Provides CircuitPython-compatible interfaces for complex input/output devices with platform-specific optimizations.

Capabilities

NeoPixel LED Strip Control

Precision-timed writing support for WS2812-style addressable RGB LED strips. Provides bit-banged timing control for accurate color data transmission.

def neopixel_write(gpio, buf: bytes) -> None:
    """
    Write RGB color buffer to NeoPixel LED strip.
    
    Args:
        gpio: DigitalInOut pin object configured as output
        buf: Color data buffer (3 bytes per LED: G, R, B order)
    
    Note:
        Each LED requires 3 bytes in GRB (Green, Red, Blue) order.
        Buffer length must be multiple of 3.
        Timing is critical - interrupts may cause color corruption.
    """

Rotary Encoder Position Tracking

Incremental encoder support for reading rotary position changes. Tracks quadrature encoder signals to provide accurate position feedback.

class IncrementalEncoder(ContextManaged):
    def __init__(self, pin_a, pin_b):
        """
        Initialize rotary encoder tracking.
        
        Args:
            pin_a: First encoder signal pin (A channel)
            pin_b: Second encoder signal pin (B channel, 90° phase shifted)
        
        Note:
            Uses interrupt-based tracking for accurate position counting.
            Encoder should have pull-up resistors on both signal lines.
        """
    
    @property
    def position(self) -> int:
        """
        Current encoder position.
        
        Returns:
            int: Position count (positive for clockwise, negative for counter-clockwise)
        """
    
    def deinit(self) -> None:
        """Release encoder resources and disable tracking"""

Keypad Scanning

Comprehensive key scanning support for individual keys, key matrices, and shift register-based keypads. Provides debounced input with event queuing.

class Event:
    def __init__(self, key_number: int = 0, pressed: bool = True):
        """
        Key transition event.
        
        Args:
            key_number: Identifier for the key (0-based index)
            pressed: True for key press, False for key release
        """
    
    @property
    def key_number(self) -> int:
        """Key number that generated this event"""
    
    @property
    def pressed(self) -> bool:
        """True if key was pressed, False if released"""
    
    @property
    def released(self) -> bool:
        """True if key was released, False if pressed"""

class EventQueue:
    def get(self) -> Event:
        """
        Get next key event from queue.
        
        Returns:
            Event: Next key transition event, or None if queue empty
        """
    
    def get_into(self, event: Event) -> bool:
        """
        Store next event in provided Event object.
        
        Args:
            event: Event object to populate
        
        Returns:
            bool: True if event was available and stored
        """
    
    def clear(self) -> None:
        """Clear all queued events"""
    
    def __len__(self) -> int:
        """Number of events in queue"""
    
    def __bool__(self) -> bool:
        """True if queue has events"""
    
    @property
    def overflowed(self) -> bool:
        """True if events were dropped due to full queue"""

class Keys(ContextManaged):
    def __init__(
        self,
        pins,
        *,
        value_when_pressed: bool,
        pull: bool = True,
        interval: float = 0.02,
        max_events: int = 64
    ):
        """
        Individual key scanner.
        
        Args:
            pins: Sequence of pin objects for keys
            value_when_pressed: True if pin reads high when pressed
            pull: Enable internal pull resistors
            interval: Scan interval in seconds (default 0.02 = 20ms)
            max_events: Maximum events to queue
        """
    
    @property
    def events(self) -> EventQueue:
        """Event queue for key transitions (read-only)"""
    
    @property
    def key_count(self) -> int:
        """Number of keys being scanned (read-only)"""
    
    def reset(self) -> None:
        """Reset scanner state - treats all keys as released"""
    
    def deinit(self) -> None:
        """Stop scanning and release pins"""

class KeyMatrix(ContextManaged):
    def __init__(
        self,
        row_pins,
        column_pins,
        columns_to_anodes: bool = True,
        interval: float = 0.02,
        max_events: int = 64
    ):
        """
        Key matrix scanner.
        
        Args:
            row_pins: Sequence of row pin objects
            column_pins: Sequence of column pin objects
            columns_to_anodes: True if diode anodes connect to columns
            interval: Scan interval in seconds
            max_events: Maximum events to queue
        
        Note:
            Key number = row * len(column_pins) + column
        """
    
    @property
    def events(self) -> EventQueue:
        """Event queue for key transitions (read-only)"""
    
    @property
    def key_count(self) -> int:
        """Total number of keys in matrix (rows × columns)"""
    
    def reset(self) -> None:
        """Reset scanner state"""
    
    def deinit(self) -> None:
        """Stop scanning and release pins"""

class ShiftRegisterKeys(ContextManaged):
    def __init__(
        self,
        *,
        clock,
        data,
        latch,
        value_to_latch: bool = True,
        key_count: int,
        value_when_pressed: bool,
        interval: float = 0.02,
        max_events: int = 64
    ):
        """
        Shift register key scanner (74HC165, CD4021).
        
        Args:
            clock: Clock pin for shift register
            data: Serial data input pin
            latch: Latch pin for parallel data capture
            value_to_latch: True if data latched on high, False on low
            key_count: Number of keys to read
            value_when_pressed: True if key reads high when pressed
            interval: Scan interval in seconds
            max_events: Maximum events to queue
        """
    
    @property
    def events(self) -> EventQueue:
        """Event queue for key transitions (read-only)"""
    
    @property
    def key_count(self) -> int:
        """Number of keys being scanned"""
    
    def reset(self) -> None:
        """Reset scanner state"""
    
    def deinit(self) -> None:
        """Stop scanning and release pins"""

USB HID Device Emulation

USB Human Interface Device emulation for creating keyboards, mice, and custom HID devices. Uses Linux USB gadget framework for device presentation.

class Device:
    # Pre-defined device types
    KEYBOARD: Device             # Standard keyboard
    MOUSE: Device               # Standard mouse  
    CONSUMER_CONTROL: Device    # Media control device
    BOOT_KEYBOARD: Device       # BIOS-compatible keyboard
    BOOT_MOUSE: Device          # BIOS-compatible mouse
    
    def __init__(
        self,
        *,
        descriptor: bytes,
        usage_page: int,
        usage: int,
        report_ids: tuple[int, ...],
        in_report_lengths: tuple[int, ...],
        out_report_lengths: tuple[int, ...]
    ):
        """
        Create custom HID device.
        
        Args:
            descriptor: HID report descriptor bytes
            usage_page: HID usage page
            usage: HID usage within page
            report_ids: Tuple of report ID numbers
            in_report_lengths: Input report sizes for each report ID
            out_report_lengths: Output report sizes for each report ID
        """
    
    def send_report(self, report: bytes, report_id: int = None) -> None:
        """
        Send HID input report to host.
        
        Args:
            report: Report data to send
            report_id: Report ID (optional if device has single report ID)
        """
    
    def get_last_received_report(self, report_id: int = None) -> bytes:
        """
        Get last received HID output report.
        
        Args:
            report_id: Report ID to check (optional)
        
        Returns:
            bytes: Last received report data, or None if none received
        """

def enable(devices: tuple[Device, ...], boot_device: int = 0) -> None:
    """
    Enable USB HID with specified devices.
    
    Args:
        devices: Tuple of Device objects to enable
        boot_device: Boot device type (0=none, 1=keyboard, 2=mouse)
    
    Note:
        Must be called before USB connection. Requires dwc2 and libcomposite
        kernel modules. Creates /dev/hidg* device files for communication.
    """

def disable() -> None:
    """Disable all USB HID devices"""

Usage Examples

NeoPixel LED Strip

import board
import digitalio
import neopixel_write
import time

# Setup NeoPixel data pin
pixel_pin = digitalio.DigitalInOut(board.D18)
pixel_pin.direction = digitalio.Direction.OUTPUT

# Number of LEDs
num_pixels = 10

def set_pixel_color(index, red, green, blue, buf):
    """Set color for single pixel in buffer (GRB order)"""
    offset = index * 3
    buf[offset] = green      # Green first
    buf[offset + 1] = red    # Red second  
    buf[offset + 2] = blue   # Blue third

# Create color buffer (3 bytes per pixel: GRB)
pixel_buffer = bytearray(num_pixels * 3)

# Rainbow effect
for cycle in range(100):
    for i in range(num_pixels):
        # Calculate rainbow colors
        hue = (i * 256 // num_pixels + cycle * 5) % 256
        
        # Simple HSV to RGB conversion
        if hue < 85:
            red, green, blue = hue * 3, 255 - hue * 3, 0
        elif hue < 170:
            hue -= 85
            red, green, blue = 255 - hue * 3, 0, hue * 3
        else:
            hue -= 170
            red, green, blue = 0, hue * 3, 255 - hue * 3
        
        set_pixel_color(i, red, green, blue, pixel_buffer)
    
    # Write to NeoPixel strip
    neopixel_write.neopixel_write(pixel_pin, pixel_buffer)
    time.sleep(0.05)

# Turn off all pixels
pixel_buffer = bytearray(num_pixels * 3)  # All zeros
neopixel_write.neopixel_write(pixel_pin, pixel_buffer)
pixel_pin.deinit()

Rotary Encoder Position Reading

import board
import rotaryio
import time

# Initialize rotary encoder
encoder = rotaryio.IncrementalEncoder(board.D2, board.D3)

# Track position
last_position = 0

print("Rotate encoder... Press Ctrl+C to stop")

try:
    while True:
        position = encoder.position
        
        if position != last_position:
            direction = "clockwise" if position > last_position else "counter-clockwise"
            print(f"Position: {position} ({direction})")
            last_position = position
        
        time.sleep(0.01)

except KeyboardInterrupt:
    print("Stopping encoder reading")

encoder.deinit()

Individual Key Scanning

import board
import keypad
import time

# Setup keys on pins D2, D3, D4
keys = keypad.Keys([board.D2, board.D3, board.D4], 
                   value_when_pressed=False,  # Grounded when pressed
                   pull=True)                 # Enable pull-ups

print("Press keys... Press Ctrl+C to stop")

try:
    while True:
        event = keys.events.get()
        
        if event:
            if event.pressed:
                print(f"Key {event.key_number} pressed")
            else:
                print(f"Key {event.key_number} released")
        
        time.sleep(0.01)

except KeyboardInterrupt:
    print("Stopping key scanning")

keys.deinit()

Key Matrix Scanning

import board
import keypad
import time

# 3x3 key matrix
row_pins = [board.D18, board.D19, board.D20]
col_pins = [board.D21, board.D22, board.D23]

matrix = keypad.KeyMatrix(row_pins, col_pins)

print(f"Scanning {matrix.key_count} keys in 3x3 matrix")
print("Press keys... Press Ctrl+C to stop")

try:
    while True:
        event = matrix.events.get()
        
        if event:
            row = event.key_number // len(col_pins)
            col = event.key_number % len(col_pins)
            action = "pressed" if event.pressed else "released"
            print(f"Key at row {row}, col {col} (#{event.key_number}) {action}")
        
        time.sleep(0.01)

except KeyboardInterrupt:
    print("Stopping matrix scanning")

matrix.deinit()

Shift Register Key Scanning

import board
import keypad
import time

# 74HC165 shift register setup
shift_keys = keypad.ShiftRegisterKeys(
    clock=board.D18,          # Clock pin
    data=board.D19,           # Serial data in
    latch=board.D20,          # Parallel load
    key_count=8,              # 8 keys
    value_when_pressed=False, # Active low
    value_to_latch=False      # Latch on low (74HC165 style)
)

print("Scanning 8 keys via shift register")
print("Press keys... Press Ctrl+C to stop")

try:
    while True:
        event = shift_keys.events.get()
        
        if event:
            action = "pressed" if event.pressed else "released"
            print(f"Shift register key {event.key_number} {action}")
        
        time.sleep(0.01)

except KeyboardInterrupt:
    print("Stopping shift register scanning")

shift_keys.deinit()

USB HID Device Support

Linux-based USB Human Interface Device (HID) support for creating keyboard, mouse, and custom HID devices using the USB Gadget framework.

class Device:
    """USB HID device specification"""
    
    def __init__(
        self,
        *,
        descriptor: bytes,
        usage_page: int,
        usage: int,
        report_ids: Sequence[int],
        in_report_lengths: Sequence[int],
        out_report_lengths: Sequence[int]
    ):
        """
        Create a custom HID device.
        
        Args:
            descriptor: HID report descriptor bytes
            usage_page: HID usage page (e.g., 0x01 for Generic Desktop)
            usage: HID usage ID (e.g., 0x06 for Keyboard)
            report_ids: List of report IDs this device uses
            in_report_lengths: List of input report lengths (device to host)
            out_report_lengths: List of output report lengths (host to device)
        """
    
    def send_report(self, report: bytearray, report_id: int = None) -> None:
        """
        Send HID report to host.
        
        Args:
            report: Report data to send
            report_id: Report ID (optional if device has only one report ID)
        """
    
    def get_last_received_report(self, report_id: int = None) -> bytes:
        """
        Get last received HID OUT or feature report.
        
        Args:
            report_id: Report ID to get (optional if device has only one)
            
        Returns:
            Last received report data, or None if nothing received
        """
    
    @property
    def last_received_report(self) -> bytes:
        """
        Last received HID OUT report (deprecated, use get_last_received_report()).
        
        Returns:
            Last received report data, or None if nothing received
        """

# Pre-defined device constants
Device.KEYBOARD: Device     # Standard USB keyboard
Device.MOUSE: Device        # Standard USB mouse

def enable(devices: Sequence[Device]) -> None:
    """
    Enable USB HID with specified devices.
    
    Args:
        devices: Tuple of Device objects to enable
        
    Raises:
        Exception: If required kernel modules not loaded or setup fails
        
    Note:
        Must be called before USB enumeration.
        Requires root privileges and dwc2/libcomposite kernel modules.
    """

def disable() -> None:
    """Disable USB HID gadget"""

USB HID Keyboard Emulation

# Note: Requires Linux with dwc2 and libcomposite kernel modules
# Must be run as root and called before USB enumeration

import usb_hid
import time

# Enable USB HID keyboard
usb_hid.enable((usb_hid.Device.KEYBOARD,))

# Get keyboard device
keyboard = usb_hid.Device.KEYBOARD

# Keyboard usage codes (simplified)
KEY_A = 0x04
KEY_ENTER = 0x28
KEY_SPACE = 0x2C

def send_key(key_code, modifier=0):
    """Send a key press and release"""
    # HID keyboard report: [modifier, reserved, key1, key2, key3, key4, key5, key6]
    report = bytearray([modifier, 0, key_code, 0, 0, 0, 0, 0])
    
    # Send key press
    keyboard.send_report(report)
    time.sleep(0.01)
    
    # Send key release
    report = bytearray([0, 0, 0, 0, 0, 0, 0, 0])
    keyboard.send_report(report)
    time.sleep(0.01)

# Type "Hello World"
print("Typing 'Hello World' via USB HID...")

# Type characters (this is simplified - real implementation needs full keycode mapping)
send_key(KEY_A)  # This would need proper character-to-keycode mapping
time.sleep(0.5)

# Send Enter
send_key(KEY_ENTER)

print("USB HID keyboard demo complete")

USB HID Mouse Emulation

import usb_hid
import time

# Enable USB HID mouse
usb_hid.enable((usb_hid.Device.MOUSE,))

# Get mouse device
mouse = usb_hid.Device.MOUSE

def move_mouse(x, y, buttons=0, wheel=0):
    """Move mouse and/or click buttons"""
    # HID mouse report: [buttons, x, y, wheel]
    # Limit movement to signed 8-bit range (-127 to 127)
    x = max(-127, min(127, x))
    y = max(-127, min(127, y))
    wheel = max(-127, min(127, wheel))
    
    report = bytearray([buttons, x, y, wheel])
    mouse.send_report(report)

# Mouse button constants
LEFT_BUTTON = 0x01
RIGHT_BUTTON = 0x02
MIDDLE_BUTTON = 0x04

print("USB HID mouse demo...")

# Move in square pattern
movements = [
    (50, 0),    # Right
    (0, 50),    # Down
    (-50, 0),   # Left
    (0, -50),   # Up
]

for dx, dy in movements:
    move_mouse(dx, dy)
    time.sleep(0.5)

# Click left button
move_mouse(0, 0, LEFT_BUTTON)
time.sleep(0.1)
move_mouse(0, 0, 0)  # Release

print("USB HID mouse demo complete")

Platform Considerations

NeoPixel Support

Supported Platforms:

  • Raspberry Pi: All models with optimized bit-banging
  • RP2040 via U2IF: Pico and compatible boards
  • OS Agnostic: Generic implementation

Timing Requirements:

  • Critical timing for WS2812 protocol (800ns/bit)
  • Interrupts can cause color corruption
  • May require disabling interrupts during transmission

Rotary Encoder Support

Supported Platforms:

  • Raspberry Pi 5: Hardware-optimized implementation
  • Generic Linux: Software-based quadrature decoding

Implementation Notes:

  • Uses interrupt-based tracking for accuracy
  • Requires pull-up resistors on encoder signals
  • Thread-safe position tracking

Keypad Limitations

Debouncing:

  • Hardware debouncing: 20ms default scan interval
  • Software debouncing: Built into scanning loop
  • Configurable scan intervals (minimum ~5ms practical)

Threading:

  • Background scanning thread for real-time response
  • Thread-safe event queue access
  • Automatic cleanup on context manager exit

USB HID Requirements

Linux Requirements:

  • dwc2 kernel module (USB Device Controller)
  • libcomposite kernel module (USB Gadget framework)
  • Root privileges for /sys/kernel/config access
  • Available USB Device Controller in /sys/class/udc/

Device Limitations:

  • Maximum endpoints limited by hardware
  • Boot devices must be first USB interface
  • Report descriptors define device capabilities

Error Handling

# NeoPixel error handling
try:
    import neopixel_write
    # Use NeoPixel functionality
except ImportError:
    print("NeoPixel not supported on this platform")

# Rotary encoder error handling
try:
    import rotaryio
    encoder = rotaryio.IncrementalEncoder(board.D2, board.D3)
except RuntimeError as e:
    print(f"Encoder not supported: {e}")

# USB HID error handling
try:
    import usb_hid
    usb_hid.enable((usb_hid.Device.KEYBOARD,))
except Exception as e:
    if "dwc2" in str(e):
        print("USB HID requires dwc2 kernel module")
    elif "libcomposite" in str(e):
        print("USB HID requires libcomposite kernel module") 
    else:
        print(f"USB HID error: {e}")

Install with Tessl CLI

npx tessl i tessl/pypi-adafruit-blinka

docs

analog-io.md

bitbangio.md

board-pins.md

communication.md

core-framework.md

digital-io.md

index.md

peripherals.md

pwm-pulse.md

utilities.md

tile.json