├── .gitignore ├── LICENSE ├── README.md ├── binding.gyp ├── package.json ├── spi.js ├── src ├── fake_spi.h ├── spi_binding.cc └── spi_binding.h └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | 17 | build/* 18 | .lock-wscript 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Russell Hay 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-spi 2 | ======== 3 | 4 | A NodeJS interface to the SPI bus typically found on embedded linux machines 5 | such as the Raspberry Pi. 6 | 7 | There is a native interface and a wrapped JS interface with a slightly 8 | better API. 9 | 10 | If you have a project that uses node-spi, please consider adding a link to your project on the wiki: 11 | 12 | https://github.com/RussTheAerialist/node-spi/wiki/Projects-that-use-node-spi 13 | 14 | The following versions exist 15 | 16 | * node-spi 0.1.x - node 0.10.x 17 | * node-spi 0.2 - node 0.12.x 18 | 19 | 20 | Basic Usage 21 | =========== 22 | 23 | ```javascript 24 | var SPI = require('spi'); 25 | 26 | var spi = new SPI.Spi('/dev/spidev0.0', { 27 | 'mode': SPI.MODE['MODE_0'], // always set mode as the first option 28 | 'chipSelect': SPI.CS['none'] // 'none', 'high' - defaults to low 29 | }, function(s){s.open();}); 30 | 31 | var txbuf = new Buffer([ 0x23, 0x48, 0xAF, 0x19, 0x19, 0x19 ]); 32 | var rxbuf = new Buffer([ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]); 33 | 34 | spi.transfer(txbuf, rxbuf, function(device, buf) { 35 | // rxbuf and buf should be the same here 36 | var s = ""; 37 | for (var i=0; i < buf.length; i++) 38 | s = s + buf[i] + " "; 39 | console.log(s + "- " + new Date().getTime()); 40 | }); 41 | ``` 42 | 43 | How you should **really** use the library 44 | ========================================= 45 | 46 | The above basic usage example is not how you should use the library, though. 47 | Ideally, for each SPI device that is being controlled should have it's own 48 | object that implements the protocol necessary to talk to your device so that 49 | the device protocol is defined in one place. 50 | 51 | An example project is [node-adafruit-pixel](https://github.com/RussTheAerialist/node-adafruit-pixel) 52 | which is a node module to control the [AdaFruit RGB Pixels](http://www.adafruit.com/products/738). 53 | The interface is defined in terms of color and pixels, and not in messages 54 | being sent via the SPI bus, but it uses node-spi to do it's work. 55 | 56 | Native Api Reference 57 | ==================== 58 | 59 | This section documents the native api which is defined in module _spi.node. 60 | This is the interface that the normal Spi interface uses, but having a good 61 | understanding of this part is important, as some people may want to use the 62 | native interface directly. 63 | 64 | Creating, Opening, and Closing the device 65 | ----------------------------------------- 66 | 67 | **\_spi.Spi constructor** - The constructor only requires the path to the spi 68 | dev file in /dev. Options and a callback are not required but can be specified. 69 | 70 | Example: 71 | ```javascript 72 | var spi = new SPI.Spi('/dev/spidev0.1'); 73 | ``` 74 | 75 | Options can include: 76 | * mode 77 | * chipSelect 78 | * size 79 | * bitOrder 80 | * maxSpeed 81 | * halfDuplex 82 | * loopback 83 | 84 | Example: 85 | ```javascript 86 | var spi = new SPI.Spi('/dev/spidev0.0', {'mode': SPI.MODE['MODE_0']}); 87 | ``` 88 | 89 | The callback returns a handle to the newly created SPI object. It might be 90 | handy to .open() it if you set all of your options in one shot. 91 | 92 | Example: 93 | ```javascript 94 | var spi = new SPI.Spi('/dev/spidev0.0', {}, function(s){s.open();}); 95 | ``` 96 | 97 | **open()** - This function takes no arguments and will open the device using 98 | all of the options that were previously set. Once the device is open, we do not 99 | allow you to change the settings on the device. 100 | 101 | Example: 102 | ```javascript 103 | var spi = new SPI.Spi('/dev/spidev0.0', {'mode': SPI.MODE['MODE_0']}); 104 | 105 | // get/set aditional options 106 | spi.maxSpeed(20000); // in Hz 107 | console.log('max speed: ' + spi.maxSpeed()); 108 | 109 | spi.open(); // once opened, you can't change the options 110 | ``` 111 | 112 | **close()** - This function should always be called before ending. Right now 113 | the destructor for the underlying C++ class does not call close(), but that 114 | might change in the future. You should always call close() when you are done 115 | with the device. 116 | 117 | Configuring the Device 118 | ---------------------- 119 | 120 | The following functions all act as getter/setters. If you do not pass an 121 | argument, it will return the value of the setting. If you pass in a value, 122 | it will change the value and then return the Spi class to allow function 123 | chaining. 124 | 125 | To understand these settings read the 126 | [Linux SPI Summary](http://www.mjmwired.net/kernel/Documentation/spi/spi-summary) 127 | 128 | **mode()** - This sets the clock phase and polarity of the clock signal. This 129 | should always be the first thing you call after open() if you plan to call it. 130 | By default it is set to SPI_MODE_0. The spi namespace provides constants for 131 | the four SPI_MODE_X values (X being 0-3). 132 | 133 | **chipSelect()** - This allows you to specify if the chip select signal should 134 | be used, and if it should go high to select the chip or low. It defaults to 135 | signal low. Pass in SPI_NO_CS to turn off Chip Select, and SPI_CS_HIGH to 136 | turn on sending high to select. 137 | 138 | **size()** - This allows you to specify the bits per word to send. 139 | This defaults to 8-bits. Check your device's datasheet for this value. 140 | 141 | **bitOrder()** - This allows you to specify the order of the bits. We default 142 | to MSB, but send True as the argument if you want LSB. This might not be the 143 | best API. 144 | 145 | **maxSpeed()** - This allows you to set the max transfer speed. Again, check 146 | your device's datasheet. This is in Hz and defaults to 1Mhz. 147 | 148 | **halfDuplex()** - Set this to True if your device does not support full duplex. 149 | This isn't fully supported yet, as I need to add a Read/Write function calls that 150 | is exposed to javascript. *This would be a great workitem for anyone who wants 151 | to contribute* 152 | 153 | **loopback()** - This sets the Loopback bit on the SPI controller. I don't 154 | fully understand what this is used for, but give you the ability to toggle it 155 | if you'd like. 156 | 157 | Getting and Sending Data 158 | ------------------------ 159 | **transfer(txbuf, rxbuf, callback)** - This takes two buffers, a write and a 160 | read buffer, and optionally a callback. SPI only reads when a byte is written 161 | so communicaton is usually full duplex. 162 | 163 | Exmple: 164 | ```javascript 165 | var txbuf = new Buffer([ 0x23, 0x48, 0xAF, 0x19, 0x19, 0x19 ]); 166 | var rxbuf = new Buffer([ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]); 167 | 168 | spi.transfer(txbuf, rxbuf, function(device, buf) { 169 | var s = ""; 170 | for (var i=0; i < buf.length; i++) 171 | s = s + buf[i] + " "; 172 | console.log(s); 173 | }); 174 | ``` 175 | 176 | As a convenience feature, read and write functions pad zeros in the opposite 177 | direction to make simple read and writes work. 178 | 179 | **read(buffer, callback)** - Reads as much data as the given buffer is big. 180 | The results of the read are available in the callback. 181 | 182 | Example: 183 | ```javascript 184 | var buf1 = new Buffer(8); 185 | spi.read(buf1, function(device, buf2) { 186 | var s = ""; 187 | for (var i=0; i < buf.length; i++) 188 | s = s + buf[i] + " "; 189 | console.log(s); 190 | }); 191 | ``` 192 | 193 | **write(buffer, callback)** - Writes out the given buffer. 194 | 195 | Example: 196 | ```javascript 197 | var buf = new Buffer([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]); 198 | spi.write(buf, function(device, buf2) { 199 | var s = ""; 200 | for (var i=0; i < buf.length; i++) 201 | s = s + buf[i] + " "; 202 | console.log(s); 203 | }); 204 | ``` 205 | 206 | Remember that these native apis are currently blocking. I will update, once I 207 | have the hardware to test this properly, to be async instead of blocking. 208 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "_spi", 5 | "sources": [ "src/spi_binding.cc" ] 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spi", 3 | "version": "0.2.0", 4 | "description": "JS Interface for the SPI bus", 5 | "author": "Russell Hay (http://russellhay.com)", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/RussTheAerialist/node-spi.git" 9 | }, 10 | "main": "./spi", 11 | "dependencies": { 12 | "bindings": "*" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spi.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012, Russell Hay 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | "use strict"; 18 | 19 | var _spi = require('bindings')('_spi.node'); 20 | 21 | // Consistance with docs 22 | var MODE = { 23 | MODE_0: _spi.SPI_MODE_0, 24 | MODE_1: _spi.SPI_MODE_1, 25 | MODE_2: _spi.SPI_MODE_2, 26 | MODE_3: _spi.SPI_MODE_3 27 | }; 28 | 29 | var CS = { 30 | none: _spi.SPI_NO_CS, 31 | high: _spi.SPI_CS_HIGH, 32 | low: _spi.SPI_CS_LOW 33 | }; 34 | 35 | var ORDER = { 36 | msb: _spi.SPI_MSB == 1, 37 | lsb: _spi.SPI_LSB == 1 38 | }; 39 | 40 | function isFunction(object) { 41 | return object && typeof object == 'function'; 42 | } 43 | 44 | var Spi = function(device, options, callback) { 45 | this._spi = new _spi._spi(); 46 | 47 | options = options || {}; // Default to an empty object 48 | 49 | for(var attrname in options) { 50 | var value = options[attrname]; 51 | if (attrname in this._spi) { 52 | this._spi[attrname](value); 53 | } 54 | else 55 | console.log("Unknown option: " + attrname + "=" + value); 56 | } 57 | 58 | this.device = device; 59 | 60 | isFunction(callback) && callback(this); // TODO: Update once open is async; 61 | } 62 | 63 | Spi.prototype.open = function() { 64 | return this._spi.open(this.device); 65 | } 66 | 67 | Spi.prototype.close = function() { 68 | return this._spi.close(); 69 | } 70 | 71 | Spi.prototype.write = function(buf, callback) { 72 | this._spi.transfer(buf, new Buffer(buf.length)); 73 | 74 | isFunction(callback) && callback(this, buf); // TODO: Update once open is async; 75 | } 76 | 77 | Spi.prototype.read = function(buf, callback) { 78 | this._spi.transfer(new Buffer(buf.length), buf); 79 | 80 | isFunction(callback) && callback(this, buf); // TODO: Update once open is async; 81 | } 82 | 83 | Spi.prototype.transfer = function(txbuf, rxbuf, callback) { 84 | // tx and rx buffers need to be the same size 85 | this._spi.transfer(txbuf, rxbuf); 86 | 87 | isFunction(callback) && callback(this, rxbuf); // TODO: Update once open is async; 88 | } 89 | 90 | Spi.prototype.mode = function(mode) { 91 | if (typeof(mode) != 'undefined') 92 | if (mode == MODE['MODE_0'] || mode == MODE['MODE_1'] || 93 | mode == MODE['MODE_2'] || mode == MODE['MODE_3']) { 94 | this._spi['mode'](mode); 95 | return this._spi; 96 | } 97 | else { 98 | console.log('Illegal mode'); 99 | return -1; 100 | } 101 | else 102 | return this._spi['mode'](); 103 | } 104 | 105 | Spi.prototype.chipSelect = function(cs) { 106 | if (typeof(cs) != 'undefined') 107 | if (cs == CS['none'] || cs == CS['high'] || cs == CS['low']) { 108 | this._spi['chipSelect'](cs); 109 | return this._spi; 110 | } 111 | else { 112 | console.log('Illegal chip selection'); 113 | return -1; 114 | } 115 | else 116 | return this._spi['chipSelect'](); 117 | } 118 | 119 | Spi.prototype.bitsPerWord = function(bpw) { 120 | if (typeof(bpw) != 'undefined') 121 | if (bpw > 1) { 122 | this._spi['bitsPerWord'](bpw); 123 | return this._spi; 124 | } 125 | else { 126 | console.log('Illegal bits per word'); 127 | return -1; 128 | } 129 | else 130 | return this._spi['bitsPerWord'](); 131 | } 132 | 133 | Spi.prototype.bitOrder = function(bo) { 134 | if (typeof(bo) != 'undefined') 135 | if (bo == ORDER['msb'] || bo == ORDER['lsb']) { 136 | this._spi['bitOrder'](bo); 137 | return this._spi; 138 | } 139 | else { 140 | console.log('Illegal bit order'); 141 | return -1; 142 | } 143 | else 144 | return this._spi['bitOrder'](); 145 | } 146 | 147 | Spi.prototype.maxSpeed = function(speed) { 148 | if (typeof(speed) != 'undefined') 149 | if (speed > 0) { 150 | this._spi['maxSpeed'](speed); 151 | return this._spi; 152 | } 153 | else { 154 | console.log('Speed must be positive'); 155 | return -1; 156 | } 157 | else 158 | return this._spi['maxSpeed'](); 159 | } 160 | 161 | Spi.prototype.halfDuplex = function(duplex) { 162 | if (typeof(duplex) != 'undefined') 163 | if (duplex) { 164 | this._spi['halfDuplex'](true); 165 | return this._spi; 166 | } 167 | else { 168 | this._spi['halfDuplex'](false); 169 | return this._spi; 170 | } 171 | else 172 | return this._spi['halfDuplex'](); 173 | } 174 | 175 | Spi.prototype.loopback = function(loop) { 176 | if (typeof(loop) != 'undefined') 177 | if (loop) { 178 | this._spi['loopback'](true); 179 | return this._spi; 180 | } 181 | else { 182 | this._spi['loopback'](false); 183 | return this._spi; 184 | } 185 | else 186 | return this._spi['loopback'](); 187 | } 188 | 189 | module.exports.MODE = MODE; 190 | module.exports.CS = CS; 191 | module.exports.ORDER = ORDER; 192 | module.exports.Spi = Spi; 193 | -------------------------------------------------------------------------------- /src/fake_spi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #pragma message ( "Warning, not linux, spi is being stubbed" ) 3 | 4 | #include 5 | 6 | #define SPI_CPHA 0x01 7 | #define SPI_CPOL 0x02 8 | 9 | #define SPI_MODE_0 (0|0) 10 | #define SPI_MODE_1 (0|SPI_CPHA) 11 | #define SPI_MODE_2 (SPI_CPOL|0) 12 | #define SPI_MODE_3 (SPI_CPOL|SPI_CPHA) 13 | 14 | #define SPI_CS_HIGH 0x04 15 | #define SPI_LSB_FIRST 0x08 16 | #define SPI_3WIRE 0x10 17 | #define SPI_LOOP 0x20 18 | #define SPI_NO_CS 0x40 19 | #define SPI_READY 0x80 20 | 21 | #define SPI_IOC_WR_MODE 0 22 | #define SPI_IOC_RD_MODE 1 23 | #define SPI_IOC_WR_BITS_PER_WORD 2 24 | #define SPI_IOC_RD_BITS_PER_WORD 3 25 | #define SPI_IOC_WR_MAX_SPEED_HZ 4 26 | #define SPI_IOC_RD_MAX_SPEED_HZ 5 27 | 28 | struct spi_ioc_transfer { 29 | uint64_t tx_buf; 30 | uint64_t rx_buf; 31 | uint32_t len; 32 | uint32_t speed_hz; 33 | uint16_t delay_usecs; 34 | uint8_t bits_per_word; 35 | uint8_t cs_change; 36 | uint8_t tx_nbits; 37 | uint8_t rx_nbits; 38 | uint16_t pad; 39 | }; 40 | 41 | #define SPI_IOC_MESSAGE(x) 0 42 | #define ioctl(x, y, z) -1 -------------------------------------------------------------------------------- /src/spi_binding.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012, Russell Hay 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | // #define BUILDING_NODE_EXTENSION 18 | 19 | #include "spi_binding.h" 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #ifdef __linux__ 26 | #include 27 | #include 28 | #else 29 | #include "fake_spi.h" 30 | #endif 31 | #include 32 | #include 33 | 34 | using namespace v8; 35 | using namespace node; 36 | 37 | extern "C" { 38 | void init (Handle target) { 39 | Spi::Initialize(target); 40 | } 41 | 42 | NODE_MODULE(_spi, init) 43 | } 44 | 45 | Persistent Spi::constructor; 46 | 47 | void Spi::Initialize(Handle target) { 48 | Isolate* isolate = Isolate::GetCurrent(); 49 | HandleScope scope(isolate); 50 | 51 | // var t = function() {}; 52 | Local t = FunctionTemplate::New(isolate, New); 53 | 54 | // t = function _spi() {}; 55 | t->SetClassName(String::NewFromUtf8(isolate, "_spi")); 56 | t->InstanceTemplate()->SetInternalFieldCount(1); 57 | 58 | NODE_SET_PROTOTYPE_METHOD(t, "open", Open); 59 | NODE_SET_PROTOTYPE_METHOD(t, "close", Close); 60 | NODE_SET_PROTOTYPE_METHOD(t, "transfer", Transfer); 61 | NODE_SET_PROTOTYPE_METHOD(t, "mode", GetSetMode); 62 | NODE_SET_PROTOTYPE_METHOD(t, "chipSelect", GetSetChipSelect); 63 | NODE_SET_PROTOTYPE_METHOD(t, "size", GetSetBitsPerWord); 64 | NODE_SET_PROTOTYPE_METHOD(t, "bitOrder", GetSetBitOrder); 65 | NODE_SET_PROTOTYPE_METHOD(t, "maxSpeed", GetSetMaxSpeed); 66 | NODE_SET_PROTOTYPE_METHOD(t, "halfDuplex", GetSet3Wire); 67 | NODE_SET_PROTOTYPE_METHOD(t, "delay", GetSetDelay); 68 | NODE_SET_PROTOTYPE_METHOD(t, "lookback", GetSetLoop); 69 | 70 | // var constructor = t; // in context of new. 71 | constructor.Reset(isolate, t->GetFunction()); 72 | 73 | // exports._spi = constructor; 74 | target->Set(String::NewFromUtf8(isolate, "_spi"), t->GetFunction()); 75 | 76 | NODE_DEFINE_CONSTANT(target, SPI_MODE_0); 77 | NODE_DEFINE_CONSTANT(target, SPI_MODE_1); 78 | NODE_DEFINE_CONSTANT(target, SPI_MODE_2); 79 | NODE_DEFINE_CONSTANT(target, SPI_MODE_3); 80 | 81 | #define SPI_CS_LOW 0 // This doesn't exist normally 82 | NODE_DEFINE_CONSTANT(target, SPI_NO_CS); 83 | NODE_DEFINE_CONSTANT(target, SPI_CS_HIGH); 84 | NODE_DEFINE_CONSTANT(target, SPI_CS_LOW); 85 | 86 | #define SPI_MSB false 87 | #define SPI_LSB true 88 | NODE_DEFINE_CONSTANT(target, SPI_MSB); 89 | NODE_DEFINE_CONSTANT(target, SPI_LSB); 90 | 91 | } 92 | 93 | // new Spi(string device) 94 | //Handle Spi::New(const FunctionCallbackInfo& args) { 95 | SPI_FUNC_IMPL(New) { 96 | Isolate *isolate = args.GetIsolate(); 97 | HandleScope scope(isolate); 98 | 99 | if (args.IsConstructCall()) { 100 | Spi* spi = new Spi(); 101 | spi->Wrap(args.This()); 102 | args.GetReturnValue().Set(args.This()); 103 | } else { 104 | Local cons = Local::New(isolate, constructor); 105 | args.GetReturnValue().Set(cons->NewInstance()); 106 | } 107 | } 108 | 109 | // TODO: Make Non-blocking once basic functionality is proven 110 | void Spi::Open(const FunctionCallbackInfo& args) { 111 | FUNCTION_PREAMBLE; 112 | if (!self->require_arguments(isolate, args, 1)) { return; } 113 | ASSERT_NOT_OPEN; 114 | 115 | String::Utf8Value device(args[0]->ToString()); 116 | int retval = 0; 117 | 118 | self->m_fd = open(*device, O_RDWR); // Blocking! 119 | if (self->m_fd < 0) { 120 | EXCEPTION("Unable to open device"); 121 | return; 122 | } 123 | 124 | SET_IOCTL_VALUE(self->m_fd, SPI_IOC_WR_MODE, self->m_mode); 125 | SET_IOCTL_VALUE(self->m_fd, SPI_IOC_WR_BITS_PER_WORD, self->m_bits_per_word); 126 | SET_IOCTL_VALUE(self->m_fd, SPI_IOC_WR_MAX_SPEED_HZ, self->m_max_speed); 127 | 128 | FUNCTION_CHAIN; 129 | } 130 | 131 | void Spi::Close(const FunctionCallbackInfo &args) { 132 | FUNCTION_PREAMBLE; 133 | ONLY_IF_OPEN; 134 | 135 | close(self->m_fd); 136 | self->m_fd = -1; 137 | 138 | FUNCTION_CHAIN; 139 | } 140 | 141 | // tranfer(write_buffer, read_buffer); 142 | void Spi::Transfer(const FunctionCallbackInfo &args) { 143 | FUNCTION_PREAMBLE; 144 | ASSERT_OPEN; 145 | if (!self->require_arguments(isolate, args, 2)) { return; } 146 | 147 | if ((args[0]->IsNull()) && (args[1]->IsNull())) { 148 | EXCEPTION("Both buffers cannot be null"); 149 | return; 150 | } 151 | 152 | char *write_buffer = NULL; 153 | char *read_buffer = NULL; 154 | size_t write_length = -1; 155 | size_t read_length = -1; 156 | Local write_buffer_obj; 157 | Local read_buffer_obj; 158 | 159 | if (Buffer::HasInstance(args[0])) { 160 | write_buffer_obj = args[0]->ToObject(); 161 | write_buffer = Buffer::Data(write_buffer_obj); 162 | write_length = Buffer::Length(write_buffer_obj); 163 | } 164 | 165 | if (Buffer::HasInstance(args[1])) { 166 | read_buffer_obj = args[1]->ToObject(); 167 | read_buffer = Buffer::Data(read_buffer_obj); 168 | read_length = Buffer::Length(read_buffer_obj); 169 | } 170 | 171 | if (write_length > 0 && read_length > 0 && write_length != read_length) { 172 | EXCEPTION("Read and write buffers MUST be the same length"); 173 | return; 174 | } 175 | 176 | self->full_duplex_transfer(isolate, args, write_buffer, read_buffer, 177 | MAX(write_length, read_length), 178 | self->m_max_speed, self->m_delay, self->m_bits_per_word); 179 | 180 | } 181 | 182 | void Spi::full_duplex_transfer( 183 | Isolate *isolate, 184 | const FunctionCallbackInfo &args, 185 | char *write, 186 | char *read, 187 | size_t length, 188 | uint32_t speed, 189 | uint16_t delay, 190 | uint8_t bits 191 | ) { 192 | struct spi_ioc_transfer data = { 193 | (unsigned long)write, 194 | (unsigned long)read, 195 | length, 196 | speed, 197 | delay, // Still unsure ... just expose to options. 198 | bits 199 | }; 200 | 201 | int ret = ioctl(this->m_fd, SPI_IOC_MESSAGE(1), &data); 202 | 203 | if (ret == -1) { 204 | EXCEPTION("Unable to send SPI message"); 205 | return; 206 | } 207 | 208 | args.GetReturnValue().Set(ret); 209 | } 210 | 211 | // This overrides any of the OTHER set functions since modes are predefined 212 | // sets of options. 213 | SPI_FUNC_IMPL(GetSetMode) { 214 | FUNCTION_PREAMBLE; 215 | 216 | if (self->get_if_no_args(isolate, args, 0, self->m_mode)) { return; } 217 | int in_mode; 218 | if (!self->get_argument(isolate, args, 0, in_mode)) { return; } 219 | 220 | ASSERT_NOT_OPEN; 221 | 222 | if (in_mode == SPI_MODE_0 || in_mode == SPI_MODE_1 || 223 | in_mode == SPI_MODE_2 || in_mode == SPI_MODE_3) { 224 | self->m_mode = in_mode; 225 | } else { 226 | EXCEPTION("Argument 1 must be one of the SPI_MODE_X constants"); 227 | return; 228 | } 229 | 230 | FUNCTION_CHAIN; 231 | } 232 | SPI_FUNC_IMPL(GetSetChipSelect) { 233 | FUNCTION_PREAMBLE; 234 | 235 | if (self->get_if_no_args(isolate, args, 0, self->m_mode&(SPI_CS_HIGH|SPI_NO_CS))) { return; } 236 | int in_value; 237 | if (!self->get_argument(isolate, args, 0, in_value)) { return; } 238 | 239 | ASSERT_NOT_OPEN; 240 | 241 | switch(in_value) { 242 | case SPI_CS_HIGH: 243 | self->m_mode |= SPI_CS_HIGH; 244 | self->m_mode &= ~SPI_NO_CS; 245 | break; 246 | case SPI_NO_CS: 247 | self->m_mode |= SPI_NO_CS; 248 | self->m_mode &= ~SPI_CS_HIGH; 249 | break; 250 | default: 251 | self->m_mode &= ~(SPI_NO_CS|SPI_CS_HIGH); 252 | break; 253 | } 254 | 255 | FUNCTION_CHAIN; 256 | } 257 | 258 | SPI_FUNC_IMPL(GetSetBitsPerWord) { 259 | FUNCTION_PREAMBLE; 260 | if (self->get_if_no_args(isolate, args, 0, (unsigned int)self->m_bits_per_word)) { return; } 261 | 262 | int in_value; 263 | if (!self->get_argument_greater_than(isolate, args, 0, 0, in_value)) { return; } 264 | ASSERT_NOT_OPEN; 265 | 266 | // TODO: Bounds checking? Need to look up what the max value is 267 | self->m_bits_per_word = in_value; 268 | 269 | FUNCTION_CHAIN; 270 | } 271 | 272 | SPI_FUNC_IMPL(GetSetMaxSpeed) { 273 | FUNCTION_PREAMBLE; 274 | 275 | if (self->get_if_no_args(isolate, args, 0, self->m_max_speed)) { return; } 276 | 277 | int in_value; 278 | if (!self->get_argument_greater_than(isolate, args, 0, 0, in_value)) { return; } 279 | ASSERT_NOT_OPEN; 280 | 281 | // TODO: Bounds Checking? Need to look up what the max value is 282 | self->m_max_speed = in_value; 283 | 284 | FUNCTION_CHAIN; 285 | } 286 | 287 | SPI_FUNC_IMPL(GetSet3Wire) { 288 | FUNCTION_PREAMBLE; 289 | 290 | if (self->get_if_no_args(isolate, args, 0, (self->m_mode&SPI_3WIRE) > 0)) { return; } 291 | 292 | bool in_value; 293 | if (!self->get_argument(isolate, args, 0, in_value)) { return; } 294 | 295 | if (in_value) { 296 | self->m_mode |= SPI_3WIRE; 297 | } else { 298 | self->m_mode &= ~SPI_3WIRE; 299 | } 300 | FUNCTION_CHAIN; 301 | } 302 | 303 | // Expose m_delay as "delay" 304 | SPI_FUNC_IMPL(GetSetDelay) { 305 | FUNCTION_PREAMBLE; 306 | 307 | if (self->get_if_no_args(isolate, args, 0, (unsigned int)self->m_delay)) { return; } 308 | 309 | int in_value; 310 | if (!self->get_argument_greater_than(isolate, args, 0, 0, in_value)) { return; } 311 | ASSERT_NOT_OPEN; 312 | 313 | self->m_delay = in_value; 314 | 315 | FUNCTION_CHAIN; 316 | } 317 | 318 | SPI_FUNC_BOOLEAN_TOGGLE_IMPL(GetSetLoop, SPI_LOOP); 319 | SPI_FUNC_BOOLEAN_TOGGLE_IMPL(GetSetBitOrder, SPI_LSB_FIRST); 320 | 321 | #define ERROR_EXPECTED_ARGUMENTS(N) "Expected " #N "arguments" 322 | #define ERROR_ARGUMENT_NOT_INTEGER(I) "Argument " #I " must be an integer" 323 | #define ERROR_ARGUMENT_NOT_BOOLEAN(I) "Argument " #I " must be an boolean" 324 | #define ERROR_OUT_OF_RANGE(I, V, R, M) "Argument " #I " must be " #R " " #M " but was " #V 325 | 326 | /********************************************************************************************************************* 327 | 328 | Internal Functions */ 329 | 330 | bool 331 | Spi::require_arguments( 332 | Isolate *isolate, 333 | const FunctionCallbackInfo& args, 334 | int count 335 | ) { 336 | if (args.Length() < count) { 337 | EXCEPTION(ERROR_EXPECTED_ARGUMENTS(count)); 338 | return false; 339 | } 340 | 341 | return true; 342 | } 343 | 344 | bool 345 | Spi::get_argument( 346 | Isolate *isolate, 347 | const FunctionCallbackInfo& args, 348 | int offset, 349 | int& value 350 | ) { 351 | if (args.Length() <= offset || !args[offset]->IsInt32()) { 352 | EXCEPTION(ERROR_ARGUMENT_NOT_INTEGER(offset)); 353 | return false; 354 | } 355 | 356 | value = args[offset]->Int32Value(); 357 | return true; 358 | } 359 | 360 | bool 361 | Spi::get_argument( 362 | Isolate *isolate, 363 | const FunctionCallbackInfo& args, 364 | int offset, 365 | bool& value 366 | ) { 367 | if (args.Length() <= offset || !args[offset]->IsBoolean()) { 368 | EXCEPTION(ERROR_ARGUMENT_NOT_BOOLEAN(offset)); 369 | return false; 370 | } 371 | 372 | value = args[offset]->BooleanValue(); 373 | return true; 374 | } 375 | 376 | bool 377 | Spi::get_if_no_args( 378 | Isolate *isolate, 379 | const FunctionCallbackInfo& args, 380 | int offset, 381 | unsigned int value 382 | ) { 383 | if (args.Length() <= offset) { 384 | args.GetReturnValue().Set(value); 385 | return true; 386 | } 387 | 388 | return false; 389 | } 390 | 391 | bool 392 | Spi::get_if_no_args( 393 | Isolate *isolate, 394 | const FunctionCallbackInfo& args, 395 | int offset, 396 | bool value 397 | ) { 398 | if (args.Length() <= offset) { 399 | args.GetReturnValue().Set(value); 400 | return true; 401 | } 402 | 403 | return false; 404 | } 405 | 406 | bool 407 | Spi::get_argument_greater_than( 408 | Isolate *isolate, 409 | const FunctionCallbackInfo& args, 410 | int offset, 411 | int target, 412 | int& value 413 | ) { 414 | if (!get_argument(isolate, args, offset, value)) { return false; } 415 | 416 | if (value <= target) { 417 | EXCEPTION(ERROR_OUT_OF_RANGE(offset, value, >, target)); 418 | return false; 419 | } 420 | 421 | return true; 422 | } 423 | 424 | void 425 | Spi::get_set_mode_toggle( 426 | Isolate *isolate, 427 | const FunctionCallbackInfo& args, 428 | int mask 429 | ) { 430 | if (get_if_no_args(isolate, args, 0, (m_mode&mask) > 0)) { return; } 431 | 432 | bool in_value; 433 | if (!get_argument(isolate, args, 0, in_value)) { return; } 434 | 435 | if (in_value) { 436 | m_mode |= mask; 437 | } else { 438 | m_mode &= ~mask; 439 | } 440 | FUNCTION_CHAIN; 441 | } 442 | 443 | -------------------------------------------------------------------------------- /src/spi_binding.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012, Russell Hay 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | using namespace v8; 24 | using namespace node; 25 | 26 | #define SPI_FUNC(NAME) static void NAME (const FunctionCallbackInfo& args) 27 | #define SPI_FUNC_IMPL(NAME) void Spi::NAME (const FunctionCallbackInfo& args) 28 | #define SPI_FUNC_EMPTY(NAME) void Spi::NAME (const FunctionCallbackInfo& args) { \ 29 | args.GetReturnValue().Set(false); \ 30 | } 31 | 32 | class Spi : public ObjectWrap { 33 | public: 34 | static Persistent constructor; 35 | static void Initialize(Handle target); 36 | 37 | private: 38 | Spi() : m_fd(-1), 39 | m_mode(0), 40 | m_max_speed(1000000), // default speed in Hz () 1MHz 41 | m_delay(0), // expose delay to options 42 | m_bits_per_word(8) { } // default bits per word 43 | 44 | 45 | ~Spi() { } // Probably close fd if it's open 46 | 47 | SPI_FUNC(New); 48 | SPI_FUNC(Open); 49 | SPI_FUNC(Close); 50 | SPI_FUNC(Transfer); 51 | SPI_FUNC(GetSetMode); 52 | SPI_FUNC(GetSetChipSelect); 53 | SPI_FUNC(GetSetMaxSpeed); 54 | SPI_FUNC(GetSet3Wire); 55 | SPI_FUNC(GetSetDelay); 56 | SPI_FUNC(GetSetLoop); 57 | SPI_FUNC(GetSetBitOrder); 58 | SPI_FUNC(GetSetBitsPerWord); 59 | 60 | void full_duplex_transfer(Isolate* isolate, const FunctionCallbackInfo &args, char *write, char *read, size_t length, uint32_t speed, uint16_t delay, uint8_t bits); 61 | bool require_arguments(Isolate* isolate, const FunctionCallbackInfo& args, int count); 62 | bool get_argument(Isolate *isolate, const FunctionCallbackInfo& args, int offset, int& value); 63 | bool get_argument(Isolate *isolate, const FunctionCallbackInfo& args, int offset, bool& value); 64 | bool get_argument_greater_than(Isolate *isolate, const FunctionCallbackInfo& args, int offset, int target, int& value); 65 | bool get_if_no_args(Isolate *isolate, const FunctionCallbackInfo& args, int offset, unsigned int value); 66 | bool get_if_no_args(Isolate *isolate, const FunctionCallbackInfo& args, int offset, bool value); 67 | 68 | void get_set_mode_toggle(Isolate *isolate, const FunctionCallbackInfo& args, int mask); 69 | 70 | int m_fd; 71 | uint32_t m_mode; 72 | uint32_t m_max_speed; 73 | uint16_t m_delay; 74 | uint8_t m_bits_per_word; 75 | }; 76 | 77 | #define EXCEPTION(X) isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, X))) 78 | 79 | #define FUNCTION_PREAMBLE \ 80 | Isolate* isolate = args.GetIsolate(); \ 81 | HandleScope scope(isolate); \ 82 | Spi* self = ObjectWrap::Unwrap(args.This()) 83 | 84 | #define FUNCTION_CHAIN args.GetReturnValue().Set(args.This()) 85 | 86 | #define ASSERT_OPEN if (self->m_fd == -1) { EXCEPTION("Device not opened"); return; } 87 | #define ASSERT_NOT_OPEN if (self->m_fd != -1) { EXCEPTION("Cannot be called once device is opened"); return; } 88 | #define ONLY_IF_OPEN if (self->m_fd == -1) { FUNCTION_CHAIN; return; } 89 | 90 | #define REQ_INT_ARG_GT(I, NAME, VAR, VAL) \ 91 | REQ_INT_ARG(I, VAR); \ 92 | if (VAR <= VAL) \ 93 | return ThrowException(Exception::TypeError( \ 94 | String::NewFromUtf8(isolate, #NAME " must be greater than " #VAL ))); 95 | 96 | #define SPI_FUNC_BOOLEAN_TOGGLE_IMPL(NAME, ARGUMENT) \ 97 | SPI_FUNC_IMPL(NAME) { \ 98 | FUNCTION_PREAMBLE; \ 99 | self->get_set_mode_toggle(isolate, args, ARGUMENT); \ 100 | } 101 | 102 | #define SET_IOCTL_VALUE(FD, CTRL, VALUE) \ 103 | retval = ioctl(FD, CTRL, &(VALUE)); \ 104 | if (retval == -1) { \ 105 | EXCEPTION("Unable to set " #CTRL); \ 106 | return; \ 107 | } 108 | 109 | #define MAX(a,b) (a>b ? a:b) 110 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | //var spi = require('./build/Release/_spi'); 2 | //console.log(spi); 3 | //var x = new spi._spi(); 4 | //console.log(x.mode()); 5 | //x.mode(spi.SPI_MODE_3); 6 | //console.log(x.mode()); 7 | //x.mode(spi.SPI_MODE_0); 8 | //x.chipSelect(spi.SPI_CS_HIGH); 9 | //console.log(x.mode()); 10 | //x.chipSelect(spi.SPI_NO_CS); 11 | //console.log(x.mode()); 12 | //x.chipSelect(0); 13 | //console.log(x.mode()); 14 | //console.log(x.bitsPerWord()); 15 | //x.bitsPerWord(10); 16 | //console.log(x.bitsPerWord()); 17 | //console.log(x.maxSpeed()); 18 | // x.bitsPerWord(-1); // Error Case: Should throw RangeError 19 | //console.log(x.open("/dev/spidev1.1")); 20 | //var buff = new Buffer([ 0x56, 0x78, 0x88 ]); 21 | //x.transfer(buff, null); 22 | // 23 | //console.log(x.close()); 24 | 25 | var spi = require('./spi'); 26 | 27 | var rotate = function(spi, buf) { 28 | var tmp = buf[0]; 29 | for(var i=0; i