or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

analog-io.mdbitbangio.mdboard-pins.mdcommunication.mdcore-framework.mddigital-io.mdindex.mdperipherals.mdpwm-pulse.mdutilities.md

peripherals.mddocs/

0

# Specialized Peripherals

1

2

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.

3

4

## Capabilities

5

6

### NeoPixel LED Strip Control

7

8

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

9

10

```python { .api }

11

def neopixel_write(gpio, buf: bytes) -> None:

12

"""

13

Write RGB color buffer to NeoPixel LED strip.

14

15

Args:

16

gpio: DigitalInOut pin object configured as output

17

buf: Color data buffer (3 bytes per LED: G, R, B order)

18

19

Note:

20

Each LED requires 3 bytes in GRB (Green, Red, Blue) order.

21

Buffer length must be multiple of 3.

22

Timing is critical - interrupts may cause color corruption.

23

"""

24

```

25

26

### Rotary Encoder Position Tracking

27

28

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

29

30

```python { .api }

31

class IncrementalEncoder(ContextManaged):

32

def __init__(self, pin_a, pin_b):

33

"""

34

Initialize rotary encoder tracking.

35

36

Args:

37

pin_a: First encoder signal pin (A channel)

38

pin_b: Second encoder signal pin (B channel, 90° phase shifted)

39

40

Note:

41

Uses interrupt-based tracking for accurate position counting.

42

Encoder should have pull-up resistors on both signal lines.

43

"""

44

45

@property

46

def position(self) -> int:

47

"""

48

Current encoder position.

49

50

Returns:

51

int: Position count (positive for clockwise, negative for counter-clockwise)

52

"""

53

54

def deinit(self) -> None:

55

"""Release encoder resources and disable tracking"""

56

```

57

58

### Keypad Scanning

59

60

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

61

62

```python { .api }

63

class Event:

64

def __init__(self, key_number: int = 0, pressed: bool = True):

65

"""

66

Key transition event.

67

68

Args:

69

key_number: Identifier for the key (0-based index)

70

pressed: True for key press, False for key release

71

"""

72

73

@property

74

def key_number(self) -> int:

75

"""Key number that generated this event"""

76

77

@property

78

def pressed(self) -> bool:

79

"""True if key was pressed, False if released"""

80

81

@property

82

def released(self) -> bool:

83

"""True if key was released, False if pressed"""

84

85

class EventQueue:

86

def get(self) -> Event:

87

"""

88

Get next key event from queue.

89

90

Returns:

91

Event: Next key transition event, or None if queue empty

92

"""

93

94

def get_into(self, event: Event) -> bool:

95

"""

96

Store next event in provided Event object.

97

98

Args:

99

event: Event object to populate

100

101

Returns:

102

bool: True if event was available and stored

103

"""

104

105

def clear(self) -> None:

106

"""Clear all queued events"""

107

108

def __len__(self) -> int:

109

"""Number of events in queue"""

110

111

def __bool__(self) -> bool:

112

"""True if queue has events"""

113

114

@property

115

def overflowed(self) -> bool:

116

"""True if events were dropped due to full queue"""

117

118

class Keys(ContextManaged):

119

def __init__(

120

self,

121

pins,

122

*,

123

value_when_pressed: bool,

124

pull: bool = True,

125

interval: float = 0.02,

126

max_events: int = 64

127

):

128

"""

129

Individual key scanner.

130

131

Args:

132

pins: Sequence of pin objects for keys

133

value_when_pressed: True if pin reads high when pressed

134

pull: Enable internal pull resistors

135

interval: Scan interval in seconds (default 0.02 = 20ms)

136

max_events: Maximum events to queue

137

"""

138

139

@property

140

def events(self) -> EventQueue:

141

"""Event queue for key transitions (read-only)"""

142

143

@property

144

def key_count(self) -> int:

145

"""Number of keys being scanned (read-only)"""

146

147

def reset(self) -> None:

148

"""Reset scanner state - treats all keys as released"""

149

150

def deinit(self) -> None:

151

"""Stop scanning and release pins"""

152

153

class KeyMatrix(ContextManaged):

154

def __init__(

155

self,

156

row_pins,

157

column_pins,

158

columns_to_anodes: bool = True,

159

interval: float = 0.02,

160

max_events: int = 64

161

):

162

"""

163

Key matrix scanner.

164

165

Args:

166

row_pins: Sequence of row pin objects

167

column_pins: Sequence of column pin objects

168

columns_to_anodes: True if diode anodes connect to columns

169

interval: Scan interval in seconds

170

max_events: Maximum events to queue

171

172

Note:

173

Key number = row * len(column_pins) + column

174

"""

175

176

@property

177

def events(self) -> EventQueue:

178

"""Event queue for key transitions (read-only)"""

179

180

@property

181

def key_count(self) -> int:

182

"""Total number of keys in matrix (rows × columns)"""

183

184

def reset(self) -> None:

185

"""Reset scanner state"""

186

187

def deinit(self) -> None:

188

"""Stop scanning and release pins"""

189

190

class ShiftRegisterKeys(ContextManaged):

191

def __init__(

192

self,

193

*,

194

clock,

195

data,

196

latch,

197

value_to_latch: bool = True,

198

key_count: int,

199

value_when_pressed: bool,

200

interval: float = 0.02,

201

max_events: int = 64

202

):

203

"""

204

Shift register key scanner (74HC165, CD4021).

205

206

Args:

207

clock: Clock pin for shift register

208

data: Serial data input pin

209

latch: Latch pin for parallel data capture

210

value_to_latch: True if data latched on high, False on low

211

key_count: Number of keys to read

212

value_when_pressed: True if key reads high when pressed

213

interval: Scan interval in seconds

214

max_events: Maximum events to queue

215

"""

216

217

@property

218

def events(self) -> EventQueue:

219

"""Event queue for key transitions (read-only)"""

220

221

@property

222

def key_count(self) -> int:

223

"""Number of keys being scanned"""

224

225

def reset(self) -> None:

226

"""Reset scanner state"""

227

228

def deinit(self) -> None:

229

"""Stop scanning and release pins"""

230

```

