Skip to content

tkroon/node-rpio

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

node-rpio

This is a high performance node.js addon which provides access to the Raspberry Pi GPIO interface, supporting regular GPIO as well as i²c, PWM, and SPI.

Node.js version NPM version Build Status

Compatibility

  • Raspberry Pi Models: A, B (revisions 1.0 and 2.0), A+, B+, 2, 3, Zero.
  • Node.js Versions: 0.8, 0.10, 0.12, 4.x, 5.x, 6.x, 7.x

Newer versions of node.js require you to install the GCC 4.8 packages for C++11 support. If you see compilation problems related to C++11, this is the likely cause.

Install

Easily install the latest via npm:

$ npm install rpio

By default the module will use /dev/gpiomem when using simple GPIO access. To access this device, your user will need to be a member of the gpio group, and you may need to configure udev with the following rule (as root):

$ cat >/etc/udev/rules.d/20-gpiomem.rules <<EOF
SUBSYSTEM=="bcm2835-gpiomem", KERNEL=="gpiomem", GROUP="gpio", MODE="0660"
EOF

For access to i²c, PWM, and SPI, or if you are running an older kernel which does not have the bcm2835-gpiomem module, you will need to run your programs as root for access to /dev/mem.

## Quickstart

All these examples use the physical numbering (P01-P40) and assume that the example is started with:

var rpio = require('rpio');

### Read a pin

Setup pin P11 / GPIO17 for read-only input and print its current value:

rpio.open(11, rpio.INPUT);
console.log('Pin 11 is currently set ' + (rpio.read(11) ? 'high' : 'low'));

Blink an LED

Blink an LED attached to P12 / GPIO18 a few times:

/*
 * Set the initial state to low.  The state is set prior to the pin becoming
 * active, so is safe for devices which require a stable setup.
 */
rpio.open(12, rpio.OUTPUT, rpio.LOW);

/*
 * The sleep functions block, but rarely in these simple programs does one care
 * about that.  Use a setInterval()/setTimeout() loop instead if it matters.
 */
for (var i = 0; i < 5; i++) {
        /* On for 1 second */
        rpio.write(12, rpio.HIGH);
        rpio.sleep(1);

        /* Off for half a second (500ms) */
        rpio.write(12, rpio.LOW);
        rpio.msleep(500);
}

Poll a button switch for events

Configure the internal pulldown resistor on P15 / GPIO22 and watch the pin for state changes from an attached button switch:

rpio.open(15, rpio.INPUT, rpio.PULL_DOWN);

function pollcb(pin)
{
        /*
         * Interrupts aren't supported by the underlying hardware, so events
         * may be missed during the 1ms poll window.  The best we can do is to
         * print the current state after a event is detected.
         */
        var state = rpio.read(pin) ? 'pressed' : 'released';
        console.log('Button event on P%d (button currently %s)', pin, state);
}

rpio.poll(15, pollcb);

A collection of example programs are also available in the examples directory.

Features

There are lots of GPIO modules available for node.js. Why use this one?

Performance

It's very fast. Part of the module is a native addon which links against Mike McCauley's bcm2835 library, providing direct access to the hardware via /dev/mem and /dev/gpiomem.

Most alternative GPIO modules use the slower /sys file system interface.

How much faster? Here is a simple test which calculates how long it takes to switch a pin on and off 1 million times:

  • rpi-gpio (using /sys): 701.023 seconds
  • rpio (using /dev/*mem): 0.684 seconds

So rpio can be anywhere up to 1000x faster than the alternatives.

Hardware support

While /sys provides a simple interface to GPIO, not all hardware features are supported, and it's not always possible to handle certain types of hardware, especially when employing an asynchronous model. Using the /dev/*mem interface means rpio can support a lot more functionality:

  • rpio supports sub-millisecond access, with features to support multiple reads/writes directly with hardware rather than being delayed by the event loop.

  • Output pins can be configured with a default state prior to being enabled, required by some devices and not possible to configure via /sys.

  • Internal pullup/pulldown registers can be configured.

  • Hardware i²c, PWM, and SPI functions are supported.

Simple programming

rpio tries to make it simple to program devices, rather than having to jump through hoops to support an asynchronous workflow. Some parts of rpio block, but that is intentional in order to provide a simpler interface, as well as being able to support time-sensitive devices.

The aim is to provide an interface familiar to Unix programmers, with the performance to match.

API

Start by requiring the addon.

var rpio = require('rpio');

GPIO

General purpose I/O tries to follow a standard open/read/write/close model.

Some useful constants are provided for use by all supporting functions:

  • rpio.HIGH: pin high/1/on
  • rpio.LOW: pin low/0/off

These can be useful to avoid magic numbers in your code.

rpio.init([options])

Initialise the bcm2835 library. This will be called automatically by .open() using the default option values if not called explicitly. The default values are:

var options = {
        gpiomem: true,          /* Use /dev/gpiomem */
        mapping: 'physical',    /* Use the P1-P40 numbering scheme */
}
gpiomem

