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

core-framework.mddocs/

Core Framework

Base classes and utilities that provide resource management, platform detection, and CircuitPython compatibility features. Forms the foundation for all hardware interface classes with automatic cleanup, resource locking, and cross-platform abstractions.

Capabilities

Context Management Base Classes

Fundamental base classes that provide automatic resource cleanup and exclusive resource access patterns used throughout the Blinka ecosystem.

class ContextManaged:
    def __enter__(self):
        """Context manager entry - returns self"""
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit - automatically calls deinit()"""
    
    def deinit(self) -> None:
        """
        Free any hardware resources used by the object.
        
        Note:
            Override this method in subclasses to implement
            specific resource cleanup logic.
        """

class Lockable(ContextManaged):
    def try_lock(self) -> bool:
        """
        Attempt to acquire exclusive lock on resource.
        
        Returns:
            bool: True if lock acquired, False if already locked
        
        Note:
            Must call try_lock() before using shared resources like SPI/I2C.
            Always pair with unlock() or use context manager for safety.
        """
    
    def unlock(self) -> None:
        """
        Release exclusive lock on resource.
        
        Note:
            Only call if try_lock() returned True.
            Context manager automatically handles unlock.
        """

CircuitPython Compatibility

Enum class that provides CircuitPython-style static symbols and introspection capabilities for constants and configuration values.

class Enum:
    def __repr__(self) -> str:
        """
        Return dot-subscripted path to enum instance.
        
        Returns:
            str: Module path to enum value (e.g., "digitalio.Direction.OUTPUT")
        """
    
    @classmethod
    def iteritems(cls):
        """
        Iterate over class attributes that are instances of this enum.
        
        Yields:
            tuple: (key, value) pairs for enum constants
        
        Example:
            for name, value in Direction.iteritems():
                print(f"{name}: {value}")
        """

Configuration Management

Utilities for loading configuration settings and applying platform-specific patches to ensure compatibility across diverse environments.

def load_settings_toml() -> dict:
    """
    Load values from settings.toml into environment variables.
    
    Returns:
        dict: Parsed TOML settings
    
    Raises:
        FileNotFoundError: If settings.toml not found in current directory
        TOMLDecodeError: If settings.toml has invalid syntax
        ValueError: If settings contain unsupported data types
    
    Note:
        Only supports bool, int, float, and str values.
        Settings are added to os.environ for later access via os.getenv().
        Existing environment variables are not overwritten.
    """

def patch_system() -> None:
    """
    Apply platform-specific patches to system modules.
    
    Note:
        Patches the time module to ensure consistent behavior
        across CPython, MicroPython, and CircuitPython platforms.
        Called automatically during Blinka initialization.
    """

Hardware Pin Abstraction

Pin class that provides a common interface for hardware pin references across all supported platforms and microcontrollers.

class Pin:
    """
    Hardware pin reference object.
    
    Note:
        Pin objects are typically accessed through board module constants
        (e.g., board.D18) rather than created directly.
        Provides platform-agnostic pin identification.
    """
    id: int    # Platform-specific pin identifier

Platform Detection

Runtime platform detection system that identifies hardware capabilities and loads appropriate drivers automatically.

# Platform detection globals (read-only)
detector: object         # Platform detection instance from adafruit-platformdetect
chip_id: str            # Detected microcontroller/SoC identifier
board_id: str           # Detected development board identifier  
implementation: str     # Python implementation name ("cpython", "micropython", etc.)

# Platform-appropriate sleep function
sleep: function         # Time.sleep or utime.sleep depending on platform

Precision Timing

Microsecond-precision delay function for applications requiring accurate timing control.

def delay_us(delay: int) -> None:
    """
    Sleep for the specified number of microseconds.
    
    Args:
        delay: Delay duration in microseconds
    
    Note:
        Provides microsecond precision timing for bit-banging protocols
        and other time-critical operations. Actual precision depends on
        platform capabilities and system load.
    """

Usage Examples

Context Manager Pattern

import digitalio
import board
import time

# Using context manager for automatic cleanup
with digitalio.DigitalInOut(board.D18) as led:
    led.direction = digitalio.Direction.OUTPUT
    
    # LED will be automatically cleaned up when exiting context
    for i in range(10):
        led.value = True
        time.sleep(0.5)
        led.value = False
        time.sleep(0.5)

# LED pin is automatically released here
print("LED cleanup completed automatically")

Manual Resource Management

import digitalio
import board

# Manual resource management
led = digitalio.DigitalInOut(board.D18)
led.direction = digitalio.Direction.OUTPUT

try:
    # Use the LED
    led.value = True
    # ... do work ...
    
finally:
    # Always clean up resources
    led.deinit()

Resource Locking for Shared Buses

import busio
import board
import time

# SPI bus requires locking for thread safety
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)

# Method 1: Manual locking
if spi.try_lock():
    try:
        spi.configure(baudrate=1000000)
        spi.write(b'\x01\x02\x03')
    finally:
        spi.unlock()
else:
    print("Could not acquire SPI lock")

# Method 2: Context manager (automatic locking)
with spi:
    spi.configure(baudrate=500000) 
    spi.write(b'\x04\x05\x06')

spi.deinit()

Custom Enum Definition

from adafruit_blinka import Enum

class MyConstants(Enum):
    """Custom enum for application constants"""
    pass

# Define enum values
MyConstants.OPTION_A = MyConstants()
MyConstants.OPTION_B = MyConstants()
MyConstants.OPTION_C = MyConstants()

# Use enum values
current_mode = MyConstants.OPTION_A
print(f"Current mode: {current_mode}")  # Shows full path

# Iterate over enum values
for name, value in MyConstants.iteritems():
    print(f"Available option: {name}")

Settings Configuration

# settings.toml file:
# wifi_ssid = "MyNetwork"
# wifi_password = "secret123"  
# debug_enabled = true
# max_retries = 5
# timeout = 30.5

from adafruit_blinka import load_settings_toml
import os

try:
    settings = load_settings_toml()
    print("Loaded settings:", settings)
    
    # Access via environment variables
    wifi_ssid = os.getenv("wifi_ssid", "default_network")
    debug_mode = os.getenv("debug_enabled", "false").lower() == "true"
    max_retries = int(os.getenv("max_retries", "3"))
    
    print(f"WiFi SSID: {wifi_ssid}")
    print(f"Debug mode: {debug_mode}")
    print(f"Max retries: {max_retries}")
    
except FileNotFoundError:
    print("No settings.toml found, using defaults")
except ValueError as e:
    print(f"Invalid settings format: {e}")

Platform Detection

from adafruit_blinka.agnostic import detector, chip_id, board_id, implementation

# Check what platform we're running on
print(f"Board: {board_id}")
print(f"Chip: {chip_id}")
print(f"Python implementation: {implementation}")

# Platform-specific feature detection
if detector.board.any_raspberry_pi:
    print("Running on Raspberry Pi")
    if detector.board.any_raspberry_pi_5_board:
        print("Pi 5 detected - using lgpio")
    else:
        print("Pi 4 or earlier - using RPi.GPIO")
        
elif detector.board.any_jetson_board:
    print("Running on NVIDIA Jetson")
    
elif detector.board.ftdi_ft232h:
    print("Using FTDI FT232H USB adapter")

# Check for specific chip families
if chip_id.startswith("BCM"):
    print("Broadcom chip detected")
elif chip_id.startswith("RK"):
    print("Rockchip SoC detected")
elif chip_id == "RP2040":
    print("Raspberry Pi Pico RP2040 detected")

# Adapt behavior based on platform
if detector.board.any_embedded_linux:
    print("Full Linux system - all features available")
else:
    print("Microcontroller or adapter - limited features")

Precision Timing

import microcontroller
import board
import digitalio

# Bit-banging a custom protocol with precise timing
data_pin = digitalio.DigitalInOut(board.D18)
data_pin.direction = digitalio.Direction.OUTPUT

def send_bit(bit_value):
    """Send a single bit with precise timing"""
    if bit_value:
        # Send '1' bit: 800ns high, 450ns low
        data_pin.value = True
        microcontroller.delay_us(1)      # ~800ns (rounded to 1μs)
        data_pin.value = False
        microcontroller.delay_us(1)      # ~450ns (rounded to 1μs)
    else:
        # Send '0' bit: 400ns high, 850ns low  
        data_pin.value = True
        microcontroller.delay_us(1)      # ~400ns (rounded to 1μs)
        data_pin.value = False
        microcontroller.delay_us(1)      # ~850ns (rounded to 1μs)

def send_byte(byte_value):
    """Send 8 bits with precise timing"""
    for bit in range(8):
        bit_value = (byte_value >> (7 - bit)) & 1
        send_bit(bit_value)

# Send custom protocol data
try:
    for data in [0xFF, 0x00, 0xAA, 0x55]:
        send_byte(data)
        microcontroller.delay_us(10)  # Inter-byte delay

finally:
    data_pin.deinit()

Custom Hardware Class

from adafruit_blinka import ContextManaged
import board
import digitalio
import time

class CustomSensor(ContextManaged):
    """Example custom hardware class using Blinka patterns"""
    
    def __init__(self, data_pin, clock_pin):
        self._data = digitalio.DigitalInOut(data_pin)
        self._data.direction = digitalio.Direction.INPUT
        
        self._clock = digitalio.DigitalInOut(clock_pin)
        self._clock.direction = digitalio.Direction.OUTPUT
        self._clock.value = False
        
    def read_value(self):
        """Read sensor value using custom protocol"""
        # Generate clock pulses and read data
        value = 0
        for bit in range(8):
            self._clock.value = True
            time.sleep(0.001)  # 1ms clock high
            
            if self._data.value:
                value |= (1 << (7 - bit))
                
            self._clock.value = False
            time.sleep(0.001)  # 1ms clock low
            
        return value
    
    def deinit(self):
        """Clean up hardware resources"""
        if hasattr(self, '_data'):
            self._data.deinit()
        if hasattr(self, '_clock'):
            self._clock.deinit()

# Use custom sensor class
with CustomSensor(board.D2, board.D3) as sensor:
    for _ in range(10):
        value = sensor.read_value()
        print(f"Sensor reading: {value}")
        time.sleep(1)

# Automatic cleanup when exiting context

Platform Considerations

Context Manager Benefits

  • Automatic Cleanup: Resources always released, even on exceptions
  • Thread Safety: Proper cleanup in multi-threaded applications
  • Memory Management: Prevents resource leaks in long-running programs
  • Best Practice: Follows Python conventions and CircuitPython patterns

Resource Locking

Required For:

  • SPI buses (shared among multiple devices)
  • I2C buses (shared among multiple devices)
  • Any hardware resource accessed by multiple threads

Not Required For:

  • GPIO pins (exclusive to single DigitalInOut instance)
  • PWM outputs (exclusive to single PWMOut instance)
  • UART ports (typically exclusive to single UART instance)

Platform Detection Reliability

Automatic Detection Works For:

  • Standard development boards with known signatures
  • Well-established SoC families with device tree information
  • USB adapters with unique vendor/product IDs

Manual Configuration May Be Needed For:

  • Custom boards without standard signatures
  • Generic x86 systems without GPIO hardware
  • Embedded systems with non-standard configurations

Error Handling

from adafruit_blinka import ContextManaged, load_settings_toml
import board

# Handle platform compatibility issues
try:
    import pwmio
    pwm = pwmio.PWMOut(board.D18)
    print("PWM available")
    pwm.deinit()
except (RuntimeError, AttributeError) as e:
    print(f"PWM not available: {e}")

# Handle configuration loading errors  
try:
    settings = load_settings_toml()
except FileNotFoundError:
    print("Using default configuration")
    settings = {}
except ValueError as e:
    print(f"Configuration error: {e}")
    settings = {}

# Handle resource contention
import busio

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)

# Timeout pattern for resource acquisition
import time
timeout = 5.0  # seconds
start_time = time.time()

while not spi.try_lock():
    if time.time() - start_time > timeout:
        print("Could not acquire SPI lock within timeout")
        break
    time.sleep(0.01)
else:
    try:
        # Use SPI bus
        spi.configure(baudrate=1000000)
        spi.write(b'\x01\x02\x03')
    finally:
        spi.unlock()

spi.deinit()

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