231

232

### USB HID Device Emulation

233

234

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

235

236

```python { .api }

237

class Device:

238

# Pre-defined device types

239

KEYBOARD: Device # Standard keyboard

240

MOUSE: Device # Standard mouse

241

CONSUMER_CONTROL: Device # Media control device

242

BOOT_KEYBOARD: Device # BIOS-compatible keyboard

243

BOOT_MOUSE: Device # BIOS-compatible mouse

244

245

def __init__(

246

self,

247

*,

248

descriptor: bytes,

249

usage_page: int,

250

usage: int,

251

report_ids: tuple[int, ...],

252

in_report_lengths: tuple[int, ...],

253

out_report_lengths: tuple[int, ...]

254

):

255

"""

256

Create custom HID device.

257

258

Args:

259

descriptor: HID report descriptor bytes

260

usage_page: HID usage page

261

usage: HID usage within page

262

report_ids: Tuple of report ID numbers

263

in_report_lengths: Input report sizes for each report ID

264

out_report_lengths: Output report sizes for each report ID

265

"""

266

267

def send_report(self, report: bytes, report_id: int = None) -> None:

268

"""

269

Send HID input report to host.

270

271

Args:

272

report: Report data to send

273

report_id: Report ID (optional if device has single report ID)

274

"""

275

276

def get_last_received_report(self, report_id: int = None) -> bytes:

277

"""

278

Get last received HID output report.

279

280

Args:

281

report_id: Report ID to check (optional)

282

283

Returns:

284

bytes: Last received report data, or None if none received

285

"""

286

287

def enable(devices: tuple[Device, ...], boot_device: int = 0) -> None:

288

"""

289

Enable USB HID with specified devices.

290

291

Args:

292

devices: Tuple of Device objects to enable

293

boot_device: Boot device type (0=none, 1=keyboard, 2=mouse)

294

295

Note:

296

Must be called before USB connection. Requires dwc2 and libcomposite

297

kernel modules. Creates /dev/hidg* device files for communication.

298

"""

299

300

def disable() -> None:

301

"""Disable all USB HID devices"""

302

```

303

304

## Usage Examples

305

306

### NeoPixel LED Strip

307

308

