microscrap/usb-drivers
Composer 安装命令:
composer require microscrap/usb-drivers
包简介
The Official ScrapyardIO USB Drivers package.
README 文档
README
PHP driver package that provides the usb carrier for the ScrapyardIO framework GPIO stack. It drives FTDI USB adapters (FT232H, FT2232HL, FT232RL) through ext-ftdi, tunnelling SPI, I²C, and digital GPIO over MPSSE and running UART as plain async serial.
This package includes:
- The
ScrapyardIOUSBManagercarrier manager, auto-discovered by the framework via the#[CarrierDriver('usb')]attribute - MPSSE-backed protocol drivers for SPI, I²C, and digital input/output
- A libftdi-backed UART driver (no MPSSE) for serial-only workflows
- Digital pin ride-along on SPI/I²C buses — control DC/RESET/BUSY pins on the same FTDI adapter that carries the bus
Requirements
- PHP 8.3+
ext-ftdi^0.4.2 — the PHP FTDI extension (requires libftdi1 at runtime)- Debian/Ubuntu/Raspberry Pi OS:
libftdi1-2(dev package for builds:libftdi1-dev) - macOS:
brew install libftdi
- Debian/Ubuntu/Raspberry Pi OS:
microscrap/ftdi^0.5.0 — globalftdi_*helpers (installed automatically)microscrap/mpsse^0.5.0 — required for SPI, I²C, and digital GPIO (suggested; UART works without it)
Currently Supported devices
| Device | MPSSE device() value (SPI/I²C/GPIO) |
UART device() value |
Product ID |
|---|---|---|---|
| FT232H | ft232h |
FtdiProductId::FT232H->value |
0x6014 |
| FT2232HL | ft2232hl-a (channel A) / ft2232hl-b (channel B) |
FtdiProductId::FT2232HL->value |
0x6010 |
| FT232RL | — (no MPSSE engine) | FtdiProductId::RS232L->value |
0x6001 |
Note, these are the only devices so far I've tested. There shouldn't be any reason why any other MPSSE-enabled devices wouldn't work if there is a valid product id hex for the device. You could make a pull request with the missing device, or use custom logic that doesn't use the MpsseSupportedDevices enum.
The two protocol families address devices differently:
- SPI / I²C / GPIO go through
Microscrap\Bindings\MPSSE\Enums\MpsseSupportedDevice, which picks the exact MPSSE engine (channel A or B) and its product ID together. The FT2232HL has two independent MPSSE engines, soft2232hl-aandft2232hl-bare addressed separately. - UART goes through
Microscrap\Bindings\FTDI\Enums\FtdiProductId, which is only the raw product ID — there is one UART per chip regardless of how many MPSSE engines it has, so the FT2232HL is addressed as a singleFtdiProductId::FT2232HL, not per channel.
Installation
Install the FTDI extension first (see php-io-extensions/ftdi):
pie install php-io-extensions/ftdi
Confirm it is loaded:
php -m | grep ftdi
Install the driver package:
composer require microscrap/usb-drivers
If your workflow includes SPI, I²C, or digital GPIO (anything other than UART), install the MPSSE package as well:
composer require microscrap/mpsse
The manager enforces these prerequisites at runtime and throws a GPIOException with install instructions when one is missing.
How it works
The framework resolves the usb carrier through attribute-based discovery — no manual registration:
GPIO::spi('usb')->…->create()
└─ SPIConnectionFactory (scrapyard-io/framework)
└─ GPIOCarriers::usb('spi')
└─ ScrapyardIOUSBManager (#[CarrierDriver('usb')], this package)
└─ MPSSESPIDriver
└─ microscrap/mpsse → microscrap/ftdi → ext-ftdi → libftdi1
Each GPIO protocol maps to a driver in this package:
| Protocol | Driver | Backend |
|---|---|---|
spi |
MPSSESPIDriver |
MPSSE, modes SPI0–SPI3 |
i2c |
MPSSEI2CDriver |
MPSSE with full ACK/NACK sequencing |
digital-in |
MPSSEDigitalInputDriver |
MPSSE GPIO mode, polled edge events |
digital-out |
MPSSEDigitalOutputDriver |
MPSSE GPIO mode |
uart |
FTDIUartDriver |
Plain async serial via ftdi_* helpers — no MPSSE |
UART is intentionally kept off MPSSE: FTDIUartDriver resets the chip to async-serial bitmode on open (ftdi_set_bitmode($context, 0x00, 0x00)) so that multi-protocol parts like the FT232H release D0/D1 back to TX/RX even if a previous session left MPSSE enabled.
Usage
All examples go through the framework's GPIO facade with the usb driver.
SPI
<?php use GPIO\Common\GPIO; use GPIO\Contracts\SPI\SPIMode; $spi = GPIO::spi('usb') ->device('ft232h') ->mode(SPIMode::MODE_0) ->speed(1_000_000) ->create(); $spi->write("\x9F"); // clock bytes out $jedec = $spi->transfer("\x9F\x00\x00\x00"); // full-duplex transfer $bytes = $spi->read(3); // clock bytes in $spi->close();
I²C
<?php use GPIO\Common\GPIO; $i2c = GPIO::i2c('usb') ->device('ft232h') ->slave(0x26) ->create(); $who_am_i = $i2c->writeRead("\x01", 1); // write register address, repeated START, read 1 byte $i2c->write([0x11, 0x40]); // write register + value $data = $i2c->read(2); // plain read $i2c->close();
UART
UART devices are addressed by FTDI product ID, not by MPSSE device name:
<?php use GPIO\Common\GPIO; use Microscrap\Bindings\FTDI\Enums\FtdiProductId; $uart = GPIO::uart('usb') ->device(FtdiProductId::RS232L->value) // 0x6001 — FT232RL ->baud(115200) ->create(); $uart->write("AT\r\n"); $response = $uart->read(64); // byte array, or false when nothing arrived $uart->flush(); $uart->close();
The same call works on a FT2232HL — pass FtdiProductId::FT2232HL->value instead of an ft2232hl-a / ft2232hl-b MPSSE string, since the chip exposes only one UART regardless of channel:
$uart = GPIO::uart('usb') ->device(FtdiProductId::FT2232HL->value) // 0x6010 ->baud(115200) ->create();
baud(), parity(), stopBits(), dataBits(), and flowControl() are all available on the factory; defaults are 9600 8N1 with no flow control.
Digital output
GPIO mode exposes 12 lines per MPSSE device — MPSSEGpioPin::GPIOL0–GPIOL3 (pins 0–3) and GPIOH0–GPIOH7 (pins 4–11):
<?php use GPIO\Common\GPIO; use Microscrap\Bindings\MPSSE\Enums\MPSSEGpioPin; $led = GPIO::digitalOut('usb') ->device('ft232h') ->pin(MPSSEGpioPin::GPIOL1->value) ->name('led') ->defaultState(false) ->create(); $led->high(); $led->low(); $led->close();
Digital input
<?php use GPIO\Common\GPIO; $button = GPIO::digitalIn('usb') ->device('ft232h') ->pin(0) ->name('button') ->withEvents(true, false) // rising, falling ->timeout(5000) // ms ->create(); $event = $button->listen(); // ?DigitalInputEvent — blocks up to the timeout $pressed = $button->isHigh(); // immediate level read $button->close();
Edge detection is polling-based (the FTDI adapter has no interrupt lines), sampling roughly every millisecond until the timeout elapses.
Digital pins on a SPI/I²C bus
Displays and similar chips need DC/RESET/BUSY pins alongside the bus. Attach them to the same MPSSE context and get a connection bus back:
<?php use GPIO\Common\GPIO; use GPIO\Contracts\SPI\SPIMode; use GPIO\Contracts\Digital\LineBias; $bus = GPIO::spi('usb') ->device('ft232h') ->mode(SPIMode::MODE_0) ->speed(1_000_000) ->digitalPins() ->digitalOut(pin: 4, name: 'dc', default_state: false) ->digitalIn(pin: 5, name: 'busy', events: [false, true], timeout: 1000, bias: LineBias::AS_IS) ->create(); // SPIConnectionBus $bus->getPin('dc')->high(); $bus->spi->write("\x2A"); while ($bus->getPin('busy')->isHigh()) { usleep(1_000); }
The same pattern works for I²C (I2CConnectionBus, with the transport on $bus->i2c).
Exceptions
| Exception | Extends | Thrown when |
|---|---|---|
MpsseUSBException |
GPIOException |
An MPSSE device context cannot be opened (includes the FTDI error string) |
FTDIUARTException |
UARTException |
A UART device fails to open (includes the FTDI error string) |
UARTException |
— | The factory's device() was never set (missingMasterDevice) |
GPIOException |
— | ext-ftdi, microscrap/ftdi, or microscrap/mpsse is missing, or a driver does not extend ScrapyardIOUSBDriver |
Note that passing an unknown product ID to GPIO::uart('usb')->device(...) fails inside FtdiProductId::from() with a PHP ValueError before any exception above is reached.
Testing (Pest v4)
Run the suite:
./vendor/bin/pest
The unit tests run without hardware — FTDI and MPSSE globals are replaced with in-memory fakes and spies (tests/Support/), so driver dispatch, MPSSE call ordering, and error paths are all exercised on any machine.
License
MIT. See LICENSE.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 2
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-07-06