commit ba25825309a6f6c2e5e0a18c39cac9e0bcb1f8db Author: oe3cjb Date: Sun Nov 25 21:07:34 2018 +0100 LoRa APRS Tracker Simple Code to use TTGO T-Beam as a LoRa APRS Tracker including OLED Display messages. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f152028 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.pioenvs +.piolibdeps +.clang_complete +.gcc-flags.json diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7c486f1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,67 @@ +# Continuous Integration (CI) is the practice, in software +# engineering, of merging all developer working copies with a shared mainline +# several times a day < https://docs.platformio.org/page/ci/index.html > +# +# Documentation: +# +# * Travis CI Embedded Builds with PlatformIO +# < https://docs.travis-ci.com/user/integration/platformio/ > +# +# * PlatformIO integration with Travis CI +# < https://docs.platformio.org/page/ci/travis.html > +# +# * User Guide for `platformio ci` command +# < https://docs.platformio.org/page/userguide/cmd_ci.html > +# +# +# Please choose one of the following templates (proposed below) and uncomment +# it (remove "# " before each line) or use own configuration according to the +# Travis CI documentation (see above). +# + + +# +# Template #1: General project. Test it using existing `platformio.ini`. +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# install: +# - pip install -U platformio +# - platformio update +# +# script: +# - platformio run + + +# +# Template #2: The project is intended to be used as a library with examples. +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# env: +# - PLATFORMIO_CI_SRC=path/to/test/file.c +# - PLATFORMIO_CI_SRC=examples/file.ino +# - PLATFORMIO_CI_SRC=path/to/test/directory +# +# install: +# - pip install -U platformio +# - platformio update +# +# script: +# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..bb15a9b --- /dev/null +++ b/platformio.ini @@ -0,0 +1,14 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:heltec_wifi_lora_32] +platform = espressif32 +board = heltec_wifi_lora_32 +framework = arduino diff --git a/src/BG_RF95.cpp b/src/BG_RF95.cpp new file mode 100644 index 0000000..63c0bee --- /dev/null +++ b/src/BG_RF95.cpp @@ -0,0 +1,452 @@ +// BG_RF95.cpp +// +// Copyright (C) 2011 Mike McCauley +// $Id: BG_RF95.cpp,v 1.11 2016/04/04 01:40:12 mikem Exp $ + +#include + +byte _lastSNR = 0; + +// Interrupt vectors for the 3 Arduino interrupt pins +// Each interrupt can be handled by a different instance of BG_RF95, allowing you to have +// 2 or more LORAs per Arduino +BG_RF95* BG_RF95::_deviceForInterrupt[BG_RF95_NUM_INTERRUPTS] = {0, 0, 0}; +uint8_t BG_RF95::_interruptCount = 0; // Index into _deviceForInterrupt for next device + +// These are indexed by the values of ModemConfigChoice +// Stored in flash (program) memory to save SRAM +PROGMEM static const BG_RF95::ModemConfig MODEM_CONFIG_TABLE[] = +{ + // 1d, 1e, 26 + { 0x72, 0x74, 0x00}, // Bw125Cr45Sf128 (the chip default) + { 0x92, 0x74, 0x00}, // Bw500Cr45Sf128 + { 0x48, 0x94, 0x00}, // Bw31_25Cr48Sf512 + { 0x78, 0xc4, 0x00}, // Bw125Cr48Sf4096 + { 0x72, 0xc7, 0x8}, // BG 125 cr45 sf12 +}; + +BG_RF95::BG_RF95(uint8_t slaveSelectPin, uint8_t interruptPin, RHGenericSPI& spi) + : + RHSPIDriver(slaveSelectPin, spi), + _rxBufValid(0) +{ + _interruptPin = interruptPin; + _myInterruptIndex = 0xff; // Not allocated yet +} + +bool BG_RF95::init() +{ + if (!RHSPIDriver::init()) + return false; + //Serial.println("RHSPIDriver::init completed"); + // Determine the interrupt number that corresponds to the interruptPin + int interruptNumber = digitalPinToInterrupt(_interruptPin); + if (interruptNumber == NOT_AN_INTERRUPT) + return false; +#ifdef RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER + interruptNumber = _interruptPin; +#endif + //Serial.println("Attach Interrupt completed"); + + // No way to check the device type :-( + + // Set sleep mode, so we can also set LORA mode: + spiWrite(BG_RF95_REG_01_OP_MODE, BG_RF95_MODE_SLEEP | BG_RF95_LONG_RANGE_MODE); + delay(10); // Wait for sleep mode to take over from say, CAD + // Check we are in sleep mode, with LORA set + if (spiRead(BG_RF95_REG_01_OP_MODE) != (BG_RF95_MODE_SLEEP | BG_RF95_LONG_RANGE_MODE)) + { + //Serial.println(spiRead(BG_RF95_REG_01_OP_MODE), HEX); + return false; // No device present? + } + + // Add by Adrien van den Bossche for Teensy + // ARM M4 requires the below. else pin interrupt doesn't work properly. + // On all other platforms, its innocuous, belt and braces + pinMode(_interruptPin, INPUT); + + // Set up interrupt handler + // Since there are a limited number of interrupt glue functions isr*() available, + // we can only support a limited number of devices simultaneously + // ON some devices, notably most Arduinos, the interrupt pin passed in is actuallt the + // interrupt number. You have to figure out the interruptnumber-to-interruptpin mapping + // yourself based on knwledge of what Arduino board you are running on. + if (_myInterruptIndex == 0xff) + { + // First run, no interrupt allocated yet + if (_interruptCount <= BG_RF95_NUM_INTERRUPTS) + _myInterruptIndex = _interruptCount++; + else + return false; // Too many devices, not enough interrupt vectors + } + _deviceForInterrupt[_myInterruptIndex] = this; + if (_myInterruptIndex == 0) + attachInterrupt(interruptNumber, isr0, RISING); + else if (_myInterruptIndex == 1) + attachInterrupt(interruptNumber, isr1, RISING); + else if (_myInterruptIndex == 2) + attachInterrupt(interruptNumber, isr2, RISING); + else + { + //Serial.println("Interrupt vector too many vectors"); + return false; // Too many devices, not enough interrupt vectors + } + + // Set up FIFO + // We configure so that we can use the entire 256 byte FIFO for either receive + // or transmit, but not both at the same time + spiWrite(BG_RF95_REG_0E_FIFO_TX_BASE_ADDR, 0); + spiWrite(BG_RF95_REG_0F_FIFO_RX_BASE_ADDR, 0); + + // Packet format is preamble + explicit-header + payload + crc + // Explicit Header Mode + // payload is TO + FROM + ID + FLAGS + message data + // RX mode is implmented with RXCONTINUOUS + // max message data length is 255 - 4 = 251 octets + + setModeIdle(); + + // Set up default configuration + // No Sync Words in LORA mode. + setModemConfig(Bw125Cr45Sf128); // Radio default +// setModemConfig(Bw125Cr48Sf4096); // slow and reliable? + setPreambleLength(8); // Default is 8 + // An innocuous ISM frequency, same as RF22's + setFrequency(433.800); + // Lowish power + setTxPower(20); + + return true; +} + +// C++ level interrupt handler for this instance +// LORA is unusual in that it has several interrupt lines, and not a single, combined one. +// On MiniWirelessLoRa, only one of the several interrupt lines (DI0) from the RFM95 is usefuly +// connnected to the processor. +// We use this to get RxDone and TxDone interrupts +void BG_RF95::handleInterrupt() +{ + // Read the interrupt register + //Serial.println("HandleInterrupt"); + uint8_t irq_flags = spiRead(BG_RF95_REG_12_IRQ_FLAGS); + if (_mode == RHModeRx && irq_flags & (BG_RF95_RX_TIMEOUT | BG_RF95_PAYLOAD_CRC_ERROR)) + { + _rxBad++; + } + else if (_mode == RHModeRx && irq_flags & BG_RF95_RX_DONE) + { + // Have received a packet + uint8_t len = spiRead(BG_RF95_REG_13_RX_NB_BYTES); + + // Reset the fifo read ptr to the beginning of the packet + spiWrite(BG_RF95_REG_0D_FIFO_ADDR_PTR, spiRead(BG_RF95_REG_10_FIFO_RX_CURRENT_ADDR)); + spiBurstRead(BG_RF95_REG_00_FIFO, _buf, len); + _bufLen = len; + spiWrite(BG_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags + + // Remember the RSSI of this packet + // this is according to the doc, but is it really correct? + // weakest receiveable signals are reported RSSI at about -66 + _lastRssi = spiRead(BG_RF95_REG_1A_PKT_RSSI_VALUE) - 137; + + _lastSNR = spiRead(BG_RF95_REG_19_PKT_SNR_VALUE); + + // We have received a message. + validateRxBuf(); + if (_rxBufValid) + setModeIdle(); // Got one + } + else if (_mode == RHModeTx && irq_flags & BG_RF95_TX_DONE) + { + _txGood++; + setModeIdle(); + } + + spiWrite(BG_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags +} + +// These are low level functions that call the interrupt handler for the correct +// instance of BG_RF95. +// 3 interrupts allows us to have 3 different devices +void BG_RF95::isr0() +{ + if (_deviceForInterrupt[0]) + _deviceForInterrupt[0]->handleInterrupt(); +} +void BG_RF95::isr1() +{ + if (_deviceForInterrupt[1]) + _deviceForInterrupt[1]->handleInterrupt(); +} +void BG_RF95::isr2() +{ + if (_deviceForInterrupt[2]) + _deviceForInterrupt[2]->handleInterrupt(); +} + +// Check whether the latest received message is complete and uncorrupted +void BG_RF95::validateRxBuf() +{ + _promiscuous = 1; + if (_bufLen < 4) + return; // Too short to be a real message + // Extract the 4 headers + //Serial.println("validateRxBuf >= 4"); + _rxHeaderTo = _buf[0]; + _rxHeaderFrom = _buf[1]; + _rxHeaderId = _buf[2]; + _rxHeaderFlags = _buf[3]; + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + _rxGood++; + _rxBufValid = true; + } +} + +bool BG_RF95::available() +{ + if (_mode == RHModeTx) + return false; + setModeRx(); + return _rxBufValid; // Will be set by the interrupt handler when a good message is received +} + +void BG_RF95::clearRxBuf() +{ + ATOMIC_BLOCK_START; + _rxBufValid = false; + _bufLen = 0; + ATOMIC_BLOCK_END; +} + + +// BG 3 Byte header +bool BG_RF95::recvAPRS(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + if (buf && len) + { + ATOMIC_BLOCK_START; + // Skip the 4 headers that are at the beginning of the rxBuf + if (*len > _bufLen-BG_RF95_HEADER_LEN) + *len = _bufLen-(BG_RF95_HEADER_LEN-1); + memcpy(buf, _buf+(BG_RF95_HEADER_LEN-1), *len); // BG only 3 Byte header (-1) + ATOMIC_BLOCK_END; + } + clearRxBuf(); // This message accepted and cleared + return true; +} + +bool BG_RF95::recv(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + if (buf && len) + { + ATOMIC_BLOCK_START; + // Skip the 4 headers that are at the beginning of the rxBuf + if (*len > _bufLen-BG_RF95_HEADER_LEN) + *len = _bufLen-BG_RF95_HEADER_LEN; + memcpy(buf, _buf+BG_RF95_HEADER_LEN, *len); + ATOMIC_BLOCK_END; + } + clearRxBuf(); // This message accepted and cleared + return true; +} + +uint8_t BG_RF95::lastSNR() +{ + return(_lastSNR); +} + + +bool BG_RF95::send(const uint8_t* data, uint8_t len) +{ + if (len > BG_RF95_MAX_MESSAGE_LEN) + return false; + + waitPacketSent(); // Make sure we dont interrupt an outgoing message + setModeIdle(); + + // Position at the beginning of the FIFO + spiWrite(BG_RF95_REG_0D_FIFO_ADDR_PTR, 0); + // The headers + spiWrite(BG_RF95_REG_00_FIFO, _txHeaderTo); + spiWrite(BG_RF95_REG_00_FIFO, _txHeaderFrom); + spiWrite(BG_RF95_REG_00_FIFO, _txHeaderId); + spiWrite(BG_RF95_REG_00_FIFO, _txHeaderFlags); + // The message data + spiBurstWrite(BG_RF95_REG_00_FIFO, data, len); + spiWrite(BG_RF95_REG_22_PAYLOAD_LENGTH, len + BG_RF95_HEADER_LEN); + + setModeTx(); // Start the transmitter + // when Tx is done, interruptHandler will fire and radio mode will return to STANDBY + return true; +} + +bool BG_RF95::sendAPRS(const uint8_t* data, uint8_t len) +{ + if (len > BG_RF95_MAX_MESSAGE_LEN) + return false; + + waitPacketSent(); // Make sure we dont interrupt an outgoing message + setModeIdle(); + + // Position at the beginning of the FIFO + spiWrite(BG_RF95_REG_0D_FIFO_ADDR_PTR, 0); + // The headers for APRS + spiWrite(BG_RF95_REG_00_FIFO, '<'); + spiWrite(BG_RF95_REG_00_FIFO, _txHeaderFrom); + spiWrite(BG_RF95_REG_00_FIFO, 0x1 ); + //spiWrite(BG_RF95_REG_00_FIFO, _txHeaderFlags); + // The message data + spiBurstWrite(BG_RF95_REG_00_FIFO, data, len); + spiWrite(BG_RF95_REG_22_PAYLOAD_LENGTH, len + BG_RF95_HEADER_LEN -1 ); // only 3 Byte header BG + + setModeTx(); // Start the transmitter + // when Tx is done, interruptHandler will fire and radio mode will return to STANDBY + return true; +} + +bool BG_RF95::printRegisters() +{ +#ifdef RH_HAVE_SERIAL + uint8_t registers[] = { 0x01, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x014, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x4d }; + + uint8_t i; + for (i = 0; i < sizeof(registers); i++) + { + Serial.print(registers[i], HEX); + Serial.print(": "); + Serial.println(spiRead(registers[i]), HEX); + } +#endif + return true; +} + +uint8_t BG_RF95::maxMessageLength() +{ + return BG_RF95_MAX_MESSAGE_LEN; +} + +bool BG_RF95::setFrequency(float centre) +{ + // Frf = FRF / FSTEP + uint32_t frf = (centre * 1000000.0) / BG_RF95_FSTEP; + spiWrite(BG_RF95_REG_06_FRF_MSB, (frf >> 16) & 0xff); + spiWrite(BG_RF95_REG_07_FRF_MID, (frf >> 8) & 0xff); + spiWrite(BG_RF95_REG_08_FRF_LSB, frf & 0xff); + + return true; +} + +void BG_RF95::setModeIdle() +{ + if (_mode != RHModeIdle) + { + spiWrite(BG_RF95_REG_01_OP_MODE, BG_RF95_MODE_STDBY); + _mode = RHModeIdle; + } +} + + +bool BG_RF95::sleep() +{ + if (_mode != RHModeSleep) + { + spiWrite(BG_RF95_REG_01_OP_MODE, BG_RF95_MODE_SLEEP); + _mode = RHModeSleep; + } + return true; +} + +void BG_RF95::setModeRx() +{ + if (_mode != RHModeRx) + { + //Serial.println("SetModeRx"); + _mode = RHModeRx; + spiWrite(BG_RF95_REG_01_OP_MODE, BG_RF95_MODE_RXCONTINUOUS); + spiWrite(BG_RF95_REG_40_DIO_MAPPING1, 0x00); // Interrupt on RxDone + } +} + +void BG_RF95::setModeTx() +{ + if (_mode != RHModeTx) + { + _mode = RHModeTx; // set first to avoid possible race condition + spiWrite(BG_RF95_REG_01_OP_MODE, BG_RF95_MODE_TX); + spiWrite(BG_RF95_REG_40_DIO_MAPPING1, 0x40); // Interrupt on TxDone + } +} + +void BG_RF95::setTxPower(int8_t power, bool useRFO) +{ + // Sigh, different behaviours depending on whther the module use PA_BOOST or the RFO pin + // for the transmitter output + if (useRFO) + { + if (power > 14) power = 14; + if (power < -1) power = -1; + spiWrite(BG_RF95_REG_09_PA_CONFIG, BG_RF95_MAX_POWER | (power + 1)); + } else { + if (power > 23) power = 23; + if (power < 5) power = 5; + + // For BG_RF95_PA_DAC_ENABLE, manual says '+20dBm on PA_BOOST when OutputPower=0xf' + // BG_RF95_PA_DAC_ENABLE actually adds about 3dBm to all power levels. We will us it + // for 21, 22 and 23dBm -= 3; + } + if (power > 20) { + spiWrite(BG_RF95_REG_0B_OCP, ( BG_RF95_OCP_ON | BG_RF95_OCP_TRIM ) ); // Trim max current tp 240mA + spiWrite(BG_RF95_REG_4D_PA_DAC, BG_RF95_PA_DAC_ENABLE); + //power -= 3; + power = 20; // and set PA_DAC_ENABLE + + } else { + spiWrite(BG_RF95_REG_4D_PA_DAC, BG_RF95_PA_DAC_DISABLE); + } + + // RFM95/96/97/98 does not have RFO pins connected to anything. Only PA_BOOST + // pin is connected, so must use PA_BOOST + // Pout = 2 + OutputPower. + // The documentation is pretty confusing on this topic: PaSelect says the max power is 20dBm, + // but OutputPower claims it would be 17dBm. + // My measurements show 20dBm is correct + //spiWrite(BG_RF95_REG_09_PA_CONFIG, (BG_RF95_PA_SELECT | (power-5)) ); + spiWrite(BG_RF95_REG_09_PA_CONFIG, (BG_RF95_PA_SELECT | BG_RF95_MAX_POWER | (power-5)) ); + + //} +} + +// Sets registers from a canned modem configuration structure +void BG_RF95::setModemRegisters(const ModemConfig* config) +{ + spiWrite(BG_RF95_REG_1D_MODEM_CONFIG1, config->reg_1d); + spiWrite(BG_RF95_REG_1E_MODEM_CONFIG2, config->reg_1e); + spiWrite(BG_RF95_REG_26_MODEM_CONFIG3, config->reg_26); +} + +// Set one of the canned FSK Modem configs +// Returns true if its a valid choice +bool BG_RF95::setModemConfig(ModemConfigChoice index) +{ + if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE) / sizeof(ModemConfig))) + return false; + + ModemConfig cfg; + memcpy_P(&cfg, &MODEM_CONFIG_TABLE[index], sizeof(BG_RF95::ModemConfig)); + setModemRegisters(&cfg); + + return true; +} + +void BG_RF95::setPreambleLength(uint16_t bytes) +{ + spiWrite(BG_RF95_REG_20_PREAMBLE_MSB, bytes >> 8); + spiWrite(BG_RF95_REG_21_PREAMBLE_LSB, bytes & 0xff); +} diff --git a/src/BG_RF95.h b/src/BG_RF95.h new file mode 100644 index 0000000..ef74b5a --- /dev/null +++ b/src/BG_RF95.h @@ -0,0 +1,743 @@ +// BG_RF95.h +// +// Definitions for HopeRF LoRa radios per: +// http://www.hoperf.com/upload/rf/RFM95_96_97_98W.pdf +// http://www.hoperf.cn/upload/rfchip/RF96_97_98.pdf +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: BG_RF95.h,v 1.11 2016/07/07 00:02:53 mikem Exp mikem $ +// modified for Lora APRS Bernd Gasser OE1ACM + +#ifndef BG_RF95_h +#define BG_RF95_h + +#include + + + + + +// This is the maximum number of interrupts the driver can support +// Most Arduinos can handle 2, Megas can handle more +#define BG_RF95_NUM_INTERRUPTS 3 + +// Max number of octets the LORA Rx/Tx FIFO can hold +#define BG_RF95_FIFO_SIZE 255 + +// This is the maximum number of bytes that can be carried by the LORA. +// We use some for headers, keeping fewer for RadioHead messages +#define BG_RF95_MAX_PAYLOAD_LEN BG_RF95_FIFO_SIZE + +// The length of the headers we add. +// The headers are inside the LORA's payload +#define BG_RF95_HEADER_LEN 4 + +// This is the maximum message length that can be supported by this driver. +// Can be pre-defined to a smaller size (to save SRAM) prior to including this header +// Here we allow for 1 byte message length, 4 bytes headers, user data and 2 bytes of FCS +#ifndef BG_RF95_MAX_MESSAGE_LEN + #define BG_RF95_MAX_MESSAGE_LEN (BG_RF95_MAX_PAYLOAD_LEN - BG_RF95_HEADER_LEN) +#endif + +// The crystal oscillator frequency of the module +#define BG_RF95_FXOSC 32000000.0 + +// The Frequency Synthesizer step = BG_RF95_FXOSC / 2^^19 +#define BG_RF95_FSTEP (BG_RF95_FXOSC / 524288) + + +// Register names (LoRa Mode, from table 85) +#define BG_RF95_REG_00_FIFO 0x00 +#define BG_RF95_REG_01_OP_MODE 0x01 +#define BG_RF95_REG_02_RESERVED 0x02 +#define BG_RF95_REG_03_RESERVED 0x03 +#define BG_RF95_REG_04_RESERVED 0x04 +#define BG_RF95_REG_05_RESERVED 0x05 +#define BG_RF95_REG_06_FRF_MSB 0x06 +#define BG_RF95_REG_07_FRF_MID 0x07 +#define BG_RF95_REG_08_FRF_LSB 0x08 +#define BG_RF95_REG_09_PA_CONFIG 0x09 +#define BG_RF95_REG_0A_PA_RAMP 0x0a +#define BG_RF95_REG_0B_OCP 0x0b +#define BG_RF95_REG_0C_LNA 0x0c +#define BG_RF95_REG_0D_FIFO_ADDR_PTR 0x0d +#define BG_RF95_REG_0E_FIFO_TX_BASE_ADDR 0x0e +#define BG_RF95_REG_0F_FIFO_RX_BASE_ADDR 0x0f +#define BG_RF95_REG_10_FIFO_RX_CURRENT_ADDR 0x10 +#define BG_RF95_REG_11_IRQ_FLAGS_MASK 0x11 +#define BG_RF95_REG_12_IRQ_FLAGS 0x12 +#define BG_RF95_REG_13_RX_NB_BYTES 0x13 +#define BG_RF95_REG_14_RX_HEADER_CNT_VALUE_MSB 0x14 +#define BG_RF95_REG_15_RX_HEADER_CNT_VALUE_LSB 0x15 +#define BG_RF95_REG_16_RX_PACKET_CNT_VALUE_MSB 0x16 +#define BG_RF95_REG_17_RX_PACKET_CNT_VALUE_LSB 0x17 +#define BG_RF95_REG_18_MODEM_STAT 0x18 +#define BG_RF95_REG_19_PKT_SNR_VALUE 0x19 +#define BG_RF95_REG_1A_PKT_RSSI_VALUE 0x1a +#define BG_RF95_REG_1B_RSSI_VALUE 0x1b +#define BG_RF95_REG_1C_HOP_CHANNEL 0x1c +#define BG_RF95_REG_1D_MODEM_CONFIG1 0x1d +#define BG_RF95_REG_1E_MODEM_CONFIG2 0x1e +#define BG_RF95_REG_1F_SYMB_TIMEOUT_LSB 0x1f +#define BG_RF95_REG_20_PREAMBLE_MSB 0x20 +#define BG_RF95_REG_21_PREAMBLE_LSB 0x21 +#define BG_RF95_REG_22_PAYLOAD_LENGTH 0x22 +#define BG_RF95_REG_23_MAX_PAYLOAD_LENGTH 0x23 +#define BG_RF95_REG_24_HOP_PERIOD 0x24 +#define BG_RF95_REG_25_FIFO_RX_BYTE_ADDR 0x25 +#define BG_RF95_REG_26_MODEM_CONFIG3 0x26 + +#define BG_RF95_REG_40_DIO_MAPPING1 0x40 +#define BG_RF95_REG_41_DIO_MAPPING2 0x41 +#define BG_RF95_REG_42_VERSION 0x42 + +#define BG_RF95_REG_4B_TCXO 0x4b +#define BG_RF95_REG_4D_PA_DAC 0x4d +#define BG_RF95_REG_5B_FORMER_TEMP 0x5b +#define BG_RF95_REG_61_AGC_REF 0x61 +#define BG_RF95_REG_62_AGC_THRESH1 0x62 +#define BG_RF95_REG_63_AGC_THRESH2 0x63 +#define BG_RF95_REG_64_AGC_THRESH3 0x64 + +// BG_RF95_REG_01_OP_MODE 0x01 +#define BG_RF95_LONG_RANGE_MODE 0x80 +#define BG_RF95_ACCESS_SHARED_REG 0x40 +#define BG_RF95_MODE 0x07 +#define BG_RF95_MODE_SLEEP 0x00 +#define BG_RF95_MODE_STDBY 0x01 +#define BG_RF95_MODE_FSTX 0x02 +#define BG_RF95_MODE_TX 0x03 +#define BG_RF95_MODE_FSRX 0x04 +#define BG_RF95_MODE_RXCONTINUOUS 0x05 +#define BG_RF95_MODE_RXSINGLE 0x06 +#define BG_RF95_MODE_CAD 0x07 + +// BG_RF95_REG_09_PA_CONFIG 0x09 +#define BG_RF95_PA_SELECT 0x80 +#define BG_RF95_MAX_POWER 0x70 +#define BG_RF95_OUTPUT_POWER 0x0f + +// BG_RF95_REG_0A_PA_RAMP 0x0a +#define BG_RF95_LOW_PN_TX_PLL_OFF 0x10 +#define BG_RF95_PA_RAMP 0x0f +#define BG_RF95_PA_RAMP_3_4MS 0x00 +#define BG_RF95_PA_RAMP_2MS 0x01 +#define BG_RF95_PA_RAMP_1MS 0x02 +#define BG_RF95_PA_RAMP_500US 0x03 +#define BG_RF95_PA_RAMP_250US 0x0 +#define BG_RF95_PA_RAMP_125US 0x05 +#define BG_RF95_PA_RAMP_100US 0x06 +#define BG_RF95_PA_RAMP_62US 0x07 +#define BG_RF95_PA_RAMP_50US 0x08 +#define BG_RF95_PA_RAMP_40US 0x09 +#define BG_RF95_PA_RAMP_31US 0x0a +#define BG_RF95_PA_RAMP_25US 0x0b +#define BG_RF95_PA_RAMP_20US 0x0c +#define BG_RF95_PA_RAMP_15US 0x0d +#define BG_RF95_PA_RAMP_12US 0x0e +#define BG_RF95_PA_RAMP_10US 0x0f + +// BG_RF95_REG_0B_OCP 0x0b +#define BG_RF95_OCP_ON 0x20 +#define BG_RF95_OCP_TRIM 0x1f + +// BG_RF95_REG_0C_LNA 0x0c +#define BG_RF95_LNA_GAIN 0xe0 +#define BG_RF95_LNA_BOOST 0x03 +#define BG_RF95_LNA_BOOST_DEFAULT 0x00 +#define BG_RF95_LNA_BOOST_150PC 0x11 + +// BG_RF95_REG_11_IRQ_FLAGS_MASK 0x11 +#define BG_RF95_RX_TIMEOUT_MASK 0x80 +#define BG_RF95_RX_DONE_MASK 0x40 +#define BG_RF95_PAYLOAD_CRC_ERROR_MASK 0x20 +#define BG_RF95_VALID_HEADER_MASK 0x10 +#define BG_RF95_TX_DONE_MASK 0x08 +#define BG_RF95_CAD_DONE_MASK 0x04 +#define BG_RF95_FHSS_CHANGE_CHANNEL_MASK 0x02 +#define BG_RF95_CAD_DETECTED_MASK 0x01 + +// BG_RF95_REG_12_IRQ_FLAGS 0x12 +#define BG_RF95_RX_TIMEOUT 0x80 +#define BG_RF95_RX_DONE 0x40 +#define BG_RF95_PAYLOAD_CRC_ERROR 0x20 +#define BG_RF95_VALID_HEADER 0x10 +#define BG_RF95_TX_DONE 0x08 +#define BG_RF95_CAD_DONE 0x04 +#define BG_RF95_FHSS_CHANGE_CHANNEL 0x02 +#define BG_RF95_CAD_DETECTED 0x01 + +// BG_RF95_REG_18_MODEM_STAT 0x18 +#define BG_RF95_RX_CODING_RATE 0xe0 +#define BG_RF95_MODEM_STATUS_CLEAR 0x10 +#define BG_RF95_MODEM_STATUS_HEADER_INFO_VALID 0x08 +#define BG_RF95_MODEM_STATUS_RX_ONGOING 0x04 +#define BG_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED 0x02 +#define BG_RF95_MODEM_STATUS_SIGNAL_DETECTED 0x01 + +// BG_RF95_REG_1C_HOP_CHANNEL 0x1c +#define BG_RF95_PLL_TIMEOUT 0x80 +#define BG_RF95_RX_PAYLOAD_CRC_IS_ON 0x40 +#define BG_RF95_FHSS_PRESENT_CHANNEL 0x3f + +// BG_RF95_REG_1D_MODEM_CONFIG1 0x1d +#define BG_RF95_BW 0xc0 +#define BG_RF95_BW_125KHZ 0x00 +#define BG_RF95_BW_250KHZ 0x40 +#define BG_RF95_BW_500KHZ 0x80 +#define BG_RF95_BW_RESERVED 0xc0 +#define BG_RF95_CODING_RATE 0x38 +#define BG_RF95_CODING_RATE_4_5 0x00 +#define BG_RF95_CODING_RATE_4_6 0x08 +#define BG_RF95_CODING_RATE_4_7 0x10 +#define BG_RF95_CODING_RATE_4_8 0x18 +#define BG_RF95_IMPLICIT_HEADER_MODE_ON 0x04 +#define BG_RF95_RX_PAYLOAD_CRC_ON 0x02 +#define BG_RF95_LOW_DATA_RATE_OPTIMIZE 0x01 + +// BG_RF95_REG_1E_MODEM_CONFIG2 0x1e +#define BG_RF95_SPREADING_FACTOR 0xf0 +#define BG_RF95_SPREADING_FACTOR_64CPS 0x60 +#define BG_RF95_SPREADING_FACTOR_128CPS 0x70 +#define BG_RF95_SPREADING_FACTOR_256CPS 0x80 +#define BG_RF95_SPREADING_FACTOR_512CPS 0x90 +#define BG_RF95_SPREADING_FACTOR_1024CPS 0xa0 +#define BG_RF95_SPREADING_FACTOR_2048CPS 0xb0 +#define BG_RF95_SPREADING_FACTOR_4096CPS 0xc0 +#define BG_RF95_TX_CONTINUOUS_MOE 0x08 +#define BG_RF95_AGC_AUTO_ON 0x04 +#define BG_RF95_SYM_TIMEOUT_MSB 0x03 + +// BG_RF95_REG_4D_PA_DAC 0x4d +#define BG_RF95_PA_DAC_DISABLE 0x04 +#define BG_RF95_PA_DAC_ENABLE 0x07 + +///////////////////////////////////////////////////////////////////// +/// \class BG_RF95 BG_RF95.h +/// \brief Driver to send and receive unaddressed, unreliable datagrams via a LoRa +/// capable radio transceiver. +/// +/// For Semtech SX1276/77/78/79 and HopeRF RF95/96/97/98 and other similar LoRa capable radios. +/// Based on http://www.hoperf.com/upload/rf/RFM95_96_97_98W.pdf +/// and http://www.hoperf.cn/upload/rfchip/RF96_97_98.pdf +/// and http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf +/// and http://www.semtech.com/images/datasheet/sx1276.pdf +/// and http://www.semtech.com/images/datasheet/sx1276_77_78_79.pdf +/// FSK/GFSK/OOK modes are not (yet) supported. +/// +/// Works with +/// - the excellent MiniWirelessLoRa from Anarduino http://www.anarduino.com/miniwireless +/// - The excellent Modtronix inAir4 http://modtronix.com/inair4.html +/// and inAir9 modules http://modtronix.com/inair9.html. +/// - the excellent Rocket Scream Mini Ultra Pro with the RFM95W +/// http://www.rocketscream.com/blog/product/mini-ultra-pro-with-radio/ +/// - Lora1276 module from NiceRF http://www.nicerf.com/product_view.aspx?id=99 +/// - Adafruit Feather M0 with RFM95 +/// +/// \par Overview +/// +/// This class provides basic functions for sending and receiving unaddressed, +/// unreliable datagrams of arbitrary length to 251 octets per packet. +/// +/// Manager classes may use this class to implement reliable, addressed datagrams and streams, +/// mesh routers, repeaters, translators etc. +/// +/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and +/// modulation scheme. +/// +/// This Driver provides an object-oriented interface for sending and receiving data messages with Hope-RF +/// RFM95/96/97/98(W), Semtech SX1276/77/78/79 and compatible radio modules in LoRa mode. +/// +/// The Hope-RF (http://www.hoperf.com) RFM95/96/97/98(W) and Semtech SX1276/77/78/79 is a low-cost ISM transceiver +/// chip. It supports FSK, GFSK, OOK over a wide range of frequencies and +/// programmable data rates, and it also supports the proprietary LoRA (Long Range) mode, which +/// is the only mode supported in this RadioHead driver. +/// +/// This Driver provides functions for sending and receiving messages of up +/// to 251 octets on any frequency supported by the radio, in a range of +/// predefined Bandwidths, Spreading Factors and Coding Rates. Frequency can be set with +/// 61Hz precision to any frequency from 240.0MHz to 960.0MHz. Caution: most modules only support a more limited +/// range of frequencies due to antenna tuning. +/// +/// Up to 2 modules can be connected to an Arduino (3 on a Mega), +/// permitting the construction of translators and frequency changers, etc. +/// +/// Support for other features such as transmitter power control etc is +/// also provided. +/// +/// Tested on MinWirelessLoRa with arduino-1.0.5 +/// on OpenSuSE 13.1. +/// Also tested with Teensy3.1, Modtronix inAir4 and Arduino 1.6.5 on OpenSuSE 13.1 +/// +/// \par Packet Format +/// +/// All messages sent and received by this BG_RF95 Driver conform to this packet format: +/// +/// - LoRa mode: +/// - 8 symbol PREAMBLE +/// - Explicit header with header CRC (handled internally by the radio) +/// - 4 octets HEADER: (TO, FROM, ID, FLAGS) +/// - 0 to 251 octets DATA +/// - CRC (handled internally by the radio) +/// +/// \par Connecting RFM95/96/97/98 and Semtech SX1276/77/78/79 to Arduino +/// +/// We tested with Anarduino MiniWirelessLoRA, which is an Arduino Duemilanove compatible with a RFM96W +/// module on-board. Therefore it needs no connections other than the USB +/// programming connection and an antenna to make it work. +/// +/// If you have a bare RFM95/96/97/98 that you want to connect to an Arduino, you +/// might use these connections (untested): CAUTION: you must use a 3.3V type +/// Arduino, otherwise you will also need voltage level shifters between the +/// Arduino and the RFM95. CAUTION, you must also ensure you connect an +/// antenna. +/// +/// \code +/// Arduino RFM95/96/97/98 +/// GND----------GND (ground in) +/// 3V3----------3.3V (3.3V in) +/// + +/// SS pin D10----------NSS (CS chip select in) +/// SCK pin D13----------SCK (SPI clock in) +/// MOSI pin D11----------MOSI (SPI Data in) +/// MISO pin D12----------MISO (SPI Data out) +/// \endcode +/// With these connections, you can then use the default constructor BG_RF95(). +/// You can override the default settings for the SS pin and the interrupt in +/// the BG_RF95 constructor if you wish to connect the slave select SS to other +/// than the normal one for your Arduino (D10 for Diecimila, Uno etc and D53 +/// for Mega) or the interrupt request to other than pin D2 (Caution, +/// different processors have different constraints as to the pins available +/// for interrupts). +/// +/// You can connect a Modtronix inAir4 or inAir9 directly to a 3.3V part such as a Teensy 3.1 like +/// this (tested). +/// \code +/// Teensy inAir4 inAir9 +/// GND----------GND (ground in) +/// 3V3----------3.3V (3.3V in) +/// interrupt 0 pin D2-----------D00 (interrupt request out) +/// SS pin D10----------CS (CS chip select in) +/// SCK pin D13----------CK (SPI clock in) +/// MOSI pin D11----------SI (SPI Data in) +/// MISO pin D12----------SO (SPI Data out) +/// \endcode +/// With these connections, you can then use the default constructor BG_RF95(). +/// you must also set the transmitter power with useRFO: +/// driver.setTxPower(13, true); +/// +/// Note that if you are using Modtronix inAir4 or inAir9,or any other module which uses the +/// transmitter RFO pins and not the PA_BOOST pins +/// that you must configure the power transmitter power for -1 to 14 dBm and with useRFO true. +/// Failure to do that will result in extremely low transmit powers. +/// +/// If you have an Arduino M0 Pro from arduino.org, +/// you should note that you cannot use Pin 2 for the interrupt line +/// (Pin 2 is for the NMI only). The same comments apply to Pin 4 on Arduino Zero from arduino.cc. +/// Instead you can use any other pin (we use Pin 3) and initialise RH_RF69 like this: +/// \code +/// // Slave Select is pin 10, interrupt is Pin 3 +/// BG_RF95 driver(10, 3); +/// \endcode +/// +/// If you have a Rocket Scream Mini Ultra Pro with the RFM95W: +/// - Ensure you have Arduino SAMD board support 1.6.5 or later in Arduino IDE 1.6.8 or later. +/// - The radio SS is hardwired to pin D5 and the DIO0 interrupt to pin D2, +/// so you need to initialise the radio like this: +/// \code +/// BG_RF95 driver(5, 2); +/// \endcode +/// - The name of the serial port on that board is 'SerialUSB', not 'Serial', so this may be helpful at the top of our +/// sample sketches: +/// \code +/// #define Serial SerialUSB +/// \endcode +/// - You also need this in setup before radio initialisation +/// \code +/// // Ensure serial flash is not interfering with radio communication on SPI bus +/// pinMode(4, OUTPUT); +/// digitalWrite(4, HIGH); +/// \endcode +/// - and if you have a 915MHz part, you need this after driver/manager intitalisation: +/// \code +/// rf95.setFrequency(915.0); +/// \endcode +/// which adds up to modifying sample sketches something like: +/// \code +/// #include +/// #include +/// BG_RF95 rf95(5, 2); // Rocket Scream Mini Ultra Pro with the RFM95W +/// #define Serial SerialUSB +/// +/// void setup() +/// { +/// // Ensure serial flash is not interfering with radio communication on SPI bus +/// pinMode(4, OUTPUT); +/// digitalWrite(4, HIGH); +/// +/// Serial.begin(9600); +/// while (!Serial) ; // Wait for serial port to be available +/// if (!rf95.init()) +/// Serial.println("init failed"); +/// rf95.setFrequency(915.0); +/// } +/// ... +/// \endcode +/// +/// For Adafruit Feather M0 with RFM95, construct the driver like this: +/// \code +/// BG_RF95 rf95(8, 3); +/// \endcode +/// +/// It is possible to have 2 or more radios connected to one Arduino, provided +/// each radio has its own SS and interrupt line (SCK, SDI and SDO are common +/// to all radios) +/// +/// Caution: on some Arduinos such as the Mega 2560, if you set the slave +/// select pin to be other than the usual SS pin (D53 on Mega 2560), you may +/// need to set the usual SS pin to be an output to force the Arduino into SPI +/// master mode. +/// +/// Caution: Power supply requirements of the RFM module may be relevant in some circumstances: +/// RFM95/96/97/98 modules are capable of pulling 120mA+ at full power, where Arduino's 3.3V line can +/// give 50mA. You may need to make provision for alternate power supply for +/// the RFM module, especially if you wish to use full transmit power, and/or you have +/// other shields demanding power. Inadequate power for the RFM is likely to cause symptoms such as: +/// - reset's/bootups terminate with "init failed" messages +/// - random termination of communication after 5-30 packets sent/received +/// - "fake ok" state, where initialization passes fluently, but communication doesn't happen +/// - shields hang Arduino boards, especially during the flashing +/// +/// \par Interrupts +/// +/// The BG_RF95 driver uses interrupts to react to events in the RFM module, +/// such as the reception of a new packet, or the completion of transmission +/// of a packet. The BG_RF95 driver interrupt service routine reads status from +/// and writes data to the the RFM module via the SPI interface. It is very +/// important therefore, that if you are using the BG_RF95 driver with another +/// SPI based deviced, that you disable interrupts while you transfer data to +/// and from that other device. Use cli() to disable interrupts and sei() to +/// reenable them. +/// +/// \par Memory +/// +/// The BG_RF95 driver requires non-trivial amounts of memory. The sample +/// programs all compile to about 8kbytes each, which will fit in the +/// flash proram memory of most Arduinos. However, the RAM requirements are +/// more critical. Therefore, you should be vary sparing with RAM use in +/// programs that use the BG_RF95 driver. +/// +/// It is often hard to accurately identify when you are hitting RAM limits on Arduino. +/// The symptoms can include: +/// - Mysterious crashes and restarts +/// - Changes in behaviour when seemingly unrelated changes are made (such as adding print() statements) +/// - Hanging +/// - Output from Serial.print() not appearing +/// +/// \par Range +/// +/// We have made some simple range tests under the following conditions: +/// - rf95_client base station connected to a VHF discone antenna at 8m height above ground +/// - rf95_server mobile connected to 17.3cm 1/4 wavelength antenna at 1m height, no ground plane. +/// - Both configured for 13dBm, 434MHz, Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range +/// - Minimum reported RSSI seen for successful comms was about -91 +/// - Range over flat ground through heavy trees and vegetation approx 2km. +/// - At 20dBm (100mW) otherwise identical conditions approx 3km. +/// - At 20dBm, along salt water flat sandy beach, 3.2km. +/// +/// It should be noted that at this data rate, a 12 octet message takes 2 seconds to transmit. +/// +/// At 20dBm (100mW) with Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. +/// (Default medium range) in the conditions described above. +/// - Range over flat ground through heavy trees and vegetation approx 2km. +/// +/// \par Transmitter Power +/// +/// You can control the transmitter power on the RF transceiver +/// with the BG_RF95::setTxPower() function. The argument can be any of +/// +5 to +23 (for modules that use PA_BOOST) +/// -1 to +14 (for modules that use RFO transmitter pin) +/// The default is 13. Eg: +/// \code +/// driver.setTxPower(10); // use PA_BOOST transmitter pin +/// driver.setTxPower(10, true); // use PA_RFO pin transmitter pin +/// \endcode +/// +/// We have made some actual power measurements against +/// programmed power for Anarduino MiniWirelessLoRa (which has RFM96W-433Mhz installed) +/// - MiniWirelessLoRa RFM96W-433Mhz, USB power +/// - 30cm RG316 soldered direct to RFM96W module ANT and GND +/// - SMA connector +/// - 12db attenuator +/// - SMA connector +/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) +/// - Tektronix TDS220 scope to measure the Vout from power head +/// \code +/// Program power Measured Power +/// dBm dBm +/// 5 5 +/// 7 7 +/// 9 8 +/// 11 11 +/// 13 13 +/// 15 15 +/// 17 16 +/// 19 18 +/// 20 20 +/// 21 21 +/// 22 22 +/// 23 23 +/// \endcode +/// +/// We have also measured the actual power output from a Modtronix inAir4 http://modtronix.com/inair4.html +/// connected to a Teensy 3.1: +/// Teensy 3.1 this is a 3.3V part, connected directly to: +/// Modtronix inAir4 with SMA antenna connector, connected as above: +/// 10cm SMA-SMA cable +/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) +/// - Tektronix TDS220 scope to measure the Vout from power head +/// \code +/// Program power Measured Power +/// dBm dBm +/// -1 0 +/// 1 2 +/// 3 4 +/// 5 7 +/// 7 10 +/// 9 13 +/// 11 14.2 +/// 13 15 +/// 14 16 +/// \endcode +/// (Caution: we dont claim laboratory accuracy for these power measurements) +/// You would not expect to get anywhere near these powers to air with a simple 1/4 wavelength wire antenna. +class BG_RF95 : public RHSPIDriver +{ +public: + /// \brief Defines register values for a set of modem configuration registers + /// + /// Defines register values for a set of modem configuration registers + /// that can be passed to setModemRegisters() if none of the choices in + /// ModemConfigChoice suit your need setModemRegisters() writes the + /// register values from this structure to the appropriate registers + /// to set the desired spreading factor, coding rate and bandwidth + typedef struct + { + uint8_t reg_1d; ///< Value for register BG_RF95_REG_1D_MODEM_CONFIG1 + uint8_t reg_1e; ///< Value for register BG_RF95_REG_1E_MODEM_CONFIG2 + uint8_t reg_26; ///< Value for register BG_RF95_REG_26_MODEM_CONFIG3 + } ModemConfig; + + /// Choices for setModemConfig() for a selected subset of common + /// data rates. If you need another configuration, + /// determine the necessary settings and call setModemRegisters() with your + /// desired settings. It might be helpful to use the LoRa calculator mentioned in + /// http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf + /// These are indexes into MODEM_CONFIG_TABLE. We strongly recommend you use these symbolic + /// definitions and not their integer equivalents: its possible that new values will be + /// introduced in later versions (though we will try to avoid it). + /// Caution: if you are using slow packet rates and long packets with RHReliableDatagram or subclasses + /// you may need to change the RHReliableDatagram timeout for reliable operations. + typedef enum + { + Bw125Cr45Sf128 = 0, ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range + Bw500Cr45Sf128, ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range + Bw31_25Cr48Sf512, ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range + Bw125Cr48Sf4096, ///< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range + Bw125Cr45Sf4096, ///< APRS + } ModemConfigChoice; + + /// Constructor. You can have multiple instances, but each instance must have its own + /// interrupt and slave select pin. After constructing, you must call init() to initialise the interface + /// and the radio module. A maximum of 3 instances can co-exist on one processor, provided there are sufficient + /// distinct interrupt lines, one for each instance. + /// \param[in] slaveSelectPin the Arduino pin number of the output to use to select the RH_RF22 before + /// accessing it. Defaults to the normal SS pin for your Arduino (D10 for Diecimila, Uno etc, D53 for Mega, D10 for Maple) + /// \param[in] interruptPin The interrupt Pin number that is connected to the RFM DIO0 interrupt line. + /// Defaults to pin 2, as required by Anarduino MinWirelessLoRa module. + /// Caution: You must specify an interrupt capable pin. + /// On many Arduino boards, there are limitations as to which pins may be used as interrupts. + /// On Leonardo pins 0, 1, 2 or 3. On Mega2560 pins 2, 3, 18, 19, 20, 21. On Due and Teensy, any digital pin. + /// On Arduino Zero from arduino.cc, any digital pin other than 4. + /// On Arduino M0 Pro from arduino.org, any digital pin other than 2. + /// On other Arduinos pins 2 or 3. + /// See http://arduino.cc/en/Reference/attachInterrupt for more details. + /// On Chipkit Uno32, pins 38, 2, 7, 8, 35. + /// On other boards, any digital pin may be used. + /// \param[in] spi Pointer to the SPI interface object to use. + /// Defaults to the standard Arduino hardware SPI interface + BG_RF95(uint8_t slaveSelectPin = SS, uint8_t interruptPin = 2, RHGenericSPI& spi = hardware_spi); + + /// Initialise the Driver transport hardware and software. + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + virtual bool init(); + + /// Prints the value of all chip registers + /// to the Serial device if RH_HAVE_SERIAL is defined for the current platform + /// For debugging purposes only. + /// \return true on success + bool printRegisters(); + + /// Sets all the registered required to configure the data modem in the RF95/96/97/98, including the bandwidth, + /// spreading factor etc. You can use this to configure the modem with custom configurations if none of the + /// canned configurations in ModemConfigChoice suit you. + /// \param[in] config A ModemConfig structure containing values for the modem configuration registers. + void setModemRegisters(const ModemConfig* config); + + /// Select one of the predefined modem configurations. If you need a modem configuration not provided + /// here, use setModemRegisters() with your own ModemConfig. + /// \param[in] index The configuration choice. + /// \return true if index is a valid choice. + bool setModemConfig(ModemConfigChoice index); + + /// Tests whether a new message is available + /// from the Driver. + /// On most drivers, this will also put the Driver into RHModeRx mode until + /// a message is actually received by the transport, when it wil be returned to RHModeIdle. + /// This can be called multiple times in a timeout loop + /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv() + virtual bool available(); + + /// Turns the receiver on if it not already on. + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to available space in buf. Set to the actual number of octets copied. + /// \return true if a valid message was copied to buf + virtual bool recv(uint8_t* buf, uint8_t* len); + +// added BG APRS Packets are sent with 3-Byte header +// turn on promiscuous + virtual bool recvAPRS(uint8_t* buf, uint8_t* len); + + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is permitted. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send + /// \return true if the message length was valid and it was correctly queued for transmit + virtual bool send(const uint8_t* data, uint8_t len); + + // Send APRS Header Format + virtual bool sendAPRS(const uint8_t* data, uint8_t len); + +virtual uint8_t lastSNR(); + + /// Sets the length of the preamble + /// in bytes. + /// Caution: this should be set to the same + /// value on all nodes in your network. Default is 8. + /// Sets the message preamble length in BG_RF95_REG_??_PREAMBLE_?SB + /// \param[in] bytes Preamble length in bytes. + void setPreambleLength(uint16_t bytes); + + /// Returns the maximum message length + /// available in this Driver. + /// \return The maximum legal message length + virtual uint8_t maxMessageLength(); + + /// Sets the transmitter and receiver + /// centre frequency. + /// \param[in] centre Frequency in MHz. 137.0 to 1020.0. Caution: RFM95/96/97/98 comes in several + /// different frequency ranges, and setting a frequency outside that range of your radio will probably not work + /// \return true if the selected frquency centre is within range + bool setFrequency(float centre); + + /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, + /// disables them. + void setModeIdle(); + + /// If current mode is Tx or Idle, changes it to Rx. + /// Starts the receiver in the RF95/96/97/98. + void setModeRx(); + + /// If current mode is Rx or Idle, changes it to Rx. F + /// Starts the transmitter in the RF95/96/97/98. + void setModeTx(); + + /// Sets the transmitter power output level, and configures the transmitter pin. + /// Be a good neighbour and set the lowest power level you need. + /// Some SX1276/77/78/79 and compatible modules (such as RFM95/96/97/98) + /// use the PA_BOOST transmitter pin for high power output (and optionally the PA_DAC) + /// while some (such as the Modtronix inAir4 and inAir9) + /// use the RFO transmitter pin for lower power but higher efficiency. + /// You must set the appropriate power level and useRFO argument for your module. + /// Check with your module manufacturer which transmtter pin is used on your module + /// to ensure you are setting useRFO correctly. + /// Failure to do so will result in very low + /// transmitter power output. + /// Caution: legal power limits may apply in certain countries. + /// After init(), the power will be set to 13dBm, with useRFO false (ie PA_BOOST enabled). + /// \param[in] power Transmitter power level in dBm. For RFM95/96/97/98 LORA with useRFO false, + /// valid values are from +5 to +23. + /// For Modtronix inAir4 and inAir9 with useRFO true (ie RFO pins in use), + /// valid values are from -1 to 14. + /// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of + /// the PA_BOOST pin (false). Choose the correct setting for your module. + void setTxPower(int8_t power, bool useRFO = false); + + /// Sets the radio into low-power sleep mode. + /// If successful, the transport will stay in sleep mode until woken by + /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) + /// Caution: there is a time penalty as the radio takes a finite time to wake from sleep mode. + /// \return true if sleep mode was successfully entered. + virtual bool sleep(); + +protected: + /// This is a low level function to handle the interrupts for one instance of BG_RF95. + /// Called automatically by isr*() + /// Should not need to be called by user code. + void handleInterrupt(); + + /// Examine the revceive buffer to determine whether the message is for this node + void validateRxBuf(); + + /// Clear our local receive buffer + void clearRxBuf(); + +private: + /// Low level interrupt service routine for device connected to interrupt 0 + static void isr0(); + + /// Low level interrupt service routine for device connected to interrupt 1 + static void isr1(); + + /// Low level interrupt service routine for device connected to interrupt 1 + static void isr2(); + + /// Array of instances connected to interrupts 0 and 1 + static BG_RF95* _deviceForInterrupt[]; + + /// Index of next interrupt number to use in _deviceForInterrupt + static uint8_t _interruptCount; + + /// The configured interrupt pin connected to this instance + uint8_t _interruptPin; + + /// The index into _deviceForInterrupt[] for this device (if an interrupt is already allocated) + /// else 0xff + uint8_t _myInterruptIndex; + + /// Number of octets in the buffer + volatile uint8_t _bufLen; + + /// The receiver/transmitter buffer + uint8_t _buf[BG_RF95_MAX_PAYLOAD_LEN]; + + /// True when there is a valid message in the buffer + volatile bool _rxBufValid; +}; + +/// @example rf95_client.pde +/// @example rf95_server.pde +/// @example rf95_reliable_datagram_client.pde +/// @example rf95_reliable_datagram_server.pde + +#endif + diff --git a/src/TTGO_Test.ino b/src/TTGO_Test.ino new file mode 100644 index 0000000..a31a32b --- /dev/null +++ b/src/TTGO_Test.ino @@ -0,0 +1,603 @@ + +// Arduino Tracker for LoRA APRS +// +// TTGO T-Beam includes GPS module + optional DHT22 (not yet DONE) +// +// can be used as tracker only, tracker plus weather reports (temperature and humidity) or weather reports station only +// +// updated from OE1ACM sketch by OE3CJB to enable WX data to be sent via LoRa APRS. +// one package is with position and battery voltage +// the next is with weather data in APRS format +// +// licensed under CC BY-NC-SA +// +// last update: 24.11.2018 +// modifications: select mode during compilation to select model + +// USER DATA - USE THESE LINES TO MODIFY YOUR PREFERENCES +// Your Callsign +String Tcall="OE3CJB-7"; //your Call Sign for normal position reports +String wxTcall="OE3CJB-7"; //your Call Sign for weather reports + +// Your symbol table and symbol for position reports incl. battery voltage +String sTable="/"; //Primer +//String sTable="\"; //Alternativ + +// String sSymbol="_"; //symbol code Weather Station +// String sSymbol=">"; //symbol code CAR +String sSymbol="["; //symbol code RUNNER +// String sSymbol="b"; //symbol code BICYCLE +// String sSymbol="<"; //symbol code MOTORCYCLE + +// SEND_WX - if true the tracker sends WX reports - needs DHT22 connected at Pin 10 +// when FIXED_POSITION is false then it sends alternating normal position packets and weather report packets +#define SEND_WX false + +// Your symbol table and symbol for weather reports +String wxTable="/"; //Primer +String wxSymbol="_"; //Symbol Code Weather Station +// String wxSymbol="W"; //Symbol Code Weather Station/ + +#define FIXED_POSITION false +// set to true if you want to use fixed position (position defined below) instead, or to false if you want to use GPS data +// also stops sending normal position reports when sending weather reports is active (SEND_WX true) + +#define LATITUDE "4813.62N" // please in APRS notation DDMM.mmN or DDMM.mmS used for FIXED_POSITION +#define LONGITUDE "01539.85E" // please in APRS notation DDDMM.mmE or DDDMM.mmW used for FIXED_POSITION +// ^^^^^LATITUDE and LONGITUDE only used when FIXED_POSITION is true + +// Tracker setting: use these lines to modify the tracker behaviour +#define TXFREQ 433.775 // Transmit frequency in MHz +#define TXdbmW 18 // Transmit power in dBm +#define TXenablePA 0 // switch internal power amplifier on (1) or off (0) + +// Transmit intervall +unsigned long nextTX = 60000L; // Send every 60 secs +// unsigned long nextTX = 5000L; // Send every 5 secs - FOR TESTS ONLY - NO CONNECTION TO SERVER PLEASE!!!! + +// STOP EDITING from here on - except you know what you do :-) +#define DEBUG false // used for debugging purposes , e.g. turning on special serial or display logging + +//Hardware definitions + +/* for feather32u4 +#define RFM95_CS 8 +#define RFM95_RST 4 +#define RFM95_INT 7 +*/ + +//Variables for DHT22 temperature and humidity sensor +int chk; +float hum; //Stores humidity value +float temp; //Stores temperature value + +//other global Variables +String Textzeile1, Textzeile2; + +//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +//PINs used for HW extensions + +// Pin for battery voltage -> bei T-Beam ADC1_CHANNEL_7 +// #define ANALOG_PIN_0 35 // connected to battery + +// Pins for GPS +static const int RXPin = 15, TXPin = 12; // changed BG A3 A2 +static const uint32_t GPSBaud = 9600; //GPS + +const byte TX_en = 0; +const byte RX_en = 0; //TX/RX enable 1W modul + +const byte TXLED = 14; //pin number for LED on TX Tracker +// const byte GPSLED = 6; // pin gps & Heartbeat +// const byte GPSLED1 = 9; // pin gps & Heartbeat + +// Pins for LoRa module +const byte lora_PReset = 23; //pin where LoRa device reset line is connected +const byte lora_PNSS = 18; //pin number where the NSS line for the LoRa device is connected. + // pin 11 MOSI + // pin 12 MISO + // pin 13 SCLK + +// #define ModemConfig BG_RF95::Bw125Cr45Sf4096 + +#define DHTPIN 10 // what pin we're connected to +#define DHTTYPE DHT22 // DHT 22 (AM2302) + +// Variables and Constants + +String InputString = ""; //data on buff is copied to this string +String Outputstring = ""; +String outString=""; //The new Output String with GPS Conversion RAW +float BattVolts; + +#if (FIXED_POSITION) +boolean wx = true; +#else +boolean wx = false; +#endif + +//byte arrays +byte lora_TXBUFF[128]; //buffer for packet to send +//byte Variables +byte lora_TXStart; //start of packet data in TXbuff +byte lora_TXEnd; //end of packet data in TXbuff +byte lora_FTXOK; //flag, set to 1 if TX OK +byte lora_TXPacketType; //type number of packet to send +byte lora_TXDestination; //destination address of packet to send +byte lora_TXSource; //source address of packet received +byte lora_FDeviceError; //flag, set to 1 if RFM98 device error +byte lora_TXPacketL; //length of packet to send, includes source, destination and packet type. + + +unsigned long lastTX = 0L; + +// Includes + +#include +#include +#include +#include +// #include + +#include +// #include +#include +#include +#include +#include + +#include +#include + +#include "xtest_bw.h" + +#include +#include +#include +#include + +static void smartDelay(unsigned long); +void recalcGPS(void); +void sendpacket(void); +void loraSend(byte, byte, byte, byte, byte, long, byte, float); +void batt_read(void); +void writedisplaytext(String, String, String, int); + + +#if (SEND_WX) +DHT dht(DHTPIN, DHTTYPE); // Initialize DHT sensor for normal 16mhz Arduino +#endif + +// SoftwareSerial ss(RXPin, TXPin); // The serial connection to the GPS device +HardwareSerial ss(1); // TTGO has HW serial +TinyGPSPlus gps; // The TinyGPS++ object + +// checkRX +uint8_t buf[BG_RF95_MAX_MESSAGE_LEN]; +uint8_t len = sizeof(buf); + +// Singleton instance of the radio driver + +BG_RF95 rf95(18, 26); // TTGO T-Beam has NSS @ Pin 18 and Interrupt IO @ Pin26 + +// initialize OLED display +#define OLED_RESET 4 // not used +Adafruit_SSD1306 display(128, 64, &Wire, OLED_RESET); + +void setup() +{ + + pinMode(TXLED, OUTPUT); + digitalWrite(TXLED, LOW); + Serial.begin(115200); + if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64 + for(;;); // Don't proceed, loop forever + } + digitalWrite(TXLED, HIGH); + writedisplaytext("Init:","Display OK!","",1000); + digitalWrite(TXLED, LOW); + Serial.println("Init: Display OK!"); + if (!rf95.init()) { + // Serial.println("init failed"); + + writedisplaytext("Init:","RF95 FAILED!",":-(",1000); + Serial.println("Init: RF95 FAILED!"); + for(;;); // Don't proceed, loop forever + } + + digitalWrite(TXLED, HIGH); + writedisplaytext("Init:","RF95 OK!","",1000); + digitalWrite(TXLED, LOW); + Serial.println("Init: RF95 OK!"); + + #if !(FIXED_POSITION) + ss.begin(GPSBaud, SERIAL_8N1, 12, 15); //Startup HW serial for GPS + #endif // #if !(FIXED_POSITION) + digitalWrite(TXLED, HIGH); + writedisplaytext("Init:","GPS Serial OK!","",1000); + digitalWrite(TXLED, LOW); + Serial.println("Init: GPS Serial OK!"); + + adc1_config_width(ADC_WIDTH_BIT_12); + adc1_config_channel_atten(ADC1_CHANNEL_7,ADC_ATTEN_DB_6); + writedisplaytext("Init:","ADC OK!","",1000); + Serial.println("Init: ADC OK!"); + + rf95.setFrequency(433.775); + rf95.setModemConfig(BG_RF95::Bw125Cr45Sf4096); // hard coded because of double definition + // rf95.setModemConfig(ModemConfig); // das ist irgendwo doppelt definiert ??? + rf95.setTxPower(5); + //rf95.printRegisters(); + //rf95.setPromiscuousbg(); + +#if (SEND_WX) + dht.begin(); // DHT22 initialisieren + writedisplaytext("Init:","DHT OK!","",1000); + Serial.println("Init: DHT OK!"); +#else //#if (SEND_WX) + writedisplaytext("Init:","no DHT configuration","",1000); + Serial.println("Init: no DHT configuration"); +#endif //#if (SEND_WX) + +digitalWrite(TXLED, HIGH); +writedisplaytext("Init:","All DONE OK!",":-D",1000); +digitalWrite(TXLED, LOW); +Serial.println("Init: ALL DONE OK! :-D"); +writedisplaytext("","","",0); +} + + +// LOOP + +void loop() +{ +#if DEBUG + writedisplaytext("DEBUG","millis()",String(millis()),0); +#endif + //while(1) { if ( ss.available() ) Serial.write(ss.read());} +#if !(FIXED_POSITION) + // digitalWrite(GPSLED, HIGH); + + while (ss.available() > 0) { + gps.encode(ss.read()); + } +#endif // #if !(FIXED_POSITION) + +if (rf95.waitAvailableTimeout(100)) +{ + // Should be a reply message for us now + if (rf95.recvAPRS(buf, &len)) + { + // Serial.print("RX: "); + // Serial.println((char*)buf); + // Serial.print("RSSI: "); + // Serial.println(rf95.lastRssi(), DEC); + + } +} + +display.clearDisplay(); +display.setTextColor(WHITE); +display.setTextSize(2); +display.setCursor(0,0); +display.println("LoRa-APRS"); +display.setTextSize(1); +display.setCursor(0,36); +display.print("LAT: "); +display.println(String(gps.location.lat(),5)); +display.setCursor(0,46); +display.print("LON: "); +display.println(String(gps.location.lng(),5)); +display.setCursor(0,56); +display.print("SPD: "); +display.print(String(gps.speed.kmph(),1)); +display.print(" CRS: "); +display.println(String(gps.course.deg(),0)); +display.display(); + +smartDelay(1000); + +// digitalWrite(GPSLED, LOW); +#if (FIXED_POSITION) + // if (gps.location.isUpdated() || ( (lastTX+nextTX) <= millis() ) ) + if ( (lastTX+nextTX) <= millis() ) +#else + if (gps.location.isValid() && ( (lastTX+nextTX) <= millis() ) ) +#endif +{ + digitalWrite(TXLED, HIGH); + sendpacket(); + writedisplaytext("State:","Packet sent!","",250); + Serial.println("State: Packet sent!"); + digitalWrite(TXLED, LOW); +} else { + if ( (lastTX+nextTX*2) <= millis() ) + { + digitalWrite(TXLED, HIGH); + sendpacket(); + writedisplaytext("State:","Packet sent!","",250); + Serial.println("State: Packet sent!"); + digitalWrite(TXLED, LOW); + } +} + + smartDelay(1000); + + #if !(FIXED_POSITION) + if (millis() > 200000 && gps.charsProcessed() < 10) + { + writedisplaytext("Warning","No GPS Signal!","",1000); + Serial.println("Warning: No GPS Signal!"); + } + #endif +} + +///////////////////////////////////////////////////////////////////////////////////////// +// This custom version of delay() ensures that the gps object +// is being "fed". +static void smartDelay(unsigned long ms) +{ + unsigned long start = millis(); + do + { + #if !(FIXED_POSITION) + while (ss.available()) + gps.encode(ss.read()); + #endif + } while (millis() - start < ms); +} + + +///////////////////////////////////////////////////////////////////////////////////////// +//@APA Recalc GPS Position +void recalcGPS(){ + + String Ns, Ew, helper; + float Tlat, Tlon; + int Talt; + float Lat; + float Lon; + + #if !(FIXED_POSITION) + Tlat=gps.location.lat(); + Tlon=gps.location.lng(); + Talt=gps.altitude.meters(); + if(Tlat<0) { Ns = "S"; } else { Ns = "N"; } + if(Tlon<0) { Ew = "W"; } else { Ew = "E"; } + if(Tlat < 0) { Tlat= -Tlat; } + unsigned int Deg_Lat = Tlat; + Lat = 100*(Deg_Lat) + (Tlat - Deg_Lat)*60; + + if(Tlon < 0) { Tlon= -Tlon; } + unsigned int Deg_Lon = Tlon; + Lon = 100*(Deg_Lon) + (Tlon - Deg_Lon)*60; + #endif + +#if !(SEND_WX) + outString = ""; + outString = (Tcall); + outString += ">APRS:!"; + #if (FIXED_POSITION) + outString += LATITUDE; + #else + if(Tlat<10) {outString += "0"; } + outString += String(Lat,2); + outString += Ns; + #endif + outString += wxTable; + #if (FIXED_POSITION) + outString += LONGITUDE; + #else + if(Tlon<100) {outString += "0"; } + if(Tlon<10) {outString += "0"; } + outString += String(Lon,2); + outString += Ew; + #endif + outString += sSymbol; + outString += " /A="; + outString += Talt; + outString += "m Batt="; + outString += String(BattVolts,2); + outString += ("V"); +#else + if ( !wx ) { // create standard position string + #if !(FIXED_POSITION) + outString = ""; + outString = (Tcall); + outString += ">APRS:!"; + if(Tlat<10) {outString += "0"; } + outString += String(Lat,2); + outString += Ns; + outString += sTable; + if(Tlon<100) {outString += "0"; } + if(Tlon<10) {outString += "0"; } + outString += String(Lon,2); + outString += Ew; + outString += sSymbol; + outString += " /A="; + outString += Talt; + outString += "m Batt="; + outString += String(BattVolts,2); + outString += ("V"); + wx = true; + #endif + } else { // create weather report string + hum = dht.readHumidity(); +// hum = 88.67; +// temp = 50.23; + temp = (dht.readTemperature() * 9/5) +32; + outString = ""; + outString = (wxTcall); + outString += ">APRS:!"; + #if (FIXED_POSITION) + outString += LATITUDE; + #else + if(Tlat<10) {outString += "0"; } + outString += String(Lat,2); + outString += Ns; + #endif + outString += wxTable; + #if (FIXED_POSITION) + outString += LONGITUDE; + #else + if(Tlon<100) {outString += "0"; } + if(Tlon<10) {outString += "0"; } + outString += String(Lon,2); + outString += Ew; + #endif + outString += wxSymbol; + outString += ".../...g...t"; + if (temp < 0) { // negative Werte erstellen + outString += "-"; + if(temp>-10) {outString += "0"; } + temp = abs(temp); + } else { // positive Werte erstellen + if(temp<100) {outString += "0"; } + if(temp<10) {outString += "0"; } + } + helper = String(temp,0); + helper.trim(); + outString += helper; + outString += "r...p...P...h"; + if(hum<10) {outString += "0"; } + helper = String(hum,0); + helper.trim(); + outString += helper; + outString += "b......DHT22"; + #if !(FIXED_POSITION) + wx = false; + #endif + } +#endif +} + +///////////////////////////////////////////////////////////////////////////////////////// +void sendpacket() +{ + + batt_read(); + Outputstring = ""; + +#if !(FIXED_POSITION) + if ( gps.location.isValid() || gps.location.isUpdated() ) + { + // digitalWrite(GPSLED, HIGH); + //New System + //recalcEncodedGPS(); +#endif + recalcGPS(); // + // digitalWrite(PLED1, HIGH); + Outputstring =outString; + + loraSend(lora_TXStart, lora_TXEnd, 60, 255, 1, 10, TXdbmW, TXFREQ); //send the packet, data is in TXbuff from lora_TXStart to lora_TXEnd + #if !(FIXED_POSITION) + } else { + Outputstring = (Tcall); + Outputstring += " No GPS-Fix"; + Outputstring += " Batt="; + Outputstring += String(BattVolts,2); + Outputstring += ("V "); + + loraSend(lora_TXStart, lora_TXEnd, 60, 255, 1, 10, 5, TXFREQ); //send the packet, data is in TXbuff from lora_TXStart to lora_TXEnd + // digitalWrite(GPSLED, LOW); + } + #endif + + // digitalWrite(PLED1, LOW); +} + +/////////////////////////////////////////////////////////////////////////////////////// +void loraSend(byte lora_LTXStart, byte lora_LTXEnd, byte lora_LTXPacketType, byte lora_LTXDestination, byte lora_LTXSource, long lora_LTXTimeout, byte lora_LTXPower, float lora_FREQ) +{ + byte i; + byte ltemp; + + if (rf95.waitAvailableTimeout(100)) + { + if (rf95.recvAPRS(buf, &len)) + { +// Serial.print("RX before TX: "); +// Serial.println((char*)buf); +// Serial.print("RSSI: "); +// Serial.println(rf95.lastRssi(), DEC); + + } + } + + // time of last TX + lastTX = millis(); + + ltemp = Outputstring.length(); + for (i = 0; i <= ltemp; i++) + { + lora_TXBUFF[i] = Outputstring.charAt(i); + } + + i--; + lora_TXEnd = i; + lora_TXBUFF[i] ='\0'; + + // digitalWrite(PLED1, HIGH); //LED on during packet + + rf95.setModemConfig(BG_RF95::Bw125Cr45Sf4096); + rf95.setFrequency(lora_FREQ); + rf95.setTxPower(lora_LTXPower); + +// Serial.print(Outputstring); +// Serial.print(" len: "); +// Serial.println(strlen(lora_TXBUFF) ); + + //digitalWrite(RX_en, LOW); //RX lo + //digitalWrite(TX_en, HIGH); //TX HIGH + //rf95.sendAPRS(lora_TXBUFF, sizeof(lora_TXBUFF)); + rf95.sendAPRS(lora_TXBUFF, Outputstring.length()); + + // rf95.sendAPRS(lora_TXBUFF, lora_TXBUFF.length()); + rf95.waitPacketSent(); + + //digitalWrite(TX_en, LOW); //TX lo + //digitalWrite(RX_en,HIGH); //RX HIGH + + + + + // digitalWrite(PLED1, LOW); + +} +/////////////////////////////////////////////////////////////////////////////////////// +void batt_read() +{ + //int BattRead = analogRead(ANALOG_PI); + int BattRead = adc1_get_raw(ADC1_CHANNEL_7); + //lora_TXBUFF[1] = (BattRead / 256); //MSB of battery volts + //lora_TXBUFF[0] = (BattRead - (lora_TXBUFF[1] * 256)); //LSB of battery volts + + BattVolts = (BattRead * (2.2 / 4096.0)); + + //Serial.print("lora_TXBUFF[0] "); + //Serial.println(lora_TXBUFF[0]); + //Serial.print("lora_TXBUFF[1] "); + //Serial.println(lora_TXBUFF[1]); + //Serial.println("Battery "); + //Serial.print(BattVolts, 2); + //Serial.println("V"); +} + +/////////////////////////////////////////////////////////////////////////////////////// +void writedisplaytext(String Line1, String Line2, String Line3, int warten) +{ + display.clearDisplay(); + display.setTextColor(WHITE); + display.setTextSize(2); + display.setCursor(0,0); + display.println("LoRa-APRS"); + display.setTextSize(1); + display.setCursor(0,36); + display.println(Line1); + display.setCursor(0,46); + display.println(Line2); + display.setCursor(0,56); + display.println(Line3); + display.display(); + smartDelay(warten); +} diff --git a/src/xtest_bw.h b/src/xtest_bw.h new file mode 100644 index 0000000..a230bb3 --- /dev/null +++ b/src/xtest_bw.h @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// File generated by LCD Assistant +// http://en.radzio.dxp.pl/bitmap_converter/ +//------------------------------------------------------------------------------ + +const unsigned char xtest_bw [] PROGMEM = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x00, 0x3E, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x03, 0xF0, 0x00, 0x00, 0x7C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00, 0xF8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xF8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x01, 0xF0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x10, 0x00, 0x3E, 0x00, 0x03, 0xE0, 0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x18, 0x00, 0x1F, 0x00, 0x07, 0xC0, 0x01, 0x81, 0xFF, 0xFE, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x1C, 0x00, 0x1F, 0x80, 0x0F, 0x80, 0x01, 0x81, 0xED, 0xB6, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x1E, 0x00, 0x0F, 0x80, 0x1F, 0x00, 0x03, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x1F, 0x00, 0x07, 0xC0, 0x1F, 0x00, 0x07, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x0F, 0x00, 0x03, 0xE0, 0x3E, 0x00, 0x0F, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x0F, 0x80, 0x01, 0xF0, 0x7C, 0x00, 0x1F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x07, 0xC0, 0x00, 0xF8, 0xF8, 0x00, 0x3E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x03, 0xE0, 0x00, 0xF9, 0xF0, 0x00, 0x3E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x7F, 0xE0, 0x00, 0x7C, 0x00, 0x20, 0x78, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xF8, 0x00, 0xE0, 0xCC, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x1F, 0xC0, 0x01, 0xF0, 0x00, 0x81, 0x86, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x0F, 0x80, 0x03, 0xE0, 0x01, 0x81, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x07, 0x00, 0x07, 0xC0, 0x01, 0x01, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x06, 0x00, 0x07, 0xC0, 0x01, 0x01, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x0F, 0x80, 0x01, 0x03, 0x01, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0x1F, 0x00, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0x3E, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x00, 0x7C, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00, 0xF8, 0x00, 0x01, 0x86, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x8C, 0x04, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x01, 0xF0, 0x00, 0x00, 0xF8, 0x1C, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0xE0, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x07, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0xE0, 0x00, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x01, 0xF0, 0x00, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xF8, 0x00, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00, 0x7C, 0x00, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x00, 0x7C, 0x00, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x00, 0x3E, 0x00, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0x1F, 0x00, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x0F, 0x80, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x02, 0x00, 0x07, 0xC0, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x06, 0x00, 0x03, 0xE0, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x0F, 0x00, 0x03, 0xE0, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x0F, 0x80, 0x01, 0xF0, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x1F, 0xC0, 0x00, 0xF8, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x3F, 0xE0, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x7F, 0xF0, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x07, 0xC0, 0x00, 0xF9, 0xF0, 0x00, 0x1F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x07, 0xC0, 0x00, 0xF8, 0xF8, 0x00, 0x1F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x0F, 0x80, 0x01, 0xF0, 0x7C, 0x00, 0x0F, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x1F, 0x00, 0x03, 0xE0, 0x3E, 0x00, 0x07, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x1E, 0x00, 0x07, 0xC0, 0x1F, 0x00, 0x03, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x1C, 0x00, 0x0F, 0x80, 0x1F, 0x00, 0x01, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x18, 0x00, 0x1F, 0x00, 0x0F, 0x80, 0x01, 0x81, 0xFF, 0xFE, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x18, 0x00, 0x1F, 0x00, 0x07, 0xC0, 0x00, 0x81, 0xEA, 0xAA, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0xE0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x01, 0xF0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xF8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00, 0xF8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x00, 0x7C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x00, 0x3E, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; diff --git a/test/README b/test/README new file mode 100644 index 0000000..df5066e --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html