```python

309

import board

310

import digitalio

311

import neopixel_write

312

import time

313

314

# Setup NeoPixel data pin

315

pixel_pin = digitalio.DigitalInOut(board.D18)

316

pixel_pin.direction = digitalio.Direction.OUTPUT

317

318

# Number of LEDs

319

num_pixels = 10

320

321

def set_pixel_color(index, red, green, blue, buf):

322

"""Set color for single pixel in buffer (GRB order)"""

323

offset = index * 3

324

buf[offset] = green # Green first

325

buf[offset + 1] = red # Red second

326

buf[offset + 2] = blue # Blue third

327

328

# Create color buffer (3 bytes per pixel: GRB)

329

pixel_buffer = bytearray(num_pixels * 3)

330

331

# Rainbow effect

332

for cycle in range(100):

333

for i in range(num_pixels):

334

# Calculate rainbow colors

335

hue = (i * 256 // num_pixels + cycle * 5) % 256

336

337

# Simple HSV to RGB conversion

338

if hue < 85:

339

red, green, blue = hue * 3, 255 - hue * 3, 0

340

elif hue < 170:

341

hue -= 85

342

red, green, blue = 255 - hue * 3, 0, hue * 3

343

else:

344

hue -= 170

345

red, green, blue = 0, hue * 3, 255 - hue * 3

346

347

set_pixel_color(i, red, green, blue, pixel_buffer)

348

349

# Write to NeoPixel strip

350

neopixel_write.neopixel_write(pixel_pin, pixel_buffer)

351

time.sleep(0.05)

352

353

# Turn off all pixels

354

pixel_buffer = bytearray(num_pixels * 3) # All zeros

355

neopixel_write.neopixel_write(pixel_pin, pixel_buffer)

356

pixel_pin.deinit()

357

```

358

359

### Rotary Encoder Position Reading

360

361

```python

362

import board

363

import rotaryio

364

import time

365

366

# Initialize rotary encoder

367

encoder = rotaryio.IncrementalEncoder(board.D2, board.D3)

368

369

# Track position

370

last_position = 0

371

372

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

373

374

try:

375

while True:

376

position = encoder.position

377

378

if position != last_position:

379

direction = "clockwise" if position > last_position else "counter-clockwise"

380

print(f"Position: {position} ({direction})")

381

last_position = position

382

383

time.sleep(0.01)

384

385

except KeyboardInterrupt:

386

print("Stopping encoder reading")

387

388

encoder.deinit()

389

```

390

391

### Individual Key Scanning

392

393

```python

394

import board

395

import keypad

396

import time

397

398

# Setup keys on pins D2, D3, D4

399

keys = keypad.Keys([board.D2, board.D3, board.D4],

400

value_when_pressed=False, # Grounded when pressed

401

pull=True) # Enable pull-ups

402

403

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

404

405

try:

406

while True:

407

event = keys.events.get()

408

409

if event:

410

if event.pressed:

411

print(f"Key {event.key_number} pressed")

412

else:

413

print(f"Key {event.key_number} released")

414

415

time.sleep(0.01)

416

417

except KeyboardInterrupt:

418

print("Stopping key scanning")

419

420

keys.deinit()

421

```

422

423

### Key Matrix Scanning

424

425

```python

426

import board

427

import keypad

428

import time

429

430

# 3x3 key matrix

431

row_pins = [board.D18, board.D19, board.D20]

432

col_pins = [board.D21, board.D22, board.D23]

433

434

matrix = keypad.KeyMatrix(row_pins, col_pins)

435

436

print(f"Scanning {matrix.key_count} keys in 3x3 matrix")

437

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

438

439

try:

440

while True:

441

event = matrix.events.get()

442

443

if event:

444

row = event.key_number // len(col_pins)

445

col = event.key_number % len(col_pins)

446

action = "pressed" if event.pressed else "released"

447

print(f"Key at row {row}, col {col} (#{event.key_number}) {action}")

448

449

time.sleep(0.01)

450

451

except KeyboardInterrupt:

452

print("Stopping matrix scanning")

453

454

matrix.deinit()

455

```

456

457

### Shift Register Key Scanning

458

459

