microscrap/native-drivers

The Official ScrapyardIO Native Drivers package.

Maintainers

Package info

github.com/microscrap/scrapyard-native-drivers

Homepage

Issues

pkg:composer/microscrap/native-drivers

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 1

Stars: 0

dev-main 2026-07-12 15:35 UTC

This package is not auto-updated.

Last update: 2026-07-12 18:43:56 UTC


README

PHP driver package that provides the native carrier for the ScrapyardIO framework GPIO stack. It drives hardware PWM on Linux single-board computers through the kernel sysfs PWM interface (/sys/class/pwm/pwmchipN) using ordinary PHP file I/O — no ext-posi, ext-ftdi, or Microscrap binding package is required for PWM itself.

This package includes:

  • The ScrapyardIONativeManager carrier manager, auto-discovered by the framework via the #[CarrierDriver('native')] attribute
  • A sysfs-backed PWM driver (NativePWMDriver) that exports channels, reads/writes period / duty_cycle / enable / polarity, and unexports cleanly on close
  • Multi-channel support — open several channels on the same pwmchip and get a MultiplePWMChannels bus back

Digital GPIO, SPI, I²C, and UART are not part of this package. On Linux SBCs those protocols use microscrap/posix-drivers (GPIO::*('posix')). USB/FTDI adapters use microscrap/usb-drivers.

Which carrier should I use?

Carrier Package Use when
native this package Hardware PWM via /sys/class/pwm only
posix microscrap/posix-drivers Linux SBC digital / SPI / I²C / UART
usb microscrap/usb-drivers FTDI adapters over MPSSE / serial

Older docs sometimes call the Linux bus stack native. For digital / SPI / I²C / UART on an SBC today, use posix. Use native only for PWM.

Requirements

  • PHP 8.3+
  • A Linux machine that exposes /sys/class/pwm (hardware PWM capable — Raspberry Pi, many other SBCs)
  • Write access to the pwmchip sysfs nodes (often root, or membership in a group that udev grants access to after export)
  • waveforms/common ^0.5.0, waveforms/contracts ^0.5.0, and scrapyard-io/nuts-and-bolts ^0.5.0 — direct Composer requirements of this package
  • waveforms/pwm ^0.5.0 (or the full scrapyard-io/framework metapackage) in the consuming app — required by NativePWMDriver / NativePWMHandle (GPIO\PWM\*) and by the GPIO::pwm(...) factory shown below

Device addressing

Factory call Meaning Sysfs path
->device(0) pwmchip number 0 /sys/class/pwm/pwmchip0
->device('0') numeric string chip number (same as 0) /sys/class/pwm/pwmchip0
->device('pwmchip0') or ->device('/sys/class/pwm/pwmchip0') trailing pwmchipN is parsed /sys/class/pwm/pwmchip0
->channel(2) channel offset on that chip /pwm2 after export

Confirm available chips with ls /sys/class/pwm/. On a Raspberry Pi 5, pwmchip0 channel 2 is a common hobby-servo / fan PWM path once the board's PWM overlay / pinmux is enabled.

Units and polarity

Kernel PWM sysfs attributes use nanoseconds:

  • setPeriod($ns) / getPeriod() — full waveform period in ns (e.g. 20_000_000 for 50 Hz / 20 ms)
  • setDutyCycle($ns) / getDutyCycle() — high time in ns (must be ≤ period)
  • setPolarity('normal'|'inversed') — any other string throws NativePWMException::invalidPolarity

Set the period before raising the duty cycle when starting from a fresh export; the kernel rejects a duty larger than the current period.

Installation

composer require microscrap/native-drivers

Confirm the host exposes PWM:

ls /sys/class/pwm

If /sys/class/pwm is missing, the manager throws a GPIOException when a PWM driver is requested: "The Native driver requires native support to work with PWM."

How it works

The framework resolves the native carrier through attribute-based discovery — no manual registration:

GPIO::pwm('native')->…->create()
  └─ PWMConnectionFactory               (scrapyard-io/framework)
       └─ GPIOCarriers::native('pwm')
            └─ ScrapyardIONativeManager  (#[CarrierDriver('native')], this package)
                 └─ NativePWMDriver
                      └─ /sys/class/pwm/pwmchip{N}/export → pwm{M}/{period,duty_cycle,enable,polarity}
Protocol Driver Backend
pwm NativePWMDriver Linux PWM sysfs (pwmchip export / attribute files)

On create(), the driver exports the channel if needed, then waits up to ~500 ms for udev to make period writable before returning a PWMChannel. On close(), it writes enable=0 and unexports the channel.

Usage

All examples go through the framework's GPIO facade with the native driver.

Single PWM channel

<?php

use GPIO\Common\GPIO;

$pwm = GPIO::pwm('native')
    ->device(0)           // pwmchip0
    ->channel(2)
    ->name('servo')
    ->create();

// 50 Hz hobby-servo frame (period / duty in nanoseconds)
$pwm->setPeriod(20_000_000);   // 20 ms
$pwm->setDutyCycle(1_500_000); // 1.5 ms neutral
$pwm->setEnable(true);

usleep(500_000);

$pwm->setDutyCycle(1_000_000); // 1.0 ms
$pwm->setDutyCycle(2_000_000); // 2.0 ms

$pwm->setEnable(false);
$pwm->close();                 // disable + unexport

name() sets the consumer label used as the primary key when you open multiple channels (default scrapyard-io-pwm).

Multiple channels on one chip

<?php

use GPIO\Common\GPIO;

$channels = GPIO::pwm('native')
    ->device(0)
    ->channel(0)
    ->name('fan')
    ->createWith(0, [
        'servo' => 2,
    ]);  // MultiplePWMChannels

$fan = $channels->getChannel('fan');
$servo = $channels->getChannel('servo');

$fan->setPeriod(40_000);       // 25 kHz fan carrier
$fan->setDutyCycle(20_000);    // 50%
$fan->setEnable(true);

$servo->setPeriod(20_000_000);
$servo->setDutyCycle(1_500_000);
$servo->setEnable(true);

$fan->close();
$servo->close();

createWith($device, $addl_channels) sets the chip from $device and opens extra named (or numeric) offsets on that chip. The primary channel still comes from a prior channel() call; its map key is name() (default scrapyard-io-pwm). Close each PWMChannel individually — MultiplePWMChannels has no bus-level close().

Polarity

<?php

use GPIO\Common\GPIO;

$pwm = GPIO::pwm('native')
    ->device(0)
    ->channel(2)
    ->name('servo')
    ->create();

$pwm->setPolarity('normal');
$pwm->setPolarity('inversed');

echo $pwm->getPolarity();

$pwm->close();

Exceptions

Exception Extends Thrown when
NativePWMException PWMChannelException Chip missing, export failed, attribute not writable yet, read/write failed, or polarity is not normal/inversed
PWMChannelException GPIOException Factory validation failures (device() / channel() never set)
GPIOException /sys/class/pwm is not present on the machine, or a driver does not extend ScrapyardIONativeDriver

Testing

Pest scaffolding lives under tests/ (Pest.php, TestCase.php, empty Unit/Feature PWM directories). This package does not currently ship a Pest dependency or any test cases — exercise the driver on a board with real /sys/class/pwm, or add fakes around sysfs paths when you introduce automated coverage.

License

MIT. See LICENSE.