• Stars
    star
    856
  • Rank 53,268 (Top 2 %)
  • Language
    C
  • Created about 12 years ago
  • Updated 6 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Raspberry Pi GPIO library for node.js

node-rpio

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

NPM version Node.js version NPM downloads Build Status License: ISC License: GPL v2 Donations

Compatibility

  • Raspberry Pi Models: A, B, A+, B+, 2, 3, 4, 400, Compute Module, Zero.
  • SunXi (Allwinner V40) Models: Orange Pi Zero, Banana Pi M2 Zero / Berry.
  • Node.js Versions: 0.8, 0.10, 0.12, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14.

Currently only basic GPIO is supported on the SunXi chipsets.

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.

Due to node-tap requirements the test suite only works on node.js version 6 or above.

Install

Install the latest using npm:

$ npm install rpio

Important System Requirements

This module will only interface with hardware on Linux, but should compile on any other platform, where it will run in mock mode by default.

Disable GPIO interrupts

If running a newer Raspbian release, you will need to add the following line to /boot/config.txt and reboot:

dtoverlay=gpio-no-irq

Without this you may see crashes with newer kernels when trying to poll for pin changes.

Enable /dev/gpiomem access

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 P15 / GPIO22 for read-only input and print its current value:

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

Blink an LED

Blink an LED attached to P16 / GPIO23 a few times:

/*
 * Set the initial state to low.  The state is set prior to the pin
 * being actived, so is safe for devices which require a stable setup.
 */
rpio.open(16, 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(16, rpio.HIGH);
        rpio.sleep(1);

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

Poll a button switch for events

Configure the internal pullup resistor on P15 / GPIO22 and watch the pin for pushes on an attached button switch:

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

function pollcb(pin)
{
        /*
         * Wait for a small period of time to avoid rapid changes which
         * can't all be caught with the 1ms polling frequency.  If the
         * pin is no longer down after the wait then ignore it.
         */
        rpio.msleep(20);

        if (rpio.read(pin))
                return;

        console.log('Button pressed on pin P%d', pin);
}

rpio.poll(15, pollcb, rpio.POLL_LOW);

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 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 */
        mock: undefined,        /* Emulate specific hardware in mock mode */
        close_on_exit: true,    /* On node process exit automatically close rpio */
}
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 */
mock

Mock mode is a dry-run environment where everything except pin access is performed. This is useful for testing scripts, and can also be used on systems which do not support GPIO at all.

If rpio is executed on unsupported hardware it will automatically start up in mock mode, and a warn event is emitted. By default the warn event is handled by a simple logger to stdout, but this can be overridden by the user creating their own warn handler.

The user can also explicitly request mock mode, where the argument is the type of hardware they wish to emulate. The currently available options are:

  • 26-pin Raspberry Pi models
    • raspi-b-r1 (early rev 1 model)
    • raspi-a
    • raspi-b
  • 40-pin Raspberry Pi models
    • raspi-a+
    • raspi-b+
    • raspi-2
    • raspi-3
    • raspi-zero
    • raspi-zero-w (zero with wireless)

The default unsupported hardware emulation is raspi-3.

Examples:

/*
 * Explicitly request mock mode to avoid warnings when running on known
 * unsupported hardware, or to test scripts in a different hardware
 * environment (e.g. to check pin settings).
 */
rpio.init({mock: 'raspi-3'});

/* Override default warn handler to avoid mock warnings */
rpio.on('warn', function() {});
close_on_exit

Rpio automatically unmaps and clears all memory maps when the node process exits. If you need to add hooks for your own cleanup routines during process exit then set close_on_exit: false. Make sure to call rpio.exit() in your exit hook.

Example:

rpio.init({close_on_exit: false});

process.on('exit', function() {
        /* Insert any custom cleanup code here. */
        rpio.exit();
});

rpio.exit()

Shuts down the rpio library, unmapping and clearing all memory maps. By default this will happen automatically. This method is provided to allow explicit shutdown when using close_on_exit: false and a custom exit handler.

Example:

rpio.init({close_on_exit: false});

process.on('exit', function() {
        /* Insert any custom cleanup code here. */
        rpio.exit();
});

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 P15 as input with the internal pulldown resistor enabled */
rpio.open(15, rpio.INPUT, rpio.PULL_DOWN);

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

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

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

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:

/* Switch P15 to output mode */
rpio.mode(15, rpio.OUTPUT);

/* Switch P16 to input mode with the internal pullup resistor enabled */
rpio.mode(16, rpio.INPUT, rpio.PULL_UP);

rpio.read(pin[, mode])

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

If mode is non-zero, perform a switch to input mode before reading. This can help with timing-critical code where the JavaScript function call overhead of calling rpio.mode() first is enough to miss input data. Altering the pullup state is not supported, as on many devices this requires a delay to activate, defeating the point of this feature.

Example:

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

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

Read length bits from pin into buffer as fast as possible.

If length isn't specified it defaults to buffer.length.

If mode is non-zero, perform a switch to input mode before reading. This can help with timing-critical code where the JavaScript function call overhead of calling rpio.mode() first is enough to miss input data. Altering the pullup state is not supported, as on many devices this requires a delay to activate, defeating the point of this feature.

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);

/*
 * Set pin 16 low, then switch pin 16 to input mode and read its value 10,000
 * times, storing each bit value in buf.
 */
rpio.write(16, rpio.LOW);
rpio.readbuf(16, buf, buf.length, true);

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(15, rpio.PULL_UP);
rpio.pud(16, 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 15 forever. */
        console.log('Button event on pin %d, is now %d', pin, rpio.read(pin));
}

/*
 * Pin 15 watches for both high and low transitions.  Pin 16 only watches for
 * high transitions (e.g. the nuke button is pushed).
 */
rpio.poll(15, regular_button);
rpio.poll(16, 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(15);
rpio.close(16, rpio.PIN_RESET);
rpio.close(13, rpio.PIN_PRESERVE);

GPIO demo

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

var rpio = require('rpio');

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

/* Set the pin high every 10ms, and low 5ms after each transition to high */
setInterval(function() {
        rpio.write(15, rpio.HIGH);
        setTimeout(function() {
                rpio.write(15, 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 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 */

Two specialised functions are available for reading and writing to devices that require a repeated start. For now see the bcm2835 documentation for more information on these.

/*
 * Read len bytes from register into buf after issuing a repeated start,
 * required by e.g. MPL3115A2 pressure and temperature sensor.
 */
rpio.i2cReadRegisterRestart(reg, rbuf, rlen);

/*
 * Write cmdlen commands from cmdbuf to device before issuing a repeated
 * start and reading rlen bytes into rbuf, for e.g. MLX90620.
 */
rpio.i2cWriteReadRestart(cmdbuf, cmdlen, rbuf, rlen);

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, 32, 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 26 (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
 *   26 |   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 (26 / 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.

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

There will be a startup cost when calling these functions, so it is worth performing some initial benchmarks to calculate the latency for your hardware when using the high resolution functions, then factoring that in to your calls.

Community benchmarks suggest that the cost for usleep() is 72 microseconds on raspi-3 and 130 microseconds on raspi-1, with latency reducing significantly after the first call.

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.