sx127x-driver
Version:
Node.js driver for Semtech SX1276/77/78/79 LoRa circuits
640 lines (513 loc) • 18.1 kB
JavaScript
let util = require('util');
let events = require('events');
let onoff = require('onoff');
let spi = require('spi-device');
let SPI_OPTIONS = {
mode: spi.MODE0,
maxSpeedHz: 12E6
};
// registers
let REG_FIFO = 0x00;
let REG_OP_MODE = 0x01;
let REG_FRF = 0x06;
let REG_PA_CONFIG = 0x09;
let REG_LNA = 0x0c;
let REG_FIFO_ADDR_PTR = 0x0d;
let REG_FIFO_TX_BASE_ADDR = 0x0e;
let REG_FIFO_RX_BASE_ADDR = 0x0f;
let REG_FIFO_RX_CURRENT_ADDR = 0x10;
let REG_IRQ_FLAGS = 0x12;
let REG_RX_NB_BYTES = 0x13;
let REG_PKT_RSSI_VALUE = 0x1a;
let REG_PKT_SNR_VALUE = 0x1b;
let REG_MODEM_CONFIG_1 = 0x1d;
let REG_MODEM_CONFIG_2 = 0x1e;
let REG_PREAMBLE = 0x20;
let REG_PAYLOAD_LENGTH = 0x22;
let REG_MODEM_CONFIG_3 = 0x26;
let REG_RSSI_WIDEBAND = 0x2c;
let REG_DETECTION_OPTIMIZE = 0x31;
let REG_INVERT_IQ = 0x33; // when inverted stops end devices communicating with each other directly
let REG_DETECTION_THRESHOLD = 0x37;
let REG_SYNC_WORD = 0x39;
let REG_IMAGE_CALIBRATION = 0x3b;
let REG_TEMPERATURE = 0x3c;
let REG_DIO_MAPPING_1 = 0x40;
let REG_VERSION = 0x42;
// modes
let MODE_LONG_RANGE_MODE = 0x80;
let MODE_SLEEP = 0x00;
let MODE_STDBY = 0x01; // rf and pll disabled
let MODE_FSTX = 0x02; // frequency synthesis tx
let MODE_TX = 0x03;
let MODE_FSRX = 0x04; // frequency synthesis rx
let MODE_RX_CONTINUOUS = 0x05;
let MODE_RX_SINGLE = 0x06;
// PA config
let PA_BOOST = 0x80;
// IRQ masks
let IRQ_PAYLOAD_CRC_ERROR_MASK = 0x20;
let IRQ_TX_DONE_MASK = 0x08;
let IRQ_RX_DONE_MASK = 0x40;
// Temperature monitor masks
let RF_IMAGECAL_TEMPMONITOR_OFF = 0x01;
let RF_IMAGECAL_TEMPMONITOR_ON = 0x00;
let RF_IMAGECAL_TEMPMONITOR_MASK = 0xFE
function SX127x(options) {
this._spiBus = options.spiBus || 0;
this._spiDevice = options.spiDevice || 0;
this._resetPin = (options.hasOwnProperty('resetPin')) ? options.resetPin : 24;
this._dio0Pin = (options.hasOwnProperty('dio0Pin')) ? options.dio0Pin : 25;
this._frequency = options.frequency || 915e6;
this._spreadingFactor = options.spreadingFactor || 7;
this._signalBandwidth = options.signalBandwidth || 125E3;
this._codingRate = options.codingRate || (4 / 5);
this._preambleLength = options.preambleLength || 8;
this._syncWord = options.syncWord || 0x12;
this._txPower = options.txPower || 17;
this._crc = options.crc || false;
this._implicitHeaderMode = false;
this._debug = options.debug || false;
this._packetIndex = 0;
this._tempCompensationFactor = options.tempCompensationFactor || 0;
this._invertIqReg = options._invertIqReg || false;
}
util.inherits(SX127x, events.EventEmitter);
async function sleep(m) {
return new Promise(r => setTimeout(r, m));
};
SX127x.prototype._openSpi = async function(spiBus, spiDevice, SPI_OPTIONS) {
return new Promise((resolve, reject) => {
let spiObj = spi.open(spiBus, spiDevice, SPI_OPTIONS, err => {
if (err) {
reject(err);
}
resolve(spiObj);
})
});
}
SX127x.prototype._trace = function(message) {
if (this._debug == true) {
console.log('\x1b[36m%s\x1b[0m','Debug sx127x: ' + message);
}
}
SX127x.prototype.open = async function()
{
// should throw errors on its own
this._dio0Gpio = new onoff.Gpio(this._dio0Pin, 'in', 'rising');
this._resetGpio = new onoff.Gpio(this._resetPin, 'out');
this._spi = await this._openSpi(this._spiBus, this._spiDevice, SPI_OPTIONS);
await this._reset();
let version = await this.readVersion();
if (version != 0x12) {
throw (new Error('Invalid version ' + version + ', expected 0x12'));
} else {
this._trace('Chip version matches 0x12');
}
await this.sleep();
if (this._invertIqReg) {
await this.invertIqReg();
}
await this.setFrequency(this._frequency);
await this.setSpreadingFactor(this._spreadingFactor);
await this.setSignalBandwidth(this._signalBandwidth);
await this.setCodingRate(this._codingRate);
await this.setPreambleLength(this._preambleLength);
await this.setSyncWord(this._syncWord);
await this.setCrc(this._crc);
await this._writeRegister(REG_FIFO_TX_BASE_ADDR, 0);
await this._writeRegister(REG_FIFO_RX_BASE_ADDR, 0);
await this.setLnaBoost(true);
// // auto ACG, LowDataRateOptimize???
await this._writeRegister(REG_MODEM_CONFIG_3, 0x04);
await this.setTxPower(this._txPower);
await this.standBy();
await this._writeRegister(REG_IRQ_FLAGS, 0x00);
};
SX127x.prototype.close = async function() {
await new Promise((resolve, reject) => {
this._spi.close(err => {
if (err) {
reject(err);
}
resolve();
})
});
this._spi = null;
this._dio0Gpio.unexport();
this._resetGpio.unexport();
};
SX127x.prototype.readVersion = async function() {
let version = await this._readRegister(REG_VERSION);
return version;
};
SX127x.prototype.setFrequency = async function(frequency, callback) {
this._frequency = frequency;
let frequencyBuffer = new Buffer(4);
frequencyBuffer.writeInt32BE(Math.floor((frequency / 32000000) * 524288));
frequencyBuffer = frequencyBuffer.slice(1);
await this._writeRegister(REG_FRF, frequencyBuffer);
};
SX127x.prototype.invertIqReg = async function() {
let regInvertIQ = await this._readRegister(REG_INVERT_IQ);
regInvertIQ |= 0x40;
await this._writeRegister(REG_INVERT_IQ, regInvertIQ);
};
SX127x.prototype.setLnaBoost = async function(boost) {
let lna = await this._readRegister(REG_LNA);
if (boost) {
lna |= 0x03;
} else {
lna &= 0xfc;
}
await this._writeRegister(REG_LNA, lna);
};
SX127x.prototype.setTxPower = async function(level, callback) {
if (level < 2) {
level = 2;
} else if (level > 17) {
level = 17;
}
this._txPower = level;
await this._writeRegister(REG_PA_CONFIG, PA_BOOST | (level - 2));
};
SX127x.prototype.setSpreadingFactor = async function(sf) {
if (sf < 6) {
sf = 6;
} else if (sf > 12) {
sf = 12;
}
this._spreadingFactor = sf;
let detectionOptimize = (sf === 6) ? 0xc5 : 0xc3;
let detectionThreshold = (sf === 6) ? 0x0c : 0x0a;
await this._writeRegister(REG_DETECTION_OPTIMIZE, detectionOptimize);
await this._writeRegister(REG_DETECTION_THRESHOLD, detectionThreshold);
let regModemConfig2 = await this._readRegister(REG_MODEM_CONFIG_2);
regModemConfig2 &= 0x0f;
regModemConfig2 |= (sf << 4);
await this._writeRegister(REG_MODEM_CONFIG_2, regModemConfig2);
};
SX127x.prototype.setSignalBandwidth = async function(sbw) {
let bw;
if (sbw <= 7.8E3) {
bw = 0;
} else if (sbw <= 10.4E3) {
bw = 1;
} else if (sbw <= 15.6E3) {
bw = 2;
} else if (sbw <= 20.8E3) {
bw = 3;
} else if (sbw <= 31.25E3) {
bw = 4;
} else if (sbw <= 41.7E3) {
bw = 5;
} else if (sbw <= 62.5E3) {
bw = 6;
} else if (sbw <= 125E3) {
bw = 7;
} else if (sbw <= 250E3) {
bw = 8;
} else /*if (sbw <= 250E3)*/ {
bw = 9;
}
this._signalBandwidth = sbw;
let regModemConfig1 = await this._readRegister(REG_MODEM_CONFIG_1);
regModemConfig1 &= 0x0f;
regModemConfig1 |= (bw << 4);
await this._writeRegister(REG_MODEM_CONFIG_1, regModemConfig1);
};
SX127x.prototype.setCodingRate = async function(cr, callback) {
let denominator;
if (cr <= (4/8)) {
denominator = 8;
} else if (cr <= (4/7)) {
denominator = 7;
} else if (cr <= (4/6)) {
denominator = 6;
} else /*if (cr <= (4/5))*/ {
denominator = 5;
}
this._codingRate = (4 / denominator);
cr = denominator - 4;
let regModemConfig1 = await this._readRegister(REG_MODEM_CONFIG_1);
regModemConfig1 &= 0xf1;
regModemConfig1 |= (cr << 1);
await this._writeRegister(REG_MODEM_CONFIG_1, regModemConfig1, callback);
};
SX127x.prototype.setPreambleLength = async function(length) {
let lengthBuffer = new Buffer(2);
this._preambleLength = length;
lengthBuffer.writeUInt16BE(length, 0);
await this._writeRegister(REG_PREAMBLE, lengthBuffer);
};
SX127x.prototype.setSyncWord = async function(sw) {
this._syncWord = sw;
await this._writeRegister(REG_SYNC_WORD, sw);
};
SX127x.prototype.setCrc = async function(crc) {
this._crc = crc;
let regModemConfig2 = await this._readRegister(REG_MODEM_CONFIG_2);
if (crc) {
regModemConfig2 |= 0x04;
} else {
regModemConfig2 &= 0xfb;
}
await this._writeRegister(REG_MODEM_CONFIG_2, regModemConfig2);
};
SX127x.prototype.readRandom = async function() {
await this._readRegister(REG_RSSI_WIDEBAND);
};
SX127x.prototype.sleep = async function() {
await this._writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_SLEEP);
};
SX127x.prototype.standBy = async function() {
await this._writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_STDBY);
};
SX127x.prototype.setContinuousReceiveMode = async function(length) {
if (arguments.length === 0) {
length = 0;
}
// watch interrupt pin
this._dio0Gpio.watch(this._onDio0Rise.bind(this));
// default mode is explicit header
this._implicitHeaderMode = (length) ? true : false;
let regModemConfig1 = await this._readRegister(REG_MODEM_CONFIG_1);
if (this._implicitHeaderMode) {
regModemConfig1 |= 0x01;
} else {
regModemConfig1 &= 0xfe;
}
await this._writeRegister(REG_MODEM_CONFIG_1, regModemConfig1);
// value of 0 is not allowed
if (this._implicitHeaderMode) {
await this._writeRegister(REG_PAYLOAD_LENGTH, length);
}
await this._writeRegister(REG_DIO_MAPPING_1, 0x00);
await this._writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_CONTINUOUS);
};
SX127x.prototype.receiveSingle = async function(length) {
if (arguments.length === 0) {
length = 0;
}
// unwatch interrupt pin
if (this._dio0Gpio) {
this._dio0Gpio.unwatch();
}
// default mode is explicit header
this._implicitHeaderMode = (length) ? true : false;
if (this._implicitHeaderMode) {
await this._writeRegister(REG_PAYLOAD_LENGTH, length);
}
let packetLength = 0;
let irqFlags = await this._readRegister(REG_IRQ_FLAGS);
// clear IRQ's
await this._writeRegister(REG_IRQ_FLAGS, irqFlags);
if ((irqFlags & IRQ_RX_DONE_MASK) && (irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) {
// received a packet
this._packetIndex = 0;
// read packet length
if (this._implicitHeaderMode) {
packetLength = await this._readRegister(REG_PAYLOAD_LENGTH);
} else {
packetLength = await this._readRegister(REG_RX_NB_BYTES);
}
// set FIFO address to current RX address
await this._writeRegister(REG_FIFO_ADDR_PTR, await this._readRegister(REG_FIFO_RX_CURRENT_ADDR));
// put in standby mode
await this.standBy();
} else if (await this._readRegister(REG_OP_MODE) != (MODE_LONG_RANGE_MODE | MODE_RX_SINGLE)) {
// not currently in RX mode
// reset FIFO address
await this._writeRegister(REG_FIFO_ADDR_PTR, 0);
// put in single RX mode
await this._writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_SINGLE);
// verify we are in rx single mode
if(await this._readRegister(REG_OP_MODE) != (MODE_LONG_RANGE_MODE | MODE_RX_SINGLE)) {
throw new Error('Could not change modes');
}
}
return packetLength;
}
// send data through REG_FIFO
SX127x.prototype.write = async function(data, implicitHeader, callback) {
this._trace("Sending: " + data);
// watch interrupt pin for purposes of callback
if (this._dio0Gpio) {
this._dio0Gpio.unwatch();
}
if (arguments.length === 2) {
callback = implicitHeader;
implicitHeader = false;
}
this._writeCallback = callback;
let regModemConfig1 = await this._readRegister(REG_MODEM_CONFIG_1);
if (implicitHeader) {
regModemConfig1 |= 0x01;
} else {
regModemConfig1 &= 0xfe;
}
await this._writeRegister(REG_MODEM_CONFIG_1, regModemConfig1);
await this.standBy();
await this._writeRegister(REG_FIFO_ADDR_PTR, 0);
await this._writeRegister(REG_PAYLOAD_LENGTH, data.length);
await this._writeRegister(REG_FIFO, data);
await this._writeRegister(REG_DIO_MAPPING_1, 0x40);
await this._writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_TX);
// synchronously wait for TX done
let i = 0;
while (((await this._readRegister(REG_IRQ_FLAGS)) & IRQ_TX_DONE_MASK) == 0) {
await sleep(1);
i = i + 1;
if (i > 100) {
throw new Error('Write timeout')
}
}
// clear IRQ's
await this._writeRegister(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK);
};
// resets the chip
SX127x.prototype._reset = async function() {
this._resetGpio.writeSync(0);
await sleep(10);
this._resetGpio.writeSync(1);
await sleep(10);
};
SX127x.prototype._readRegister = async function(register) {
let readMessage = {
sendBuffer: new Buffer([register & 0x7f, 0x00]),
receiveBuffer: new Buffer(2),
byteLength: 2
};
if (!this._spi) {
throw new Error('Spi not defined');
}
return new Promise ((resolve, reject) => this._spi.transfer([readMessage], function(err, messages) {
if (err) {
reject(err);
}
resolve(messages[0].receiveBuffer.readUInt8(1));
}));
};
SX127x.prototype._readRegisterBytes = async function(register, length, callback) {
let sendBuffer = Buffer.concat([
new Buffer([register & 0x7f]),
new Buffer(length)
]);
let readMessage = {
sendBuffer: sendBuffer,
receiveBuffer: new Buffer(sendBuffer.length),
byteLength: sendBuffer.length
};
if (!this._spi) {
throw new Error('Spi not defined');
}
return new Promise ((resolve, reject) => this._spi.transfer([readMessage], function(err, messages) {
if (err) {
reject(err);
}
resolve(messages[0].receiveBuffer.slice(1));
}));
};
SX127x.prototype._writeRegister = async function(register, value) {
let sendBuffer;
if (Buffer.isBuffer(value)) {
sendBuffer = Buffer.concat([
new Buffer([register | 0x80]),
value
]);
} else {
sendBuffer = new Buffer([register | 0x80, value]);
}
let writeMessage = {
sendBuffer: sendBuffer,
byteLength: sendBuffer.length
};
return new Promise ((resolve, reject) => this._spi.transfer([writeMessage], function(err, messages) {
if (err) {
reject(err);
}
resolve();
}));
};
// checks if we have any bytes incoming available for read
SX127x.prototype.available = async function()
{
return (await this._readRegister(REG_RX_NB_BYTES) - this._packetIndex);
}
// reads one char of available incoming buffer
SX127x.prototype.read = async function()
{
if (!(await this.available())) {
return -1;
}
this._packetIndex++;
return await this._readRegister(REG_FIFO);
}
// interrupt handler for receive
SX127x.prototype._onDio0Rise = async function(err, value) {
this._trace("Dio0 interrupt triggered");
if (err || value === 0) {
return;
}
// if this is in response to a write operation, writeCallback will be set
if (this._writeCallback) {
let irqFlags = await this._readRegister(REG_IRQ_FLAGS);
await this._writeRegister(REG_IRQ_FLAGS, irqFlags);
this._writeCallback();
this._writeCallback = null;
} else {
let event = {};
let irqFlags = await this._readRegister(REG_IRQ_FLAGS);
event.irqFlags = irqFlags;
await this._writeRegister(REG_IRQ_FLAGS, irqFlags);
let rxAddr = await this._readRegister(REG_FIFO_RX_CURRENT_ADDR);
await this._writeRegister(REG_FIFO_ADDR_PTR, rxAddr);
let nbBytes = await this._readRegister(
this._implicitHeaderMode ? REG_PAYLOAD_LENGTH : REG_RX_NB_BYTES);
let data = await this._readRegisterBytes(REG_FIFO, nbBytes);
event.data = data;
let rssi = await this._readRegister(REG_PKT_RSSI_VALUE);
event.rssi = rssi - (this._frequency < 868E6 ? 164 : 157);
let snr = await this._readRegister(REG_PKT_SNR_VALUE);
event.snr = (new Buffer([snr])).readInt8() * 0.25;
await this._writeRegister(REG_FIFO_ADDR_PTR, 0x00);
if ((event.irqFlags & 0x20) === 0) {
this._trace('Message received: ' + event.data.toString());
this.emit('data', event.data, event.rssi, event.snr);
}
}
};
SX127x.prototype.readTemperature = async function(err, value) {
// save previous OP mode
let previousOpMode = await this._readRegister(REG_OP_MODE);
// lora sleep mode
await this._writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE);
// Set the device to Standby (FSK SLEEP MODE) and wait for oscillator startup
await this._writeRegister(REG_OP_MODE, MODE_SLEEP);
// Set the device to FSRx mode
await this._writeRegister(REG_OP_MODE, MODE_FSRX);
// Set TempMonitorOff = 0 (enables the sensor). It is not required to wait for the PLL Lock indication
await this._writeRegister(REG_IMAGE_CALIBRATION,
(await this._readRegister(REG_IMAGE_CALIBRATION) & RF_IMAGECAL_TEMPMONITOR_MASK) | (RF_IMAGECAL_TEMPMONITOR_ON));
// Wait for 140 microseconds (in our case, we just use 1ms)
await sleep(1);
// Set TempMonitorOff = 1
await this._writeRegister(REG_IMAGE_CALIBRATION,
(await this._readRegister(REG_IMAGE_CALIBRATION) & RF_IMAGECAL_TEMPMONITOR_MASK) | (RF_IMAGECAL_TEMPMONITOR_OFF));
// Set device back to Sleep of Standby mode (FSK SLEEP MODE)
await this._writeRegister(REG_OP_MODE, MODE_SLEEP);
// Access temperature value in RegTemp
let temperature = await this._readRegister(REG_TEMPERATURE);
// see figure 41 in datasheet for more info
if (temperature > 128) {
temperature = 255 - temperature;
} else {
temperature = temperature * -1;
}
// account for compensation factor (calibration value)
temperature = temperature + this._tempCompensationFactor;
// restore previous op mode
await this._writeRegister(REG_OP_MODE, previousOpMode );
return temperature;
}
module.exports = SX127x;