There are two device nodes for GPIO access. The default is /dev/gpiomem which, when configured with gpio group access, allows users in that group to read/write directly to that device. This removes the need to run as root, but is limited to GPIO functions.

For non-GPIO functions (i²c, PWM, SPI) the /dev/mem device is required for full access to the Broadcom peripheral address range and the program needs to be executed as the root user (e.g. via sudo). If you do not explicitly call .init() when using those functions, the library will do it for you with gpiomem: false.

You may also need to use gpiomem: false if you are running on an older Linux kernel which does not support the gpiomem module.

rpio will throw an exception if you try to use one of the non-GPIO functions after already opening with /dev/gpiomem, as well as checking to see if you have the necessary permissions.

Valid options:

  • true: use /dev/gpiomem for non-root but GPIO-only access
  • false: use /dev/mem for full access but requires root

mapping

There are two naming schemes when referring to GPIO pins:

  • By their physical header location: Pins 1 to 26 (A/B) or Pins 1 to 40 (A+/B+)
  • Using the Broadcom hardware map: GPIO 0-25 (B rev1), GPIO 2-27 (A/B rev2, A+/B+)

Confusingly however, the Broadcom GPIO map changes between revisions, so for example P3 maps to GPIO0 on Model B Revision 1 models, but maps to GPIO2 on all later models.

This means the only sane default mapping is the physical layout, so that the same code will work on all models regardless of the underlying GPIO mapping.

