rpi-softspi
Version:
Software SPI implementation to use any Raspberry Pi pins as a SPI interface
372 lines (340 loc) • 9.56 kB
JavaScript
/*
* Copyright Armin Junge
*/
/* global Uint8Array */
var rpio = require("rpio")
/**
* Represents a SPI (Serial Peripheral Interface) using user defined pins.
* So it is a software solution instead of using the hardware SPI.
* The class implements the SPI protocol ({@link https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus}),
* but currently without a frequency support.
*/
class SoftSPI
{
/**
* Creates a new SPI instance for a slave device.
* @param {Object} options
* @param {Number} options.clock - Pin for clock (SCLK)
* @param {Number} [options.miso] - Pin for master input slave output (MISO)
* @param {Number} [options.mosi] - Pin for master output slave input (MOSI)
* @param {Number} [options.client] - Pin for client select (CS)
* @param {Number} [options.clientSelect=LOW (0)] - Clients needed polarity for activation
* @param {Number} [options.mode=0] - SPI mode (0 - 3)
* @param {Number} [options.bitOrder=MSB (1)] - Most Significant Bit (MSB = 1) or Least Significant Bit (LSB = 0)
* @returns {SoftSPI}
*/
constructor(options)
{
this.ClkPhase = { First:0, Second:1 }
this.ClkTrigger = { High:0, Low:2 }
this.assert(options.clock > 0, "Clock pin must be specified") // mandatory field "clock" filled?
this.default = {
clock: null,
miso: null,
mosi: null,
client: null,
clientSelect: rpio.LOW,
mode: 0,
bitOrder: SoftSPI.MSB
}
options = Object.assign([], this.default, options)
this.clock = options.clock
this.miso = options.miso
this.mosi = options.mosi
this.client = options.client
this.clientSelect = options.clientSelect
this.mode = options.mode
this.bitOrder = options.bitOrder
this.valid = false
}
/**
* Most Significant Bit (= 1)
* @constant
*/
static get MSB()
{
return 1
}
/**
* Least Significatn Bit (= 0)
* @constant
*/
static get LSB()
{
return 0
}
/**
* Helper method for the assertion of the given expression.
* If the expression is false, the method throws the given message as exception.
* @private
* @param {Boolean} expression - any logical expression
* @param {String} message - text, which will be thrown as exception
* @returns {undefined}
*/
assert(expression, message)
{
if(!expression)
throw message
}
/**
* Sets the SPI mode.
* @param {Number} value - SPI Mode
* @returns {undefined}
*/
set mode(value)
{
/*
Mode = Clock Polarity | Clock Phase
0 = 0 | 0
1 = 0 | 1
2 = 1 | 0
3 = 1 | 1
*/
this.assert(value >= 0 && value <= 3, "Mode " + value + " not supported")
this._mode = value
this.clockPhase = value & this.ClkPhase.Second
this.clockTrigger = (value & this.ClkTrigger.Low)?rpio.LOW:rpio.HIGH
this.valid = false
}
/**
* Returns the SPI mode.
* @returns {Number}
*/
get mode()
{
return this._mode
}
/**
* Sets the bit order at transfering data.
* @param {Number} order - MSB (1) or LSB (0)
* @returns {undefined}
*/
set bitOrder(order)
{
if(order === SoftSPI.MSB){
this._bitOrder = SoftSPI.MSB
this.bitMask = 0x80
this.writeShift = SoftSPI.shiftLeft
this.readShift = SoftSPI.shiftRight
}
else{
this._bitOrder = SoftSPI.LSB
this.bitMask = 0x01
this.writeShift = SoftSPI.shiftRight
this.readShift = SoftSPI.shiftLeft
}
this.valid = false
}
/**
* Returns the bit order.
* @returns {Number}
*/
get bitOrder()
{
return this._bitOrder
}
/**
* Static helper method to get pin value (LOW = 0 or HIGH = 1) from given any value.
* @private
* @param {Any} value
* @returns {Number}
*/
static pinVal(value)
{
return (value)?rpio.HIGH:rpio.LOW
}
/**
* Returns the pin value for triggering clock on.
* @private
* @returns {Number}
*/
get clockOn()
{
return SoftSPI.pinVal(this.clockTrigger)
}
/**
* Returns the pin value for triggering clock off.
* @private
* @returns {Number}
*/
get clockOff()
{
return SoftSPI.pinVal(!this.clockTrigger)
}
/**
* Returns the pin value for triggering client on.
* @private
* @returns {Number}
*/
get clientOn()
{
return SoftSPI.pinVal(this.clientSelect)
}
/**
* Returns the pin value for triggering client off.
* @private
* @returns {Number}
*/
get clientOff()
{
return SoftSPI.pinVal(!this.clientSelect)
}
/**
* Opens the connection to the client by setting up the pins.
* @returns {SoftSPI}
*/
open()
{
rpio.open(this.clock, rpio.OUTPUT, this.clockOff )
if(this.client)
rpio.open(this.client, rpio.OUTPUT, this.clientOff )
if(this.mosi)
rpio.open(this.mosi, rpio.OUTPUT, rpio.LOW)
if(this.miso)
rpio.open(this.miso, rpio.INPUT)
this.valid = true
return this
}
/**
* Activates client to start communication.
* @private
* @returns {undefined}
*/
activateClient()
{
if(this.client)
rpio.write(this.client, this.clientOn)
}
/**
* Deactivates client to finish communication.
* @private
* @returns {undefined}
*/
deactivateClient()
{
if(this.client)
rpio.write(this.client, this.clientOff)
}
/**
* Reads the number of bytes from client and supplies them as an array.
* @param {Number} bytes - Number of bytes to read
* @returns {Array}
*/
read(bytes)
{
return this.transfer(new Uint8Array(bytes), true, false)
}
/**
* Writes the given data to the client.
* @param {Any} data
* @returns {SoftSPI}
*/
write(data)
{
this.transfer(data, false, true)
return this
}
/**
* Transfers a single bit to and from the client.
* The given byte and the given offset indicate the to be written bit.
* Returns the read bit.
* @private
* @param {Byte} byte - Current byte for transfer to the client
* @param {Number} offset - Offset of the affected bit in the byte
* @param {Boolean} read - Read a bit from the client
* @param {Boolean} write - Write a bit to the client
* @returns {Number}
*/
transferBit(byte, offset, read, write)
{
let res = 0
if(write)
rpio.write(this.mosi, SoftSPI.pinVal(this.writeShift(byte, offset) & this.bitMask))
rpio.write(this.clock, this.clockOn)
if(read && this.clockPhase === this.ClkPhase.First)
res = rpio.read(this.miso)
rpio.write(this.clock, this.clockOff)
if(read && this.clockPhase === this.ClkPhase.Second)
res = rpio.read(this.miso)
return res
}
/**
* Transfers a single byte to and from the client.
* @private
* @param {Byte} byte - Current byte for transfer to the client
* @param {Boolean} read - Read a byte from the client
* @param {Boolean} write - Write a byte to the client
* @returns {Byte}
*/
transferByte(byte, read, write){
let res = 0
for(let b = 0; b < 8; ++b)
if(this.transferBit(byte, b, read, write))
res |= this.readShift(this.bitMask, b) //set bit to 1
else
res &= ~this.readShift(this.bitMask, b) //set bit to 0
return res
}
/**
* Transfers the given data to the client and returns data from the client.
* The given data can be any data, which is transformable to a byte array.
* Returns the read data from the client as an array. Each element is one byte.
* The read data has exactly the same number of bytes like the written data.
* @param {Any} data
* @param {Boolean} [read=true] - Read data from the client
* @param {Boolean} [write=true] - Write data to the client
* @returns {Array}
*/
transfer(data, read = true, write = true) // write and read
{
this.assert(this.valid, "Not initialized")
this.assert(!read || this.miso, "No input pin defined")
this.assert(!write || this.mosi, "No output pin defined")
let trans = Uint8Array.from(data)
let result = []
this.activateClient()
for(let byte of trans)
result.push(this.transferByte(byte, read, write))
this.deactivateClient()
return result
}
/**
* Closes the connection to the client by closing the pins.
* @returns {SoftSPI}
*/
close()
{
if(this.miso)
rpio.close(this.miso)
if(this.mosi)
rpio.close(this.mosi)
rpio.close(this.clock)
if(this.client)
rpio.close(this.client)
this.valid = false
return this
}
/**
* Static helper method to make a bit shift to the left.
* @private
* @param {Byte} value
* @param {Number} offset
* @returns {Byte}
*/
static shiftLeft(value, offset)
{
return value << offset
}
/**
* Static helper method to make a bit shift to the right.
* @private
* @param {Byte} value
* @param {Number} offset
* @returns {Byte}
*/
static shiftRight(value, offset)
{
return value >> offset
}
}
module.exports = SoftSPI