... cue the "GPIO on the web platform?? wat" comments ... OK, can we move
on now? :)
This is a very early-stage document that I'm floating around for early
feedback and collection of requirements/use cases that are omitted here.
What is GPIO?
``````````````````````
Hobbyist development boards like the Raspberry Pi and Beaglebone Black
include pins that aren't assigned a specific purpose when the board is
developed. (Hence, "general purpose".) Hobbyists use these GPIO pins to
control a dizzying variety of electronics, from simple LEDs to robotics.
If you took a basic microelectronics class, you may have programmed a
microcontroller to make LEDs blink and buzzers buzz when a button was
pressed: that's GPIO.
Why a GPIO API?
``````````````````````````
There's been interest in bringing the web platform to hobbyist devices in
the past few years. In particular, Firefox OS has been ported to run on
the Raspberry Pi in the past, and a new effort is underway. The use cases
for web code on these devices encompass GPIO, just as the use cases do for
programmers working in C/C++/python/... on those boards. Programmers using
a GPIO API in a web-OS-environment like Firefox OS could see some distinct
advantages: (i) their code doesn't need to run as "root" to use GPIO; (ii)
the runtime can enforce exclusive access to pins; (iii) their pin
configurations and operations can be sanity-checked.
By definition, some of the capabilities of a GPIO API overlap with other
APIs, for example controlling lights or reading input from certain kinds of
sensors. However, the higher-level APIs are designed for a static board
configuration, whereas GPIO is intended for a dynamic user-designed
configuration. I see room in the future for building GPIO configuration
"under" higher-level APIs, so that I could f.e. instantiate a high-level
thermal sensor instance from a low-level dynamic GPIO configuration. But
for now I'm going to wave my hands and say "extensible web" and not discuss
this further here ;).
GPIO capabilities and use cases
````````````````````````````````````````````````
Because GPIO pins are general-purpose by definition, the set of use cases
is unbounded. But I'll provide some examples with each.
o Digital output: set a pin's output to "high" (+V) or "low" (ground).
Example: turn an LED on and off.
o Digital input: check if a pin's input is high or low. Example: check if
a switch is open or closed.
o Digital-input interrupt: fired when a digital input either changes,
changes low->high, or changes high->low. Example: be notified when a push
button has been pressed down.
o Analog input (ADC): read an analog input voltage between ground and a
reference voltage (with a resolution determined by hardware). Example:
read a thermistor.
o Analog output (DAC): output an analog voltage between ground and a
reference voltage (resolution determined by hardware). Example: play audio
through a speaker.
o Pulse-width modulation (PWM): output a digital square wave with a
specified duty cycle and angle. The frequency of the square wave is often
determined by hardware and is usually 500-1000 Hz. Example: control a
servo motor.
Not all devices have all these capabilities. Further, different pins
usually have different capabilities. A common configuration is
o All pins capable of digital input/output
o Subset of pins capable of raising digital-input interrupts
o Subset of pins capable of analog input
o Subset of pins capable of *hardware* PWM. (PWM can be emulated by
software on any digital-output pin).
Security
````````````
GPIO access is kind of an odd duck. The interface is low-level and
exceptionally dangerous; buggy code could even set fire to peripheral
hardware in extreme cases. But the primary use case is power-user
hobbyists who are aware of these risks and want to run code to control
hardware they've built. (Remember, the "competition" in this space is
python scripts and C code running as root.) For now, I'll wave my hands
and say GPIO should be a "certified" permission and we can worry about
developer ergononomics down the road (though it is important).
Strawman API
``````````````````````
There's not much "design space" for GPIO because the capabilities are so
basic. So the APIs used on different platforms generally look about the
same. This strawman is an attempt to follow those conventions in a "webby"
way. The following is pseudocode, not any literal *IDL.
interface Gpio { // navigator.gpio
readonly attribute Map<string, GpioPin> pins; //[1]
[throws]
Promise requestPins(GpioPin[] pins); //[2]
void releasePins(GpioPins[] pins); //[2]
};
interface GpioPin : DOMEventTarget { //[3]
readonly attribute string id; //[4]
readonly attribute Set<string> aliases; //[4]
readonly attribute Set<string> capabilities; //[5]
[throws]
void writeDigital(bool value); //[6]
[throws]
bool readDigital();
[throws]
float readAnalog();
[throws]
void pwm(float duty, float angle=0, float freq=undefined);
[throws]
void writeAnalog(float value); //[7]
readonly attribute int? readAnalogResolutionBits; //[8]
readonly attribute int? pwmFrequencyHz;
readonly attribute int? writeAnalogResolutionBits;
};
Notes
[1] The intention is to enumerate available pins. The set of available
pins are known at UA startup time, so there's no problem with enumerating
them synchronously. The pin objects are mapped by GpioPin.id and all
GpioPin.aliases; see note [4] below. That means more than one map entry
may point at the same GpioPin.
[2] requestPins()/releasePins() are intended to "lock" a set of pins for a
particular requestee. Multiple code modules (perhaps across applications,
though I don't have use cases for that in mind) may wish to request pins.
This interface allows them to not step on each other and sanity-check
allocations. Note that a hobbyist must decide on a pin allocation before
running an application that uses GPIO, because the hobbyist has to set up
the hardware to match. requestPins() guards against software bugs that
mismatch the hardware design. (This interface also allows implementors to
optimize pin access in certain ways.)
[3] I propose exposing digital-input interrupts through DOM events, so
applications can listen for them with |addEventListener(event)|. Here's a
list of strawman event names
* "digitalchange" --- digital input level changed.
* "digitalup" --- input level changed low->high
* "digitaldown" --- output level changed high->low
If a particular interrupt type isn't supported by the hardware, UAs could
either be forced to emulate them through polling, or be allowed to throw an
exception. I don't have a strong opinion on that right now. (I lean
towards requiring emulation.)
[4] Pins are documented by board manufacturers using a set of identifiers.
Often there are multiple names for the same pin. For example: on the
Raspberry Pi, there is a pin that's called pin "10" by the RPi SoC
(Broadcom 2835). The RPi documentation calls this pin "GPIO15"; that's the
pin's primary name. This pin also has the alias "UART0_RXD". So with this
strawman interface,
pin.id = "GPIO15"
pin.aliases = { "10", "UART0_RXD" }
and then |navigator.gpio[id]| for id in { "GPIO15", "10", "UART0_RXD" }
returns a reference to the same GpioPin.
[5] Let programmers write sanity checks like |"readAnalog" in
pin.capabilities|. Also lets developers probe capabilities without having
to look up external UA and board docs (get capabilities "from the horse's
mouth"). I don't know what the current style is for this kind of
capability-checking interface, so alternative suggestions welcome.
[6] These are the fairly standard GPIO access APIs you'll find on a variety
of boards. Typing is open to discussion. A few specific notes
* writeDigital(bool value) --- some systems let you use symbols/strings
like "high" or "low". I don't have a strong opinion on whether to allow
that. writeDigital(1) / writeDigital(0) are pretty clear on their own IMO.
* float readAnalog() --- return a value in the range [ 0.0, 1.0 ].
* void pwm(float duty) --- duty cycle is value in the range [ 0.0 1.0 ].
Calling one of these functions on a pin that doesn't support that
capability would throw an exception.
[7] The Arduino API uses writeAnalog() to refer to PWM. I'm also not
familiar with a hobbyist board that has an integrated DAC (though I'm sure
one exists). So I'd be happy to drop this interface for a v1 API.
[8] Let programmers see hardware capabilities. Similar in vein to [5].
Attributes that don't make sense for a given pin, for example
|readAnalogResolutionBits| on a pin that's not capable of analog input,
would not be present.
Issues
``````````
Most GPIO APIs provide a function to explicitly set a pin's mode, for
example |pinMode(INPUT)|. I'd like mode setting to happen implicitly if
possible. An alternative is to make the pin mode one of the parameters to
|requestPins()|, and then lock the pin to that mode. It doesn't really
make sense to change a pin mode from input to output in a live circuit; my
guess is that's usually programmer error and it would be nice for the
runtime to flag those. But most hardware is capable of "live" mode
switches, so perhaps this low-level API should quietly allow them.
Some GPIO use cases work "better" when the system can respond with low
latency. Hitting real-time constraints will never be possible in this
setup, but I propose we keep WebWorker threads in mind for this API. It
might be useful for some applications to do GPIO on a separate,
lower-latency worker thread.
Many people who've read this far are going to be asking, "what about SPI /
i2c / RS232?" They're definitely interesting for this class of
applications, but let's discuss them separately.
Implementation notes
````````````````````````````````
I'm confident that these APIs can be efficiently implemented on the boards
I'm familiar with. Happy to follow up on implementation questions.
Comments and questions welcome.
Cheers,
Chris