If you prefer to use the Broadcom GPIO scheme for whatever reason (e.g. to use the P5 header pins on the Raspberry Pi 1 revision 2.0 model which aren't currently mapped to the physical layout), you can set mapping to gpio to switch to the GPIOxx naming.

Valid options:

  • gpio: use the Broadcom GPIOxx naming
  • physical: use the physical P01-P40 header layout

Examples:

rpio.init({gpiomem: false});    /* Use /dev/mem for i²c/PWM/SPI */
rpio.init({mapping: 'gpio'});   /* Use the GPIOxx numbering */

rpio.open(pin, mode[, option])

Open a pin for input or output. Valid modes are:

  • rpio.INPUT: pin is input (read-only).
  • rpio.OUTPUT: pin is output (read-write).
  • rpio.PWM: configure pin for hardware PWM (see PWM section below).

For input pins, option can be used to configure the internal pullup or pulldown resistors using options as described in the .pud() documentation below.

For output pins, option defines the initial state of the pin, rather than having to issue a separate .write() call. This can be critical for devices which must have a stable value, rather than relying on the initial floating value when a pin is enabled for output but hasn't yet been configured with a value.

Examples:

/* Configure P11 as input with the internal pulldown resistor enabled */
rpio.open(11, rpio.INPUT, rpio.PULL_DOWN);

/* Configure P12 as output with the initiate state set high */
rpio.open(12, rpio.OUTPUT, rpio.HIGH);

/* Configure P13 as output, but leave it in its initial undefined state */
rpio.open(13, rpio.OUTPUT);

rpio.mode(pin, mode)

Switch a pin that has already been opened in one mode to a different mode. This is provided primarily for performance reasons, as it avoids some of the setup work done by .open().

Example:

rpio.mode(12, rpio.INPUT);      /* Switch P12 back to input mode */

rpio.read(pin)

Read the current value of pin, returning either 1 (high) or 0 (low).

Example:

console.log('Pin 12 = %d', rpio.read(12));

rpio.readbuf(pin, buffer[, length])

Read length bits from pin into buffer as fast as possible. If length isn't specified it defaults to buffer.length.

This is useful for devices which send out information faster than the JavaScript function call overhead can handle, e.g. if you need microsecond accuracy. See dht11.js for an example which uses this to pull data from a DHT11 temperature/humidity sensor.

Example:

var buf = new Buffer(10000);

/* Read the value of Pin 12 10,000 times in a row, storing the values in buf */
rpio.readbuf(12, buf);

rpio.write(pin, value)

Set the specified pin either high or low, using either the rpio.HIGH/rpio.LOW constants, or simply 1 or 0.

Example:

rpio.write(13, rpio.HIGH);

rpio.writebuf(pin, buffer[, length])

Write length bits to pin from buffer as fast as possible. If length isn't specified it defaults to buffer.length.

Example:

/* Write 1 0 1 0 1 0 1 0 to Pin 13 */
var buf = new Buffer(8).fill(rpio.LOW);
buf[0] = buf[2] = buf[4] = buf[6] = rpio.HIGH;
rpio.writebuf(13, buf);

rpio.readpad(group)

Read the current state of the GPIO pad control for the specified GPIO group. On current models of Raspberry Pi there are three groups with corresponding defines:

  • rpio.PAD_GROUP_0_27: GPIO0 - GPIO27. Use this for the main GPIO header.
  • rpio.PAD_GROUP_28_45: GPIO28 - GPIO45. Use this to configure the P5 header.
  • rpio.PAD_GROUP_46_53: GPIO46 - GPIO53. Internal, you probably won't need this.

The value returned will be a bit mask of the following defines:

  • rpio.PAD_SLEW_UNLIMITED: 0x10. Slew rate unlimited if set.
  • rpio.PAD_HYSTERESIS: 0x08. Hysteresis is enabled if set.

The bottom three bits determine the drive current:

  • rpio.PAD_DRIVE_2mA: 0b000
  • rpio.PAD_DRIVE_4mA: 0b001
  • rpio.PAD_DRIVE_6mA: 0b010
  • rpio.PAD_DRIVE_8mA: 0b011
  • rpio.PAD_DRIVE_10mA: 0b100
  • rpio.PAD_DRIVE_12mA: 0b101
  • rpio.PAD_DRIVE_14mA: 0b110
  • rpio.PAD_DRIVE_16mA: 0b111

Note that the pad control registers are not available via /dev/gpiomem, so you will need to use .init({gpiomem: false}) and run as root.

Example:

var curpad = rpio.readpad(rpio.PAD_GROUP_0_27);

var slew = ((curpad & rpio.PAD_SLEW_UNLIMITED) == rpio.PAD_SLEW_UNLIMITED);
var hysteresis = ((curpad & rpio.PAD_HYSTERESIS) == rpio.PAD_HYSTERESIS);
var drive = (curpad & 0x7);

console.log('GPIO Pad Control for GPIO0 - GPIO27 is currently set to:');
console.log('\tSlew rate: ' + (slew ? 'unlimited' : 'limited'));
console.log('\tInput hysteresis: ' + (hysteresis ? 'enabled' : 'disabled'));
console.log('\tDrive rate: ' + (drive * 2 + 2) + 'mA');

rpio.writepad(group, control)

Write control settings to the pad control for group. Uses the same defines as above for .readpad().

Example:

/* Disable input hysteresis but retain other current settings. */
var control = rpio.readpad(rpio.PAD_GROUP_0_27);
control &= ~rpio.PAD_HYSTERESIS;
rpio.writepad(rpio.PAD_GROUP_0_27, control);

rpio.pud(pin, state)

Configure the pin's internal pullup or pulldown resistors, using the following state constants:

  • rpio.PULL_OFF: disable configured resistors.
  • rpio.PULL_DOWN: enable the pulldown resistor.
  • rpio.PULL_UP: enable the pullup resistor.

Examples:

rpio.pud(11, rpio.PULL_UP);
rpio.pud(12, rpio.PULL_DOWN);

rpio.poll(pin, cb[, direction])

Watch pin for changes and execute the callback cb() on events. cb() takes a single argument, the pin which triggered the callback.

The optional direction argument can be used to watch for specific events:

  • rpio.POLL_LOW: poll for falling edge transitions to low.
  • rpio.POLL_HIGH: poll for rising edge transitions to high.
  • rpio.POLL_BOTH: poll for both transitions (the default).

Due to hardware/kernel limitations we can only poll for changes, and the event detection only says that an event occurred, not which one. The poll interval is a 1ms setInterval() and transitions could come in between detecting the event and reading the value. Therefore this interface is only useful for events which transition slower than approximately 1kHz.

To stop watching for pin changes, call .poll() again, setting the callback to null (or anything else which isn't a function).

Example:

function nuke_button(pin)
{
        console.log('Nuke button on pin %d pressed', pin);

        /* No need to nuke more than once. */
        rpio.poll(pin, null);
}

function regular_button(pin)
{
        /* Watch pin 11 forever. */
        console.log('Button event on pin %d, is now %d', pin, rpio.read(pin));
}

/*
 * Pin 11 watches for both high and low transitions.  Pin 12 only watches for
 * high transitions (e.g. the nuke button is pushed).
 */
rpio.poll(11, regular_button);
rpio.poll(12, nuke_button, rpio.POLL_HIGH);

rpio.close(pin[, reset])

Indicate that the pin will no longer be used, and clear any poll events associated with it.

The optional reset argument can be used to configure the state that pin will be left in after close:

  • rpio.PIN_RESET: return pin to rpio.INPUT and clear any pullup/pulldown resistors. This is the default.
  • rpio.PIN_PRESERVE: leave pin in its currently configured state.

Examples:

rpio.close(11);
rpio.close(12, rpio.PIN_RESET);
rpio.close(13, rpio.PIN_PRESERVE);

GPIO demo

The code below continuously flashes an LED connected to pin 11 at 100Hz.

var rpio = require('rpio');

/* Configure P11 as an output pin, setting its initial state to low */
rpio.open(11, rpio.OUTPUT, rpio.LOW);

/* Set the pin high every 10ms, and low 5ms after each transition to high */
setInterval(function() {
        rpio.write(11, rpio.HIGH);
        setTimeout(function() {
                rpio.write(11, rpio.LOW);
        }, 5);
}, 10);

i²c

i²c is primarily of use for driving LCD displays, and makes use of pins 3 and 5 (GPIO0/GPIO1 on Rev 1, GPIO2/GPIO3 on Rev 2 and newer). The bcm2835 library automatically detects which Raspberry Pi revision you are running, so you do not need to worry about which i²c bus to configure.

To get started call .i2cBegin() which assigns pins 3 and 5 to i²c use. Until .i2cEnd() is called they won't be available for GPIO use. The pin assignments are:

  • Pin 3: SDA (Serial Data)
  • Pin 5: SCL (Serial Clock)

.i2cBegin() will call .init() if it hasn't already been called, with gpiomem: false set. Hardware i²c support requires /dev/mem access and therefore root.

rpio.i2cBegin();

Configure the slave address. This is between 0 - 0x7f, and it can be helpful to run the i2cdetect program to figure out where your devices are if you are unsure.

rpio.i2cSetSlaveAddress(0x20);

Set the baud rate. You can do this two different ways, depending on your preference. Either use .i2cSetBaudRate() to directly set the speed in hertz, or .i2cSetClockDivider() to set it based on a divisor of the base 250MHz rate.

rpio.i2cSetBaudRate(100000);    /* 100kHz */
rpio.i2cSetClockDivider(2500);  /* 250MHz / 2500 = 100kHz */

Read from and write to the i²c slave. Both functions take a buffer and optional length argument, defaulting to the length of the buffer if not specified.

var txbuf = new Buffer([0x0b, 0x0e, 0x0e, 0x0f]);
var rxbuf = new Buffer(32);

rpio.i2cWrite(txbuf);           /* Sends 4 bytes */
rpio.i2cRead(rxbuf, 16);        /* Reads 16 bytes */

Finally, turn off the i²c interface and return the pins to GPIO.

rpio.i2cEnd();

i²c demo

The code below writes two strings to a 16x2 LCD.

var rpio = require('rpio');

/*
 * Magic numbers to initialise the i2c display device and write output,
 * cribbed from various python drivers.
 */
var init = new Buffer([0x03, 0x03, 0x03, 0x02, 0x28, 0x0c, 0x01, 0x06]);
var LCD_LINE1 = 0x80, LCD_LINE2 = 0xc0;
var LCD_ENABLE = 0x04, LCD_BACKLIGHT = 0x08;

/*
 * Data is written 4 bits at a time with the lower 4 bits containing the mode.
 */
function lcdwrite4(data)
{
        rpio.i2cWrite(Buffer([(data | LCD_BACKLIGHT)]));
        rpio.i2cWrite(Buffer([(data | LCD_ENABLE | LCD_BACKLIGHT)]));
        rpio.i2cWrite(Buffer([((data & ~LCD_ENABLE) | LCD_BACKLIGHT)]));
}
function lcdwrite(data, mode)
{
        lcdwrite4(mode | (data & 0xF0));
        lcdwrite4(mode | ((data << 4) & 0xF0));
}

/*
 * Write a string to the specified LCD line.
 */
function lineout(str, addr)
{
        lcdwrite(addr, 0);

        str.split('').forEach(function (c) {
                lcdwrite(c.charCodeAt(0), 1);
        });
}

/*
 * We can now start the program, talking to the i2c LCD at address 0x27.
 */
rpio.i2cBegin();
rpio.i2cSetSlaveAddress(0x27);
rpio.i2cSetBaudRate(10000);

for (var i = 0; i < init.length; i++)
        lcdwrite(init[i], 0);

lineout('node.js i2c LCD!', LCD_LINE1);
lineout('npm install rpio', LCD_LINE2);

rpio.i2cEnd();

PWM

Pulse Width Modulation (PWM) allows you to create analog output from the digital pins. This can be used, for example, to make an LED appear to pulse rather than be fully off or on.

The Broadcom chipset supports hardware PWM, i.e. you configure it with the appropriate values and it will generate the required pulse. This is much more efficient and accurate than emulating it in software (by setting pins high and low at particular times), but you are limited to only certain pins supporting hardware PWM:

  • 26-pin models: pin 12
  • 40-pin models: pins 12, 19, 33, 35

Hardware PWM also requires gpiomem: false and root privileges. .open() will call .init() with the appropriate values if you do not explicitly call it yourself.

To enable a PIN for PWM, use the rpio.PWM argument to open():

rpio.open(12, rpio.PWM); /* Use pin 12 */

Set the PWM refresh rate with pwmSetClockDivider(). This is a power-of-two divisor of the base 19.2MHz rate, with a maximum value of 4096 (4.6875kHz).

rpio.pwmSetClockDivider(64);    /* Set PWM refresh rate to 300kHz */

Set the PWM range for a pin with pwmSetRange(). This determines the maximum pulse width.

rpio.pwmSetRange(12, 1024);

Finally, set the PWM width for a pin with pwmSetData().

rpio.pwmSetData(12, 512);

PWM demo

The code below pulses an LED 5 times before exiting.

var rpio = require('rpio');

var pin = 12;           /* P12/GPIO18 */
var range = 1024;       /* LEDs can quickly hit max brightness, so only use */
var max = 128;          /*   the bottom 8th of a larger scale */
var clockdiv = 8;       /* Clock divider (PWM refresh rate), 8 == 2.4MHz */
var interval = 5;       /* setInterval timer, speed of pulses */
var times = 5;          /* How many times to pulse before exiting */

/*
 * Enable PWM on the chosen pin and set the clock and range.
 */
rpio.open(pin, rpio.PWM);
rpio.pwmSetClockDivider(clockdiv);
rpio.pwmSetRange(pin, range);

/*
 * Repeatedly pulse from low to high and back again until times runs out.
 */
var direction = 1;
var data = 0;
var pulse = setInterval(function() {
        rpio.pwmSetData(pin, data);
        if (data === 0) {
                direction = 1;
                if (times-- === 0) {
                        clearInterval(pulse);
                        rpio.open(pin, rpio.INPUT);
                        return;
                }
        } else if (data === max) {
                direction = -1;
        }
        data += direction;
}, interval, data, direction, times);

SPI

SPI switches pins 19, 21, 23, 24 and 25 (GPIO7-GPIO11) to a special mode where you can bulk transfer data at high speeds to and from SPI devices, with the controller handling the chip enable, clock and data in/out functions.

/*
 *  Pin | Function
 * -----|----------
 *   19 |   MOSI
 *   21 |   MISO
 *   23 |   SCLK
 *   24 |   CE0
 *   25 |   CE1
 */

Once SPI is enabled, the SPI pins are unavailable for GPIO use until spiEnd() is called.

Use .spiBegin() to initiate SPI mode. SPI requires gpiomem: false and root privileges. .spiBegin() will call .init() with the appropriate values if you do not explicitly call it yourself.

rpio.spiBegin();           /* Switch GPIO7-GPIO11 to SPI mode */

Choose which of the chip select / chip enable pins to control:

/*
 *  Value | Pin
 *  ------|---------------------
 *    0   | SPI_CE0 (24 / GPIO8)
 *    1   | SPI_CE1 (25 / GPIO7)
 *    2   | Both
 */
rpio.spiChipSelect(0);

Commonly chip enable (CE) pins are active low, and this is the default. If your device's CE pin is active high, use spiSetCSPolarity() to change the polarity.

rpio.spiSetCSPolarity(0, rpio.HIGH);    /* Set CE0 high to activate */

Set the SPI clock speed with spiSetClockDivider(div). The div argument is an even divisor of the base 250MHz rate ranging between 0 and 65536.

rpio.spiSetClockDivider(128);   /* Set SPI speed to 1.95MHz */

Set the SPI Data Mode:

/*
 *  Mode | CPOL | CPHA
 *  -----|------|-----
 *    0  |  0   |  0
 *    1  |  0   |  1
 *    2  |  1   |  0
 *    3  |  1   |  1
 */
rpio.spiSetDataMode(0);         /* 0 is the default */

Once everything is set up we can transfer data. Data is sent and received in 8-bit chunks via buffers which should be the same size.

var txbuf = new Buffer([0x3, 0x0, 0xff, 0xff]);
var rxbuf = new Buffer(txbuf.length);

rpio.spiTransfer(txbuf, rxbuf, txbuf.length);

If you only need to send data and do not care about the data coming back, you can use the slightly faster spiWrite() call:

rpio.spiWrite(txbuf, txbuf.length);

When you're finished call .spiEnd() to release the pins back to general purpose use.

rpio.spiEnd();

SPI demo

The code below reads the 128x8 contents of an AT93C46 serial EEPROM.

var rpio = require('rpio');

rpio.spiBegin();
rpio.spiChipSelect(0);                  /* Use CE0 */
rpio.spiSetCSPolarity(0, rpio.HIGH);    /* AT93C46 chip select is active-high */
rpio.spiSetClockDivider(128);           /* AT93C46 max is 2MHz, 128 == 1.95MHz */
rpio.spiSetDataMode(0);

/*
 * There are various magic numbers below.  A quick overview:
 *
 *   tx[0] is always 0x3, the EEPROM READ instruction.
 *   tx[1] is set to var i which is the EEPROM address to read from.
 *   tx[2] and tx[3] can be anything, at this point we are only interested in
 *     reading the data back from the EEPROM into our rx buffer.
 *
 * Once we have the data returned in rx, we have to do some bit shifting to
 * get the result in the format we want, as the data is not 8-bit aligned.
 */
var tx = new Buffer([0x3, 0x0, 0x0, 0x0]);
var rx = new Buffer(4);
var out;
var i, j = 0;

for (i = 0; i < 128; i++, ++j) {
        tx[1] = i;
        rpio.spiTransfer(tx, rx, 4);
        out = ((rx[2] << 1) | (rx[3] >> 7));
        process.stdout.write(out.toString(16) + ((j % 16 == 0) ? '\n' : ' '));
}
rpio.spiEnd();

Misc

To make code simpler a few sleep functions are supported, using the hardware directly so should be reasonably accurate.

rpio.sleep(n);          /* Sleep for n seconds */
rpio.msleep(n);         /* Sleep for n milliseconds */
rpio.usleep(n);         /* Sleep for n microseconds */

## Authors And Licenses

Mike McCauley wrote src/bcm2835.{c,h} which are under the GPL.

I wrote the rest, which is under the ISC license unless otherwise specified.

About

Raspberry Pi GPIO library for node.js

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C 83.3%
  • JavaScript 9.2%
  • C++ 7.4%
  • Python 0.1%