```python

460

import board

461

import keypad

462

import time

463

464

# 74HC165 shift register setup

465

shift_keys = keypad.ShiftRegisterKeys(

466

clock=board.D18, # Clock pin

467

data=board.D19, # Serial data in

468

latch=board.D20, # Parallel load

469

key_count=8, # 8 keys

470

value_when_pressed=False, # Active low

471

value_to_latch=False # Latch on low (74HC165 style)

472

)

473

474

print("Scanning 8 keys via shift register")

475

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

476

477

try:

478

while True:

479

event = shift_keys.events.get()

480

481

if event:

482

action = "pressed" if event.pressed else "released"

483

print(f"Shift register key {event.key_number} {action}")

484

485

time.sleep(0.01)

486

487

except KeyboardInterrupt:

488

print("Stopping shift register scanning")

489

490

shift_keys.deinit()

491

```

492

493

### USB HID Device Support

494

495

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

496

497

```python { .api }

498

class Device:

499

"""USB HID device specification"""

500

501

def __init__(

502

self,

503

*,

504

descriptor: bytes,

505

usage_page: int,

506

usage: int,

507

report_ids: Sequence[int],

508

in_report_lengths: Sequence[int],

509

out_report_lengths: Sequence[int]

510

):

511

"""

512

Create a custom HID device.

513

514

Args:

515

descriptor: HID report descriptor bytes

516

usage_page: HID usage page (e.g., 0x01 for Generic Desktop)

517

usage: HID usage ID (e.g., 0x06 for Keyboard)

518

report_ids: List of report IDs this device uses

519

in_report_lengths: List of input report lengths (device to host)

520

out_report_lengths: List of output report lengths (host to device)

521

"""

522

523

def send_report(self, report: bytearray, report_id: int = None) -> None:

524

"""

525

Send HID report to host.

526

527

Args:

528

report: Report data to send

529

report_id: Report ID (optional if device has only one report ID)

530

"""

531

532

def get_last_received_report(self, report_id: int = None) -> bytes:

533

"""

534

Get last received HID OUT or feature report.

535

536

Args:

537

report_id: Report ID to get (optional if device has only one)

538

539

Returns:

540

Last received report data, or None if nothing received

541

"""

542

543

@property

544

def last_received_report(self) -> bytes:

545

"""

546

Last received HID OUT report (deprecated, use get_last_received_report()).

547

548

Returns:

549

Last received report data, or None if nothing received

550

"""

551

552

# Pre-defined device constants

553

Device.KEYBOARD: Device # Standard USB keyboard

554

Device.MOUSE: Device # Standard USB mouse

555

556

def enable(devices: Sequence[Device]) -> None:

557

"""

558

Enable USB HID with specified devices.

559

560

Args:

561

devices: Tuple of Device objects to enable

562

563

Raises:

564

Exception: If required kernel modules not loaded or setup fails

565

566

Note:

567

Must be called before USB enumeration.

568

Requires root privileges and dwc2/libcomposite kernel modules.

569

"""

570

571

def disable() -> None:

572

"""Disable USB HID gadget"""

573

```

574

575

### USB HID Keyboard Emulation

576

577

```python

578

# Note: Requires Linux with dwc2 and libcomposite kernel modules

579

# Must be run as root and called before USB enumeration

580

581

import usb_hid

582

import time

583

584

# Enable USB HID keyboard

585

usb_hid.enable((usb_hid.Device.KEYBOARD,))

586

587

# Get keyboard device

588

keyboard = usb_hid.Device.KEYBOARD

589

590

# Keyboard usage codes (simplified)

591

KEY_A = 0x04

592

KEY_ENTER = 0x28

593

KEY_SPACE = 0x2C

594

595

def send_key(key_code, modifier=0):

596

"""Send a key press and release"""

597

# HID keyboard report: [modifier, reserved, key1, key2, key3, key4, key5, key6]

598

report = bytearray([modifier, 0, key_code, 0, 0, 0, 0, 0])

599

600

# Send key press

601

keyboard.send_report(report)

602

time.sleep(0.01)

603

604

# Send key release

605

report = bytearray([0, 0, 0, 0, 0, 0, 0, 0])

606

keyboard.send_report(report)

607

time.sleep(0.01)

608

609

# Type "Hello World"

610

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

611

612

# Type characters (this is simplified - real implementation needs full keycode mapping)

613

send_key(KEY_A) # This would need proper character-to-keycode mapping

614

time.sleep(0.5)

615

616

# Send Enter

617

send_key(KEY_ENTER)

618

619

print("USB HID keyboard demo complete")

620

```

