]> git.tnoah.ca Git - lora-radio.git/commitdiff
Initial commit
authorMark Qvist <mark@adepta.io>
Thu, 5 Apr 2018 16:10:42 +0000 (18:10 +0200)
committerMark Qvist <mark@adepta.io>
Thu, 5 Apr 2018 16:10:42 +0000 (18:10 +0200)
.gitignore [new file with mode: 0644]
Config.h [new file with mode: 0644]
Framing.h [new file with mode: 0644]
LoRa.cpp [new file with mode: 0644]
LoRa.h [new file with mode: 0644]
RNode_Firmware.ino [new file with mode: 0644]
Utilities.cpp [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..2e61a39
--- /dev/null
@@ -0,0 +1,4 @@
+.DS_Store
+*.hex
+TODO
+
diff --git a/Config.h b/Config.h
new file mode 100644 (file)
index 0000000..a7e02e0
--- /dev/null
+++ b/Config.h
@@ -0,0 +1,63 @@
+#ifndef CONFIG_H
+       #define CONFIG_H
+
+       #define MCU_328P 0x90
+       #define MCU_1284P 0x91
+
+       #if defined(__AVR_ATmega328P__)
+               #define MCU_VARIANT MCU_328P
+               #warning "Firmware is being compiled for atmega328p based boards"
+       #elif defined(__AVR_ATmega1284P__)
+               #define MCU_VARIANT MCU_1284P
+               #warning "Firmware is being compiled for atmega1284p based boards"
+       #else
+               #error "The firmware cannot be compiled for the selected MCU variant"
+       #endif
+
+       #define MTU        500
+       #define SINGLE_MTU 255
+       #define HEADER_L   1
+       #define CMD_L      4
+
+       // MCU dependent configuration parameters
+       #if MCU_VARIANT == MCU_328P
+               const int pin_cs = 7;
+               const int pin_reset = 6;
+               const int pin_dio = 2;
+               const int pin_led_rx = 5;
+               const int pin_led_tx = 4;
+       #endif
+
+       #if MCU_VARIANT == MCU_1284P
+               const int pin_cs = 4;
+               const int pin_reset = 3;
+               const int pin_dio = 2;
+               const int pin_led_rx = 12;
+               const int pin_led_tx = 13;
+       #endif
+
+       // MCU independent configuration parameters
+       const long serial_baudrate = 115200;
+       const int  rssi_offset     = 164;
+
+       // Default LoRa settings
+       int  lora_sf   = 0;
+       int  lora_txp  = 0xFF;
+       uint32_t lora_bw   = 0;
+       uint32_t lora_freq = 0;
+
+       // Operational variables
+       bool radio_locked = true;
+       bool radio_online = false;
+       
+       int             last_rssi               = -164;
+       size_t  read_len                = 0;
+       uint8_t seq                             = 0xFF;
+       uint8_t pbuf[MTU];
+       uint8_t sbuf[MTU];
+       uint8_t cbuf[CMD_L];
+
+       uint32_t stat_rx                = 0;
+       uint32_t stat_tx                = 0;
+
+#endif
\ No newline at end of file
diff --git a/Framing.h b/Framing.h
new file mode 100644 (file)
index 0000000..0107764
--- /dev/null
+++ b/Framing.h
@@ -0,0 +1,55 @@
+#ifndef FRAMING_H
+       #define FRAMING_H
+
+       #define FEND                    0xC0
+       #define FESC                    0xDB
+       #define TFEND                   0xDC
+       #define TFESC                   0xDD
+
+       #define CMD_UNKNOWN             0xFE
+       #define CMD_DATA                0x00
+       #define CMD_FREQUENCY   0x01
+       #define CMD_BANDWIDTH   0x02
+       #define CMD_TXPOWER             0x03
+       #define CMD_SF                  0x04
+       #define CMD_RADIO_STATE 0x05
+       #define CMD_RADIO_LOCK  0x06
+
+       #define CMD_STAT_RX             0x21
+       #define CMD_STAT_TX             0x22
+       #define CMD_STAT_RSSI   0x23
+       #define CMD_BLINK               0x30
+       #define CMD_RANDOM              0x40
+
+       #define RADIO_STATE_OFF 0x00
+       #define RADIO_STATE_ON  0x01
+
+       #define CMD_ERROR               0x90
+       #define ERROR_INITRADIO 0x01
+       #define ERROR_TXFAILED  0x02
+
+       #define NIBBLE_SEQ              0xF0
+       #define NIBBLE_FLAGS    0x0F
+       #define FLAG_SPLIT              0x01
+       #define SEQ_UNSET               0xFF
+
+       // Serial framing variables
+       size_t frame_len;
+       bool IN_FRAME = false;
+       bool ESCAPE = false;
+       uint8_t command = CMD_UNKNOWN;
+
+#endif
+
+/*
+Frequency 433.200              0xc00119d21b80c0
+Bandwidth 20.800               0xc00200005140c0
+TX Power 8dbm                  0xc00308c0
+SF 7                                   0xc00407c0
+
+All:                                   0xc00119d21b80c00200005140c00308c00407c0
+
+Radio on                               0xc00501c0
+
+Config+on                              0xc00119d21b80c00200005140c00308c00407c00501c0
+*/
\ No newline at end of file
diff --git a/LoRa.cpp b/LoRa.cpp
new file mode 100644 (file)
index 0000000..f012253
--- /dev/null
+++ b/LoRa.cpp
@@ -0,0 +1,584 @@
+// Copyright (c) Sandeep Mistry. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#include <LoRa.h>
+
+// registers
+#define REG_FIFO                 0x00
+#define REG_OP_MODE              0x01
+#define REG_FRF_MSB              0x06
+#define REG_FRF_MID              0x07
+#define REG_FRF_LSB              0x08
+#define REG_PA_CONFIG            0x09
+#define REG_LNA                  0x0c
+#define REG_FIFO_ADDR_PTR        0x0d
+#define REG_FIFO_TX_BASE_ADDR    0x0e
+#define REG_FIFO_RX_BASE_ADDR    0x0f
+#define REG_FIFO_RX_CURRENT_ADDR 0x10
+#define REG_IRQ_FLAGS            0x12
+#define REG_RX_NB_BYTES          0x13
+#define REG_PKT_SNR_VALUE        0x19
+#define REG_PKT_RSSI_VALUE       0x1a
+#define REG_MODEM_CONFIG_1       0x1d
+#define REG_MODEM_CONFIG_2       0x1e
+#define REG_PREAMBLE_MSB         0x20
+#define REG_PREAMBLE_LSB         0x21
+#define REG_PAYLOAD_LENGTH       0x22
+#define REG_MODEM_CONFIG_3       0x26
+#define REG_FREQ_ERROR_MSB       0x28
+#define REG_FREQ_ERROR_MID       0x29
+#define REG_FREQ_ERROR_LSB       0x2a
+#define REG_RSSI_WIDEBAND        0x2c
+#define REG_DETECTION_OPTIMIZE   0x31
+#define REG_DETECTION_THRESHOLD  0x37
+#define REG_SYNC_WORD            0x39
+#define REG_DIO_MAPPING_1        0x40
+#define REG_VERSION              0x42
+
+// modes
+#define MODE_LONG_RANGE_MODE     0x80
+#define MODE_SLEEP               0x00
+#define MODE_STDBY               0x01
+#define MODE_TX                  0x03
+#define MODE_RX_CONTINUOUS       0x05
+#define MODE_RX_SINGLE           0x06
+
+// PA config
+#define PA_BOOST                 0x80
+
+// IRQ masks
+#define IRQ_TX_DONE_MASK           0x08
+#define IRQ_PAYLOAD_CRC_ERROR_MASK 0x20
+#define IRQ_RX_DONE_MASK           0x40
+
+#define MAX_PKT_LENGTH           255
+
+LoRaClass::LoRaClass() :
+  _spiSettings(8E6, MSBFIRST, SPI_MODE0),
+  _ss(LORA_DEFAULT_SS_PIN), _reset(LORA_DEFAULT_RESET_PIN), _dio0(LORA_DEFAULT_DIO0_PIN),
+  _frequency(0),
+  _packetIndex(0),
+  _implicitHeaderMode(0),
+  _onReceive(NULL)
+{
+  // overide Stream timeout value
+  setTimeout(0);
+}
+
+int LoRaClass::begin(long frequency)
+{
+  // setup pins
+  pinMode(_ss, OUTPUT);
+  // set SS high
+  digitalWrite(_ss, HIGH);
+
+  if (_reset != -1) {
+    pinMode(_reset, OUTPUT);
+
+    // perform reset
+    digitalWrite(_reset, LOW);
+    delay(10);
+    digitalWrite(_reset, HIGH);
+    delay(10);
+  }
+
+  // start SPI
+  SPI.begin();
+
+  // check version
+  uint8_t version = readRegister(REG_VERSION);
+  if (version != 0x12) {
+    return 0;
+  }
+
+  // put in sleep mode
+  sleep();
+
+  // set frequency
+  setFrequency(frequency);
+
+  // set base addresses
+  writeRegister(REG_FIFO_TX_BASE_ADDR, 0);
+  writeRegister(REG_FIFO_RX_BASE_ADDR, 0);
+
+  // set LNA boost
+  writeRegister(REG_LNA, readRegister(REG_LNA) | 0x03);
+
+  // set auto AGC
+  writeRegister(REG_MODEM_CONFIG_3, 0x04);
+
+  // set output power to 17 dBm
+  setTxPower(17);
+
+  // put in standby mode
+  idle();
+
+  return 1;
+}
+
+void LoRaClass::end()
+{
+  // put in sleep mode
+  sleep();
+
+  // stop SPI
+  SPI.end();
+}
+
+int LoRaClass::beginPacket(int implicitHeader)
+{
+  // put in standby mode
+  idle();
+
+  if (implicitHeader) {
+    implicitHeaderMode();
+  } else {
+    explicitHeaderMode();
+  }
+
+  // reset FIFO address and paload length
+  writeRegister(REG_FIFO_ADDR_PTR, 0);
+  writeRegister(REG_PAYLOAD_LENGTH, 0);
+
+  return 1;
+}
+
+int LoRaClass::endPacket()
+{
+  // put in TX mode
+  writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_TX);
+
+  // wait for TX done
+  while ((readRegister(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == 0) {
+    yield();
+  }
+
+  // clear IRQ's
+  writeRegister(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK);
+
+  return 1;
+}
+
+int LoRaClass::parsePacket(int size)
+{
+  int packetLength = 0;
+  int irqFlags = readRegister(REG_IRQ_FLAGS);
+
+  if (size > 0) {
+    implicitHeaderMode();
+
+    writeRegister(REG_PAYLOAD_LENGTH, size & 0xff);
+  } else {
+    explicitHeaderMode();
+  }
+
+  // clear IRQ's
+  writeRegister(REG_IRQ_FLAGS, irqFlags);
+
+  if ((irqFlags & IRQ_RX_DONE_MASK) && (irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) {
+    // received a packet
+    _packetIndex = 0;
+
+    // read packet length
+    if (_implicitHeaderMode) {
+      packetLength = readRegister(REG_PAYLOAD_LENGTH);
+    } else {
+      packetLength = readRegister(REG_RX_NB_BYTES);
+    }
+
+    // set FIFO address to current RX address
+    writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_CURRENT_ADDR));
+
+    // put in standby mode
+    idle();
+  } else if (readRegister(REG_OP_MODE) != (MODE_LONG_RANGE_MODE | MODE_RX_SINGLE)) {
+    // not currently in RX mode
+
+    // reset FIFO address
+    writeRegister(REG_FIFO_ADDR_PTR, 0);
+
+    // put in single RX mode
+    writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_SINGLE);
+  }
+
+  return packetLength;
+}
+
+int LoRaClass::packetRssi()
+{
+  return (readRegister(REG_PKT_RSSI_VALUE) - (_frequency < 868E6 ? 164 : 157));
+}
+
+float LoRaClass::packetSnr()
+{
+  return ((int8_t)readRegister(REG_PKT_SNR_VALUE)) * 0.25;
+}
+
+long LoRaClass::packetFrequencyError()
+{
+  int32_t freqError = 0;
+  freqError = static_cast<int32_t>(readRegister(REG_FREQ_ERROR_MSB) & B111);
+  freqError <<= 8L;
+  freqError += static_cast<int32_t>(readRegister(REG_FREQ_ERROR_MID));
+  freqError <<= 8L;
+  freqError += static_cast<int32_t>(readRegister(REG_FREQ_ERROR_LSB));
+
+  if (readRegister(REG_FREQ_ERROR_MSB) & B1000) { // Sign bit is on
+     freqError -= 524288; // B1000'0000'0000'0000'0000
+  }
+
+  const float fXtal = 32E6; // FXOSC: crystal oscillator (XTAL) frequency (2.5. Chip Specification, p. 14)
+  const float fError = ((static_cast<float>(freqError) * (1L << 24)) / fXtal) * (getSignalBandwidth() / 500000.0f); // p. 37
+
+  return static_cast<long>(fError);
+}
+
+size_t LoRaClass::write(uint8_t byte)
+{
+  return write(&byte, sizeof(byte));
+}
+
+size_t LoRaClass::write(const uint8_t *buffer, size_t size)
+{
+  int currentLength = readRegister(REG_PAYLOAD_LENGTH);
+
+  // check size
+  if ((currentLength + size) > MAX_PKT_LENGTH) {
+    size = MAX_PKT_LENGTH - currentLength;
+  }
+
+  // write data
+  for (size_t i = 0; i < size; i++) {
+    writeRegister(REG_FIFO, buffer[i]);
+  }
+
+  // update length
+  writeRegister(REG_PAYLOAD_LENGTH, currentLength + size);
+
+  return size;
+}
+
+int LoRaClass::available()
+{
+  return (readRegister(REG_RX_NB_BYTES) - _packetIndex);
+}
+
+int LoRaClass::read()
+{
+  if (!available()) {
+    return -1;
+  }
+
+  _packetIndex++;
+
+  return readRegister(REG_FIFO);
+}
+
+int LoRaClass::peek()
+{
+  if (!available()) {
+    return -1;
+  }
+
+  // store current FIFO address
+  int currentAddress = readRegister(REG_FIFO_ADDR_PTR);
+
+  // read
+  uint8_t b = readRegister(REG_FIFO);
+
+  // restore FIFO address
+  writeRegister(REG_FIFO_ADDR_PTR, currentAddress);
+
+  return b;
+}
+
+void LoRaClass::flush()
+{
+}
+
+void LoRaClass::onReceive(void(*callback)(int))
+{
+  _onReceive = callback;
+
+  if (callback) {
+    pinMode(_dio0, INPUT);
+
+    writeRegister(REG_DIO_MAPPING_1, 0x00);
+#ifdef SPI_HAS_NOTUSINGINTERRUPT
+    SPI.usingInterrupt(digitalPinToInterrupt(_dio0));
+#endif
+    attachInterrupt(digitalPinToInterrupt(_dio0), LoRaClass::onDio0Rise, RISING);
+  } else {
+    detachInterrupt(digitalPinToInterrupt(_dio0));
+#ifdef SPI_HAS_NOTUSINGINTERRUPT
+    SPI.notUsingInterrupt(digitalPinToInterrupt(_dio0));
+#endif
+  }
+}
+
+void LoRaClass::receive(int size)
+{
+  if (size > 0) {
+    implicitHeaderMode();
+
+    writeRegister(REG_PAYLOAD_LENGTH, size & 0xff);
+  } else {
+    explicitHeaderMode();
+  }
+
+  writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_CONTINUOUS);
+}
+
+void LoRaClass::idle()
+{
+  writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_STDBY);
+}
+
+void LoRaClass::sleep()
+{
+  writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_SLEEP);
+}
+
+void LoRaClass::setTxPower(int level, int outputPin)
+{
+  if (PA_OUTPUT_RFO_PIN == outputPin) {
+    // RFO
+    if (level < 0) {
+      level = 0;
+    } else if (level > 14) {
+      level = 14;
+    }
+
+    writeRegister(REG_PA_CONFIG, 0x70 | level);
+  } else {
+    // PA BOOST
+    if (level < 2) {
+      level = 2;
+    } else if (level > 17) {
+      level = 17;
+    }
+
+    writeRegister(REG_PA_CONFIG, PA_BOOST | (level - 2));
+  }
+}
+
+void LoRaClass::setFrequency(long frequency)
+{
+  _frequency = frequency;
+
+  uint64_t frf = ((uint64_t)frequency << 19) / 32000000;
+
+  writeRegister(REG_FRF_MSB, (uint8_t)(frf >> 16));
+  writeRegister(REG_FRF_MID, (uint8_t)(frf >> 8));
+  writeRegister(REG_FRF_LSB, (uint8_t)(frf >> 0));
+}
+
+long LoRaClass::getFrequency() {
+  uint8_t msb = readRegister(REG_FRF_MSB);
+  uint8_t mid = readRegister(REG_FRF_MID);
+  uint8_t lsb = readRegister(REG_FRF_LSB);
+
+  uint64_t frf = msb << 16 | mid << 8 | lsb;
+  long frequency = (uint64_t)(frf*32000000) >> 19;
+
+  return frequency;
+}
+
+void LoRaClass::setSpreadingFactor(int sf)
+{
+  if (sf < 6) {
+    sf = 6;
+  } else if (sf > 12) {
+    sf = 12;
+  }
+
+  if (sf == 6) {
+    writeRegister(REG_DETECTION_OPTIMIZE, 0xc5);
+    writeRegister(REG_DETECTION_THRESHOLD, 0x0c);
+  } else {
+    writeRegister(REG_DETECTION_OPTIMIZE, 0xc3);
+    writeRegister(REG_DETECTION_THRESHOLD, 0x0a);
+  }
+
+  writeRegister(REG_MODEM_CONFIG_2, (readRegister(REG_MODEM_CONFIG_2) & 0x0f) | ((sf << 4) & 0xf0));
+}
+
+long LoRaClass::getSignalBandwidth()
+{
+  byte bw = (readRegister(REG_MODEM_CONFIG_1) >> 4);
+  switch (bw) {
+    case 0: return 7.8E3;
+    case 1: return 10.4E3; 
+    case 2: return 15.6E3; 
+    case 3: return 20.8E3; 
+    case 4: return 31.25E3; 
+    case 5: return 41.7E3; 
+    case 6: return 62.5E3; 
+    case 7: return 125E3; 
+    case 8: return 250E3; 
+    case 9: return 500E3; 
+  }
+}
+
+void LoRaClass::setSignalBandwidth(long sbw)
+{
+  int 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;
+  }
+
+  writeRegister(REG_MODEM_CONFIG_1, (readRegister(REG_MODEM_CONFIG_1) & 0x0f) | (bw << 4));
+}
+
+void LoRaClass::setCodingRate4(int denominator)
+{
+  if (denominator < 5) {
+    denominator = 5;
+  } else if (denominator > 8) {
+    denominator = 8;
+  }
+
+  int cr = denominator - 4;
+
+  writeRegister(REG_MODEM_CONFIG_1, (readRegister(REG_MODEM_CONFIG_1) & 0xf1) | (cr << 1));
+}
+
+void LoRaClass::setPreambleLength(long length)
+{
+  writeRegister(REG_PREAMBLE_MSB, (uint8_t)(length >> 8));
+  writeRegister(REG_PREAMBLE_LSB, (uint8_t)(length >> 0));
+}
+
+void LoRaClass::setSyncWord(int sw)
+{
+  writeRegister(REG_SYNC_WORD, sw);
+}
+
+void LoRaClass::enableCrc()
+{
+  writeRegister(REG_MODEM_CONFIG_2, readRegister(REG_MODEM_CONFIG_2) | 0x04);
+}
+
+void LoRaClass::disableCrc()
+{
+  writeRegister(REG_MODEM_CONFIG_2, readRegister(REG_MODEM_CONFIG_2) & 0xfb);
+}
+
+byte LoRaClass::random()
+{
+  return readRegister(REG_RSSI_WIDEBAND);
+}
+
+void LoRaClass::setPins(int ss, int reset, int dio0)
+{
+  _ss = ss;
+  _reset = reset;
+  _dio0 = dio0;
+}
+
+void LoRaClass::setSPIFrequency(uint32_t frequency)
+{
+  _spiSettings = SPISettings(frequency, MSBFIRST, SPI_MODE0);
+}
+
+void LoRaClass::dumpRegisters(Stream& out)
+{
+  for (int i = 0; i < 128; i++) {
+    out.print("0x");
+    out.print(i, HEX);
+    out.print(": 0x");
+    out.println(readRegister(i), HEX);
+  }
+}
+
+void LoRaClass::explicitHeaderMode()
+{
+  _implicitHeaderMode = 0;
+
+  writeRegister(REG_MODEM_CONFIG_1, readRegister(REG_MODEM_CONFIG_1) & 0xfe);
+}
+
+void LoRaClass::implicitHeaderMode()
+{
+  _implicitHeaderMode = 1;
+
+  writeRegister(REG_MODEM_CONFIG_1, readRegister(REG_MODEM_CONFIG_1) | 0x01);
+}
+
+void LoRaClass::handleDio0Rise()
+{
+  int irqFlags = readRegister(REG_IRQ_FLAGS);
+
+  // clear IRQ's
+  writeRegister(REG_IRQ_FLAGS, irqFlags);
+
+  if ((irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) {
+    // received a packet
+    _packetIndex = 0;
+
+    // read packet length
+    int packetLength = _implicitHeaderMode ? readRegister(REG_PAYLOAD_LENGTH) : readRegister(REG_RX_NB_BYTES);
+
+    // set FIFO address to current RX address
+    writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_CURRENT_ADDR));
+
+    if (_onReceive) {
+      _onReceive(packetLength);
+    }
+
+    // reset FIFO address
+    writeRegister(REG_FIFO_ADDR_PTR, 0);
+  }
+}
+
+uint8_t LoRaClass::readRegister(uint8_t address)
+{
+  return singleTransfer(address & 0x7f, 0x00);
+}
+
+void LoRaClass::writeRegister(uint8_t address, uint8_t value)
+{
+  singleTransfer(address | 0x80, value);
+}
+
+uint8_t LoRaClass::singleTransfer(uint8_t address, uint8_t value)
+{
+  uint8_t response;
+
+  digitalWrite(_ss, LOW);
+
+  SPI.beginTransaction(_spiSettings);
+  SPI.transfer(address);
+  response = SPI.transfer(value);
+  SPI.endTransaction();
+
+  digitalWrite(_ss, HIGH);
+
+  return response;
+}
+
+void LoRaClass::onDio0Rise()
+{
+  LoRa.handleDio0Rise();
+}
+
+LoRaClass LoRa;
\ No newline at end of file
diff --git a/LoRa.h b/LoRa.h
new file mode 100644 (file)
index 0000000..327244c
--- /dev/null
+++ b/LoRa.h
@@ -0,0 +1,96 @@
+// Copyright (c) Sandeep Mistry. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#ifndef LORA_H
+#define LORA_H
+
+#include <Arduino.h>
+#include <SPI.h>
+
+#define LORA_DEFAULT_SS_PIN    10
+#define LORA_DEFAULT_RESET_PIN 9
+#define LORA_DEFAULT_DIO0_PIN  2
+
+#define PA_OUTPUT_RFO_PIN      0
+#define PA_OUTPUT_PA_BOOST_PIN 1
+
+class LoRaClass : public Stream {
+public:
+  LoRaClass();
+
+  int begin(long frequency);
+  void end();
+
+  int beginPacket(int implicitHeader = false);
+  int endPacket();
+
+  int parsePacket(int size = 0);
+  int packetRssi();
+  float packetSnr();
+  long packetFrequencyError();
+
+  // from Print
+  virtual size_t write(uint8_t byte);
+  virtual size_t write(const uint8_t *buffer, size_t size);
+
+  // from Stream
+  virtual int available();
+  virtual int read();
+  virtual int peek();
+  virtual void flush();
+
+  void onReceive(void(*callback)(int));
+
+  void receive(int size = 0);
+  void idle();
+  void sleep();
+
+  void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN);
+  long getFrequency();
+  void setFrequency(long frequency);
+  void setSpreadingFactor(int sf);
+  long getSignalBandwidth();
+  void setSignalBandwidth(long sbw);
+  void setCodingRate4(int denominator);
+  void setPreambleLength(long length);
+  void setSyncWord(int sw);
+  void enableCrc();
+  void disableCrc();
+
+  // deprecated
+  void crc() { enableCrc(); }
+  void noCrc() { disableCrc(); }
+
+  byte random();
+
+  void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN);
+  void setSPIFrequency(uint32_t frequency);
+
+  void dumpRegisters(Stream& out);
+
+private:
+  void explicitHeaderMode();
+  void implicitHeaderMode();
+
+  void handleDio0Rise();
+
+  uint8_t readRegister(uint8_t address);
+  void writeRegister(uint8_t address, uint8_t value);
+  uint8_t singleTransfer(uint8_t address, uint8_t value);
+
+  static void onDio0Rise();
+
+private:
+  SPISettings _spiSettings;
+  int _ss;
+  int _reset;
+  int _dio0;
+  long _frequency;
+  int _packetIndex;
+  int _implicitHeaderMode;
+  void (*_onReceive)(int);
+};
+
+extern LoRaClass LoRa;
+
+#endif
\ No newline at end of file
diff --git a/RNode_Firmware.ino b/RNode_Firmware.ino
new file mode 100644 (file)
index 0000000..e00c160
--- /dev/null
@@ -0,0 +1,311 @@
+#include <SPI.h>
+#include <LoRa.h>
+#include "Config.h"
+#include "Framing.h"
+#include "Utilities.cpp"
+
+void setup() {
+  // Seed the PRNG
+  randomSeed(analogRead(0));
+
+  // Initialise serial communication
+  Serial.begin(serial_baudrate);
+  while (!Serial);
+
+  // Configure input and output pins
+  pinMode(pin_led_rx, OUTPUT);
+  pinMode(pin_led_tx, OUTPUT);
+
+  // Set up buffers
+  memset(pbuf, 0, sizeof(pbuf));
+  memset(sbuf, 0, sizeof(sbuf));
+  memset(cbuf, 0, sizeof(cbuf));
+
+  // Set chip select, reset and interrupt
+  // pins for the LoRa module
+  LoRa.setPins(pin_cs, pin_reset, pin_dio);
+}
+
+bool startRadio() {
+  update_radio_lock();
+  if (!radio_online) {
+    if (!radio_locked) {
+      if (!LoRa.begin(lora_freq)) {
+        // The radio could not be started.
+        // Indicate this failure over both the
+        // serial port and with the onboard LEDs
+        kiss_indicate_error(ERROR_INITRADIO);
+        led_indicate_error(0);
+      } else {
+        radio_online = true;
+
+        setTXPower();
+        setBandwidth();
+        setSpreadingFactor();
+        getFrequency();
+
+        LoRa.enableCrc();
+        LoRa.onReceive(receiveCallback);
+
+        LoRa.receive();
+
+        // Flash an info pattern to indicate
+        // that the radio is now on
+        led_indicate_info(3);
+      }
+
+    } else {
+      // Flash a warning pattern to indicate
+      // that the radio was locked, and thus
+      // not started
+      led_indicate_warning(3);
+    }
+  } else {
+    // If radio is already on, we silently
+    // ignore the request.
+  }
+}
+
+void stopRadio() {
+  LoRa.end();
+  radio_online = false;
+}
+
+void update_radio_lock() {
+  if (lora_freq != 0 && lora_bw != 0 && lora_txp != 0xFF && lora_sf != 0) {
+    radio_locked = false;
+  } else {
+    radio_locked = true;
+  }
+}
+
+void receiveCallback(int packet_size) {
+  led_rx_on();
+
+  uint8_t header   = LoRa.read(); packet_size--;
+  uint8_t sequence = packetSequence(header);
+  bool    ready    = false;
+
+  if (isSplitPacket(header) && seq == SEQ_UNSET) {
+    // This is the first part of a split
+    // packet, so we set the seq variable
+    // and add the data to the buffer
+    read_len = 0;
+    seq = sequence;
+    last_rssi = LoRa.packetRssi();
+    getPacketData(packet_size);
+  } else if (isSplitPacket(header) && seq == sequence) {
+    // This is the second part of a split
+    // packet, so we add it to the buffer
+    // and set the ready flag.
+    last_rssi = (last_rssi+LoRa.packetRssi())/2;
+    getPacketData(packet_size);
+    seq = SEQ_UNSET;
+    ready = true;
+  } else if (isSplitPacket(header) && seq != sequence) {
+    // This split packet does not carry the
+    // same sequence id, so we must assume
+    // that we are seeing the first part of
+    // a new split packet.
+    read_len = 0;
+    seq = sequence;
+    last_rssi = LoRa.packetRssi();
+    getPacketData(packet_size);
+  } else if (!isSplitPacket(header)) {
+    // This is not a split packet, so we
+    // just read it and set the ready
+    // flag to true.
+
+    if (seq != SEQ_UNSET) {
+      // If we already had part of a split
+      // packet in the buffer, we clear it.
+      read_len = 0;
+      seq = SEQ_UNSET;
+    }
+
+    last_rssi = LoRa.packetRssi();
+    getPacketData(packet_size);
+    ready = true;
+  }
+
+  if (ready) {
+    // We first signal the RSSI of the
+    // recieved packet to the host.
+    Serial.write(FEND);
+    Serial.write(CMD_STAT_RSSI);
+    Serial.write((uint8_t)(last_rssi-rssi_offset));
+
+    // And then write the entire packet
+    Serial.write(FEND);
+    Serial.write(CMD_DATA);
+    for (int i = 0; i < read_len; i++) {
+      uint8_t byte = pbuf[i];
+      if (byte == FEND) { Serial.write(FESC); byte = TFEND; }
+      if (byte == FESC) { Serial.write(FESC); byte = TFESC; }
+      Serial.write(byte);
+    }
+    Serial.write(FEND);
+    read_len = 0;
+  }
+  led_rx_off();
+}
+
+void transmit(size_t size) {
+  if (radio_online) {
+    led_tx_on();
+    size_t  written = 0;
+    uint8_t header  = random(256) & 0xF0;
+
+    if (size > SINGLE_MTU - HEADER_L) {
+      header = header | FLAG_SPLIT;
+    }
+
+    LoRa.beginPacket();
+    LoRa.write(header); written++;
+
+    for (size_t i; i < size; i++) {
+      LoRa.write(sbuf[i]);
+      written++;
+
+      if (written == 255) {
+        LoRa.endPacket();
+        LoRa.beginPacket();
+        LoRa.write(header);
+        written = 1;
+      }
+    }
+
+    LoRa.endPacket();
+    led_tx_off();
+    LoRa.receive();
+  } else {
+    kiss_indicate_error(ERROR_TXFAILED);
+    led_indicate_error(5);
+  }
+}
+
+void serialCallback(uint8_t sbyte) {
+  if (IN_FRAME && sbyte == FEND && command == CMD_DATA) {
+    IN_FRAME = false;
+    transmit(frame_len);
+  } else if (sbyte == FEND) {
+    IN_FRAME = true;
+    command = CMD_UNKNOWN;
+    frame_len = 0;
+  } else if (IN_FRAME && frame_len < MTU) {
+    // Have a look at the command byte first
+    if (frame_len == 0 && command == CMD_UNKNOWN) {
+        command = sbyte;
+    } else if (command == CMD_DATA) {
+        if (sbyte == FESC) {
+            ESCAPE = true;
+        } else {
+            if (ESCAPE) {
+                if (sbyte == TFEND) sbyte = FEND;
+                if (sbyte == TFESC) sbyte = FESC;
+                ESCAPE = false;
+            }
+            sbuf[frame_len++] = sbyte;
+        }
+    } else if (command == CMD_FREQUENCY) {
+      if (sbyte == FESC) {
+            ESCAPE = true;
+        } else {
+            if (ESCAPE) {
+                if (sbyte == TFEND) sbyte = FEND;
+                if (sbyte == TFESC) sbyte = FESC;
+                ESCAPE = false;
+            }
+            cbuf[frame_len++] = sbyte;
+        }
+
+        if (frame_len == 4) {
+          uint32_t freq = (uint32_t)cbuf[0] << 24 | (uint32_t)cbuf[1] << 16 | (uint32_t)cbuf[2] << 8 | (uint32_t)cbuf[3];
+
+          if (freq == 0) {
+            kiss_indicate_frequency();
+          } else {
+            lora_freq = freq;
+            setFrequency();
+            kiss_indicate_frequency();
+          }
+        }
+    } else if (command == CMD_BANDWIDTH) {
+      if (sbyte == FESC) {
+            ESCAPE = true;
+        } else {
+            if (ESCAPE) {
+                if (sbyte == TFEND) sbyte = FEND;
+                if (sbyte == TFESC) sbyte = FESC;
+                ESCAPE = false;
+            }
+            cbuf[frame_len++] = sbyte;
+        }
+
+        if (frame_len == 4) {
+          uint32_t bw = (uint32_t)cbuf[0] << 24 | (uint32_t)cbuf[1] << 16 | (uint32_t)cbuf[2] << 8 | (uint32_t)cbuf[3];
+
+          if (bw == 0) {
+            kiss_indicate_bandwidth();
+          } else {
+            lora_bw = bw;
+            setBandwidth();
+            kiss_indicate_bandwidth();
+          }
+        }
+    } else if (command == CMD_TXPOWER) {
+      if (sbyte == 0xFF) {
+        kiss_indicate_txpower();
+      } else {
+        int txp = sbyte;
+        if (txp > 17) txp = 17;
+
+        lora_txp = txp;
+        setTXPower();
+        kiss_indicate_txpower();
+      }
+    } else if (command == CMD_SF) {
+      if (sbyte == 0xFF) {
+        kiss_indicate_spreadingfactor();
+      } else {
+        int sf = sbyte;
+        if (sf < 7) sf = 7;
+        if (sf > 12) sf = 12;
+
+        lora_sf = sf;
+        setSpreadingFactor();
+        kiss_indicate_spreadingfactor();
+      }
+    } else if (command == CMD_RADIO_STATE) {
+      if (sbyte == 0xFF) {
+        kiss_indicate_radiostate();
+      } else if (sbyte == 0x00) {
+        stopRadio();
+        kiss_indicate_radiostate();
+      } else if (sbyte == 0x01) {
+        startRadio();
+        kiss_indicate_radiostate();
+      }
+    } else if (command == CMD_STAT_RX) {
+      kiss_indicate_stat_rx();
+    } else if (command == CMD_STAT_TX) {
+      kiss_indicate_stat_tx();
+    } else if (command == CMD_STAT_RSSI) {
+      kiss_indicate_stat_rssi();
+    } else if (command == CMD_RADIO_LOCK) {
+      update_radio_lock();
+      kiss_indicate_radio_lock();
+    } else if (command == CMD_BLINK) {
+      led_indicate_info(sbyte);
+    } else if (command == CMD_RANDOM) {
+      kiss_indicate_random(getRandom());
+    }
+  }
+}
+
+void loop() {
+  if (Serial.available()) {
+    char sbyte = Serial.read();
+    serialCallback(sbyte);
+  }
+}
diff --git a/Utilities.cpp b/Utilities.cpp
new file mode 100644 (file)
index 0000000..ff81abd
--- /dev/null
@@ -0,0 +1,199 @@
+#include <Arduino.h>
+#include <LoRa.h>
+#include "Config.h"
+#include "Framing.h"
+
+void led_rx_on()  { digitalWrite(pin_led_rx, HIGH); }
+void led_rx_off() {    digitalWrite(pin_led_rx, LOW); }
+void led_tx_on()  { digitalWrite(pin_led_tx, HIGH); }
+void led_tx_off() { digitalWrite(pin_led_tx, LOW); }
+
+void led_indicate_error(int cycles) {
+       bool forever = (cycles == 0) ? true : false;
+       cycles = forever ? 1 : cycles;
+       while(cycles > 0) {
+        digitalWrite(pin_led_rx, HIGH);
+        digitalWrite(pin_led_tx, LOW);
+        delay(100);
+        digitalWrite(pin_led_rx, LOW);
+        digitalWrite(pin_led_tx, HIGH);
+        delay(100);
+        if (!forever) cycles--;
+    }
+    digitalWrite(pin_led_rx, LOW);
+    digitalWrite(pin_led_tx, LOW);
+}
+
+void led_indicate_warning(int cycles) {
+       bool forever = (cycles == 0) ? true : false;
+       cycles = forever ? 1 : cycles;
+       digitalWrite(pin_led_tx, HIGH);
+       while(cycles > 0) {
+        digitalWrite(pin_led_tx, LOW);
+        delay(100);
+        digitalWrite(pin_led_tx, HIGH);
+        delay(100);
+        if (!forever) cycles--;
+    }
+    digitalWrite(pin_led_tx, LOW);
+}
+
+void led_indicate_info(int cycles) {
+       bool forever = (cycles == 0) ? true : false;
+       cycles = forever ? 1 : cycles;
+       while(cycles > 0) {
+        digitalWrite(pin_led_rx, LOW);
+        delay(100);
+        digitalWrite(pin_led_rx, HIGH);
+        delay(100);
+        if (!forever) cycles--;
+    }
+    digitalWrite(pin_led_rx, LOW);
+}
+
+void kiss_indicate_error(uint8_t error_code) {
+       Serial.write(FEND);
+       Serial.write(CMD_ERROR);
+       Serial.write(error_code);
+       Serial.write(FEND);
+}
+
+void kiss_indicate_radiostate() {
+       Serial.write(FEND);
+       Serial.write(CMD_RADIO_STATE);
+       Serial.write(radio_online);
+       Serial.write(FEND);
+}
+
+void kiss_indicate_stat_rx() {
+       Serial.write(FEND);
+       Serial.write(CMD_STAT_RX);
+       Serial.write(stat_rx>>24);
+       Serial.write(stat_rx>>16);
+       Serial.write(stat_rx>>8);
+       Serial.write(stat_rx);
+       Serial.write(FEND);
+}
+
+void kiss_indicate_stat_tx() {
+       Serial.write(FEND);
+       Serial.write(CMD_STAT_TX);
+       Serial.write(stat_tx>>24);
+       Serial.write(stat_tx>>16);
+       Serial.write(stat_tx>>8);
+       Serial.write(stat_tx);
+       Serial.write(FEND);
+}
+
+void kiss_indicate_stat_rssi() {
+       Serial.write(FEND);
+       Serial.write(CMD_STAT_RSSI);
+       Serial.write((uint8_t)last_rssi+rssi_offset);
+       Serial.write(FEND);
+}
+
+void kiss_indicate_radio_lock() {
+       Serial.write(FEND);
+       Serial.write(CMD_RADIO_LOCK);
+       Serial.write(radio_locked);
+       Serial.write(FEND);
+}
+
+void kiss_indicate_spreadingfactor() {
+       Serial.write(FEND);
+       Serial.write(CMD_SF);
+       Serial.write((uint8_t)lora_sf);
+       Serial.write(FEND);
+}
+
+void kiss_indicate_txpower() {
+       Serial.write(FEND);
+       Serial.write(CMD_TXPOWER);
+       Serial.write((uint8_t)lora_txp);
+       Serial.write(FEND);
+}
+
+void kiss_indicate_bandwidth() {
+       Serial.write(FEND);
+       Serial.write(CMD_BANDWIDTH);
+       Serial.write(lora_bw>>24);
+       Serial.write(lora_bw>>16);
+       Serial.write(lora_bw>>8);
+       Serial.write(lora_bw);
+       Serial.write(FEND);
+}
+
+void kiss_indicate_frequency() {
+       Serial.write(FEND);
+       Serial.write(CMD_FREQUENCY);
+       Serial.write(lora_freq>>24);
+       Serial.write(lora_freq>>16);
+       Serial.write(lora_freq>>8);
+       Serial.write(lora_freq);
+       Serial.write(FEND);
+}
+
+void kiss_indicate_random(uint8_t byte) {
+       Serial.write(FEND);
+       Serial.write(CMD_RANDOM);
+       Serial.write(byte);
+       Serial.write(FEND);
+}
+
+bool isSplitPacket(uint8_t header) {
+       return (header & FLAG_SPLIT);
+}
+
+uint8_t packetSequence(uint8_t header) {
+       return header >> 4;
+}
+
+void getPacketData(int len) {
+       while (len--) {
+               pbuf[read_len++] = LoRa.read();
+       }
+}
+
+
+void setSpreadingFactor() {
+       if (radio_online) LoRa.setSpreadingFactor(lora_sf);
+}
+
+void setTXPower() {
+       if (radio_online) LoRa.setTxPower(lora_txp);
+}
+
+
+void getBandwidth() {
+       if (radio_online) {
+                       lora_bw = LoRa.getSignalBandwidth();
+       }
+}
+
+void setBandwidth() {
+       if (radio_online) {
+               LoRa.setSignalBandwidth(lora_bw);
+               getBandwidth();
+       }
+}
+
+void getFrequency() {
+       if (radio_online) {
+               lora_freq = LoRa.getFrequency();
+       }
+}
+
+void setFrequency() {
+       if (radio_online) {
+               LoRa.setFrequency(lora_freq);
+               getFrequency();
+       }
+}
+
+uint8_t getRandom() {
+       if (radio_online) {
+               return LoRa.random();
+       } else {
+               return 0x00;
+       }
+}