diff --git a/README.md b/README.md index 313504a..0d79f2b 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,37 @@ node-spi ======== -A NodeJS interface to the SPI bus on embedded linux machines. +A NodeJS interface to the SPI bus typically found on embedded linux machines +such as the Raspberry Pi. There is a native interface and a wrapped JS interface with a slightly better API. -**THIS CODE IS NOT FINISHED YET, NOTHING IS FUNCTIONING YET!** - -*Note: The first version will be blocking. I know this is antithetics to -the nodejs philosophy, but I think it's important, when dealing with blocking -interfaces, to get the code working in a blocking manner, and then introduce -the async calls using eio.* +*Note: The first version will be blocking. I know this is antithetical to +the node.js philosophy, but I think its important to get the code working in a +blocking manner first, and then introduce the async calls using eio.* Basic Usage =========== ```javascript -var spi = require("spi"); - -var MyDevice = new spi.Spi("/dev/spidev1.1", { - "mode": 0, // Always do mode first if you need something other than Mode 0 - "chip_select": spi.NO_CS - "max_speed": 1000000, // In Hz - "size": 8, // How many bits per word -}); - -var out_buffer = new Buffer([ 0x23, 0x48, 0xAF, 0x19, 0x19, 0x19 ]); - -MyDevice.transfer(out_buffer, outbuffer.Length(), - function(device, recv_buffer) { - // Do Something with the data in the recv buffer, if anything exists -}); +var SPI = require('spi'); + +var spi = new SPI.Spi('/dev/spidev0.0', { + 'mode': SPI.MODE['MODE_0'], // always set mode as the first option + 'chipSelect': SPI.CS['none'] // 'none', 'high' - defaults to low + }, function(s){s.open();}); + +var txbuf = new Buffer([ 0x23, 0x48, 0xAF, 0x19, 0x19, 0x19 ]); +var rxbuf = new Buffer([ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]); + +spi.transfer(txbuf, rxbuf, function(device, buf) { + // rxbuf and buf should be the same here + var s = ""; + for (var i=0; i < buf.length; i++) + s = s + buf[i] + " "; + console.log(s + "- " + new Date().getTime()); + }); ``` How you should **really** use the library @@ -42,17 +42,15 @@ Ideally, for each SPI device that is being controlled should have it's own object that implements the protocol necessary to talk to your device so that the device protocol is defined in one place. -An example project is -[node-adafruit-pixel](https://github.com/RussTheAerialist/node-adafruit-pixel) -which is a node module to control the -[AdaFruit RGB Pixels](http://www.adafruit.com/products/738). The interface is -defined in terms of color and pixels, and not in messages being sent via the -SPI bus, but it uses node-spi to do it's work. +An example project is [node-adafruit-pixel](https://github.com/RussTheAerialist/node-adafruit-pixel) +which is a node module to control the [AdaFruit RGB Pixels](http://www.adafruit.com/products/738). +The interface is defined in terms of color and pixels, and not in messages +being sent via the SPI bus, but it uses node-spi to do it's work. Native Api Reference ==================== -This section documents the native api which is defined in module \_spi.node. +This section documents the native api which is defined in module _spi.node. This is the interface that the normal Spi interface uses, but having a good understanding of this part is important, as some people may want to use the native interface directly. @@ -60,13 +58,50 @@ native interface directly. Creating, Opening, and Closing the device ----------------------------------------- -**\_spi.Spi constructor** - The constructor takes a single argument, the path -to the spi dev file in /dev. We do not check that the file exists until you -call open. +**\_spi.Spi constructor** - The constructor only requires the path to the spi +dev file in /dev. Options and a callback are not required but can be specified. + +Example: +```javascript +var spi = new SPI.Spi('/dev/spidev0.1'); +``` + +Options can include: +* mode +* chipSelect +* bitsPerWord +* bitOrder +* maxSpeed +* halfDuplex +* loopback + +Example: +```javascript +var spi = new SPI.Spi('/dev/spidev0.0', {'mode': SPI.MODE['MODE_0']}); +``` + +The callback returns a handle to the newly created SPI object. It might be +handy to .open() it if you set all of your options in one shot. -**open()** - This function takes no arguments and will open the device, setting +Example: +```javascript +var spi = new SPI.Spi('/dev/spidev0.0', {}, function(s){s.open();}); +``` + +**open()** - This function takes no arguments and will open the device using all of the options that were previously set. Once the device is open, we do not -allow you to change the settings to the device. +allow you to change the settings on the device. + +Example: +```javascript +var spi = new SPI.Spi('/dev/spidev0.0', {'mode': SPI.MODE['MODE_0']}); + +// get/set aditional options +spi.maxSpeed(20000); // in Hz +console.log('max speed: ' + spi.maxSpeed()); + +spi.open(); // once opened, you can't change the options +``` **close()** - This function should always be called before ending. Right now the destructor for the underlying C++ class does not call close(), but that @@ -115,21 +150,51 @@ if you'd like. Getting and Sending Data ------------------------ +**transfer(txbuf, rxbuf, callback)** - This takes two buffers, a write and a +read buffer, and optionally a callback. SPI only reads when a byte is written +so communicaton is usually full duplex. + +Exmple: +```javascript +var txbuf = new Buffer([ 0x23, 0x48, 0xAF, 0x19, 0x19, 0x19 ]); +var rxbuf = new Buffer([ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]); + +spi.transfer(txbuf, rxbuf, function(device, buf) { + var s = ""; + for (var i=0; i < buf.length; i++) + s = s + buf[i] + " "; + console.log(s); + }); +``` + +As a convenience feature, read and write functions pad zeros in the opposite +direction to make simple read and writes work. -**transfer()** - This takes two buffers, a write buffer and a read buffer. -If you only want to do one way transfer, then pass null to that argument. For -example, writes would look like this: +**read(buffer, callback)** - Reads as much data as the given buffer is big. +The results of the read are available in the callback. +Example: ```javascript -var buff = new Buffer([0x12, 0x12, 0x12]); -spi.transfer(buff, null); +var buf1 = new Buffer(8); +spi.read(buf1, function(device, buf2) { + var s = ""; + for (var i=0; i < buf.length; i++) + s = s + buf[i] + " "; + console.log(s); + }); ``` -Reads would look like this: +**write(buffer, callback)** - Writes out the given buffer. +Example: ```javascript -var buff = new Buffer(8); -spi.transfer(null, buff); +var buf = new Buffer([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]); +spi.write(buf, function(device, buf2) { + var s = ""; + for (var i=0; i < buf.length; i++) + s = s + buf[i] + " "; + console.log(s); + }); ``` Remember that these native apis are currently blocking. I will update, once I diff --git a/spi.js b/spi.js index 75923de..6f2c9d3 100644 --- a/spi.js +++ b/spi.js @@ -18,69 +18,175 @@ var _spi = require('bindings')('_spi.node'); -var MODE = [ - _spi.SPI_MODE_0, - _spi.SPI_MODE_1, - _spi.SPI_MODE_2, - _spi.SPI_MODE_3 -]; +// Consistance with docs +var MODE = { + MODE_0: _spi.MODE_0, + MODE_1: _spi.MODE_1, + MODE_2: _spi.MODE_2, + MODE_3: _spi.MODE_3 +}; var CS = { - 'high': _spi.SPI_CS_HIGH, - 'low': 0, - 'none': _spi.SPI_NO_CS + none: _spi.NO_CS, + high: _spi.CS_HIGH, + low: _spi.CS_LOW +}; + +var ORDER = { + msb: _spi.ORDER_MSB, + lsb: _spi.ORDER_LSB }; function isFunction(object) { - return object && getClass.call(object) == '[object Function]'; + return object && typeof object == 'function'; } var Spi = function(device, options, callback) { - this._spi = new _spi._spi(); + this._spi = new _spi._spi(); - if (callback == undefined) { - callback = options; - options = {}; - } + options = options || {}; // Default to an empty object - options = options || { }; // Default to an empty object - - for(var attrname in options) { - var value = options[attrname]; - if (attrname in this._spi) { - console.trace("Setting " + attrname + "=" + value); - this._spi[attrname](value); + for(var attrname in options) { + var value = options[attrname]; + if (attrname in this._spi) { + this._spi[attrname](value); + } + else + console.log("Unknown option: " + attrname + "=" + value); } - } - this._spi.open(device); + this.device = device; + + isFunction(callback) && callback(this); // TODO: Update once open is async; +} + +Spi.prototype.open = function() { + return this._spi.open(this.device); +} - callback(this); // TODO: Update once open is async; +Spi.prototype.close = function() { + return this._spi.close(); } Spi.prototype.write = function(buf, callback) { - this._spi.transfer(buf, null); + this._spi.transfer(buf, new Buffer(buf.length)); - if (callback !== undefined) { - callback(this, buf); // TODO: Update once transfer is async; - } + isFunction(callback) && callback(this, buf); // TODO: Update once open is async; } Spi.prototype.read = function(buf, callback) { - this._spi.transfer(null, buf); + this._spi.transfer(new Buffer(buf.length), buf); + + isFunction(callback) && callback(this, buf); // TODO: Update once open is async; +} + +Spi.prototype.transfer = function(txbuf, rxbuf, callback) { + // tx and rx buffers need to be the same size + this._spi.transfer(txbuf, rxbuf); + + isFunction(callback) && callback(this, rxbuf); // TODO: Update once open is async; +} + +Spi.prototype.mode = function(mode) { + if (typeof(mode) != 'undefined') + if (mode == MODE['MODE_0'] || mode == MODE['MODE_1'] || + mode == MODE['MODE_2'] || mode == MODE['MODE_3']) { + this._spi['mode'](mode); + return this._spi; + } + else { + console.log('Illegal mode'); + return -1; + } + else + return this._spi['mode'](); +} + +Spi.prototype.chipSelect = function(cs) { + if (typeof(cs) != 'undefined') + if (cs == CS['none'] || cs == CS['high'] || cs == MODE['low']) { + this._spi['chipSelect'](cs); + return this._spi; + } + else { + console.log('Illegal chip selection'); + return -1; + } + else + return this._spi['chipSelect'](); +} + +Spi.prototype.bitsPerWord = function(bpw) { + if (typeof(bpw) != 'undefined') + if (bpw > 1) { + this._spi['bitsPerWord'](bpw); + return this._spi; + } + else { + console.log('Illegal bits per word'); + return -1; + } + else + return this._spi['bitsPerWord'](); +} + +Spi.prototype.bitOrder = function(bo) { + if (typeof(bo) != 'undefined') + if (bo == ORDER['msb'] || bo == ORDER['lsb']) { + this._spi['bitOrder'](bo); + return this._spi; + } + else { + console.log('Illegal bit order'); + return -1; + } + else + return this._spi['bitOrder'](); +} + +Spi.prototype.maxSpeed = function(speed) { + if (typeof(speed) != 'undefined') + if (speed > 0) { + this._spi['maxSpeed'](speed); + return this._spi; + } + else { + console.log('Speed must be positive'); + return -1; + } + else + return this._spi['maxSpeed'](); +} - if (callback !== undefined) { - callback(this, buf); // TODO: Update once transfer is async; - } +Spi.prototype.halfDuplex = function(duplex) { + if (typeof(duplex) != 'undefined') + if (duplex) { + this._spi['halfDuplex'](true); + return this._spi; + } + else { + this._spi['halfDuplex'](false); + return this._spi; + } + else + return this._spi['halfDuplex'](); } -Spi.prototype.transfer = function(wrbuf, rdbuf, callback) { - this._spi.transfer(wrbuf, rdbuf); - if (callback !== undefined) { - callback(this, rdbuf); // TODO: Update once transfer is async; - } +Spi.prototype.loopback = function(loop) { + if (typeof(loop) != 'undefined') + if (loop) { + this._spi['loopback'](true); + return this._spi; + } + else { + this._spi['loopback'](false); + return this._spi; + } + else + return this._spi['loopback'](); } module.exports.MODE = MODE; module.exports.CS = CS; +module.exports.ORDER = ORDER; module.exports.Spi = Spi; diff --git a/src/spi_binding.cc b/src/spi_binding.cc index d4757fc..d49cc16 100644 --- a/src/spi_binding.cc +++ b/src/spi_binding.cc @@ -43,54 +43,117 @@ Persistent Spi::constructor; void Spi::Initialize(Handle target) { HandleScope scope; + // var t = function() {}; Local t = FunctionTemplate::New(New); + // t = function _spi() {}; t->SetClassName(String::NewSymbol("_spi")); t->InstanceTemplate()->SetInternalFieldCount(1); + // t.prototype.open = open; t->PrototypeTemplate()->Set(String::NewSymbol("open"), FunctionTemplate::New(Open)->GetFunction()); + // t.prototype.close = close; t->PrototypeTemplate()->Set(String::NewSymbol("close"), FunctionTemplate::New(Close)->GetFunction()); + + // t.prototype.transfer = transfer; t->PrototypeTemplate()->Set(String::NewSymbol("transfer"), FunctionTemplate::New(Transfer)->GetFunction()); + + // t.prototype.mode = GetSetMode; t->PrototypeTemplate()->Set(String::NewSymbol("mode"), FunctionTemplate::New(GetSetMode)->GetFunction()); + + // t.prototype.chipSelect = GetSetChipSelect; t->PrototypeTemplate()->Set(String::NewSymbol("chipSelect"), FunctionTemplate::New(GetSetChipSelect)->GetFunction()); - t->PrototypeTemplate()->Set(String::NewSymbol("bitsPerWord"), + + // t.prototype.bitsPerWord = GetSetBitsPerWord; + t->PrototypeTemplate()->Set(String::NewSymbol("size"), FunctionTemplate::New(GetSetBitsPerWord)->GetFunction()); + + // t.prototype.bitOrder = GetSetBitOrder; t->PrototypeTemplate()->Set(String::NewSymbol("bitOrder"), FunctionTemplate::New(GetSetBitOrder)->GetFunction()); + + // t.prototype.maxSpeed = GetSetMaxSpeed; t->PrototypeTemplate()->Set(String::NewSymbol("maxSpeed"), FunctionTemplate::New(GetSetMaxSpeed)->GetFunction()); + + // t.prototype.halfDuplex = GetSet3Wire; t->PrototypeTemplate()->Set(String::NewSymbol("halfDuplex"), FunctionTemplate::New(GetSet3Wire)->GetFunction()); + + // t.prototype.delay = GetSetDelay; + t->PrototypeTemplate()->Set(String::NewSymbol("delay"), + FunctionTemplate::New(GetSetDelay)->GetFunction()); + + // t.prototype.loopback = GetSetLoop; t->PrototypeTemplate()->Set(String::NewSymbol("loopback"), FunctionTemplate::New(GetSetLoop)->GetFunction()); + // var constructor = t; // in context of new. constructor = Persistent::New(t->GetFunction()); - target->Set(String::NewSymbol("_spi"), constructor); - - // SPI modes - NODE_DEFINE_CONSTANT(target, SPI_MODE_0); - NODE_DEFINE_CONSTANT(target, SPI_MODE_1); - NODE_DEFINE_CONSTANT(target, SPI_MODE_2); - NODE_DEFINE_CONSTANT(target, SPI_MODE_3); - - // Logic Level High for Chip Select - NODE_DEFINE_CONSTANT(target, SPI_CS_HIGH); - // No Chip Select - NODE_DEFINE_CONSTANT(target, SPI_NO_CS); + // exports._spi = constructor; + target->Set(String::NewSymbol("_spi"), constructor); + /* Should be change here. + + // SPI modes + NODE_DEFINE_CONSTANT(target, SPI_MODE_0); + NODE_DEFINE_CONSTANT(target, SPI_MODE_1); + NODE_DEFINE_CONSTANT(target, SPI_MODE_2); + NODE_DEFINE_CONSTANT(target, SPI_MODE_3); + + // Logic Level High for Chip Select + NODE_DEFINE_CONSTANT(target, SPI_CS_HIGH); + + // No Chip Select + NODE_DEFINE_CONSTANT(target, SPI_NO_CS); + */ + /* From: + _spi = { + SPI_MODE_0: 0 + , SPI_MODE_1: 1 + , SPI_MODE_2: 2 + , SPI_MODE_3: 3 + + , SPI_NO_CS: 64 + , SPI_CS_HIGH: 4 + }; + */ + /* To: + var spi = require('spi'); + + spi.MODE_0 == 0; + spi.MODE_1 == 1; + spi.MODE_2 == 2; + spi.MODE_3 == 3; + + spi.CS_LOW == 0; + spi.CS_HIGH == 4; + spi.NO_CS == 64; + */ + + // Expose constants + target->Set(v8::String::NewSymbol("MODE_0"), v8::Integer::New(SPI_MODE_0)); + target->Set(v8::String::NewSymbol("MODE_1"), v8::Integer::New(SPI_MODE_1)); + target->Set(v8::String::NewSymbol("MODE_2"), v8::Integer::New(SPI_MODE_2)); + target->Set(v8::String::NewSymbol("MODE_3"), v8::Integer::New(SPI_MODE_3)); + + target->Set(v8::String::NewSymbol("NO_CS"), v8::Integer::New(SPI_NO_CS)); + target->Set(v8::String::NewSymbol("CS_LOW"), v8::Integer::New(0)); + target->Set(v8::String::NewSymbol("CS_HIGH"), v8::Integer::New(SPI_CS_HIGH)); + + target->Set(v8::String::NewSymbol("ORDER_MSB"), v8::Boolean::New(false)); + target->Set(v8::String::NewSymbol("ORDER_LSB"), v8::Boolean::New(true)); } // new Spi(string device) //Handle Spi::New(const Arguments& args) { SPI_FUNC_IMPL(New) { - - Spi* spi = new Spi(); spi->Wrap(args.This()); @@ -165,7 +228,9 @@ Handle Spi::Transfer(const Arguments &args) { } Handle retval = self->full_duplex_transfer(write_buffer, read_buffer, - MAX(write_length, read_length)); + MAX(write_length, read_length), + self->m_max_speed, self->m_delay, self->m_bits_per_word); + if (!retval->IsUndefined()) { return retval; } @@ -173,23 +238,23 @@ Handle Spi::Transfer(const Arguments &args) { FUNCTION_CHAIN; } -Handle Spi::full_duplex_transfer(char *write, char *read, size_t length) { - struct spi_ioc_transfer data = { 0 }; - - // .tx_buf = (unsigned long)write, - // .rx_buf = (unsigned long)read, - // .len = length, - // .delay_usecs = 0, // Unsure exactly what delay is for.. - // .speed_hz = 0, // Use the max speed set when opened - // .bits_per_word = 0, // Use the bits per word set when opened - //}; +Handle Spi::full_duplex_transfer(char *write, char *read, size_t length, uint32_t speed, uint16_t delay, uint8_t bits) { + struct spi_ioc_transfer data = { + (unsigned long)write, + (unsigned long)read, + length, + speed, + delay, // Still unsure ... just expose to options. + bits, + }; int ret = ioctl(this->m_fd, SPI_IOC_MESSAGE(1), &data); + if (ret == 1) { return ERROR("Unable to send SPI message"); } - return Undefined(); + return v8::Integer::New(ret); } // This overrides any of the OTHER set functions since modes are predefined @@ -256,6 +321,7 @@ SPI_FUNC_IMPL(GetSetMaxSpeed) { FUNCTION_CHAIN; } + SPI_FUNC_IMPL(GetSet3Wire) { FUNCTION_PREAMBLE; GETTER(1, Boolean::New((self->m_mode&SPI_3WIRE) > 0)); @@ -269,5 +335,17 @@ SPI_FUNC_IMPL(GetSet3Wire) { FUNCTION_CHAIN; } +// Expose m_delay as "delay" +SPI_FUNC_IMPL(GetSetDelay) { + FUNCTION_PREAMBLE; + GETTER(1, Number::New(self->m_delay)); + REQ_INT_ARG_GT(0, Delay, in_value, 0); + ASSERT_NOT_OPEN; + + self->m_delay = in_value; + + FUNCTION_CHAIN; +} + SPI_FUNC_BOOLEAN_TOGGLE_IMPL(GetSetLoop, SPI_LOOP); SPI_FUNC_BOOLEAN_TOGGLE_IMPL(GetSetBitOrder, SPI_LSB_FIRST); diff --git a/src/spi_binding.h b/src/spi_binding.h index 8c55790..f8d289d 100644 --- a/src/spi_binding.h +++ b/src/spi_binding.h @@ -35,9 +35,12 @@ class Spi : ObjectWrap { static void Initialize(Handle target); private: - Spi() : m_fd(-1), m_mode(0), m_bits_per_word(8), - m_max_speed(1000000) { } - ~Spi() { } // Probably close fd if it's open + Spi() : m_fd(-1), + m_mode(0), + m_max_speed(1000000), // default speed in Hz () 1MHz + m_delay(0), // expose delay to options + m_bits_per_word(8) { } // default bits per word + ~Spi() { } // Probably close fd if it's open SPI_FUNC(New); SPI_FUNC(Open); @@ -47,16 +50,18 @@ class Spi : ObjectWrap { SPI_FUNC(GetSetChipSelect); SPI_FUNC(GetSetMaxSpeed); SPI_FUNC(GetSet3Wire); + SPI_FUNC(GetSetDelay); SPI_FUNC(GetSetLoop); SPI_FUNC(GetSetBitOrder); SPI_FUNC(GetSetBitsPerWord); - Handle full_duplex_transfer(char *write, char *read, size_t length); + Handle full_duplex_transfer(char *write, char *read, size_t length, uint32_t speed, uint16_t delay, uint8_t bits); int m_fd; int m_mode; - int m_bits_per_word; - int m_max_speed; + uint32_t m_max_speed; + uint16_t m_delay; + uint8_t m_bits_per_word; }; #define ERROR(STR) ThrowException(Exception::Error(String::New(STR)))