621

622

### USB HID Mouse Emulation

623

624

```python

625

import usb_hid

626

import time

627

628

# Enable USB HID mouse

629

usb_hid.enable((usb_hid.Device.MOUSE,))

630

631

# Get mouse device

632

mouse = usb_hid.Device.MOUSE

633

634

def move_mouse(x, y, buttons=0, wheel=0):

635

"""Move mouse and/or click buttons"""

636

# HID mouse report: [buttons, x, y, wheel]

637

# Limit movement to signed 8-bit range (-127 to 127)

638

x = max(-127, min(127, x))

639

y = max(-127, min(127, y))

640

wheel = max(-127, min(127, wheel))

641

642

report = bytearray([buttons, x, y, wheel])

643

mouse.send_report(report)

644

645

# Mouse button constants

646

LEFT_BUTTON = 0x01

647

RIGHT_BUTTON = 0x02

648

MIDDLE_BUTTON = 0x04

649

650

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

651

652

# Move in square pattern

653

movements = [

654

(50, 0), # Right

655

(0, 50), # Down

656

(-50, 0), # Left

657

(0, -50), # Up

658

]

659

660

for dx, dy in movements:

661

move_mouse(dx, dy)

662

time.sleep(0.5)

663

664

# Click left button

665

move_mouse(0, 0, LEFT_BUTTON)

666

time.sleep(0.1)

667

move_mouse(0, 0, 0) # Release

668

669

print("USB HID mouse demo complete")

670

```

671

672

## Platform Considerations

673

674

### NeoPixel Support

675

676

**Supported Platforms:**

677

- **Raspberry Pi**: All models with optimized bit-banging

678

- **RP2040 via U2IF**: Pico and compatible boards

679

- **OS Agnostic**: Generic implementation

680

681

**Timing Requirements:**

682

- Critical timing for WS2812 protocol (800ns/bit)

683

- Interrupts can cause color corruption

684

- May require disabling interrupts during transmission

685

686

### Rotary Encoder Support

687

688

**Supported Platforms:**

689

- **Raspberry Pi 5**: Hardware-optimized implementation

690

- **Generic Linux**: Software-based quadrature decoding

691

692

**Implementation Notes:**

693

- Uses interrupt-based tracking for accuracy

694

- Requires pull-up resistors on encoder signals

695

- Thread-safe position tracking

696

697

### Keypad Limitations

698

699

**Debouncing:**

700

- Hardware debouncing: 20ms default scan interval

701

- Software debouncing: Built into scanning loop

702

- Configurable scan intervals (minimum ~5ms practical)

703

704

**Threading:**

705

- Background scanning thread for real-time response

706

- Thread-safe event queue access

707

- Automatic cleanup on context manager exit

708

709

### USB HID Requirements

710

711

**Linux Requirements:**

712

- dwc2 kernel module (USB Device Controller)

713

- libcomposite kernel module (USB Gadget framework)

714

- Root privileges for /sys/kernel/config access

715

- Available USB Device Controller in /sys/class/udc/

716

717

**Device Limitations:**

718

- Maximum endpoints limited by hardware

719

- Boot devices must be first USB interface

720

- Report descriptors define device capabilities

721

722

### Error Handling

723

724

```python

725

# NeoPixel error handling

726

try:

727

import neopixel_write

728

# Use NeoPixel functionality

729

except ImportError:

730

print("NeoPixel not supported on this platform")

731

732

# Rotary encoder error handling

733

try:

734

import rotaryio

735

encoder = rotaryio.IncrementalEncoder(board.D2, board.D3)

736

except RuntimeError as e:

737

print(f"Encoder not supported: {e}")

738

739

# USB HID error handling

740

try:

741

import usb_hid

742

usb_hid.enable((usb_hid.Device.KEYBOARD,))

743

except Exception as e:

744

if "dwc2" in str(e):

745

print("USB HID requires dwc2 kernel module")

746

elif "libcomposite" in str(e):

747

print("USB HID requires libcomposite kernel module")

748

else:

749

print(f"USB HID error: {e}")

750

```