commit f2762af57665061881759d2b96d92676703e17c1 Author: Mark Qvist Date: Thu Apr 5 18:10:42 2018 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e61a39 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +*.hex +TODO + diff --git a/Config.h b/Config.h new file mode 100644 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 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 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 + +// 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(readRegister(REG_FREQ_ERROR_MSB) & B111); + freqError <<= 8L; + freqError += static_cast(readRegister(REG_FREQ_ERROR_MID)); + freqError <<= 8L; + freqError += static_cast(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(freqError) * (1L << 24)) / fXtal) * (getSignalBandwidth() / 500000.0f); // p. 37 + + return static_cast(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 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 +#include + +#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 index 0000000..e00c160 --- /dev/null +++ b/RNode_Firmware.ino @@ -0,0 +1,311 @@ +#include +#include +#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 index 0000000..ff81abd --- /dev/null +++ b/Utilities.cpp @@ -0,0 +1,199 @@ +#include +#include +#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; + } +}